pax_global_header00006660000000000000000000000064141417770540014524gustar00rootroot0000000000000052 comment=e43f383dae3a35237e42f6acfe1207a8e7e7bdf5 django-extensions-3.1.5/000077500000000000000000000000001414177705400151715ustar00rootroot00000000000000django-extensions-3.1.5/.editorconfig000066400000000000000000000004311414177705400176440ustar00rootroot00000000000000# 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 django-extensions-3.1.5/.github/000077500000000000000000000000001414177705400165315ustar00rootroot00000000000000django-extensions-3.1.5/.github/FUNDING.yml000066400000000000000000000003311414177705400203430ustar00rootroot00000000000000# These are supported funding model platforms github: [trbs] patreon: djangoextensions open_collective: djangoextensions custom: ['https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=P57EJJ9QYL232'] django-extensions-3.1.5/.github/workflows/000077500000000000000000000000001414177705400205665ustar00rootroot00000000000000django-extensions-3.1.5/.github/workflows/compile_catalog.yml000066400000000000000000000007331414177705400244360ustar00rootroot00000000000000name: Compile Catalog on: pull_request: push: branches: - main jobs: compile_catalog: name: Compile Catalog runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 - name: Set up Python 3.x uses: actions/setup-python@v2 with: python-version: "3.10" - run: python -m pip install tox - name: Compile Catalog run: tox env: TOXENV: compile-catalog django-extensions-3.1.5/.github/workflows/linters.yml000066400000000000000000000014361414177705400227750ustar00rootroot00000000000000name: Linters on: pull_request: push: branches: - main jobs: flake8: name: flake8 runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 - name: Set up Python 3.x uses: actions/setup-python@v2 with: python-version: "3.10" - run: python -m pip install tox - name: tox py310-flake8 run: tox env: TOXENV: py310-flake8 mypy: name: mypy runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 - name: Set up Python 3.x uses: actions/setup-python@v2 with: python-version: "3.10" - run: python -m pip install tox - name: tox mypy run: tox env: TOXENV: mypy django-extensions-3.1.5/.github/workflows/precommit.yml000066400000000000000000000007111414177705400233070ustar00rootroot00000000000000name: Pre-Commit Checks on: pull_request: push: branches: - main jobs: precommit: name: precommit runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 - name: Set up Python 3.x uses: actions/setup-python@v2 with: python-version: "3.10" - run: python -m pip install tox - name: tox precommit run: tox env: TOXENV: precommit django-extensions-3.1.5/.github/workflows/pytest.yml000066400000000000000000000035541414177705400226500ustar00rootroot00000000000000name: PyTest on: pull_request: push: branches: - main jobs: pytest: runs-on: ubuntu-latest strategy: fail-fast: false max-parallel: 4 matrix: python-version: - 3.6 - 3.7 - 3.8 - 3.9 - "3.10" - pypy3 tox-django-version: - "22" - "30" - "31" - "32" # GH Actions don't support something like allow-failure ? # - "master" steps: - name: Checkout uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - run: python -m pip install tox - name: Pytest run: tox -e py-dj${{ matrix.tox-django-version }} pytest-postgresql: runs-on: ubuntu-latest services: postgres: image: postgres env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: django_extensions_test ports: - 5432:5432 options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 strategy: fail-fast: false max-parallel: 4 matrix: python-version: - "3.10" tox-django-version: - "32" steps: - name: Checkout uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - run: python -m pip install tox - name: Pytest run: tox -e py-dj${{ matrix.tox-django-version }}-postgres env: DJANGO_EXTENSIONS_DATABASE_HOST: localhost DJANGO_EXTENSIONS_DATABASE_USER: postgres DJANGO_EXTENSIONS_DATABASE_PASSWORD: postgres django-extensions-3.1.5/.github/workflows/security.yml000066400000000000000000000007061414177705400231630ustar00rootroot00000000000000name: Check Security Vulnerabilities on: pull_request: push: branches: - main jobs: safety: name: safety runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 - name: Set up Python 3.x uses: actions/setup-python@v2 with: python-version: "3.10" - run: python -m pip install tox - name: safety run: tox env: TOXENV: safety django-extensions-3.1.5/.gitignore000066400000000000000000000003021414177705400171540ustar00rootroot00000000000000*.py[cod] *.egg-info MANIFEST build dist docs/_build docs/_static venv* .tox *.bak .DS_Store .eggs/ .idea/ htmlcov/ .coverage .cache/ .mypy_cache/ .pytest_cache/ django-sample-app*/ *.swp *.swo django-extensions-3.1.5/.pre-commit-config.yaml000066400000000000000000000016631414177705400214600ustar00rootroot00000000000000- repo: git://github.com/pre-commit/pre-commit-hooks sha: v0.9.1 hooks: - id: trailing-whitespace - id: check-added-large-files args: - --maxkb=128 - id: check-ast - id: check-case-conflict - id: check-docstring-first - id: check-json - id: check-merge-conflict - id: check-xml - id: check-yaml - id: detect-private-key - id: end-of-file-fixer - id: fix-encoding-pragma - id: flake8 - id: name-tests-test args: - --django exclude: ^tests/testapp|^tests/management/|^tests/collisions/|^tests/pythonrc.py|^tests/runner.py - repo: git://github.com/Lucas-C/pre-commit-hooks.git sha: v1.0.1 hooks: - id: forbid-crlf - repo: git://github.com/trbs/pre-commit-hooks-trbs.git sha: e233916fb2b4b9019b4a3cc0497994c7926fe36b hooks: - id: forbid-executables exclude: manage.py|setup.py django-extensions-3.1.5/.tx/000077500000000000000000000000001414177705400157025ustar00rootroot00000000000000django-extensions-3.1.5/.tx/config000066400000000000000000000003311414177705400170670ustar00rootroot00000000000000[django-extensions.master] file_filter = django_extensions/locale//LC_MESSAGES/django.po source_file = django_extensions/locale/en/LC_MESSAGES/django.po source_lang = en [main] host = https://www.transifex.com django-extensions-3.1.5/CHANGELOG.md000066400000000000000000001235421414177705400170110ustar00rootroot00000000000000Changelog ========= See https://github.com/django-extensions/django-extensions/releases 3.1.4 ----- Changes: - Fix: set_default_site, improve django.contrib.sites application detection - Improvement: documentation, Fix name of mixin in docs - Improvement: mypy, type ignore backwards compatible imports - Improvement: graph_models, add --rankdir to change graph direction - Improvement: runserver_plus, Add --sql-truncate cli modifier - Improvement: shell_plus, Add --sql-truncate cli modifier 3.1.3 ----- Changes: - Fix: Django 3.2, Run tests against Django 3.2 - Fix: Django 3.2, Handle warnings for default_app_config (#1654) - Fix: sqldiff, Fix for missing field/index in model case 3.1.2 ----- Changes: - Improvement: shell_plus, not save ipython history when using Jupyter - Improvement: docs, fix spelling mistakes - Improvement: tests, move to Github Actions instead of Travis - Improvement: drop_test_database, delete all cloned test databases (#1637) - Improvement: setup.py, Added minimum Django>=2.2 version to PyPI package - Improvement: shell_plus, fix --command globals / locals error 3.1.1.post1 ----------- Changes: - Improvement: setup.py, Added minimum Django>=2.2 version to PyPI package 3.1.1 ----- Changes: - Improvement: graph_models, add option --app-labels - Improvement: shell_plus, update shell_plus for jupyterlab 3 - Improvement: tests, add Python 3.9 3.1.0 ----- Changes: - Improvement: pipchecker, sleep 60s if pypi raises a fault - Improvement: add django_zero_downtime_migrations to list of supported postgresql engines - Improvement: use list of supported database engines from settings for all database commands - Improvement: reset_db, documentation - Fix: tests, Python 3.9 fixes for some tests - Fix: runserver_plus, parsing of RUNSERVER_PLUS_EXTRA_FILES 3.0.9 ----- Changes: - Improvement: runserver_plus, survive syntax and configuration errors part II - Improvement: tests, refactor test runner - Improvement: sqlcreate, support postgresql unix domain socket 3.0.8 ----- Changes: - Improvement: setup.cfg, remove universal flag from wheel, we only support Python 3 and up - Improvement: sqlcreate, fixed mentioned of old syncdb - Fix: runserver_plus, stop catching SyntaxError since reload for it was not working properly 3.0.7 ----- Changes: - Improvement: runserver_plus, gh #1575 survive syntax and configuration errors - Improvement: runscript, use exit-code 1 if script is not found 3.0.6 ----- Changes: - Improvement: runscript, add --continue-on-error unless set runscript will exit on errors - Improvement: runscript, allow to return exit-code - Improvement: runscript, support raise CommandError(... returncode=...) - Improvement: runscript, run Django checks() and check_migrations() before executing scripts - Improvement: shell_plus, set application name on all postgresql backends 3.0.5 ----- Changes: - Fix: runserver_plus, exceptions must derive from BaseException error 3.0.4 ----- Changes: - Various cleanups - Deprecated using `--router` instead use `--database` - Locales: Indonesian and Polish, updated - Improvement: show_dsn, fix crash with django-postgres-extra - Improvement: print_settings, added wildcard support - Improvement: print_settings, added --fail option - Improvement: delete_squashed_migrations, add --database option - Improvement: runserver_plus, added RUNSERVER_PLUS_EXTRA_FILES setting - Improvement: runserver_plus, added runserver_plus_started signal 3.0.3 ----- Changes: - New: InternalIPS, allows to specify CIDRs for INTERNAL_IPS - Docs: restructure toctree 3.0.2 ----- Changes: - Fix: shell_plus, fix honouring SHELL_PLUS in settings.py 3.0.1 ----- Changes: - Fix: setup.py, add python_requires and remove legacy trove classifiers 3.0.0 ----- This is the first Django Extensions release which only targets Django 2.2 and above. It drops official support for Python 2.7. Changes: - Removal of Python 2 support - Removal of deprecated keyczar encrypted fields EncryptedTextField and EncryptedCharField - Removal of deprecated passwd command - Removal of truncate_letters filter - Change: TimeStampedModel; Removed default ordering on abstract model - New: DjangoExtensionsConfig AppConfig - New: shell_plus, JupyterLab support - New: list_signals, List all signals by model and signal type - Improvement: shell_plus, use -- to directly pass additional arguments to Jupyter - Improvement: shell_plus, improvements to MySQL support - Improvement: jobs, use logging to record errors - Improvement: syncdata, added --remove-before flag - Improvement: graph_models, add field and model to template context - Fix: syncdata, fix non existent field in fixture data - Fix: pipchecker, compatibility with pip 20.1 2.2.9 ----- Changes: - Fix: shell_plus, move notebook down the list of preferred shells - Fix: sqldiff, fix KeyError when detecting missing (unique) indexes - Improvement: encrypted fields, make it harder to use deprecated keyczar fields - Locale: Removed empty localizations 2.2.8 ----- Changes: - Locale: zh_Hans, removed as it generated UnicodeDecodeError errors (#1478) 2.2.7 ----- Changes: - Improvement: shell_plus, #865 always add manage.py basedir to path for notebook kernel - Improvement: docs, add zh-Hans locale - Improvement: runserver_plus, fix broken import for werkzeug v1.0.0 - Improvement: runserver_plus, #1461 fix always trying to load StaticFilesHandler - Improvement: pipchecker, #1471 fix import of PipSession 2.2.6 ----- Changes: - Improvement: travis, update pypy and pypy3 versions - Improvement: shell_plus, ability to print location/traceback besides sql - Improvement: runserver_plus, ability to print location/traceback besides sql - Improvement: UniqueFieldMixin, Support Django 2.2 UniqueConstraint.condition - Improvement: DEFAULT_MYSQL_ENGINES, add mysql.connector.django - Improvement: shell_plus, allow setting SHELL_PLUS="notebook" - Improvement: shell_plus, add -c/--command to shell_plus mirroring django's shell command - Fix: shell_plus, fix postgresql debug wrapper on django 3.0 or higher - Fix: runserver_plus, fix postgresql debug wrapper on django 3.0 or higher 2.2.5 ----- Changes: - Improvement: travis, add Python 3.8 - Improvement: setup.py, update classifiers 2.2.4 ----- Changes: - Improvement: RandomCharField, Support unique_together - Improvement: export_emails, add settings for overriding queryset fields, order_by and the full_name function 2.2.3 ----- Changes: - Fix: admin widgets, fix import of static template tag (part 2) 2.2.2 ----- Changes: - Fix: autoslugfield, find unique method overrideable - Fix: notes, do not replace dot in template dirs - Fix: admin widgets, fix import of static template tag - Improvement: print_user_for_session, use session backend - Improvement: sqlcreate, postgis support - Improvement: graph_models, permit combination of includes and excludes - Improvement: Adds missing GIS engine to DEFAULT_MYSQL_ENGINES - Improvement: sqldiff, use lowercase field names in MySQL - Improvement: sqldiff, mysql code could duplicate AUTO_INCREMENT and UNSIGNED statements 2.2.1 ----- Changes: - Fix: tests, support for newer versions of pytest - Fix: tests, disable test with drf dependency for older python versions 2.2.0 ----- Changes: - Fix: removing wrongly released text_tags template - Fix: graph_models, support for Python <3.6 - Improvement: ForeignKeySearchInput, wrap media files in static() - Improvement: UniqField, added tests - Improvement: dumpscript, fix orm_item_locator to use dateutil - Improvement: graph_models, added argument to change arrow_shape 2.1.9 ----- Changes: - Fix: show_urls, fix for traceback on multi language sites - Improvement: reset_db, fix typo's in help test 2.1.8 ----- Changes: - New: HexValidator, validate hex strings - Improvement: reset_db, move settings to `django_settings.settings` which makes it easier to override. - Improvement: AutoSlugField, extend support for custom slugify function - Fix: runprofileserver, fix autoreloader for newer Django versions 2.1.7 ----- Changes: - New: test, many many more tests :-) thanks everybody - New: docs, many documentation updates - New: graph_model, add simple theming support and django2018 theme - Improvement: ModificationDateTimeField, make modificationfield name modifiable - Improvement: graph_model, option to not showrelations labels in the graph - Improvement: reset_db, allow to override list of backends for database engines - Improvement: reset_db, add psqlextra backend - Improvement: runserver_plus, idle support - Improvement: generate_secret_key, removed get_random_string in favour of get_random_secret_key - Improvement: update_permissions, add create-only and update-only flags - Improvement: update_permissions, update changed names of permission to match correct permission name - Improvement: syncdata, add --database option - Improvement: runscript, allow to override RUNSCRIPT_SCRIPT_DIR - Fix: create_command, fix mknod error on macos - Fix: runserver_plus, fix in resolving ssl certificate path - Fix: sqldiff, fix hstorefield - Deprecate: truncate_letters, use Django's truncatechars - Deprecate: passwd, use Django's changepassword - Deprecate: Keyczar encrypted fields, Keyczar is abandoned / deprecated 2.1.6 ----- Changes: - Fix: runserver_plus, auto_reloader fix for compatibility with Django 2.2 - New: test, many many more tests :-) thanks @kuter 2.1.5 ----- Changes: - New: ipdb, pdb and wdb filters - Fix: ForeignKeySearchInput, error with widget render(...) parameters on Django 2.1 - Fix: pipchecker, unsupported format string passed to NoneType.format error - Tests: bunch of new test cases 2.1.4 ----- Changes: - Fix: null_technical_500_response, handle function-based middleware - Fix: shell_plus, fix #1261 check for --notebook-dir=... argument style - Fix: graph_models, Excluded models displayed as an underscore - Fix: set_fake_password, requires_model_validation has been replaced with requires_system_checks since 1.9 - Docs: admin_generator, new documentation and examples - Improvement: JSONField, use new from_db_value syntax on Django 2 and up - Improvement: EncryptedTextField, use new from_db_value syntax on Django 2 and up - Improvement: graph_models, add --dot option - Improvement: graph_models, allow to redirect (text) output to file - Improvement: sqldiff, better support for indexes, index_together and unique_together 2.1.3 ----- Changes: - Fix: Readme, add direct link to screencast video - Fix: graph_models, regression under Python 2 - Fix: ForeignKeyAutocompleteAdmin, 2.0.8 breaks ForeignKeyAutocompleteAdmin - Fix: AutoSlugField, fix regression when copying an autoslug model require the explicit clearing of the slug if it needs to be recalculated - Fix: technical_response, check for AttributeError - Improvement: graph_models, Add feature disable_abstract_fields - Improvement: AutoSlugField, Add overwrite_on_add - Improvement: runscript, Improve module existence test in runscript 2.1.2 ----- Changes: - Fix: AutoSlugField, fix check on list or tuple type 2.1.1 ----- Removed support for Django versions before 1.11 Changes: - Fix: foreignkey_searchinput, remove unnecessary img tag - Fix: sqldiff, fix deprecated get_indexes call - Fix: AutoSlugField, check that any non-callable value passed to populate_from is a string type - Fix: tests, fix ChangingDirectoryTests: cd back in tearDown - Fix: show_template_tags, should handle AppConfig class in INSTALLED applications - Improvement: runserver_plus, reduce reraise pollution in traceback page - Improvement: dumpscript, prevent many2many field with custom intermediate models to be added directly on the parent model - Docs: fix typos 2.1.0 ----- Changes: - Fix: travis 2.0.9 ----- Changes: - Improvement: use README as project description on PyPI 2.0.8 ----- Please stop using ForeignKeyAutocompleteAdmin edition :-) Changes: - Fix: special markers in runserver_plus.rst - Fix: shell_plus, refactor reading pythonrc file outside of exec(compile(...)) - Fix: reset_db, fix default utf8 support - Fix: autoslugfield, Fix autoslug generation when existing model is copied - Improvement: Cleanup management commands options after argparse migration #916 - Improvement: sqldiff, add more tests - Improvement: sqldiff, add DurationField and SearchVectorField - Improvement: shell_plus, add more tests - Improvement: shell_plus, backport macos fix for tab completion - Improvement: clear_cache, add --all option - Improvement: pipchecker, treat dev versions as unstable - Deprecation: ForeignKeyAutocompleteAdmin, Django 2.0 has similar capabilities, which are much better supported. 2.0.7 ----- Changes: - Fix: pipchecker, pip 10.0.0 compatibility - Fix: sqldiff, improve support of GIS fields by using Django introspection - Fix: shell_plus, fix bug in windows when PYTHONPATH is defined - Fix: shell_plus, Call execute on CursorWrapper instead of directly on cursor to ensure wrappers are run - Fix: runserver_plus, Call execute on CursorWrapper instead of directly on cursor to ensure wrappers are run - Improvement: sqldiff, drop old compatibility code - Improvement: ForeignKeyAutocompleteAdminMixin, improvements for Django >1.9 2.0.6 ----- Changes: - Fix: shell_plus, Fix of deprecation warning in collision resolvers 2.0.5 ----- Changes: - Improvement: setup.py, Use PEP 508 when setuptools is version 36 or higher should fix issues with pipenv - Fix: docs, Docs should show that django 2.0 is supported 2.0.4 ----- Changes: - Fix: setup.py, fix installation of typing in python < 3.5 2.0.3 ----- Changes: - Fix: shell_plus, python 2.7 support broken due to use of Python3 super() 2.0.2 ----- Changes: - Improvement: sqldiff, add --include-defaults to include default value in missing field for sqldiff #1064 2.0.1 ----- Changes: - Fix: setup.py, do not include `typing` requirement in recent versions of Python - Improvement: shell_plus, add support for using -- to pass cli argument directly to underlying python shell implementation - New: generate_password, Generates a new password based on `BaseUserManager.make_random_password` 2.0.0 ----- Changes: - Fix: runserver_plus, for 1.11 still using MIDDLEWARE_CLASSES - Fix: show_urls, Fix display in Django 2.0 - Fix: validate_templates, remove realpath in validate_templates - Fix: sqldiff, bug with including proxy models in sqldiff output - Improvement: shell_plus, allow configurating of sqlparse formatting and pygments formatting - Improvement: shell_plus, add collision resolvers based on app label - Improvement: shell_plus, automatic importing of subclasses defined in SHELL_PLUS_SUBCLASSES_IMPORT - New: reset_schema, simple command to recreate public schema in PostgreSQL - Docs: fix links to Werkzeug documentation 1.9.9 ----- Changes: - Fix: runserver_plus, fix for Django 2.0 middleware handling - Fix: shell_plus, fixed app_name resolving - Fix: AutoSlugField, deconstruct did not match construction values - Fix: runjob, not compatible with apps that use AppConfig in INSTALLED_APPS - Improvement: runserver_plus, added configuring paths to certificates - Improvement: sample.py template, add newline to avoid linter warnings - Improvement: jobs, add integration tests for runjob and runjobs management commands - New: merge_model_instances, new management command for de-duplicating model instances 1.9.8 ----- Changes: - Fix: show_urls, fix for Django 2.0 (Locale URL Resolvers are still broken) - Fix: runserver_plus, fix rendering of ipv6 link - Improvement: validate_templates, allow relative paths - Improvement: validate_templates, automatically include app templates - Improvement: pip_checker, could not find some packages - Docs: shell_plus, `--print-sql` usage clearification 1.9.7 ----- This release add checking types with MyPy to the test suite. At this point only a few lines of code are explicitly typed. Changes: - Improvement: shell_plus, Collision resolver implemented. - Improvement: shell_plus, Skipping all models importing feature added. - Improvement: runscript, Script execution directory policy feature added. - django-extensions now requires the [typing](https://pypi.python.org/pypi/typing) package. 1.9.6 ----- Fix boo-boo with release version in django_extensions/__init__.py 1.9.4 ----- Changes: - Fix missing test case 1.9.3 ----- Changes: - Tests: shell_plus, simple test for get_imported_objects 1.9.2 ----- Changes: - Fix: mail_debug, regression in mail_debug for older Pythons - Fix: shell_plus, SyntaxError on exec(), python compatibility - Fix: ForeignKeyAutocompleteAdminMixin, use text/plain 1.9.1 ----- Changes: - Fix: graph_models, fix json option - Fix: runserver_plus, avoid duplicate messages logged to console - Fix: mail_debug, python3 fix - Improvement: sqldiff, basic support for array types in postgresql - Improvement: runscript, handle import errors better - Docs: updated documentation for model extensions 1.9.0 ----- The change to --no-startup/--use-pythonrc in `shell_plus` changes the default behaviour to automatically load PYTHONSTARTUP and ~/.pythonrc.py unless --no-startup is set. Changes: - Fix: pipchecker, fix up-to-date check for Github sha commits - Fix: JSONField, fix handling to_python() for strings with tests - Fix: print_settings, fix print_settings to receive positional args - Improvement: shell_plus, update PYTHONSTARTUP / pythonrc handling to match Django - Improvement: shell_plus, added new 1.11 features from django.db.models to shell_plus import list - Improvement: runserver_plus, startup message now accounts for https - Docs: jobs, improve documentation about jobs scheduling - Docs: admin, add documentation for ForeignKeyAutocompleteStackedInline and ForeignKeyAutocompleteTabularInline - Docs: fix typos 1.8.1 ----- Changes: - Build: use tox's 'TOXENV' environment variable - Fix: resetdb, fix problem that 'utf8_support' option is ignored - Improvement: export_emails, moved custom csv UnicodeWriter (for py2) into compat.py - Translations: pt, removed since it was causing issues with the builds if anybody wants to update and fix it that would be much appreciated ! 1.8.0 ----- UUIDField has been removed after being deprecated. Deprecation schedule for JSONField has been removed after requests from the community. Changes: - Fix: runserver_plus, fixed Python 3 print syntax - Fix: sqldiff, Use 'display_size', not 'precision' to identify MySQL bool field - Fix: export_emails, fix and refactor the command and all its output options - Improvement: tests, added Python 3.6 and PyPy3.5-5.8.0 - Improvement: clear_cache, add --cache option to support multiple caches - Improvement: runserver_plus, limit printing SQL queries to avoid flooding the terminal - Improvement: shell_plus, limit printing SQL queries to avoid flooding the terminal - Docs: graph_models, document including/excluding specific models - Docs: shell_plus, added PTPython 1.7.9 ----- Changes: - Fix: AutoSlugField, foreignkey relationships - Fix: shell_plus, supported backends 'postgresql' for set_application_name - Improvement: various commands, Add syntax highlighting when printing SQL - Improvement: pipchecker, treat rc versions as unstable - Improvement: shell_plus, allow to subclass and overwrite import_objects - Improvement: shell_plus, fix SHELL_PLUS_PRE_IMPORTS example - Improvement: setup.py, fix and unify running tests - Improvement: runserver_plus, add RUNSERVERPLUS_POLLER_RELOADER_TYPE setting - Improvement: generate_secret_key, use algorithm from django - Docs: fix grammar and spelling mistakes 1.7.8 ----- Changes: - Improvement: django 1.11, add testing for Django 1.11 - Improvement: pipchecker, make it possible to parse https github urls - Improvement: unreferenced_files, make command much faster by using set() - Docs: add undocumented commands - Docs: shell_plus, additional documentation for referencing nested modules - Fix: sync_s3, fix exclusion of directories - Fix: runprofileserver, fix ip:port specification - Fix: runprofileserver, support --nothreading 1.7.7 ----- Changes: - Improvement: admin_generator, use decorator style for registering ModelAdmins. - Improvement: sqldiff, quote tablename for PRAGMA in sqlite - Fix: graph_models, Fix `attributes` referenced before assignment - Fix: pipchecker, Fix AttributeError caused by missing method 1.7.6 ----- Changes: - Improvement: sqldiff, ignore proxy models in diff (with cli option to include them if wanted) 1.7.5 ----- Changes: - New: ForeignKeyAutocompleteAdmin, Add autocomplete for inline model admins - Improvement: graph_models, Rewrite modelviz module from method to class based processor - Improvement: db fields, make MAX_UNIQUE_QUERY_ATTEMPTS configurable per field and via settings - Improvement: runserver_plus, Added nopin option to disable pin - Fix: graph_models, Support PyDot 1.2.0 and higher - Fix: shell_plus, Fix that aliases from SHELL_PLUS_MODEL_ALIASES were not applied - Removed: validate_templatetags, remove support for pre django-1.5 style {% url %} tags - Cleanup: removing support for end-of-life Python 3.2 - Docs: simplify installation instructions - Docs: fix example for NOTEBOOK_ARGUMENTS - Docs: Remove extraneous '}' characters from shell_plus docs 1.7.4 ----- Changes: - Improvement: show_urls, support --no-color option - Fix: notes, Fix reading templates setting after django 1.8 - Fix: create_app, Fixed typo in deprecation warning - Fix: shell_plus, Use new location for url reverse import - Docs: some commands where missing from the docs - Docs: runscript, added documentation for --traceback 1.7.3 ----- Changes: - Fix: ForeignKeySearchInput, fix bug with constructing search_path urls - Docs: runscript, fix runscript example - Deprecation: JSONField, Django now includes JSONField our field is now deprecated 1.7.2 ----- Changes: - Fix: passwd, Update passwd command up to Django>=1.8 - Improvement: shell_plus, add settings.SHELL_PLUS_PRINT_SQL config option - Improvement: shell_plus, allow to specifies the connection file to use if using the --kernel option 1.7.1 ----- Changes: - Fix: sqldiff, fix optional app_label arguments - Fix: runscript, remove args from command class - Doc: runscript, remove = from --script-args example 1.7.0 ----- The "Letting go of the past" release. From this moment on Django Extensions requires version 1.8 or higher. A lot of work has been done to remove old backwards compatibility code and make sure that Django Extensions uses the current Django API's. This should result in better and easier to maintain code (and hopefully less bugs :). This release touches a lot of code if you have any issues please report them at [https://github.com/django-extensions/django-extensions/issues] We still need more tests to make sure we don't break people's projects when refactoring. If you have some spare time please contribute tests ! Changes: - Cleanup: removing backwards compatibility hacks for (now) unsupported versions of Django - Cleanup: use six instead of home grown functions - Fix: AutoSlugField, allow_duplicates didn't set slug value to model instance - Fix: MongoDB fields, verbose_name on mongoengine fields does not seem to be supported - Fix: MongoDB fields, fix relative import problem with json.py - Improvement: Start using pre-commit - Improvement: syncdata, Replace custom transaction logic with transaction.atomic - Improvement: Django 1.10, use from_db_value instead of models.SubfieldBase - Improvement: print_user_session, support for non standard user model - Improvement: widont, tests to work with py2 and py3 - Improvement: runserver_plus, prevent 2nd reload of debugger on runserver_plus - Improvement: runserver_plus, prevent killing the server when request.META values are evaluated - Improvement: reset_db, add argument to make closing sessions optional - Improvement: print_settings, Fix positional arguments - Improvement: runscript, migrate to argparse and add_arguments - Improvement: graph_models, do not rely on .models_module for inclusion in output - Improvement: jsonfield, fix issues with mutable default - Docs: Convert readthedocs links for their .org -> .io migration 1.6.7 ----- Changes: - Fix: describe_form, fix No module named 'django.db.models.loading' error - Improvement: shell_plus, Add a setting to prefix all models in an application #887 - Improvement: pipchecker, check for requirements-{dev,prod}.txt as well - Docs: pipchecker, update documentation 1.6.6 ----- Changes: - Fix: admin_generator, fix for using all apps in Django <1.7 - Fix: dump_script, fix for using all apps in Django <1.7 - Fix: UniqueFieldMixin, resolve get_fields_with_model deprecation Django 1.10 - Fix: runprofileserver, Fix call grind format to enable source code navigation in qcachegrind. - Docs: runserver_plus, add a little note about the debugger PIN. 1.6.5 ----- Bumped version number since PyPi returns 500 errors while uploading packages :( 1.6.4 ----- Changes: - Fix: jobs cache_cleanup, use `caches` instead of deprecated `get_cache` - Fix: ModificationDateTimeField, missing default value for `update_modified` - Fix: modelviz, use get_model_compat and look up missing app_label - Fix: modelviz, use get_models_for_app instead of get_models_compat - Fix: dumpscript, use `list_app_labels` instead of `get_apps` when no app_labels are given - Improvement: compat.py, move code from try to else block for Django 1.7+ - Docstring: get_models_for_app, clearify argument 1.6.3 ----- Bumped version number for incomplete PyPi upload 1.6.2 ----- The long over due release :-) Changes: - Fix: JsonFields, do not parse floats as decimals. This fixes bugs that causes them to be returned as strings after multiple saves. Note that this can be backwards incompatible ! - Fix: use add_arguments() instead of option_list (Django 1.10) - Fix: create_command, Django 1.9 fixes - Fix: create_jobs, Django 1.9 fixes - Fix: RandomCharField, when not unique get the first value from the generator - Fix: graph_models, render() must be called with a dict - Fix: graph_models, use force_bytes fixes command for Python 3 - Fix: graph_models, fix django 1.6 compatibility for strings defined relation - Fix: graph_models, fix settings.GRAPH_MODELS breaking the command - Fix: graph_models, add support for lazy relationships - Fix: ForeignKeyAutocompleteAdmin, url_patterns is just a list (Django 1.9+) - Fix: ForeignKeySearchInput, use url reversing instead of hardcoded paths - Fix: find_template, Fix for Django 1.8+ - Fix: admin_generator, incompatible "default" identifier raising TypeError - Improvement: show_urls, add json and pretty-json formatting - Improvement: runserver_plus, add support for whitenoise - Improvement: ModificationDateTimeField, add parameter to preserve timestamps on save - Improvement: runprofileserver, raise command error when hotspot is not available - Improvement: reset_db, better parsing of mysql cnf file - Improvement: restored coverage for Python 3.2 - Improvement: pep8 fixes, remove unused shims & imports & commented code - Improvement: graph_models, JSON output - Improvement: graph_models, add wildcard filters - Docs: removed text on donations, the hope was that we could generate some funds to have more consistent development and outreach. - Docs: runserver_plus, added some documentation about LOGGING - Docs: runscript, update documentation to match Django tutorial for Django 1.8+ - Docs: runprofileserver, add documentation on profiler choices - Docs: update_permissions, add basic documentation for command 1.6.1 ----- Changes: - Revert: JSONField, revert Django 1.9 fix as it breaks the field (ticket #781) 1.6.0 ----- Changes: - Fix: Django 1.9 compatibility - New: runserver_plus, add --startup-messages to control when to show them - New: added support for Python 3.5 - Improvement: show_template_tags, renamed from show_templatetags for consistency - Removed: jquery library (after dropping support for Django 1.5) 1.5.9 ----- Changes: - Fix: wheel still had the old migrations directory in the package 1.5.8 ----- Changes: - Fix: migrations, fix BadMigrationError with Django 1.8+ - Fix: reset_db, Django 1.8+ compatibility fix - Fix: runserver_plus, fix signature of null_technical_500_response for Django 1.8+ - Fix: graph_models, use force_bytes instead of .decode('utf8') - Improvement: print_settings, add format option to only print values - Improvement: print_settings, add format option for simple key = value text output - Improvement: email_export, documentation updates - Improvement: shell_plus, auto load conditional db expressions Case and When 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, correctly 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 unused 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 ----- 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 compatibility 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 ------ Changes: - Fix: show_urls, fix bug in new formatter when column is empty 1.3.9 ----- Changes: - Feature: shell_plus, add --kernel option to start as standalone IPython kernel - Feature: reset_db, Programmatically determine PostGIS template - Feature: sqldiff, add support for PointField and MultiPolygonField - Test: renamed test app - Fix: runserver_plus, --print-sql for Django 1.7 - Fix: shell_plus, --print-sql for Django 1.7 - Fix: show_urls, add support for functions that use functools.partial - Fix: show_urls, add formatter for aligned output (will most likely become future default) - Fix: shell_plus / notebook, support for Django 1.7 - Docs: various fixes and improvements - Cleanup: Remove work arounds for Django 0.96 and earlier 1.3.8 ----- Changes: - Feature: show_urls, add option to specify dense or verbose output format - Improvement: better support for django 1.7 migrations - Improvement: better support for django's admin docs - BugFix: runjob, job_name and app_name was swapped in error message - Docs: Update link to chinese docs - Python3: unreferenced_files, fix python3 compatibility - Python3: pipchecker, fix python3 compatibility 1.3.7 ----- Changes: - Reinstated: clean_pyc and compile_pyc commands, these now depends on BASE_DIR in settings.py as per Django 1.6. We urge everybody to include a BASE_DIR settings in their project file! auto-detecting the project-root is now deprecated and will be removed in 1.4.0. - I18N: Added russian locale - Docs: runscript, Add section about passing arguments to scripts - Python3: show_url, Fixed to AttributeError 'func_globals' - Deprecated: clean_pyc, compile_pyc, Auto-detecting project root 1.3.6 ----- Changes: - Additional version bump because we mistakenly already uploaded version 1.3.5 of the wheel package with the code of 1.3.4 1.3.5 ----- Changes: - Feature: Django-Extensions is now also distributed as a Wheel package - Improvement: dumpscript, improved the readability of comments in generated script - Improvement: sqldiff, backported get_constraints() for PostgreSQL - Improvement: shell_plus, consistent colorization - BugFix: encrypted fields, there is no decoding to unicode in Python 3 - BugFix: shell_plus, importing modules failed in some edge cases - Django 1.7: included Django 1.7 in test suite - Python 3.4: included Python 3.4 in test suite 1.3.4 ----- Changes: - Feature: Start maintaining a CHANGELOG file in the repository - Feature: ActivatorModelManager now has an ActivatorQuerySet - Feature: Add a deconstruct() method for future Django 1.7 migration compatibility - Feature: show_urls, now support --language for i18n_patterns - Feature: show_urls, now shows the decoraters set on a view function - Feature: graph_models, now support --include-models to restrict the graph to specified models - Feature: print_settings, allow to specify the settings you want to see - Improvement: graph_models, use '//' instead of '#' as comment character in dot files - Improvement: graph_models, added error message for abstract models without explicit relations - Improvement: JSONField, use python's built-in json support by default with fallback on django.utils.simplejson - Improvement: PostgreSQLUUIDField, parse value into UUID type before sending it to the database - Improvement: Use django.JQuery in autocomplete.js if available - Improvement: use "a not in b" instead of "not a in b" in the entire codebase - Removed: clean_pyc command since it does not work correctly in many cases - Removed: sync_media_s3 command in favor of sync_s3 - BugFix: syncdata, use pk instead of id for identifying primary key of objects - BugFix: sync_s3, use safer content type per default - BugFix: export_emails, filtering on groups - BugFix: print_user_for_session, use USERNAME_FIELD if defined - BugFix: update_permission, fixed TypeError issue - BugFix: JSONField, do not coerse a json string into a python list - BugFix: import json issue by using absolute imports - BugFix: add minimal version number to six (>=1.2) - Docs: graph_models, Added some documentation about using dot templates - Docs: reset_db, short description on SQL DDL used - Docs: Added specific list of supported Python and Django versions - Docs: Add link to GoDjango screencast - Docs: Add ShortUUIDField to docs - Python3: fixes to graph_models and export_emails for Python3 compatibility 1.3.3 ----- Changes: - Docs: Made it clearer that Django Extensions requires Django 1.4 or higher - Translations: FR Updated - Python3: Fix for shell_plus 1.3.0 ----- Changes: - Feature: SQLDiff much better notnull detection - Feature: reset_db add option to explicit set the PostGreSQL owner of the newly created DB - Feature: shell_plus added support for MongoEngine - Feature: sync_s3 enable syncing to other cloud providers compatible with s3 - Improvement: ForeignKeyAutocompleteAdmin add option to limit queryset - BugFix: graph_models fix issue with models without primary key - BugFix: graph_models fix UnicodeDecodeError using --verbose-names - BugFix: dumpscript fix problems with date/datetimes by saving them now as ISO8601 - Docs: many improvements - Docs: Chinese translation !!! - Python3: various improvements - Tests: add Django 1.6 django-extensions-3.1.5/CONTRIBUTING.md000066400000000000000000000024351414177705400174260ustar00rootroot00000000000000# Contributing to Django Extensions There are many ways to contribute to the project. You may improve the documentation, address a bug, add some feature to the code or do something else. All sort of contributions are welcome. ### Development To start development on this project, fork this repository and follow the following instructions. ```bash # clone the forked repository $ git clone YOUR_FORKED_REPO_URL # create a virtual environment $ python3 -m venv venv # activate the virtual environment $ source venv/bin/activate # install django-extensions in development mode (venv) $ pip install -e . # install dependencies (venv) $ pip install Django -r requirements-dev.txt # for accessing the GUI portion of the test application (venv) $ export DJANGO_EXTENSIONS_DATABASE_NAME="db.sqlite3" # you may change if you want to use any other database # start the development server (venv) $ python manage.py runserver ``` ### Testing To run tests against a particular `python` and `django` version installed inside your virtual environment, you may use: ```bash (venv) $ pytest # `python manage.py test` or `make test` also work ``` To run tests against all supported `python` and `django` versions, you may run: ```bash # install dependency (venv) $ pip install tox # run tests (venv) $ tox ``` django-extensions-3.1.5/LICENSE000066400000000000000000000020411414177705400161730ustar00rootroot00000000000000Copyright (c) 2007 Michael Trier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. django-extensions-3.1.5/MANIFEST.in000066400000000000000000000005361414177705400167330ustar00rootroot00000000000000recursive-include django_extensions/conf *.tmpl recursive-include django_extensions/templates *.dot *.html recursive-include django_extensions/static * recursive-include django_extensions/locale *.po *.mo recursive-include docs * include LICENSE include README.rst include CHANGELOG.md include tox.ini manage.py Makefile recursive-include tests *.py django-extensions-3.1.5/Makefile000066400000000000000000000023511414177705400166320ustar00rootroot00000000000000help: @echo "clean - remove all build, test, coverage and Python artifacts" @echo "clean-build - remove build artifacts" @echo "clean-pyc - remove Python file artifacts" @echo "clean-test - remove test and coverage artifacts" @echo "compile-catalog - compile translation catalogs" @echo "test - run tests quickly with the default Python" @echo "coverage - check code coverage quickly with the default Python" @echo "install - install the package to the active Python's site-packages" clean: clean-test clean-build clean-pyc clean-build: rm -fr build/ rm -fr dist/ rm -fr .eggs/ find . -name '*.egg-info' -exec rm -fr {} + find . -name '*.egg' -exec rm -fr {} + clean-pyc: find . -name '*.pyc' -exec rm -f {} + find . -name '*.pyo' -exec rm -f {} + find . -name '*~' -exec rm -f {} + find . -name '__pycache__' -exec rm -fr {} + clean-test: rm -fr .cache/ rm -fr .tox/ rm -f .coverage rm -fr htmlcov/ compile-catalog: for loc in django_extensions/locale/*; do \ python setup.py compile_catalog --directory django_extensions/locale/ --locale $$(basename $$loc) --domain django || exit 1; \ done test: pytest django_extensions tests coverage: test coverage report -i -m coverage html install: clean python setup.py install django-extensions-3.1.5/README.rst000066400000000000000000000106301414177705400166600ustar00rootroot00000000000000=================== Django Extensions =================== .. image:: https://img.shields.io/pypi/l/django-extensions.svg :target: https://raw.githubusercontent.com/django-extensions/django-extensions/master/LICENSE .. image:: https://github.com/django-extensions/django-extensions/actions/workflows/compile_catalog.yml/badge.svg :target: https://github.com/django-extensions/django-extensions/actions .. image:: https://github.com/django-extensions/django-extensions/actions/workflows/linters.yml/badge.svg :target: https://github.com/django-extensions/django-extensions/actions .. image:: https://github.com/django-extensions/django-extensions/actions/workflows/precommit.yml/badge.svg :target: https://github.com/django-extensions/django-extensions/actions .. image:: https://github.com/django-extensions/django-extensions/actions/workflows/pytest.yml/badge.svg :target: https://github.com/django-extensions/django-extensions/actions .. image:: https://github.com/django-extensions/django-extensions/actions/workflows/security.yml/badge.svg :target: https://github.com/django-extensions/django-extensions/actions .. image:: https://img.shields.io/pypi/v/django-extensions.svg :target: https://pypi.python.org/pypi/django-extensions/ :alt: Latest PyPI version .. image:: https://img.shields.io/pypi/wheel/django-extensions.svg :target: https://pypi.python.org/pypi/django-extensions/ :alt: Supports Wheel format .. image:: https://coveralls.io/repos/django-extensions/django-extensions/badge.svg?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. Getting Started =============== The easiest way to figure out what Django Extensions are all about is to watch the `excellent screencast by Eric Holscher`__ (`watch the video on vimeo`__). In a couple minutes Eric walks you through a half a dozen command extensions. There is also a `short screencast on GoDjango's Youtube Channel`__ to help show you even more. Requirements ============ Django Extensions requires Django 2.2 or later. Getting It ========== You can get Django Extensions by using pip:: $ pip install django-extensions If you want to install it from source, grab the git repository from GitHub and run setup.py:: $ git clone git://github.com/django-extensions/django-extensions.git $ cd django-extensions $ python setup.py install Installing It ============= To enable `django_extensions` in your project you need to add it to `INSTALLED_APPS` in your projects `settings.py` file:: INSTALLED_APPS = ( ... 'django_extensions', ... ) Using It ======== Generate (and view) a graphviz graph of app models:: $ python manage.py graph_models -a -o myapp_models.png Produce a tab-separated list of `(url_pattern, view_function, name)` tuples for a project:: $ python manage.py show_urls Check templates for rendering errors:: $ python manage.py validate_templates Run the enhanced django shell:: $ python manage.py shell_plus Run the enhanced django runserver, (requires Werkzeug install):: $ python manage.py runserver_plus Getting Involved ================ Open Source projects can always use more help. Fixing a problem, documenting a feature, adding translation in your language. If you have some time to spare and like to help us, here are the places to do so: - GitHub: https://github.com/django-extensions/django-extensions - Mailing list: http://groups.google.com/group/django-extensions - Translations: https://www.transifex.net/projects/p/django-extensions/ Documentation ============= You can view documentation online at: - https://django-extensions.readthedocs.io Or you can look at the docs/ directory in the repository. Support ======= Django Extensions is free and always will be. It is developed and maintained by developers in an Open Source manner. Any support is welcome. You could help by writing documentation, pull-requests, report issues and/or translations. Please remember that nobody is paid directly to develop or maintain Django Extensions so we do have to divide our time between putting food on the table, family, this project and the rest of life :-) __ http://ericholscher.com/blog/2008/sep/12/screencast-django-command-extensions/ __ http://vimeo.com/1720508 __ https://www.youtube.com/watch?v=1F6G3ONhr4k django-extensions-3.1.5/django_extensions/000077500000000000000000000000001414177705400207125ustar00rootroot00000000000000django-extensions-3.1.5/django_extensions/__init__.py000066400000000000000000000014271414177705400230270ustar00rootroot00000000000000# -*- coding: utf-8 -*- VERSION = (3, 1, 5) def get_version(version): """Dynamically calculate the version based on VERSION tuple.""" if len(version) > 2 and version[2] is not None: if len(version) == 4: str_version = "%s.%s.%s.%s" % version elif isinstance(version[2], int): str_version = "%s.%s.%s" % version[:3] else: str_version = "%s.%s_%s" % version[:3] else: str_version = "%s.%s" % version[:2] return str_version __version__ = get_version(VERSION) try: import django if django.VERSION < (3, 2): default_app_config = 'django_extensions.apps.DjangoExtensionsConfig' except ModuleNotFoundError: # this part is useful for allow setup.py to be used for version checks pass django-extensions-3.1.5/django_extensions/admin/000077500000000000000000000000001414177705400220025ustar00rootroot00000000000000django-extensions-3.1.5/django_extensions/admin/__init__.py000066400000000000000000000153051414177705400241170ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Autocomplete feature for admin panel # import operator from functools import update_wrapper, reduce from typing import Tuple, Dict, Callable # NOQA from django.apps import apps from django.http import HttpResponse, HttpResponseNotFound from django.conf import settings from django.db import models from django.db.models.query import QuerySet from django.utils.encoding import smart_str from django.utils.translation import gettext as _ from django.utils.text import get_text_list from django.contrib import admin from django_extensions.admin.widgets import ForeignKeySearchInput class ForeignKeyAutocompleteAdminMixin: """ Admin class for models using the autocomplete feature. There are two additional fields: - related_search_fields: defines fields of managed model that have to be represented by autocomplete input, together with a list of target model fields that are searched for input string, e.g.: related_search_fields = { 'author': ('first_name', 'email'), } - related_string_functions: contains optional functions which take target model instance as only argument and return string representation. By default __unicode__() method of target object is used. And also an optional additional field to set the limit on the results returned by the autocomplete query. You can set this integer value in your settings file using FOREIGNKEY_AUTOCOMPLETE_LIMIT or you can set this per ForeignKeyAutocompleteAdmin basis. If any value is set the results will not be limited. """ related_search_fields = {} # type: Dict[str, Tuple[str]] related_string_functions = {} # type: Dict[str, Callable] autocomplete_limit = getattr(settings, 'FOREIGNKEY_AUTOCOMPLETE_LIMIT', None) def get_urls(self): from django.urls import path def wrap(view): def wrapper(*args, **kwargs): return self.admin_site.admin_view(view)(*args, **kwargs) return update_wrapper(wrapper, view) return [ path('foreignkey_autocomplete/', wrap(self.foreignkey_autocomplete), name='%s_%s_autocomplete' % (self.model._meta.app_label, self.model._meta.model_name)) ] + super().get_urls() def foreignkey_autocomplete(self, request): """ Search in the fields of the given related model and returns the result as a simple string to be used by the jQuery Autocomplete plugin """ query = request.GET.get('q', None) app_label = request.GET.get('app_label', None) model_name = request.GET.get('model_name', None) search_fields = request.GET.get('search_fields', None) object_pk = request.GET.get('object_pk', None) try: to_string_function = self.related_string_functions[model_name] except KeyError: to_string_function = lambda x: x.__str__() if search_fields and app_label and model_name and (query or object_pk): def construct_search(field_name): # use different lookup methods depending on the notation if field_name.startswith('^'): return "%s__istartswith" % field_name[1:] elif field_name.startswith('='): return "%s__iexact" % field_name[1:] elif field_name.startswith('@'): return "%s__search" % field_name[1:] else: return "%s__icontains" % field_name model = apps.get_model(app_label, model_name) queryset = model._default_manager.all() data = '' if query: for bit in query.split(): or_queries = [models.Q(**{construct_search(smart_str(field_name)): smart_str(bit)}) for field_name in search_fields.split(',')] other_qs = QuerySet(model) other_qs.query.select_related = queryset.query.select_related 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] data = ''.join([str('%s|%s\n') % (to_string_function(f), f.pk) for f in queryset]) elif object_pk: try: obj = queryset.get(pk=object_pk) except Exception: # FIXME: use stricter exception checking pass else: data = to_string_function(obj) return HttpResponse(data, content_type='text/plain') 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. """ return None def get_help_text(self, field_name, model_name): searchable_fields = self.related_search_fields.get(field_name, None) if searchable_fields: help_kwargs = { 'model_name': model_name, 'field_list': get_text_list(searchable_fields, _('and')), } return _('Use the left field to do %(model_name)s lookups in the fields %(field_list)s.') % help_kwargs return '' def formfield_for_dbfield(self, db_field, **kwargs): """ Override the default widget for Foreignkey fields if they are specified in the related_search_fields class attribute. """ if isinstance(db_field, models.ForeignKey) and db_field.name in self.related_search_fields: help_text = self.get_help_text(db_field.name, db_field.remote_field.model._meta.object_name) if kwargs.get('help_text'): help_text = str('%s %s') % (kwargs['help_text'], help_text) kwargs['widget'] = ForeignKeySearchInput(db_field.remote_field, self.related_search_fields[db_field.name]) kwargs['help_text'] = help_text return super().formfield_for_dbfield(db_field, **kwargs) class ForeignKeyAutocompleteAdmin(ForeignKeyAutocompleteAdminMixin, admin.ModelAdmin): pass class ForeignKeyAutocompleteTabularInline(ForeignKeyAutocompleteAdminMixin, admin.TabularInline): pass class ForeignKeyAutocompleteStackedInline(ForeignKeyAutocompleteAdminMixin, admin.StackedInline): pass django-extensions-3.1.5/django_extensions/admin/filter.py000066400000000000000000000034331414177705400236440ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django.contrib.admin import FieldListFilter from django.contrib.admin.utils import prepare_lookup_value from django.utils.translation import gettext_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().__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')), ) django-extensions-3.1.5/django_extensions/admin/widgets.py000066400000000000000000000061671414177705400240340ustar00rootroot00000000000000# -*- coding: utf-8 -*- import urllib 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.templatetags.static import static from django.urls import reverse from django.utils.safestring import mark_safe from django.utils.text import Truncator class ForeignKeySearchInput(ForeignKeyRawIdWidget): """ Widget for displaying ForeignKeys in an autocomplete search input instead in a django-extensions-3.1.5/django_extensions/templatetags/000077500000000000000000000000001414177705400234045ustar00rootroot00000000000000django-extensions-3.1.5/django_extensions/templatetags/__init__.py000066400000000000000000000000001414177705400255030ustar00rootroot00000000000000django-extensions-3.1.5/django_extensions/templatetags/debugger_tags.py000066400000000000000000000011241414177705400265560ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Make debugging Django templates easier. Example: {% load debugger_tags %} {{ object|ipdb }} """ from django import template register = template.Library() @register.filter def ipdb(obj): # pragma: no cover """Interactive Python debugger filter.""" __import__('ipdb').set_trace() return obj @register.filter def pdb(obj): """Python debugger filter.""" __import__('pdb').set_trace() return obj @register.filter def wdb(obj): # pragma: no cover """Web debugger filter.""" __import__('wdb').set_trace() return obj django-extensions-3.1.5/django_extensions/templatetags/highlighting.py000066400000000000000000000062301414177705400264240ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Similar to syntax_color.py but this is intended more for being able to copy+paste actual code into your Django templates without needing to escape or anything crazy. http://lobstertech.com/2008/aug/30/django_syntax_highlight_template_tag/ Example: {% load highlighting %}

check out this code

{% highlight 'python' 'Excerpt: blah.py' %} def need_food(self): print("Love is than &death&") {% endhighlight %} """ from django import template from django.template import ( Context, Node, Template, TemplateSyntaxError, Variable, ) from django.template.defaultfilters import stringfilter from django.utils.safestring import mark_safe try: from pygments import highlight as pyghighlight from pygments.lexers import get_lexer_by_name from pygments.formatters import HtmlFormatter HAS_PYGMENTS = True except ImportError: # pragma: no cover HAS_PYGMENTS = False register = template.Library() @register.filter(is_safe=True) @stringfilter def parse_template(value): return mark_safe(Template(value).render(Context())) class CodeNode(Node): def __init__(self, language, nodelist, name=''): self.language = Variable(language) self.nodelist = nodelist if name: self.name = Variable(name) else: self.name = None def render(self, context): code = self.nodelist.render(context).strip() lexer = get_lexer_by_name(self.language.resolve(context)) formatter = HtmlFormatter(linenos=False) html = "" if self.name: name = self.name.resolve(context) html = '
%s
' % name return html + pyghighlight(code, lexer, formatter) @register.tag def highlight(parser, token): """ Tag to put a highlighted source code
 block in your code.
    This takes two arguments, the language and a little explaination message
    that will be generated before the code.  The second argument is optional.

    Your code will be fed through pygments so you can use any language it
    supports.

    Usage::

      {% load highlighting %}
      {% highlight 'python' 'Excerpt: blah.py' %}
      def need_food(self):
          print("Love is colder than death")
      {% endhighlight %}

    """
    if not HAS_PYGMENTS:  # pragma: no cover
        raise ImportError("Please install 'pygments' library to use highlighting.")
    nodelist = parser.parse(('endhighlight',))
    parser.delete_first_token()
    bits = token.split_contents()[1:]
    if len(bits) < 1:
        raise TemplateSyntaxError("'highlight' statement requires an argument")
    return CodeNode(bits[0], nodelist, *bits[1:])
django-extensions-3.1.5/django_extensions/templatetags/indent_text.py000066400000000000000000000033271414177705400263100ustar00rootroot00000000000000# -*- coding: utf-8 -*-
from django import template

register = template.Library()


class IndentByNode(template.Node):
    def __init__(self, nodelist, indent_level, if_statement):
        self.nodelist = nodelist
        self.indent_level = template.Variable(indent_level)
        if if_statement:
            self.if_statement = template.Variable(if_statement)
        else:
            self.if_statement = None

    def render(self, context):
        indent_level = self.indent_level.resolve(context)
        if self.if_statement:
            try:
                if_statement = bool(self.if_statement.resolve(context))
            except template.VariableDoesNotExist:
                if_statement = False
        else:
            if_statement = True
        output = self.nodelist.render(context)
        if if_statement:
            indent = " " * indent_level
            output = indent + indent.join(output.splitlines(True))
        return output


@register.tag
def indentby(parser, token):
    """
    Add indentation to text between the tags by the given indentation level.

    {% indentby  [if ] %}
    ...
    {% endindentby %}

    Arguments:
      indent_level - Number of spaces to indent text with.
      statement - Only apply indent_level if the boolean statement evalutates to True.
    """
    args = token.split_contents()
    largs = len(args)
    if largs not in (2, 4):
        raise template.TemplateSyntaxError("indentby tag requires 1 or 3 arguments")
    indent_level = args[1]
    if_statement = None
    if largs == 4:
        if_statement = args[3]
    nodelist = parser.parse(('endindentby', ))
    parser.delete_first_token()
    return IndentByNode(nodelist, indent_level, if_statement)
django-extensions-3.1.5/django_extensions/templatetags/syntax_color.py000066400000000000000000000061431414177705400265060ustar00rootroot00000000000000# -*- coding: utf-8 -*-
r"""
Template filter for rendering a string with syntax highlighting.
It relies on Pygments to accomplish this.

Some standard usage examples (from within Django templates).
Coloring a string with the Python lexer:

    {% load syntax_color %}
    {{ code_string|colorize:"python" }}

You may use any lexer in Pygments. The complete list of which
can be found [on the Pygments website][1].

[1]: http://pygments.org/docs/lexers/

You may also have Pygments attempt to guess the correct lexer for
a particular string. However, if may not be able to choose a lexer,
in which case it will simply return the string unmodified. This is
less efficient compared to specifying the lexer to use.

    {{ code_string|colorize }}

You may also render the syntax highlighed text with line numbers.

    {% load syntax_color %}
    {{ some_code|colorize_table:"html+django" }}
    {{ let_pygments_pick_for_this_code|colorize_table }}

Please note that before you can load the ``syntax_color`` template filters
you will need to add the ``django_extensions.utils`` application to the
``INSTALLED_APPS``setting in your project's ``settings.py`` file.
"""
import os

from django import template
from django.template.defaultfilters import stringfilter
from django.utils.safestring import mark_safe

try:
    from pygments import highlight
    from pygments.formatters import HtmlFormatter
    from pygments.lexers import get_lexer_by_name, guess_lexer, ClassNotFound
    HAS_PYGMENTS = True
except ImportError:  # pragma: no cover
    HAS_PYGMENTS = False

__author__ = 'Will Larson '


register = template.Library()


def pygments_required(func):
    """Raise ImportError if pygments is not installed."""
    def wrapper(*args, **kwargs):
        if not HAS_PYGMENTS:  # pragma: no cover
            raise ImportError(
                "Please install 'pygments' library to use syntax_color.")
        rv = func(*args, **kwargs)
        return rv
    return wrapper


@pygments_required
@register.simple_tag
def pygments_css():
    return HtmlFormatter().get_style_defs('.highlight')


def generate_pygments_css(path=None):
    path = os.path.join(path or os.getcwd(), 'pygments.css')
    f = open(path, 'w')
    f.write(pygments_css())
    f.close()


def get_lexer(value, arg):
    if arg is None:
        return guess_lexer(value)
    return get_lexer_by_name(arg)


@pygments_required
@register.filter(name='colorize')
@stringfilter
def colorize(value, arg=None):
    try:
        return mark_safe(highlight(value, get_lexer(value, arg), HtmlFormatter()))
    except ClassNotFound:
        return value


@pygments_required
@register.filter(name='colorize_table')
@stringfilter
def colorize_table(value, arg=None):
    try:
        return mark_safe(highlight(value, get_lexer(value, arg), HtmlFormatter(linenos='table')))
    except ClassNotFound:
        return value


@pygments_required
@register.filter(name='colorize_noclasses')
@stringfilter
def colorize_noclasses(value, arg=None):
    try:
        return mark_safe(highlight(value, get_lexer(value, arg), HtmlFormatter(noclasses=True)))
    except ClassNotFound:
        return value
django-extensions-3.1.5/django_extensions/templatetags/widont.py000066400000000000000000000036051414177705400252660ustar00rootroot00000000000000# -*- coding: utf-8 -*-
import re

from django.template import Library
from django.utils.encoding import force_str


register = Library()
re_widont = re.compile(r'\s+(\S+\s*)$')
re_widont_html = re.compile(r'([^<>\s])\s+([^<>\s]+\s*)(]*>|$)', re.IGNORECASE)


@register.filter
def widont(value, count=1):
    """
    Add an HTML non-breaking space between the final two words of the string to
    avoid "widowed" words.

    Examples:

    >>> print(widont('Test   me   out'))
    Test   me out

    >>> print("'",widont('It works with trailing spaces too  '), "'")
    ' It works with trailing spaces too   '

    >>> print(widont('NoEffect'))
    NoEffect
    """
    def replace(matchobj):
        return force_str(' %s' % matchobj.group(1))
    for i in range(count):
        value = re_widont.sub(replace, force_str(value))
    return value


@register.filter
def widont_html(value):
    """
    Add an HTML non-breaking space between the final two words at the end of
    (and in sentences just outside of) block level tags to avoid "widowed"
    words.

    Examples:

    >>> print(widont_html('

Here is a simple example

Single

'))

Here is a simple example

Single

>>> print(widont_html('

test me
out

Ok?

Not in a p

and this

'))

test me
out

Ok?

Not in a p

and this

>>> print(widont_html('leading text

test me out

trailing text')) leading text

test me out

trailing text """ def replace(matchobj): return force_str('%s %s%s' % matchobj.groups()) return re_widont_html.sub(replace, force_str(value)) if __name__ == "__main__": def _test(): import doctest doctest.testmod() _test() django-extensions-3.1.5/django_extensions/utils/000077500000000000000000000000001414177705400220525ustar00rootroot00000000000000django-extensions-3.1.5/django_extensions/utils/__init__.py000066400000000000000000000001061414177705400241600ustar00rootroot00000000000000# -*- coding: utf-8 -*- from .internal_ips import InternalIPS # NOQA django-extensions-3.1.5/django_extensions/utils/deprecation.py000066400000000000000000000002331414177705400247170ustar00rootroot00000000000000# -*- coding: utf-8 -*- class MarkedForDeprecationWarning(DeprecationWarning): pass class RemovedInNextVersionWarning(DeprecationWarning): pass django-extensions-3.1.5/django_extensions/utils/dia2django.py000066400000000000000000000237011414177705400244310ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Author Igor Támara igor@tamarapatino.org Use this little program as you wish, if you include it in your work, let others know you are using it preserving this note, you have the right to make derivative works, Use it at your own risk. Tested to work on(etch testing 13-08-2007): Python 2.4.4 (#2, Jul 17 2007, 11:56:54) [GCC 4.1.3 20070629 (prerelease) (Debian 4.1.2-13)] on linux2 """ import codecs import gzip import re import sys from xml.dom.minidom import Node, parseString dependclasses = ["User", "Group", "Permission", "Message"] # Type dictionary translation types SQL -> Django tsd = { "text": "TextField", "date": "DateField", "varchar": "CharField", "int": "IntegerField", "float": "FloatField", "serial": "AutoField", "boolean": "BooleanField", "numeric": "FloatField", "timestamp": "DateTimeField", "bigint": "IntegerField", "datetime": "DateTimeField", "time": "TimeField", "bool": "BooleanField", } # convert varchar -> CharField v2c = re.compile(r'varchar\((\d+)\)') def find_index(fks, id_): """ Look for the id on fks, fks is an array of arrays, each array has on [1] the id of the class in a dia diagram. When not present returns None, else it returns the position of the class with id on fks """ for i, _ in fks.items(): if fks[i][1] == id_: return i return None def addparentstofks(rels, fks): """ Get a list of relations, between parents and sons and a dict of clases named in dia, and modifies the fks to add the parent as fk to get order on the output of classes and replaces the base class of the son, to put the class parent name. """ for j in rels: son = find_index(fks, j[1]) parent = find_index(fks, j[0]) fks[son][2] = fks[son][2].replace("models.Model", parent) if parent not in fks[son][0]: fks[son][0].append(parent) def dia2django(archivo): models_txt = '' f = codecs.open(archivo, "rb") # dia files are gzipped data = gzip.GzipFile(fileobj=f).read() ppal = parseString(data) # diagram -> layer -> object -> UML - Class -> name, (attribs : composite -> name,type) datos = ppal.getElementsByTagName("dia:diagram")[0].getElementsByTagName("dia:layer")[0].getElementsByTagName("dia:object") clases = {} herit = [] imports = str("") for i in datos: # Look for the classes if i.getAttribute("type") == "UML - Class": myid = i.getAttribute("id") for j in i.childNodes: if j.nodeType == Node.ELEMENT_NODE and j.hasAttributes(): if j.getAttribute("name") == "name": actclas = j.getElementsByTagName("dia:string")[0].childNodes[0].data[1:-1] myname = "\nclass %s(models.Model) :\n" % actclas clases[actclas] = [[], myid, myname, 0] if j.getAttribute("name") == "attributes": for ll in j.getElementsByTagName("dia:composite"): if ll.getAttribute("type") == "umlattribute": # Look for the attribute name and type for k in ll.getElementsByTagName("dia:attribute"): if k.getAttribute("name") == "name": nc = k.getElementsByTagName("dia:string")[0].childNodes[0].data[1:-1] elif k.getAttribute("name") == "type": tc = k.getElementsByTagName("dia:string")[0].childNodes[0].data[1:-1] elif k.getAttribute("name") == "value": val = k.getElementsByTagName("dia:string")[0].childNodes[0].data[1:-1] if val == '##': val = '' elif k.getAttribute("name") == "visibility" and k.getElementsByTagName("dia:enum")[0].getAttribute("val") == "2": if tc.replace(" ", "").lower().startswith("manytomanyfield("): # If we find a class not in our model that is marked as being to another model newc = tc.replace(" ", "")[16:-1] if dependclasses.count(newc) == 0: dependclasses.append(newc) if tc.replace(" ", "").lower().startswith("foreignkey("): # If we find a class not in our model that is marked as being to another model newc = tc.replace(" ", "")[11:-1] if dependclasses.count(newc) == 0: dependclasses.append(newc) # Mapping SQL types to Django varch = v2c.search(tc) if tc.replace(" ", "").startswith("ManyToManyField("): myfor = tc.replace(" ", "")[16:-1] if actclas == myfor: # In case of a recursive type, we use 'self' tc = tc.replace(myfor, "'self'") elif clases[actclas][0].count(myfor) == 0: # Adding related class if myfor not in dependclasses: # In case we are using Auth classes or external via protected dia visibility clases[actclas][0].append(myfor) tc = "models." + tc if len(val) > 0: tc = tc.replace(")", "," + val + ")") elif tc.find("Field") != -1: if tc.count("()") > 0 and len(val) > 0: tc = "models.%s" % tc.replace(")", "," + val + ")") else: tc = "models.%s(%s)" % (tc, val) elif tc.replace(" ", "").startswith("ForeignKey("): myfor = tc.replace(" ", "")[11:-1] if actclas == myfor: # In case of a recursive type, we use 'self' tc = tc.replace(myfor, "'self'") elif clases[actclas][0].count(myfor) == 0: # Adding foreign classes if myfor not in dependclasses: # In case we are using Auth classes clases[actclas][0].append(myfor) tc = "models." + tc if len(val) > 0: tc = tc.replace(")", "," + val + ")") elif varch is None: tc = "models." + tsd[tc.strip().lower()] + "(" + val + ")" else: tc = "models.CharField(max_length=" + varch.group(1) + ")" if len(val) > 0: tc = tc.replace(")", ", " + val + " )") if not (nc == "id" and tc == "AutoField()"): clases[actclas][2] += " %s = %s\n" % (nc, tc) elif i.getAttribute("type") == "UML - Generalization": mycons = ['A', 'A'] a = i.getElementsByTagName("dia:connection") for j in a: if len(j.getAttribute("to")): mycons[int(j.getAttribute("handle"))] = j.getAttribute("to") print(mycons) if 'A' not in mycons: herit.append(mycons) elif i.getAttribute("type") == "UML - SmallPackage": a = i.getElementsByTagName("dia:string") for j in a: if len(j.childNodes[0].data[1:-1]): imports += str("from %s.models import *" % j.childNodes[0].data[1:-1]) addparentstofks(herit, clases) # Ordering the appearance of classes # First we make a list of the classes each classs is related to. ordered = [] for j, k in clases.items(): k[2] += "\n def __str__(self):\n return u\"\"\n" for fk in k[0]: if fk not in dependclasses: clases[fk][3] += 1 ordered.append([j] + k) i = 0 while i < len(ordered): mark = i j = i + 1 while j < len(ordered): if ordered[i][0] in ordered[j][1]: mark = j j += 1 if mark == i: i += 1 else: # swap %s in %s" % ( ordered[i] , ordered[mark]) to make ordered[i] to be at the end if ordered[i][0] in ordered[mark][1] and ordered[mark][0] in ordered[i][1]: # Resolving simplistic circular ForeignKeys print("Not able to resolve circular ForeignKeys between %s and %s" % (ordered[i][1], ordered[mark][0])) break a = ordered[i] ordered[i] = ordered[mark] ordered[mark] = a if i == len(ordered) - 1: break ordered.reverse() if imports: models_txt = str(imports) for i in ordered: models_txt += '%s\n' % str(i[3]) return models_txt if __name__ == '__main__': if len(sys.argv) == 2: dia2django(sys.argv[1]) else: print(" Use:\n \n " + sys.argv[0] + " diagram.dia\n\n") django-extensions-3.1.5/django_extensions/utils/internal_ips.py000066400000000000000000000036401414177705400251160ustar00rootroot00000000000000# -*- coding: utf-8 -*- from collections.abc import Container import ipaddress import itertools class InternalIPS(Container): """ InternalIPS allows to specify CIDRs for INTERNAL_IPS. It takes an iterable of ip addresses or ranges. Inspiration taken from netaddr.IPSet, please use it if you can since it support more advanced features like optimizing ranges and lookups. """ __slots__ = ["_cidrs"] def __init__(self, iterable, sort_by_size=False): """ Constructor. :param iterable: (optional) an iterable containing IP addresses and subnets. :param sort_by_size: sorts internal list according to size of ip ranges, largest first. """ self._cidrs = [] for address in iterable: self._cidrs.append(ipaddress.ip_network(address)) if sort_by_size: self._cidrs = sorted(self._cidrs) def __contains__(self, address): """ :param ip: An IP address or subnet. :return: ``True`` if IP address or subnet is a member of this InternalIPS set. """ address = ipaddress.ip_address(address) for cidr in self._cidrs: if address in cidr: return True return False def __hash__(self): """ Raises ``TypeError`` if this method is called. """ raise TypeError('InternalIPS containers are unhashable!') def __len__(self): """ :return: the cardinality of this InternalIPS set. """ return sum(cidr.num_addresses for cidr in self._cidrs) def __iter__(self): """ :return: an iterator over the IP addresses within this IP set. """ return itertools.chain(*self._cidrs) def iter_cidrs(self): """ :return: an iterator over individual IP subnets within this IP set. """ return sorted(self._cidrs) django-extensions-3.1.5/django_extensions/validators.py000066400000000000000000000072521414177705400234420ustar00rootroot00000000000000# -*- coding: utf-8 -*- import unicodedata import binascii from django.core.exceptions import ValidationError from django.utils.deconstruct import deconstructible from django.utils.encoding import force_str from django.utils.translation import gettext_lazy as _ @deconstructible class NoControlCharactersValidator: message = _("Control Characters like new lines or tabs are not allowed.") code = "no_control_characters" whitelist = None def __init__(self, message=None, code=None, whitelist=None): if message: self.message = message if code: self.code = code if whitelist: self.whitelist = whitelist def __call__(self, value): value = force_str(value) whitelist = self.whitelist category = unicodedata.category for character in value: if whitelist and character in whitelist: continue if category(character)[0] == "C": params = {'value': value, 'whitelist': whitelist} raise ValidationError(self.message, code=self.code, params=params) def __eq__(self, other): return ( isinstance(other, NoControlCharactersValidator) and (self.whitelist == other.whitelist) and (self.message == other.message) and (self.code == other.code) ) @deconstructible class NoWhitespaceValidator: message = _("Leading and Trailing whitespaces are not allowed.") code = "no_whitespace" def __init__(self, message=None, code=None, whitelist=None): if message: self.message = message if code: self.code = code def __call__(self, value): value = force_str(value) if value != value.strip(): params = {'value': value} raise ValidationError(self.message, code=self.code, params=params) def __eq__(self, other): return ( isinstance(other, NoWhitespaceValidator) and (self.message == other.message) and (self.code == other.code) ) @deconstructible class HexValidator: messages = { 'invalid': _("Only a hex string is allowed."), 'length': _("Invalid length. Must be %(length)d characters."), 'min_length': _("Ensure that there are more than %(min)s characters."), 'max_length': _("Ensure that there are no more than %(max)s characters."), } code = "hex_only" def __init__(self, length=None, min_length=None, max_length=None, message=None, code=None): self.length = length self.min_length = min_length self.max_length = max_length if message: self.message = message if code: self.code = code def __call__(self, value): value = force_str(value) if self.length and len(value) != self.length: raise ValidationError(self.messages['length'], code='hex_only_length', params={'length': self.length}) if self.min_length and len(value) < self.min_length: raise ValidationError(self.messages['min_length'], code='hex_only_min_length', params={'min': self.min_length}) if self.max_length and len(value) < self.max_length: raise ValidationError(self.messages['max_length'], code='hex_only_max_length', params={'max': self.max_length}) try: binascii.unhexlify(value) except (TypeError, binascii.Error): raise ValidationError(self.messages['invalid'], code='hex_only') def __eq__(self, other): return ( isinstance(other, HexValidator) and (self.message == other.message) and (self.code == other.code) ) django-extensions-3.1.5/docs/000077500000000000000000000000001414177705400161215ustar00rootroot00000000000000django-extensions-3.1.5/docs/AUTHORS000066400000000000000000000017771414177705400172050ustar00rootroot00000000000000The following individuals have contributed to this project Antonio Cavedoni - For the GraphViz stuff (http://cavedoni.com/) Ludvig Ericson (toxic) Collin Grady (magus) Gabriel Grant (gabrielgrant) Rob Hudson (robhudson) Jannis Leidel (leidel) Brian Rosner (brosner) Michael Trier (empty) Doug Napoleone (dougn) Bas van Oostveen (trbs) David Krauth (dakrauth) Will Larson (lethain) - syntax_color template filters. Patrick Altman (paltman) - patched sync_media_s3 Chris Beaven (smileychris) - widont filter qMax - various graph_model patches Tyson Clugg (tclugg) - patched sqldiff Domen Kožar (iElectric) - staticfiles patch improvement Ceesjan Luiten (quinox) - original staticfiles patch Camilo Nova (camilonova) Wiktor Kołodziej (viciu) Marc Tudurí (marctc) Rick van Hattem (WoLpH) Rodolphe Quiédeville (rodo) Nik Nyby (nikolas) Joshua Miller (thecardcheat) Piotr Domański (domandinho) Please see https://github.com/django-extensions/django-extensions/graphs/contributors for a more complete list of contributors. django-extensions-3.1.5/docs/Makefile000066400000000000000000000045071414177705400175670ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d _build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html web pickle htmlhelp latex changes linkcheck help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " changes to make an overview over all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" clean: -rm -rf _build/* html: mkdir -p _build/html _build/doctrees _static $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html @echo @echo "Build finished. The HTML pages are in _build/html." pickle: mkdir -p _build/pickle _build/doctrees $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) _build/pickle @echo @echo "Build finished; now you can process the pickle files." web: pickle json: mkdir -p _build/json _build/doctrees $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) _build/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: mkdir -p _build/htmlhelp _build/doctrees $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) _build/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in _build/htmlhelp." latex: mkdir -p _build/latex _build/doctrees $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex @echo @echo "Build finished; the LaTeX files are in _build/latex." @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ "run these through (pdf)latex." changes: mkdir -p _build/changes _build/doctrees $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) _build/changes @echo @echo "The overview file is in _build/changes." linkcheck: mkdir -p _build/linkcheck _build/doctrees $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) _build/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in _build/linkcheck/output.txt." django-extensions-3.1.5/docs/admin_extensions.rst000066400000000000000000000063751414177705400222350ustar00rootroot00000000000000Admin Extensions ================ :synopsis: Admin Extensions * *ForeignKeyAutocompleteAdmin* - ForeignKeyAutocompleteAdmin will enable the admin app to show ForeignKey fields with an search input field. The search field is rendered by the ForeignKeySearchInput form widget and uses jQuery to do configurable autocompletion. * *ForeignKeyAutocompleteStackedInline*, *ForeignKeyAutocompleteTabularInline* - in the same fashion of the *ForeignKeyAutocompleteAdmin* these two classes enable a search input field for ForeignKey fields in AdminInline classes. Depreciation ------------ Django 2.0 now contains similar functionality as *ForeignKeyAutocompleteAdmin* therefore we are deprecating this extension and highly encouraging everyone to update to it. This code will be removed in the near future when support for Django older then 2.0 is dropped. More on this: https://docs.djangoproject.com/en/2.0/ref/contrib/admin/#django.contrib.admin.ModelAdmin.autocomplete_fields Known Issues ------------ * SECURITY ISSUE: Autocompletion does not check permissions nor the requested models on the autocompletion view. This can be used by users with access to the admin to expose data from other models. Please be aware and careful when using *ForeignKeyAutocompleteAdmin*. * The current version of the *ForeignKeyAutocompleteAdmin* has issues with recent Django versions. * We strongly suggest project using this extension to update to Django 2.0 and use the native *autocomplete_fields*. Example Usage ------------- To enable the Admin Autocomplete you can follow this code example in your admin.py file: :: from django.contrib import admin from foo.models import Permission from django_extensions.admin import ForeignKeyAutocompleteAdmin class PermissionAdmin(ForeignKeyAutocompleteAdmin): # User is your FK attribute in your model # first_name and email are attributes to search for in the FK model related_search_fields = { 'user': ('first_name', 'email'), } fields = ('user', 'avatar', 'is_active') ... admin.site.register(Permission, PermissionAdmin) If you are using django-reversion you should follow this code example: :: from django.contrib import admin from foo.models import MyVersionModel from reversion.admin import VersionAdmin from django_extensions.admin import ForeignKeyAutocompleteAdmin class MyVersionModelAdmin(VersionAdmin, ForeignKeyAutocompleteAdmin): ... admin.site.register(MyVersionModel, MyVersionModelAdmin) If you need to limit the autocomplete search, you can override the ``get_related_filter`` method of the admin. For example if you want to allow non-superusers to attach attachments only to articles they own you can use:: class AttachmentAdmin(ForeignKeyAutocompleteAdmin): ... def get_related_filter(self, model, request): user = request.user if not issubclass(model, Article) or user.is_superuser(): return super(AttachmentAdmin, self).get_related_filter( model, request ) return Q(owner=user) Note that this does not protect your application from malicious attempts to circumvent it (e.g. sending fabricated requests via cURL). django-extensions-3.1.5/docs/admin_generator.rst000066400000000000000000000070721414177705400220170ustar00rootroot00000000000000admin_generator =============== :synopsis: Generate automatic Django Admin classes by providing an app name. Outputs source code at STDOUT. Generating automatically the admin for a given app -------------------------------------------------- You have to provide the app_name you want the admin to be generated. :: $ python manage.py admin_generator Example ------- Given the app name "brody", with the models: :: from django.contrib.auth import get_user_model from django.contrib.auth.models import User from django.db import models from django.utils.translation import gettext_lazy as _ from isbn_field import ISBNField class Author(models.Model): first_name = models.CharField(max_length=30, verbose_name=_('First name')) last_name = models.CharField(max_length=40, verbose_name=_('Last name')) def __str__(self): return '{} {}'.format(self.first_name, self.last_name) class Meta: verbose_name = _('Author') verbose_name_plural = _('Authors') class Tag(models.Model): word = models.CharField(max_length=35, verbose_name=_('Word')) slug = models.CharField(max_length=50, verbose_name=_('Slug')) def __str__(self): return self.word class Meta: verbose_name = _('Tag') verbose_name_plural = _('Tags') class Book(models.Model): title = models.CharField(max_length=40, verbose_name=_('Title')) cover = models.ImageField(upload_to='book-covers', verbose_name=_('Cover'), blank=True) tags = models.ManyToManyField(Tag, verbose_name=_('Tags'), related_name='books') authors = models.ManyToManyField(Author, verbose_name=_('Authors'), related_name='books') publication_date = models.DateField(verbose_name=_('Publication date')) isbn = ISBNField(verbose_name=_('ISBN code')) def __str__(self): return self.title class Meta: verbose_name = _('Book') verbose_name_plural = _('Books') class Borrow(models.Model): user = models.OneToOneField(get_user_model(), verbose_name=_('Usuario'), on_delete=models.PROTECT) borrow_date = models.DateField(verbose_name=_('Borrow date')) returned_date = models.DateField(verbose_name=_('Returned date'), blank=True, null=True) book = models.ForeignKey(Book, verbose_name=_('Book'), on_delete=models.PROTECT) class Meta: verbose_name = _('Borrow') verbose_name_plural = _('Borrows') def __str__(self): return '{}_{}'.format(self.user, self.borrow_date) the following command: :: $ python manage.py admin_generator brody will output to STDOUT the following code: :: # -*- coding: utf-8 -*- from django.contrib import admin from .models import Author, Tag, Book, Borrow @admin.register(Author) class AuthorAdmin(admin.ModelAdmin): list_display = ('id', 'first_name', 'last_name') @admin.register(Tag) class TagAdmin(admin.ModelAdmin): list_display = ('id', 'word', 'slug') search_fields = ('slug',) @admin.register(Book) class BookAdmin(admin.ModelAdmin): list_display = ('id', 'title', 'cover', 'publication_date', 'isbn') list_filter = ('publication_date',) raw_id_fields = ('tags', 'authors') @admin.register(Borrow) class BorrowAdmin(admin.ModelAdmin): list_display = ('id', 'user', 'borrow_date', 'returned_date', 'book') list_filter = ('user', 'borrow_date', 'returned_date', 'book') django-extensions-3.1.5/docs/command_extensions.rst000066400000000000000000000151441414177705400225550ustar00rootroot00000000000000Command Extensions ================== :synopsis: Command Extensions .. toctree:: :maxdepth: 3 shell_plus create_template_tags delete_squashed_migrations dumpscript runscript export_emails generate_password graph_models list_model_info list_signals merge_model_instances print_settings reset_db runprofileserver runserver_plus sync_s3 syncdata sqldiff sqlcreate sqldsn validate_templates admin_generator * :doc:`shell_plus` - An enhanced version of the Django shell. It will autoload all your models making it easy to work with the ORM right away. * :doc:`admin_generator` - Generate automatic Django Admin classes by providing an app name. Outputs source code at STDOUT. * *clean_pyc* - Remove all python bytecode compiled files from the project * *create_command* - Creates a command extension directory structure within the specified application. This makes it easy to get started with adding a command extension to your application. * :doc:`create_template_tags` - Creates a template tag directory structure within the specified application. * *create_jobs* - Creates a Django jobs command directory structure for the given app name in the current directory. This is part of the impressive jobs system. * *clear_cache* - Clear django cache, useful when testing or deploying. * *compile_pyc* - Compile python bytecode files for the project. * *describe_form* - Used to display a form definition for a model. Copy and paste the contents into your forms.py and you're ready to go. * :doc:`delete_squashed_migrations` - Deletes leftover migrations after squashing and converts squashed migration to a normal one. * :doc:`dumpscript ` - Generates a Python script that will repopulate the database using objects. The advantage of this approach is that it is easy to understand, and more flexible than directly populating the database, or using XML. * `export_emails`_ - export the email addresses for your users in one of many formats. Currently supports Address, Google, Outlook, LinkedIn, and VCard formats. * *find_template* - Finds the location of the given template by resolving its path * *generate_secret_key* - Creates a new secret key that you can put in your settings.py module. * `graph_models`_ - Creates a GraphViz_ dot file. You need to send this output to a file yourself. Great for graphing your models. Pass multiple application names to combine all the models into a single dot file. * `list_model_info`_ - Lists out all the fields and methods for models in installed apps. This is helpful when you don't remember how to refer to a related field or want to quickly identify the fields and methods available in a particular model. * *mail_debug* - Starts a mail server which echos out the contents of the email instead of sending it. * :doc:`merge_model_instances` - Merges duplicate model instances by reassigning related model references to a chosen primary model instance. * *notes* - Show all annotations like TODO, FIXME, BUG, HACK, WARNING, NOTE or XXX in your py and HTML files. * *passwd* - DEPRECATED: Use Django's ``changepassword``. * *pipchecker* - Scan pip requirement file(s)s for out-of-date packages. Similar to ``pip list -o`` which used installed packages (in virtualenv) instead of requirements file(s). * `print_settings`_ - Similar to ``diffsettings`` but shows *selected* active Django settings or *all* if no args passed. * *print_user_for_session* - Print the user information for the provided session key. this is very helpful when trying to track down the person who experienced a site crash. It seems this works only if setting ``SESSION_ENGINE`` is ``'django.contrib.sessions.backends.db'`` (default value). * *drop_test_database* - Drops the test database. Usefull when running Django test via some automated system (BuildBot, Jenkins, etc) and making sure that the test database is always dropped at the end. * :doc:`reset_db` - Resets a database (currently sqlite3, mysql, postgres). Uses "DROP DATABASE" and "CREATE DATABASE". * *runjob* - Run a single maintenance job. Part of the jobs system. * *runjobs* - Runs scheduled maintenance jobs. Specify hourly, daily, weekly, monthly. Part of the jobs system. * :doc:`runprofileserver ` - Starts *runserver* with hotshot/profiling tools enabled. I haven't had a chance to check this one out, but it looks really cool. * `runscript`_ - Runs a script in the django context. * `runserver_plus`_ - The standard runserver stuff but with the Werkzeug debugger baked in. Requires Werkzeug_. This one kicks ass. * *set_fake_emails* - Give all users a new email based on their account data ("%(username)s@example.com" by default). Possible parameters are: username, first_name, last_name. *DEBUG only* * *set_fake_passwords* - Sets all user passwords to a common value (*password* by default). *DEBUG only*. * *show_template_tags* - Displays template tags and filters available in the current project. * *show_urls* - Displays the url routes that are defined in your project. Very crude at this point. * :doc:`sqldiff` - Prints the (approximated) difference between an app's models and what is in the database. This is very nice, but also very experimental at the moment. It can not catch everything but it's a great sanity check. * :doc:`sqlcreate` - Generates the SQL to create your database for you, as specified in settings.py. * :doc:`sqldsn` - Reads the Django settings and extracts the parameters needed to connect to databases using other programs. * `sync_s3`_ - Copies files found in settings.MEDIA_ROOT to S3. Optionally can also gzip CSS and Javascript files and set the Content-Encoding header, and also set a far future expires header for browser caching. * :doc:`syncdata` - Makes the current database have the same data as the fixture(s), no more, no less. * *unreferenced_files* - Prints a list of all files in MEDIA_ROOT that are not referenced in the database. * *update_permissions* - Reloads permissions for specified apps, or all apps if no args are specified. * :doc:`validate_templates` - Validate templates on syntax and compile errors. * *set_default_site* - Set parameters of the default `django.contrib.sites` Site using `name` and `domain` or `system-fqdn`. .. _`export_emails`: export_emails.html .. _`graph_models`: graph_models.html .. _`list_model_info`: list_model_info.html .. _`print_settings`: print_settings.html .. _`runscript`: runscript.html .. _`runserver_plus`: runserver_plus.html .. _`sync_s3`: sync_s3.html .. _GraphViz: http://www.graphviz.org/ .. _Werkzeug: http://werkzeug.pocoo.org/ django-extensions-3.1.5/docs/command_signals.rst000066400000000000000000000057031414177705400220160ustar00rootroot00000000000000Command Signals =============== :synopsis: Signals fired before and after a command is executed. A signal is thrown pre/post each management command allowing your application to hook into each commands execution. Basic Example ------------- An example hooking into show_template_tags: :: from django_extensions.management.signals import pre_command, post_command from django_extensions.management.commands.show_template_tags import Command def pre_receiver(sender, args, kwargs): # I'm executed prior to the management command def post_receiver(sender, args, kwargs, outcome): # I'm executed after the management command pre_command.connect(pre_receiver, Command) post_command.connect(post_receiver, Command) Custom Permissions For All Models --------------------------------- You can use the post signal to hook into the ``update_permissions`` command so that you can add your own permissions to each model. For instance, lets say you want to add ``list`` and ``view`` permissions to each model. You could do this by adding them to the ``permissions`` tuple inside your models ``Meta`` class but this gets pretty tedious. An easier solution is to hook into the ``update_permissions`` call, as follows; :: from django.db.models.signals import post_syncdb from django.contrib.contenttypes.models import ContentType from django.contrib.auth.models import Permission from django_extensions.management.signals import post_command from django_extensions.management.commands.update_permissions import Command as UpdatePermissionsCommand def add_permissions(sender, **kwargs): """ Add view and list permissions to all content types. """ # for each of our content types for content_type in ContentType.objects.all(): for action in ['view', 'list']: # build our permission slug codename = "%s_%s" % (action, content_type.model) try: Permission.objects.get(content_type=content_type, codename=codename) # Already exists, ignore except Permission.DoesNotExist: # Doesn't exist, add it Permission.objects.create(content_type=content_type, codename=codename, name="Can %s %s" % (action, content_type.name)) print "Added %s permission for %s" % (action, content_type.name) post_command.connect(add_permissions, UpdatePermissionsCommand) Each time ``update_permissions`` is called ``add_permissions`` will be called which ensures there are view and list permissions to all content types. Using pre/post signals on your own commands ------------------------------------------- The signals are implemented using a decorator on the handle method of a management command, thus using this functionality in your own application is trivial: :: from django_extensions.management.utils import signalcommand class Command(BaseCommand): @signalcommand def handle(self, *args, **kwargs): ... ... django-extensions-3.1.5/docs/conf.py000066400000000000000000000145061414177705400174260ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # django-extensions documentation build configuration file, created by # sphinx-quickstart on Wed Apr 1 20:39:40 2009. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.append(os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. # source_encoding = 'utf-8' # The master toctree document. master_doc = 'index' # General information about the project. project = 'django-extensions' copyright = 'Copyright (C) 2008-2015 Michael Trier, Bas van Oostveen and contributors' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '3.1' # The full version, including alpha/beta/rc tags. release = '3.1.5' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. # unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_use_modindex = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'django-extensionsdoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). # latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). # latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [( 'index', 'django-extensions.tex', 'django-extensions Documentation', 'Michael Trier, Bas van Oostveen, and contributors', 'manual' ), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # Additional stuff for the LaTeX preamble. # latex_preamble = '' # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_use_modindex = True django-extensions-3.1.5/docs/create_template_tags.rst000066400000000000000000000010261414177705400230260ustar00rootroot00000000000000create_template_tags ==================== :synopsis: Creates a template tag directory structure within the specified application. Usage ----- Create templatetags directory for *foobar* app:: $ python manage.py create_template_tags foobar it will create directory structure:: foobar/ __init__.py models.py templatetags/ __init__.py foobar_tags.py you can pass custom tags filename by providing ``--name`` argument:: $ python manage.py create_template_tags foobar --name custom_tags django-extensions-3.1.5/docs/creating_release.txt000066400000000000000000000020421414177705400221540ustar00rootroot00000000000000Creating a release ================== :synopsis: Creating a django-extensions release How to make a new release ------------------------- Get a fresh copy:: $ git clone git@github.com:django-extensions/django-extensions.git $ cd django-extensions Run tests:: $ flake8 django_extensions $ tox --recreate Change version numbers in django_extensions/__init__.py and docs/conf.py:: $ vi django_extensions/__init__.py (1 occurance) $ vi docs/conf.py (2 occurances) $ git commit -m v0.4.1 django_extensions/__init__.py docs/conf.py Tag it:: $ git tag 0.4.1 Remove old build directory (if exists):: $ rm -r build dist Prepare the release tarball:: $ python ./setup.py sdist bdist_wheel Upload release to pypi:: $ twine upload -i key-identity -s dist/* Bumb version number to new in-development pre version:: $ vi django_extensions/__init__.py $ git commit -m 'bumped version number' django_extensions/__init__.py Push changes back to github:: $ git push --tags $ git push django-extensions-3.1.5/docs/debugger_tags.rst000066400000000000000000000010171414177705400214540ustar00rootroot00000000000000Debugger Tags ============= :synopsis: Allows you to use debugger breakpoints on Django templates. Introduction ------------ These templatetags make debugging Django templates easier. You can choose between ipdb, pdb or wdb filters. Usage ----- Make sure that you load `debugger_tags`:: {% load debugger_tags %} Now, you're ready to use debugger filters inside a template:: {% for object in object_list %} {{ object|ipdb }} {% endfor %} When rendering the template an ipdb session will be started. django-extensions-3.1.5/docs/delete_squashed_migrations.rst000066400000000000000000000020661414177705400242520ustar00rootroot00000000000000delete_squashed_migrations ========================== :synopsis: Deletes leftover migrations after squashing and converts squashed migration to a normal one. Deletes leftover migrations after squashing and converts squashed migration to a normal one by removing the replaces attribute. This automates the clean up procedure outlined at the end of the `Django migration squashing documentation`__. Modifies your source tree! Use with care! __ MigrationSquashingDocs_ Example Usage ------------- With *django-extensions* installed you cleanup squashed migrations using the *delete_squashed_migrations* command:: # Delete leftover migrations from the first squashed migration found in myapp $ ./manage.py delete_squashed_migrations myapp # As above but non-interactive $ ./manage.py --noinput delete_squashed_migrations myapp # Explicitly specify the squashed migration to clean up $ ./manage.py delete_squashed_migrations myapp 0001_squashed .. _MigrationSquashingDocs: https://docs.djangoproject.com/en/dev/topics/migrations/#migration-squashing django-extensions-3.1.5/docs/dumpscript.rst000066400000000000000000000047341414177705400210550ustar00rootroot00000000000000dumpscript ========== :synopsis: Generates a standalone Python script that will repopulate the database using objects. The `dumpscript` command generates a standalone Python script that will repopulate the database using objects. The advantage of this approach is that it is easy to understand, and more flexible than directly populating the database, or using XML. Why? ---- There are a few benefits to this: * less drama with model evolution: foreign keys handled naturally without IDs, new and removed columns are ignored * edit script to create 1,000s of generated entries using for loops, generated names, python modules etc. For example, an edited script can populate the database with test data:: for i in xrange(2000): poll = Poll() poll.question = "Question #%d" % i poll.pub_date = date(2001,01,01) + timedelta(days=i) poll.save() Real databases will probably be bigger and more complicated so it is useful to enter some values using the admin interface and then edit the generated scripts. Features -------- * *ForeignKey* and *ManyToManyFields* (using python variables, not object IDs) * Self-referencing *ForeignKey* (and M2M) fields * Sub-classed models * *ContentType* fields and generic relationships * Recursive references * *AutoFields* are excluded * Parent models are only included when no other child model links to it * Individual models can be referenced How? ---- To dump the data from all the models in a given Django app (`appname`):: $ ./manage.py dumpscript appname > scripts/testdata.py To dump the data from just a single model (`appname.ModelName`):: $ ./manage.py dumpscript appname.ModelName > scripts/testdata.py To reset a given app, and reload with the saved data:: $ ./manage.py reset appname $ ./manage.py runscript testdata Note: Runscript needs *scripts* to be a module, so create the directory and a *__init__.py* file. Caveats ------- Naming conflicts ~~~~~~~~~~~~~~~~ Please take care that when naming the output files these filenames do not clash with other names in your import path. For instance, if the appname is the same as the script name, an importerror can occur because rather than importing the application modules it tries to load the modules from the dumpscript file itself. Examples:: # Wrong $ ./manage.py dumpscript appname > dumps/appname.py # Right $ ./manage.py dumpscript appname > dumps/appname_all.py # Right $ ./manage.py dumpscript appname.Somemodel > dumps/appname_somemodel.py django-extensions-3.1.5/docs/export_emails.rst000066400000000000000000000065361414177705400215400ustar00rootroot00000000000000export_emails ============= :synopsis: export the email addresses for your users in one of many formats Most Django sites include a registered user base. There are times when you would like to import these e-mail addresses into other systems (generic mail program, Gmail, Google Docs invites, give edit permissions, LinkedIn Group pre-approved listing, etc.). The export_emails command extension gives you this ability. Exported users can be filtered by Group name association. Example Usage ------------- :: # Export all the addresses in the '"First Last" ;' format. $ ./manage.py export_emails > addresses.txt :: # Export users from the group 'Attendees' in the linked in pre-approve Group csv format. $ ./manage.py export_emails -g Attendees -f linkedin pycon08.csv :: # Create a csv file importable by Gmail or Google Docs $ ./manage.py export_emails --format=google google.csv Supported Formats ----------------- address ^^^^^^^ This is the default basic text format. Each entry is on its own line in the format:: "First Last" ; This can be used with all known mail programs (that I know about anyway). google ^^^^^^ A CSV (comma separated value) format which Google applications can import. This can be used to import directly into Gmail, a Gmail mailing group, Google Docs invite (to read), Google Docs grant edit permissions, Google Calendar invites, etc. Only two columns are supplied. One for the person's name and one for the email address. This is also nice for importing into spreadsheets. outlook ^^^^^^^ A CSV (comma separated value) format which Outlook can parse and import. Supplies all the columns that Outlook 'requires', but only the name and email address are supplied. linkedin ^^^^^^^^ A CSV (comma separated value) format which can be imported by `LinkedIn Groups`_ to pre-approve a list of people for joining the group. This supplies 3 columns: first name, last name, and email address. This is the best generic csv file for importing into spreadsheets as well. vcard ^^^^^ A vCard format which Apple Address Book can parse and import. Settings -------- There are a couple of settings keys which can be configured in `settings.py`. Below the default values are shown: :: EXPORT_EMAILS_ORDER_BY = ['last_name', 'first_name', 'username', 'email'] EXPORT_EMAILS_FIELDS = ['last_name', 'first_name', 'username', 'email'] EXPORT_EMAILS_FULL_NAME_FUNC = None EXPORT_EMAILS_ORDER_BY ^^^^^^^^^^^^^^^^^^^^^^ Specifies the `order_by(...)` clause on the query being done into the database to retrieve the users. This determines the order of the output. EXPORT_EMAILS_FIELDS ^^^^^^^^^^^^^^^^^^^^ Specifies which fields will be selected from the database. This is most useful in combination with `EXPORT_EMAILS_FULL_NAME_FUNC` to select other fields you might want to use inside the custom function or when using a custom User model which does not have fields like 'first_name' and 'last_name'. EXPORT_EMAILS_FULL_NAME_FUNC ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A function to use to create a full name based on the database fields selected by `EXPORT_EMAILS_FULL_NAME_FUNC`. The default implementation can be looked up in https://github.com/django-extensions/django-extensions/blob/master/django_extensions/management/commands/export_emails.py#L23 .. _`LinkedIn Groups`: http://www.linkedin.com/static?key=groups_info django-extensions-3.1.5/docs/field_extensions.rst000066400000000000000000000113131414177705400222140ustar00rootroot00000000000000Field Extensions ================ :synopsis: Field Extensions Current Database Model Field Extensions --------------------------------------- * *AutoSlugField* - AutoSlugField will automatically create a unique slug incrementing an appended number on the slug until it is unique. Inspired by SmileyChris' Unique Slugify snippet. AutoSlugField takes a `populate_from` argument that specifies which field, list of fields, or model method the slug will be populated from, for instance:: slug = AutoSlugField(populate_from=['title', 'description', 'get_author_name']) `populate_from` can traverse a ForeignKey relationship by using Django ORM syntax:: slug = AutoSlugField(populate_from=['related_model__title', 'related_model__get_readable_name']) AutoSlugField uses Django's slugify_ function by default to "slugify" ``populate_from`` field. .. _slugify: https://docs.djangoproject.com/en/dev/ref/utils/#django.utils.text.slugify To provide custom "slugify" function you could either provide the function as an argument to :py:class:`~AutoSlugField` or define your ``slugify_function`` method within a model. 1. ``slugify_function`` as an argument to :py:class:`~AutoSlugField`. .. code-block:: python # models.py from django.db import models from django_extensions.db.fields import AutoSlugField def my_slugify_function(content): return content.replace('_', '-').lower() class MyModel(models.Model): title = models.CharField(max_length=42) slug = AutoSlugField(populate_from='title', slugify_function=my_slugify_function) 2. ``slugify_function`` as a method within a model class. .. code-block:: python # models.py from django.db import models from django_extensions.db.fields import AutoSlugField class MyModel(models.Model): title = models.CharField(max_length=42) slug = AutoSlugField(populate_from='title') def slugify_function(self, content): return content.replace('_', '-').lower() **Important.** If you both provide ``slugify_function`` in a model class and pass ``slugify_function`` to :py:class:`~AutoSlugField` field, then model's ``slugify_function`` method will take precedence. * *RandomCharField* - AutoRandomCharField will automatically create a unique random character field with the specified length. By default upper/lower case and digits are included as possible characters. Given a length of 8 that yields 3.4 million possible combinations. A 12 character field would yield about 2 billion. Below are some examples:: >>> RandomCharField(length=8, unique=True) BVm9GEaE >>> RandomCharField(length=4, include_alpha=False) 7097 >>> RandomCharField(length=12, include_punctuation=True) k[ZS.TR,0LHO >>> RandomCharField(length=12, lowercase=True, include_digits=False) pzolbemetmok * *CreationDateTimeField* - DateTimeField that will automatically set its date when the object is first saved to the database. Works in the same way as the auto_now_add keyword. * *ModificationDateTimeField* - DateTimeField that will automatically set its date when an object is saved to the database. Works in the same way as the auto_now keyword. It is possible to preserve the current timestamp by setting update_modified to False:: >>> example = MyTimeStampedModel.objects.get(pk=1) >>> print example.modified datetime.datetime(2016, 3, 18, 10, 3, 39, 740349, tzinfo=) >>> example.save(update_modified=False) >>> print example.modified datetime.datetime(2016, 3, 18, 10, 3, 39, 740349, tzinfo=) >>> example.save() >>> print example.modified datetime.datetime(2016, 4, 8, 14, 25, 43, 123456, tzinfo=) It is also possible to set the attribute directly on the model, for example when you don't use the TimeStampedModel provided in this package, or when you are in a migration:: >>> example = MyCustomModel.objects.get(pk=1) >>> print example.modified datetime.datetime(2016, 3, 18, 10, 3, 39, 740349, tzinfo=) >>> example.update_modified=False >>> example.save() >>> print example.modified datetime.datetime(2016, 3, 18, 10, 3, 39, 740349, tzinfo=) * *ShortUUIDField* - CharField which transparently generates a UUID and pass it to base57. It result in shorter 22 characters values useful e.g. for concise, unambiguous URLS. It's possible to get shorter values with length parameter: they are not Universal Unique any more but probability of collision is still low * *JSONField* - a generic TextField that neatly serializes/unserializes JSON objects seamlessly. Django 1.9 introduces a native JSONField for PostgreSQL, which is preferred for PostgreSQL users on Django 1.9 and above. django-extensions-3.1.5/docs/generate_password.rst000066400000000000000000000011731414177705400223710ustar00rootroot00000000000000generate_password ================= :synopsis: Generates a new password that can be used for a user password. Introduction ------------- This is a handy command to generate a new password which can be used for a user password. This uses Django core's default password generator ``django.contrib.auth.base_user.BaseUserManager.make_random_password()`` to generate a password. You can specify the length of password with the option ``--length``. If you don't specify ``--length``, the default value of ``make_random_password()`` is applied. Usage ------------- Run :: $ python manage.py generate_password [--length=] django-extensions-3.1.5/docs/graph_models.rst000066400000000000000000000112201414177705400213130ustar00rootroot00000000000000Graph models ============ :synopsis: Renders a graphical overview of your project or specified apps. Creates a GraphViz_ dot file for the specified app names based on their models.py. You can pass multiple app names and they will all be combined into a single model. Output is usually directed to a dot file. Several options are available: grouping models, including inheritance, excluding models and columns, and changing the layout when rendering to an output image. With the latest revisions it's also possible to specify an output file if pygraphviz_ is installed and render directly to an image or other supported file-type. Selecting a library ------------------- You need to select the library to generate the image. You can do so by passing the --pygraphviz or --pydot parameter, depending on which library you want to use. When neither of the command line parameters are given the default is to try and load pygraphviz or pydot (in that order) to generate the image. To install pygraphviz you usually need to run this command:: $ pip install pygraphviz It is possible you can't install it because it needs some C extensions to build. In that case you can try other methods to install or you can use PyDot. To install pydot you need to run this command:: $ pip install pyparsing pydot Installation should be fast and easy. Remember to install this exact version of pyparsing, otherwise it's possible you get this error: Couldn't import dot_parser, loading of dot files will not be possible. Default Settings ---------------- The option GRAPH_MODELS = {} can be used in the settings file to specify default options:: GRAPH_MODELS = { 'all_applications': True, 'group_models': True, } It uses the same names as on the command line only with the leading two dashes removed and the other dashes replaced by underscores. You can specify a list of applications with the *app_labels* key:: GRAPH_MODELS = { 'app_labels': ["myapp1", "myapp2", "auth"], } Templates --------- Django templates are used to generate the dot code. This in turn can be drawn into a image by libraries like *pygraphviz* or *pydot*. You can extend or override the templates if needed. Templates used: - django_extensions/graph_models/digraph.dot - django_extensions/graph_models/label.dot - django_extensions/graph_models/relation.dot Documentation on how to create dot files can be found here: http://www.graphviz.org/documentation/ .. warning:: Modifying Django's default templates behaviour might break *graph_models* Please be aware that if you use any *template_loaders* or extensions that change the way templates are rendered that this can cause *graph_models* to fail. An example of this is the Django app *django-template-minifier* this automatically removed the newlines before/after template tags even for non-HTML templates which leads to a malformed file. Example Usage ------------- With *django-extensions* installed you can create a dot-file or an image by using the *graph_models* command:: # Create a dot file $ ./manage.py graph_models -a > my_project.dot :: # Create a PNG image file called my_project_visualized.png with application grouping $ ./manage.py graph_models -a -g -o my_project_visualized.png # Same example but with explicit selection of pygraphviz or pydot $ ./manage.py graph_models --pygraphviz -a -g -o my_project_visualized.png $ ./manage.py graph_models --pydot -a -g -o my_project_visualized.png :: # Create a dot file for only the 'foo' and 'bar' applications of your project $ ./manage.py graph_models foo bar > my_project.dot :: # Create a graph for only certain models $ ./manage.py graph_models -a -I Foo,Bar -o my_project_subsystem.png :: # Create a graph excluding certain models $ ./manage.py graph_models -a -X Foo,Bar -o my_project_sans_foo_bar.png :: # Create a graph including models matching a given pattern and excluding some of them # It will first select the included ones, then filter out the ones to exclude $ ./manage.py graph_models -a -I Product* -X *Meta -o my_project_products_sans_meta.png :: # Create a graph without showing its edges' labels $ ./manage.py graph_models -a --hide-edge-labels -o my_project_sans_foo_bar.png :: # Create a graph with 'normal' arrow shape for relations $ ./manage.py graph_models -a --arrow-shape normal -o my_project_sans_foo_bar.png :: # Create a graph with different layout direction, # supported directions: "TB", "LR", "BT", "RL" $ ./manage.py graph_models -a --rankdir BT -o my_project_sans_foo_bar.png .. _GraphViz: http://www.graphviz.org/ .. _pygraphviz: https://pygraphviz.github.io/ .. _pydot: https://pypi.python.org/pypi/pydot django-extensions-3.1.5/docs/index.rst000066400000000000000000000044201414177705400177620ustar00rootroot00000000000000.. django-extensions documentation master file, created by sphinx-quickstart on Wed Apr 1 20:39:40 2009. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to the django-extensions documentation! =============================================== Django Extensions is a collection of custom extensions for the Django Framework. These include management commands, additional database fields, admin extensions and much more. `这篇文档当然还有中文版 `_ Getting Started =============== The easiest way to figure out what Django Extensions are all about is to watch the `excellent screencast by Eric Holscher`__ (`Direct Vimeo link`__). In a couple minutes Eric walks you through a half a dozen command extensions. Getting it ========== You can get Django Extensions by using pip:: $ pip install django-extensions If you want to install it from source, grab the git repository and run setup.py:: $ git clone git://github.com/django-extensions/django-extensions.git $ cd django-extensions $ python setup.py install Then you will need to add the *django_extensions* application to the ``INSTALLED_APPS`` setting of your Django project *settings.py* file. For more detailed instructions check out our :doc:`installation_instructions`. Enjoy. Compatibility with versions of Python and Django ================================================= We follow the Django guidelines for supported Python and Django versions. See more at `Django Supported Versions `_ This might mean the django-extensions may work with older or unsupported versions but we do not guarantee it and most likely will not fix bugs related to incompatibilities with older versions. Contents ======== .. toctree:: :maxdepth: 2 installation_instructions admin_extensions command_extensions command_signals debugger_tags field_extensions jobs_scheduling model_extensions permissions utilities validators Indices and tables ================== * :ref:`search` __ http://ericholscher.com/blog/2008/sep/12/screencast-django-command-extensions/ __ https://vimeo.com/1720508 django-extensions-3.1.5/docs/installation_instructions.rst000066400000000000000000000037071414177705400242070ustar00rootroot00000000000000Installation instructions ========================= :synopsis: Installing django-extensions Installing ---------- You can use pip to install django-extensions for usage:: $ pip install django-extensions Development ----------- Django-extensions is hosted on github:: https://github.com/django-extensions/django-extensions Source code can be accessed by performing a Git clone. Tracking the development version of *django command extensions* should be pretty stable and will keep you up-to-date with the latest fixes. $ pip install -e git+https://github.com/django-extensions/django-extensions.git#egg=django-extensions You find the sources in src/django-extensions now. You can verify that the application is available on your PYTHONPATH by opening a python interpreter and entering the following commands: :: >>> import django_extensions >>> django_extensions.VERSION (0, 8) Keep in mind that the current code in the git repository may be different from the packaged release. It may contain bugs and backwards-incompatible changes but most likely also new goodies to play with. Configuration ------------- You will need to add the *django_extensions* application to the INSTALLED_APPS setting of your Django project *settings.py* file.:: INSTALLED_APPS = ( ... 'django_extensions', ) This will make sure that Django finds the additional management commands provided by *django-extensions*. The next time you invoke *./manage.py help* you should be able to see all the newly available commands. Some commands or options require additional applications or python libraries, for example: * 'export_emails' will require the *python vobject* module to create vcard files. * 'graph_models' requires *pygraphviz* to render directly to image file. If the given application or python library is not installed on your system (or not in the python path) the executed command will raise an exception and inform you of the missing dependency. django-extensions-3.1.5/docs/jobs_scheduling.rst000066400000000000000000000050061414177705400220160ustar00rootroot00000000000000Jobs Scheduling =============== :synopsis: Documentation on creating/using jobs in Django-extensions Creating jobs works much like management commands work in Django. Setup ----- Run :: $ python manage.py create_jobs to make a ``jobs`` directory inside of an application. The ``jobs`` directory will have the following tree:: jobs ├── daily │   └── __init__.py ├── hourly │   └── __init__.py ├── monthly │   └── __init__.py ├── weekly │   └── __init__.py ├── yearly │   └── __init__.py ├── __init__.py └── sample.py Create a job ------------ A job is a Python script with a mandatory ``BaseJob`` class which extends from ``MinutelyJob``, ``QuarterHourlyJob``, ``HourlyJob``, ``DailyJob``, ``WeeklyJob``, ``MonthlyJob`` or ``Yearly``. It has one method that must be implemented called ``execute``, which is called when the job is run. The directories ``hourly``, ``daily``, ``monthly``, ``weekly`` and ``yearly`` are used only to for organisation purpose. Note: If you want to use ``QuarterHourlyJob`` or ``Minutely`` job, create python package with name ``quarter_hourly`` or ``minutely`` respectively (similar to ``hourly`` or ``daily`` package). To create your first job you can start copying ``sample.py``. Remember to replace ``BaseJob`` with ``MinutelyJob``, ``QuarterHourlyJob``, ``HourlyJob``, ``DailyJob``, ``WeeklyJob``, ``MonthlyJob`` or ``Yearly``. Some simple examples are provided by the `django_extensions.jobs package `_. Note that each job should be in a new python script (within respective directory) and the class implementing the cron should be named ``Job``. Also, ``__init__.py`` file is not used for identifying jobs. Run a job --------- The following commands are related to jobs: * ``create_jobs``, create the directory structure for jobs * ``runjob``, run a single job * ``runjobs``, run all hourly/daily/weekly/monthly jobs Use "runjob(s) -l" to list all jobs recognized. Jobs do not run automatically! You must either run a job manually specifying the exact time on which the command is to be run, or use crontab: :: @hourly /path/to/my/project/manage.py runjobs hourly :: @daily /path/to/my/project/manage.py runjobs daily :: @weekly /path/to/my/project/manage.py runjobs weekly :: @monthly /path/to/my/project/manage.py runjobs monthly django-extensions-3.1.5/docs/list_model_info.rst000066400000000000000000000040751414177705400220270ustar00rootroot00000000000000list_model_info =============== :synopsis: Lists out all the fields and methods for models in installed apps. Introduction ------------ When working with large projects or when returning to a code base after some time away, it can be challenging to remember all of the fields and methods associated with your models. This command makes it easy to see: * what fields are available * how they are referred to in queries * each field's class * each field's representation in the database * what methods are available * method signatures Commandline arguments ^^^^^^^^^^^^^^^^^^^^^ You can configure the output in a number of ways. :: # Show each field's class $ ./manage.py list_model_info --field-class :: # Show each field's database type representation $ ./manage.py list_model_info --db-type :: # Show each method's signature $ ./manage.py list_model_info --signature :: # Show all model methods, including private methods and django's default methods $ ./manage.py list_model_info --all-methods :: # Output only information for a single model, specifying the app and model using dot notation $ ./manage.py list_model_info --model users.User You can combine arguments. for instance, to list all methods and show the method signatures for the User model within the users app:: $ ./manage.py list_model_info --all --signature --model users.User Settings Configuration ^^^^^^^^^^^^^^^^^^^^^^ You can specify default values in your settings.py to simplify running this command. .. tip:: Commandline arguments override the following settings, allowing you to change options on the fly. To show each field's class:: MODEL_INFO_FIELD_CLASS = True To show each field's database type representation:: MODEL_INFO_DB_TYPE = True To show each method's signature:: MODEL_INFO_SIGNATURE = True To show all model methods, including private methods and django's default methods:: MODEL_INFO_ALL_METHODS = True To output only information for a single model, specify the app and model using dot notation:: MODEL_INFO_MODEL = 'users.User' django-extensions-3.1.5/docs/list_signals.rst000066400000000000000000000004771414177705400213560ustar00rootroot00000000000000list_signals ============ :synopsis: Lists all signals defined in the project grouped by model and signal type Example Usage ------------- With *django-extensions* installed you can review all defined handlers using *list_signals* command:: # As above but non-interactive $ ./manage.py list_signals django-extensions-3.1.5/docs/merge_model_instances.rst000066400000000000000000000015611414177705400232040ustar00rootroot00000000000000merge_model_instances ========================== :synopsis: Merges duplicate model instances by reassigning related model references to a chosen primary model instance. *Note: This management command is in beta. Use with care, and make sure to test thoroughly before implementing.* Allows the user to choose a model to de-duplicate and a field on which to de-duplicate model instances. Provides an interactive session with the user to select the model to de-duplicate and the field on which to de-duplicate model instances. After merging model instances to one instance, deletes the merged model instances. Use with care! Example Usage ------------- With *django-extensions* installed you merge model instances using the *merge_model_instances* command:: # Delete leftover migrations from the first squashed migration found in myapp $ ./manage.py merge_model_instances django-extensions-3.1.5/docs/model_extensions.rst000066400000000000000000000047401414177705400222370ustar00rootroot00000000000000Model Extensions ================ :synopsis: Model Extensions Introduction ------------ Django Extensions provides you a set of Abstract Base Classes for models that implements commonly used patterns like holding the model's creation and last modification dates. Database Model Extensions ------------------------- * *ActivatorModel* - Abstract Base Class that provides a ``status``, ``activate_date``, and ``deactivate_date`` fields. The ``status`` field is an ``IntegerField`` whose value is chosen from a tuple of choices - active and inactive - defaulting to active. This model also exposes a custom manager, allowing the user to easily query for active or inactive objects. E.g.: ``Model.objects.active()`` returns all instances of ``Model`` that have an active status. * *TitleDescriptionModel* - This Abstract Base Class model provides ``title`` and ``description`` fields. The ``title`` field is ``CharField`` with a maximum length of 255 characters, non-nullable. ``description``. On the other hand, ``description`` is a nullable ``TextField``. * *TimeStampedModel* - An Abstract Base Class model that provides self-managed ``created`` and ``modified`` fields. Both of the fields are customly defined in Django Extensions as ``CreationDateTimeField`` and ``ModificationDateTimeField``. Those fields are subclasses of Django's ``DateTimeField`` and will store the value of ``django.utils.timezone.now()`` on the model's creation and modification, respectively * *TitleSlugDescriptionModel* - An Abstract Base Class model that, like the ``TitleDescriptionModel``, provides ``title`` and ``description`` fields but also provides a self-managed ``slug`` field which populates from the title. That field's class is a custom defined `AutoSlugField `_, based on Django's ``SlugField``. By default, it uses ``-`` as a separator, is unique and does not accept blank values. It is possible to customize ``slugify_function`` by defining your custom function within a model: .. code-block:: python # models.py from django.db import models from django_extensions.db.models import TitleSlugDescriptionModel class MyModel(TitleSlugDescriptionModel, models.Model): def slugify_function(self, content): """ This function will be used to slugify the title (default `populate_from` field) """ return content.replace('_', '-').lower() See `AutoSlugField docs `_ for more details. django-extensions-3.1.5/docs/permissions.rst000066400000000000000000000023321414177705400212260ustar00rootroot00000000000000Permissions ============== :synopsis: Permissions Mixins to limit access and model instances in a view. Introduction ------------ Django Extensions offers mixins for Class Based Views that make it easier to query and limit access to certain views. Current Mixins --------------------------------- * *ModelUserFieldPermissionMixin* - A Class Based View mixin that limits the accessibility to the view based on the "owner" of the view. This will check if the currently logged in user (``self.request.user``) matches the owner of the model instance. By default, the "owner" will be called "user". .. code-block:: python # models.py from django.db import models from django.conf import settings class MyModel(models.Model): author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete = models.CASCADE) content = models.TextField() .. code-block:: python # views.py from django.views.generic import UpdateView from django_extensions.auth.mixins import ModelUserFieldPermissionMixin from .models import MyModel class MyModelUpdateView(ModelUserFieldPermissionMixin, UpdateView): model = MyModel template_name = 'mymodels/update.html' model_permission_user_field = 'author' django-extensions-3.1.5/docs/print_settings.rst000066400000000000000000000065511414177705400217360ustar00rootroot00000000000000print_settings ============== :synopsis: Django management command similar to ``diffsettings`` but shows *selected* active Django settings or *all* if no args passed. Introduction ------------ Django comes with a ``diffsettings`` command that shows how your project's settings differ from the Django defaults. Sometimes it is useful to just see the settings that are in effect for your project. This is particularly true if you have a more complex system for settings than just a single :file:`settings.py` file. For example, you might have settings files that import other settings file, such as dev, test, and production settings files that source a base settings file. This command also supports dumping the data in a few different formats. More Info --------------- The simplest way to run it is with no arguments:: $ python manage.py print_settings Some variations:: $ python manage.py print_settings --format=json $ python manage.py print_settings --format=yaml # Requires PyYAML $ python manage.py print_settings --format=pprint $ python manage.py print_settings --format=text $ python manage.py print_settings --format=value Show just selected settings:: $ python manage.py print_settings DEBUG INSTALLED_APPS $ python manage.py print_settings DEBUG INSTALLED_APPS --format=pprint $ python manage.py print_settings INSTALLED_APPS --format=value It is also possible to use shell-style wildcards:: $ python manage.py print_settings TIME* $ python manage.py print_settings *_DIRS STATIC* $ python manage.py print_settings INSTALLED_???? Yielding an error when a settings does not exist:: $ ./manage.py print_settings -f INSTALLED_APPZ CommandError: INSTALLED_APPZ not found in settings. For more info, take a look at the built-in help:: $ python manage.py print_settings --help usage: manage.py print_settings [-h] [-f] [--format FORMAT] [--indent INDENT] [--version] [-v {0,1,2,3}] [--settings SETTINGS] [--pythonpath PYTHONPATH] [--traceback] [--no-color] [--force-color] [--skip-checks] [setting [setting ...]] Print the active Django settings. positional arguments: setting Specifies setting to be printed. optional arguments: -h, --help show this help message and exit -f, --fail Fail if invalid setting name is given. --format FORMAT Specifies output format. --indent INDENT Specifies indent level for JSON and YAML --version show program's version number and exit -v {0,1,2,3}, --verbosity {0,1,2,3} Verbosity level; 0=minimal output, 1=normal output, 2=verbose output, 3=very verbose output --settings SETTINGS The Python path to a settings module, e.g. "myproject.settings.main". If this isn't provided, the DJANGO_SETTINGS_MODULE environment variable will be used. --pythonpath PYTHONPATH A directory to add to the Python path, e.g. "/home/djangoprojects/myproject". --traceback Raise on CommandError exceptions --no-color Don't colorize the command output. --force-color Force colorization of the command output. --skip-checks Skip system checks. django-extensions-3.1.5/docs/reset_db.rst000066400000000000000000000040241414177705400204420ustar00rootroot00000000000000reset_db ======== :synopsis: Fully resets your database by running DROP DATABASE and CREATE DATABASE Django command that resets your Django database, removing all data from all tables. This allows you to run all migrations again. By default the command will prompt you to confirm that all data will be deleted. This can be turned off with the ``--noinput``-argument. Supported engines ----------------- The command detects whether you're using a SQLite, MySQL, or Postgres database by looking up your Django database engine in the following lists. :: DEFAULT_SQLITE_ENGINES = ( 'django.db.backends.sqlite3', 'django.db.backends.spatialite', ) DEFAULT_MYSQL_ENGINES = ( 'django.db.backends.mysql', 'django.contrib.gis.db.backends.mysql', 'mysql.connector.django', ) DEFAULT_POSTGRESQL_ENGINES = ( 'django.db.backends.postgresql', 'django.db.backends.postgresql_psycopg2', 'django.db.backends.postgis', 'django.contrib.gis.db.backends.postgis', 'psqlextra.backend', 'django_zero_downtime_migrations.backends.postgres', 'django_zero_downtime_migrations.backends.postgis', ) If the engine you're using is not listed above, check the optional settings section below. Example Usage ------------- :: # Reset the DB so that it contains no data and migrations can be run again $ ./manage.py reset_db mybucket :: # Don't ask for a confirmation before doing the reset $ ./manage.py reset_db --noinput :: # Use a different user and password than the one from settings.py $ ./manage.py reset_db --user db_root --password H4rd2Guess Optional settings ----------------- It is possible to use a Django DB engine not in the lists above -- to do that add the approriate setting as shown below to your Django settings file:: # settings.py DJANGO_EXTENSIONS_RESET_DB_SQLITE_ENGINES = ['your_custom_sqlite_engine'] DJANGO_EXTENSIONS_RESET_DB_MYSQL_ENGINES = ['your_custom_mysql_engine'] DJANGO_EXTENSIONS_RESET_DB_POSTGRESQL_ENGINES = ['your_custom_postgres_engine'] django-extensions-3.1.5/docs/runprofileserver.rst000066400000000000000000000073671414177705400223040ustar00rootroot00000000000000RunProfileServer ================ *We recommend that before you start profiling any language or framework you learn enough about it so that you feel comfortable with digging into its internals.* *Without sufficient knowledge it will not only be (very) hard but you're likely to make wrong assumptions (and fixes). As a rule of thumb, clean, well written code will help you a lot more than overzealous micro-optimizations will.* *This document is work in progress. If you feel you can help with better/clearer or additional information about profiling Django please leave a comment.* Introduction ------------ *runprofileserver* starts Django's runserver command with hotshot/profiling tools enabled. It will save .prof files containing the profiling information into the --prof-path directory. Note that for each request made one profile data file is saved. By default the profile-data-files are saved in /tmp use the --prof-path option to specify your own target directory. Saving the data in a meaningful directory structure helps to keep your profile data organized and keeps /tmp uncluttered. (Yes this probably malfunctions systems such as Windows where /tmp does not exist) To define profile filenames use --prof-file option. Default format is "{path}.{duration:06d}ms.{time}" (Python `Format Specification `_ is used). Examples: * "{time}-{path}-{duration}ms" - to order profile-data-files by request time * "{duration:06d}ms.{path}.{time}" - to order by request duration Profiler choice --------------- *runprofileserver* supports two profilers: *hotshot* and *cProfile*. Both come with the standard Python library but *cProfile* is more recent and may not be available on all systems. For this reason, *hotshot* is the default profiler. However, *hotshot* `is not maintained anymore `_ and using *cProfile* is usually the recommended way. If it is available on your system, you can use it with the option ``--use-cprofile``. Example:: $ mkdir /tmp/my-profile-data $ ./manage.py runprofileserver --use-cprofile --prof-path=/tmp/my-profile-data If you used the default profiler but are not able to open the profiling results with the ``pstats`` module or with your profiling GUI of choice because of an error "*ValueError: bad marshal data (unknown type code)*", try using *cProfile* instead. KCacheGrind ----------- Recent versions of *runprofileserver* have an option to save the profile data into a KCacheGrind compatible format. So you can use the excellent KCacheGrind tool for analyzing the profile data. Example:: $ mkdir /tmp/my-profile-data $ ./manage.py runprofileserver --kcachegrind --prof-path=/tmp/my-profile-data Validating models... 0 errors found Django version X.Y.Z, using settings 'complete_project.settings' Development server is running at http://127.0.0.1:8000/ Quit the server with CONTROL-C. [13/Nov/2008 06:29:38] "GET / HTTP/1.1" 200 41107 [13/Nov/2008 06:29:39] "GET /site_media/base.css?743 HTTP/1.1" 200 17227 [13/Nov/2008 06:29:39] "GET /site_media/logo.png HTTP/1.1" 200 3474 [13/Nov/2008 06:29:39] "GET /site_media/jquery.js HTTP/1.1" 200 31033 [13/Nov/2008 06:29:39] "GET /site_media/heading.png HTTP/1.1" 200 247 [13/Nov/2008 06:29:39] "GET /site_media/base.js HTTP/1.1" 200 751 $ kcachegrind /tmp/my-profile-data/root.12574391.592.prof Links ----- * http://code.djangoproject.com/wiki/ProfilingDjango * http://www.rkblog.rk.edu.pl/w/p/django-profiling-hotshot-and-kcachegrind/ * http://code.djangoproject.com/browser/django/trunk/django/bin/profiling/gather_profile_stats.py * http://www.oluyede.org/blog/2007/03/07/profiling-django/ * http://simonwillison.net/2008/May/22/debugging/ django-extensions-3.1.5/docs/runscript.rst000066400000000000000000000135171414177705400207130ustar00rootroot00000000000000RunScript ============= :synopsis: Runs a script in the Django context. Introduction ------------ The runscript command lets you run an arbitrary set of python commands within the Django context. It offers the same usability and functionality as running a set of commands in shell accessed by:: $ python manage.py shell Getting Started --------------- This example assumes you have followed the tutorial for Django 1.8+, and created a polls app containing a ``Question`` model. We will create a script that deletes all of the questions from the database. To get started create a scripts directory in your project root, next to manage.py:: $ mkdir scripts $ touch scripts/__init__.py Note: The *__init__.py* file is necessary so that the folder is picked up as a python package. Next, create a python file with the name of the script you want to run within the scripts directory:: $ touch scripts/delete_all_questions.py This file must implement a *run()* function. This is what gets called when you run the script. You can import any models or other parts of your django project to use in these scripts. For example:: # scripts/delete_all_questions.py from polls.models import Question def run(): # Fetch all questions questions = Question.objects.all() # Delete questions questions.delete() Note: You can put a script inside a *scripts* folder in any of your apps too. Usage ----- To run any script you use the command *runscript* with the name of the script that you want to run. For example:: $ python manage.py runscript delete_all_questions Note: The command first checks for scripts in your apps i.e. *app_name/scripts* folder and runs them before checking for and running scripts in the *project_root/scripts* folder. You can have multiple scripts with the same name and they will all be run sequentially. Passing arguments ----------------- You can pass arguments from the command line to your script by passing a space separated list of values with ``--script-args``. For example:: $ python manage.py runscript delete_all_questions --script-args staleonly The list of argument values gets passed as arguments to your *run()* function. For example:: # scripts/delete_all_questions.py from datetime import timedelta from django.utils import timezone from polls.models import Question def run(*args): # Get all questions questions = Question.objects.all() if 'staleonly' in args: # Only get questions more than 100 days old questions = questions.filter(pub_date__lt=timezone.now() - timedelta(days=100)) # Delete questions questions.delete() Setting execution directory --------------------------- You can set scripts execution directory using ``--chdir`` option or ``settings.RUNSCRIPT_CHDIR``. You can also set scripts execution directory policy using ``--dir-policy`` option or ``settings.RUNSCRIPT_CHDIR_POLICY``. It can be one of the following: * **none** - start all scripts in current directory. * **each** - start all scripts in their directories. * **root** - start all scripts in ``BASE_DIR`` directory. Assume this simplified directory structure:: django_project_dir/ ├-first_app/ │ └-scripts/ │ ├-first_script.py ├-second_app/ │ └-scripts/ │ ├-second_script.py ├-manage.py ├-other_folder/ │ └-some_file.py Assume you are in ``other_folder`` directory. You can set execution directory for both scripts using this command:: $ python ../manage.py runscript first_script second_script --chdir /django_project_dir/second_app # scripts will be executed from second_app directory You can run both scripts with ``NONE`` policy using this command:: $ python ../manage.py runscript first_script second_script --dir-policy none # scripts will be executed from other_folder directory You can run both scripts with ``EACH`` policy using this command:: $ python ../manage.py runscript first_script second_script --dir-policy each # first_script will be executed from first_app and second script will be executed from second_app You can run both scripts with ``ROOT`` policy using this command:: $ python ../manage.py runscript first_script second_script --dir-policy root # scripts will be executed from django_project_dir directory Errors and exit codes --------------------- If an exception is encountered the execution of the scripts will stop, a traceback is shown and the command will return an exit code. To control the exit-code you can either use `CommandError("something went terribly wrong", returncode=123)` in your script or has the `run(...)` function return the exit_code. Where any exit code other then 0 will indicate failure, just like regular shell commands. This means you can use `runscript` in your CI/CD pipelines or other automated scripts and it should behave like any other shell command. Continue on errors ------------------ If you want runscript to continue running scripts even if errors occurs you can set `-c`:: $ python manage.py runscript delete_all_questions another_script --continue-on-error This will continue running 'another_script' even if an exception was raised or exit code was returned in 'delete_all_questions'. When all the scripts has been run `runscript` will exit with the last non-zero exit code. Note: It is possible to do `raise CommandError(..., returncode=0)` which will lead to an exception with exit code 0. Debugging --------- If an exception occurs you will get a traceback by default. You can use `CommandError` in the same way as with other custom management commands. To get a traceback from a `CommandError` specify ``--traceback``. For example:: $ python manage.py runscript delete_all_questions --traceback If you do not want to see tracebacks at all you can specify:: $ python manage.py runscript delete_all_questions --no-traceback django-extensions-3.1.5/docs/runserver_plus.rst000066400000000000000000000222721414177705400217560ustar00rootroot00000000000000RunServerPlus ============= :synopsis: RunServerPlus-typical runserver with Werkzeug debugger baked in Introduction ------------ This item requires that you have the `Werkzeug WSGI utilities` installed. Included with Werkzeug_ is a kick ass debugger that renders nice debugging tracebacks and adds an AJAX based debugger (which allows code execution in the context of the traceback’s frames). Additionally it provides a nice access view to the source code. Getting Started --------------- To get started we just use the *runserver_plus* command instead of the normal *runserver* command:: $ python manage.py runserver_plus * Running on http://127.0.0.1:8000/ * Restarting with reloader... Validating models... 0 errors found Django version X.Y.Z, using settings 'screencasts.settings' Development server is running at http://127.0.0.1:8000/ Using the Werkzeug debugger (http://werkzeug.pocoo.org/) Quit the server with CONTROL-C. Note: all normal runserver options apply. In other words, if you need to change the port number or the host information, you can do so like you would normally. Usage ----- Instead of the default Django traceback page, the Werkzeug traceback page will be shown when an exception occurs. .. image:: https://f.cloud.github.com/assets/202559/1261027/2637f826-2c22-11e3-83c6-646acc87808b.png :alt: werkzeug-traceback Along with the typical traceback information we have a couple of options. These options appear when hovering over a particular traceback line. Notice that two buttons appear to the right: .. image:: https://f.cloud.github.com/assets/202559/1261035/558ad0ee-2c22-11e3-8ddd-6678d84d77e7.png :alt: werkzeug-options The options are: View Source ^^^^^^^^^^^ This displays the source underneath the traceback: .. image:: https://f.cloud.github.com/assets/202559/1261036/583c8c42-2c22-11e3-9eb9-5c16b8732512.png :alt: werkzeug-source Being able to view the source file is handy because it provides more context information around the error. The actual traceback areas are highlighted so they are easy to spot. One awkward aspect of the UI is that the page is not scrolled to the bottom. At first I thought nothing was happening because of this. Interactive Debugging Console ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Clicking on this button opens up a new pane under the traceback line you're on. This is the money shot: .. image:: https://f.cloud.github.com/assets/202559/1261037/5d12eda6-2c22-11e3-802a-2639ff8813fa.png :alt: werkzeug-debugger An ajax based console appears in the pane and you can start debugging. Notice in the screenshot above I did a `print environ` to see what was in the environment parameter coming into the function. *WARNING*: This should *never* be used in any kind of production environment. Not even for a quick problem check. I cannot emphasize this enough. The interactive debugger allows you to evaluate python code right against the server. You've been warned. .. _`Werkzeug WSGI utilities`: http://werkzeug.pocoo.org/ SSL ^^^ runserver_plus also supports SSL, so that you can easily debug bugs that pop up when https is used. To use SSL simply provide a file name for certificates; a key and certificate file will be automatically generated:: $ python manage.py runserver_plus --cert-file cert.crt Validating models... 0 errors found Django version X.Y.Z, using settings 'mysite.settings' Development server is running at http://127.0.0.1:8000/ Using the Werkzeug debugger (http://werkzeug.pocoo.org/) Quit the server with CONTROL-C. * Running on https://127.0.0.1:8000/ * Restarting with reloader Validating models... 0 errors found Django version X.Y.Z, using settings 'mysite.settings' Development server is running at http://127.0.0.1:8000/ Using the Werkzeug debugger (http://werkzeug.pocoo.org/) Quit the server with CONTROL-C. After running this command, your web application can be accessed through https://127.0.0.1:8000. You will also find that two files are created in the current working directory: a key file and a certificate file. If you run the above command again, these certificate files will be reused so that you do not have to keep accepting the self-generated certificates from your browser every time. You can also provide a specific file for the certificate to be used if you already have one:: $ python manage.py runserver_plus --cert-file /tmp/cert.crt Note that you need the OpenSSL library to use SSL, and Werkzeug 0.9 or later if you want to reuse existing certificates. To install OpenSSL:: $ pip install pyOpenSSL Certificates paths ^^^^^^^^^^^^^^^^^^ You can configure different paths to .crt and .key files. At least one of ``--cert-file`` or ``--key-file`` must be defined to use SSL. You can set path to .crt file using ``--cert-file`` option or deprecated ``--cert`` option which is currently an alias for ``--cert-file``. If this option is not set than runserver_plus assumes that, this file is in the same directory as file from ``--key-file`` option. You can set path to .key file using ``--key-file`` option. If this option is not set than runserver_plus assumes that, this file is in the same directory as file from ``--cert-file`` option. If you want to create new files, than you can pass file name without extension. Proper files with this name and .crt and .key extensions will be created. Configuration ^^^^^^^^^^^^^ The `RUNSERVERPLUS_SERVER_ADDRESS_PORT` setting can be configured to specify which address and port the development server should bind to. If you find yourself frequently starting the server with:: $ python manage.py runserver_plus 0.0.0.0:8000 You can use settings to automatically default your development to an address/port:: RUNSERVERPLUS_SERVER_ADDRESS_PORT = '0.0.0.0:8000' To ensure Werkzeug can log to the console, you may need to add the following to your settings:: LOGGING = { ... 'handlers': { ... 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', }, }, 'loggers': { ... 'werkzeug': { 'handlers': ['console'], 'level': 'DEBUG', 'propagate': True, }, }, } Other configuration options and their defaults include: :: # Truncate SQL queries to this many characters (None means no truncation) RUNSERVER_PLUS_PRINT_SQL_TRUNCATE = 1000 # After how many seconds auto-reload should scan for updates in poller-mode RUNSERVERPLUS_POLLER_RELOADER_INTERVAL = 1 # Werkzeug reloader type [auto, watchdog, or stat] RUNSERVERPLUS_POLLER_RELOADER_TYPE = 'auto' # Add extra files to watch RUNSERVER_PLUS_EXTRA_FILES = [] IO Calls and CPU Usage ^^^^^^^^^^^^^^^^^^^^^^ As noted in gh625_ `runserver_plus` can be seen to use a lot of CPU and generate many I/O when idle. This is due to the way Werkzeug_ has implemented the auto reload capability. It supports two ways of doing auto reloading either via `stat polling` or `file system events`. The `stat polling` approach is pretty brute force and continously issues `stat` system calls which causes the CPU and IO load. If possible try to install the Watchdog_ package, this should automatically cause Werkzeug_ to use `file system events` whenever possible. You can read more about this in `Werkzeug documentation `_ You can also increase the poll interval when using `stat polling` from the default of 1 second. This will decrease the CPU load at the expense of file edits taking longer to pick up. This can be set two ways, in the django settings file:: RUNSERVERPLUS_POLLER_RELOADER_INTERVAL = 5 or as a commad line argument:: $ python manage.py runserver_plus --reloader-interval 5 Debugger PIN ------------ .. epigraph:: The following text about the debugger PIN is taken verbatim from the Werkzeug `documentation about its debugger PIN `_. Starting with Werkzeug 0.11 the debugger is additionally protected by a PIN. This is a security helper to make it less likely for the debugger to be exploited in production as it has happened to people to keep the debugger active. The PIN based authentication is enabled by default. When the debugger comes up, on first usage it will prompt for a PIN that is printed to the command line. The PIN is generated in a stable way that is specific to the project. In some situations it might be not possible to generate a stable PIN between restarts in which case an explicit PIN can be provided through the environment variable WERKZEUG_DEBUG_PIN. This can be set to a number and will become the PIN. This variable can also be set to the value off to disable the PIN check entirely. The PIN can also be disabled by passing the argument ``--nopin`` when calling the runserver_plus command. If the PIN is entered too many times incorrectly the server needs to be restarted. **This feature is not supposed to entirely secure the debugger. It’s intended to make it harder for an attacker to exploit the debugger. Never enable the debugger in production.** .. _gh625: https://github.com/django-extensions/django-extensions/issues/625 .. _Werkzeug: http://werkzeug.pocoo.org/ .. _Watchdog: https://pypi.python.org/pypi/watchdog django-extensions-3.1.5/docs/shell_plus.rst000066400000000000000000000347401414177705400210350ustar00rootroot00000000000000shell_plus ========== :synopsis: Django shell with autoloading of the apps database models and subclasses of user-defined classes. Interactive Python Shells ------------------------- There is support for three different types of interactive python shells. IPython:: $ ./manage.py shell_plus --ipython bpython:: $ ./manage.py shell_plus --bpython ptpython:: $ ./manage.py shell_plus --ptpython Python:: $ ./manage.py shell_plus --plain It is possible to directly add command line arguments to the underlying Python shell using `--`:: $ ./manage.py shell_plus --ipython -- --profile=foo The default resolution order is: ptpython, bpython, ipython, python. You can also set the configuration option SHELL_PLUS to explicitly specify which version you want. :: # Always use IPython for shell_plus SHELL_PLUS = "ipython" It is also possible to use `IPython Notebook`_, an interactive Python shell which uses a web browser as its user interface, as an alternative shell:: $ ./manage.py shell_plus --notebook In addition to being savable, IPython Notebooks can be updated (while running) to reflect changes in a Django application's code with the menu command `Kernel > Restart`. Configuration ------------- Sometimes, models from your own apps and other people's apps have colliding names, or you may want to completely skip loading an app's models. Here are some examples of how to do that. Note: These settings are only used inside shell_plus and will not affect your environment. :: # Rename the automatic loaded module Messages in the app blog to blog_messages. SHELL_PLUS_MODEL_ALIASES = {'blog': {'Messages': 'blog_messages'},} :: # Prefix all automatically loaded models in the app blog with myblog. SHELL_PLUS_APP_PREFIXES = {'blog': 'myblog',} :: # Dont load the 'sites' app, and skip the model 'pictures' in the app 'blog' SHELL_PLUS_DONT_LOAD = ['sites', 'blog.pictures'] :: # Dont load any models SHELL_PLUS_DONT_LOAD = ['*'] You can also combine model_aliases and dont_load. When referencing nested modules, e.g. `somepackage.someapp.models.somemodel`, omit the package name and the reference to `models`. For example: :: SHELL_PLUS_DONT_LOAD = ['someapp.somemodel', ] # This works SHELL_PLUS_DONT_LOAD = ['somepackage.someapp.models.somemodel', ] # This does NOT work It is possible to ignore autoloaded modules when using manage.py, like:: $ ./manage.py shell_plus --dont-load app1 --dont-load app2.module1 Command line parameters and settings in the configuration file are merged, so you can safely append modules to ignore from the commandline for one-time usage. Other configuration options include: :: # Always use IPython for shell_plus SHELL_PLUS = "ipython" :: SHELL_PLUS_PRINT_SQL = True # Truncate sql queries to this number of characters (this is the default) SHELL_PLUS_PRINT_SQL_TRUNCATE = 1000 # To disable truncation of sql queries use SHELL_PLUS_PRINT_SQL_TRUNCATE = None # Specify sqlparse configuration options when printing sql queries to the console SHELL_PLUS_SQLPARSE_FORMAT_KWARGS = dict( reindent_aligned=True, truncate_strings=500, ) # Specify Pygments formatter and configuration options when printing sql queries to the console import pygments.formatters SHELL_PLUS_PYGMENTS_FORMATTER = pygments.formatters.TerminalFormatter SHELL_PLUS_PYGMENTS_FORMATTER_KWARGS = {} :: # Additional IPython arguments to use IPYTHON_ARGUMENTS = [] IPYTHON_KERNEL_DISPLAY_NAME = "Django Shell-Plus" # Additional Notebook arguments to use NOTEBOOK_ARGUMENTS = [] NOTEBOOK_KERNEL_SPEC_NAMES = ["python3", "python"] Collision resolvers ------------------- You don't have to worry about inaccessibility of models with conflicting names. If you have conflicting model names, all conflicts can be resolved automatically. All models will be available under shell_plus, some of them with intuitive aliases. This mechanism is highly configurable and you must only set ``SHELL_PLUS_MODEL_IMPORTS_RESOLVER``. You should set full path to collision resolver class. All predefined collision resolvers are in ``django_extensions.collision_resolvers`` module. Example:: SHELL_PLUS_MODEL_IMPORTS_RESOLVER = 'django_extensions.collision_resolvers.FullPathCR' All collision resolvers searches for models with the same name. If conflict is detected they decides, which model to choose. Some of them are creating aliases for all conflicting models. **Example** Suppose that we have two apps: - programming(with models Language and Framework) - workers(with models Language and Worker) 'workers' app is last in alphabetical order, but suppose that 'programming' app is occurs firstly in ``INSTALLED_APPS``. Collision resolvers won't change aliases for models Framework and Worker, because their names are unique. There are several types of collision resolvers: **LegacyCR** Default collision resolver. Model from last application in alphabetical order is selected:: from workers import Language **InstalledAppsOrderCR** Collision resolver which selects the first model from INSTALLED_APPS. You can set your own app priorities list subclassing him and overwriting ``APP_PRIORITIES`` field. This collision resolver will select a model from the first app on this list. If both app's are absent on this list, resolver will choose a model from the first app in alphabetical order:: from programming import Language **FullPathCR** Collision resolver which transform full model name to alias by changing dots to underscores. He also removes 'models' part of alias, because all models are in models.py files. Model from last application in alphabetical order is selected:: from programming import Language (as programming_Language) from workers import Language, Language (as workers_Language) **AppNamePrefixCR** Collision resolver which transform pair (app name, model_name) to alias ``{app_name}_{model_name}``. Model from last application in alphabetical order is selected. Result is different than FullPathCR, when model has app_label other than current app:: from programming import Language (as programming_Language) from workers import Language, Language (as workers_Language) **AppNameSuffixCR** Collision resolver which transform pair (app name, model_name) to alias ``{model_name}_{app_name}`` Model from last application in alphabetical order is selected:: from programming import Language (as Language_programming) from workers import Language, Language (as Language_workers) **AppNamePrefixCustomOrderCR** Collision resolver which is mixin of AppNamePrefixCR and InstalledAppsOrderCR. In case of collisions he sets aliases like AppNamePrefixCR, but sets the default model using InstalledAppsOrderCR:: from programming import Language, Language (as programming_Language) from workers import Language (as workers_Language) **AppNameSuffixCustomOrderCR** Collision resolver which is a mixin of AppNameSuffixCR and InstalledAppsOrderCR. In case of collisions he sets aliases like AppNameSuffixCR, but sets the default model using InstalledAppsOrderCR:: from programming import Language, Language (as Language_programming) from workers import Language (as Language_workers) **FullPathCustomOrderCR** Collision resolver which is a mixin of FullPathCR and InstalledAppsOrderCR. In case of collisions he sets aliases like FullPathCR, but sets the default model using InstalledAppsOrderCR:: from programming import Language, Language (as programming_Language) from workers import Language (as workers_Language) **AppLabelPrefixCR** Collision resolver which transform pair (app_label, model_name) to alias ``{app_label}_{model_name}`` This is very similar to ``AppNamePrefixCR`` but this may generate shorter names in the case of apps nested into several namespace (like Django's auth app):: # with AppNamePrefixCR from django.contrib.auth.models import Group (as django_contrib_auth_Group) # with AppLabelPrefixCR from django.contrib.auth.models import Group (as auth_Group) **AppLabelSuffixCR** Collision resolver which transform pair (app_label, model_name) to alias ``{model_name}_{app_label}`` Similar idea as the above, but based on ``AppNameSuffixCR``:: # with AppNamePrefixCR from django.contrib.auth.models import Group (as Group_django_contrib_auth) # with AppLabelSuffixCR from django.contrib.auth.models import Group (as Group_auth) Writing your custom collision resolver -------------------------------------- You can customize models import behaviour by subclassing one of the abstract collision resolvers: **PathBasedCR** Abstract resolver which transforms full model name into alias. To use him you need to overwrite transform_import function which should have one parameter. It will be a full model name. It should return valid alias as a str instance. **AppNameCR** Abstract collision resolver which transform pair (app name, model_name) to alias by changing dots to underscores. You must define ``MODIFICATION_STRING`` which should be string to format with two keyword arguments: app_name and model_name. For example: ``{app_name}_{model_name}``. Model from last application in alphabetical order is selected. You can mix PathBasedCR or AppNameCR with InstalledAppsOrderCR, but InstalledAppsOrderCR should be the second base class. **BaseCR** Abstract base collision resolver. All collision resolvers needs to inherit from this class. To write a custom collision resolver you need to overwrite the resolve_collisions function. It receives ``Dict[str, List[str]]`` where key is model name and values are full model names (full model name means: module + model_name). You should return ``Dict[str, str]``, where key is model name and value is full model name. Import Subclasses ------------------- If you want to load automatically all project subclasses of some base class, you can achieve this by setting ``SHELL_PLUS_SUBCLASSES_IMPORT`` option. It must be a list of either classes or strings containing paths to these classes. For example, if you want to load all your custom managers then you should provide:: from django.db.models import Manager SHELL_PLUS_SUBCLASSES_IMPORT = [Manager] Then shell_plus will load all your custom managers:: # Shell Plus Subclasses Imports from utils.managers import AbstractManager from myapp.managers import MyCustomManager from somewhere.else import MyOtherManager # django.db.models.Manager is not loaded because only project classes are. By default, all subclasses of your base class from all projects modules will be loaded. You can exclude some modules and all their submodules by passing ``SHELL_PLUS_SUBCLASSES_IMPORT_MODULES_BLACKLIST`` option:: SHELL_PLUS_SUBCLASSES_IMPORT_MODULES_BLACKLIST = ['utils', 'somewhere.else'] Elements of this list must be strings containing full modules paths. If these modules are excluded only ``MyCustomManager`` from ``myapp.managers`` will be loaded. If you are using ``SHELL_PLUS_SUBCLASSES_IMPORT`` shell_plus loads all project modules for finding subclasses. Sometimes it can lead to some errors(for example when we have an old unused module which contains syntax errors). Excluding these modules can help avoid shell_plus crashes in some situations. It is recommended to exclude all ``setup.py`` files. IPython Notebook ---------------- There are two settings that you can use to pass your custom options to the IPython Notebook in your Django settings. The first one is ``NOTEBOOK_ARGUMENTS`` that can be used to hold those options that available via:: $ ipython notebook -h For example:: NOTEBOOK_ARGUMENTS = [ '--ip', 'x.x.x.x', '--port', 'xx', ] Another one is ``IPYTHON_ARGUMENTS`` that for those options that available via:: $ ipython -h The Django settings module and database models are auto-loaded into the interactive shell's global namespace also for IPython Notebook. Auto-loading is done by a custom IPython extension which is activated by default by passing the ``--ext django_extensions.management.notebook_extension`` argument to the Notebook. If you need to pass custom options to the IPython Notebook, you can override the default options in your Django settings using the ``IPYTHON_ARGUMENTS`` setting. For example:: IPYTHON_ARGUMENTS = [ '--ext', 'django_extensions.management.notebook_extension', '--ext', 'myproject.notebook_extension', '--debug', ] To activate auto-loading, remember to either include the django-extensions' default notebook extension or copy its auto-loading code into your own extension. Note that the IPython Notebook feature doesn't currently honor the ``--dont-load`` option. .. _`IPython Notebook`: http://ipython.org/ipython-doc/dev/interactive/htmlnotebook.html Additional Imports ------------------ In addition to importing the models, you can specify other items to import by default. These can be specified with the settings ``SHELL_PLUS_IMPORTS``, ``SHELL_PLUS_PRE_IMPORTS`` and ``SHELL_PLUS_POST_IMPORTS``. The order of import loading is as follows: - ``SHELL_PLUS_PRE_IMPORTS`` - Subclasses (if enabled) - Models (if not disabled) - Default Django imports (if not disabled) - ``SHELL_PLUS_IMPORTS`` - ``SHELL_PLUS_POST_IMPORTS`` Example for in your ``settings.py`` file: :: SHELL_PLUS_IMPORTS = [ 'from module.submodule1 import class1, function2', 'from module.submodule2 import function3 as another1', 'from module.submodule3 import *', 'import module.submodule4', ] These symbols will be available as soon as the shell starts. Database application signature ------------------------------ If using PostgreSQL the ``application_name`` is set by default to ``django_shell`` to help identify queries made under shell_plus. SQL queries ------------------------- If the configuration option DEBUG is set to True, it is possible to print SQL queries as they're executed in shell_plus like:: $ ./manage.py shell_plus --print-sql You can also set the configuration option SHELL_PLUS_PRINT_SQL to omit the above command line option. :: # print SQL queries in shell_plus SHELL_PLUS_PRINT_SQL = True Printing SQL queries also comes with the possibility of specifying the maximum amount of characters to display: $ ./manage.py shell_plus --print-sql --truncate-sql `--truncate-sql` accepts an int value starting from 0 (which disables truncation). Defaults to 1000. You can also set the configuration option SHELL_PLUS_PRINT_SQL_TRUNCATE to omit the above command line option. :: # print SQL queries in shell_plus SHELL_PLUS_PRINT_SQL_TRUNCATE = None django-extensions-3.1.5/docs/sqlcreate.rst000066400000000000000000000027101414177705400206360ustar00rootroot00000000000000sqlcreate ========== :synopsis: Helps you setup your database(s) more easily Introduction ------------- Stop creating databases by hand. Your settings.py file already contains the correct information, so DRY. Usage ------------- $ python manage.py sqlcreate [--database=] | It will spit out SQL which you can review (if you want). Ultimately you want to pipe it into the database shell command of your choice. If there were a good way to ensure that the user in the database settings had the proper permissions, we could submit the commands straight to the database. However, due to the nature of this portion of the project setup, that will never happen. Example ------------- PostgreSQL ~~~~~~~~~~ $ ./manage.py sqlcreate [--database=] | psql -U -W .. note:: If `USER` or `PASSWORD` are empty string or None, the `sqlcreate` assumes that unix domain socket connection mode is being used, and because of that the SQL clauses `CREATE USER` and privilege grants to the database and database user are not generated. MySQL ~~~~~ $ ./manage.py sqlcreate [--database=] | mysql -u -p Known Issues ------------ * CREATE DATABASE is not SQL standard so might not work everywhere. * When using fallback user is not created and password is not set. But it does try to do a GRANT to the database user. * Missing options for tablespaces, etc. django-extensions-3.1.5/docs/sqldiff.rst000066400000000000000000000020571414177705400203070ustar00rootroot00000000000000sqldiff ======= :synopsis: Prints the ALTER TABLE statements for the given appnames. Django command that scans all models for the given appnames and compares their database schema with the real database tables. It indicates how columns in the database are different from the SQL that would be generated by Django. This command is not a database migration tool, though it might certainly be of help during migrations. Its purpose is to show the current differences as a way to check or debug your models compared to the real database tables and columns. Supported Databases ------------------- Currently the following databases are supported: * PostgreSQL * Sqlite3 * MySQL * Oracle Patches to support other databases are welcome! :-) Exit Codes ---------- Exit status is 0 if inputs are the same, 1 if different, 2 if trouble. Example Usage ------------- :: # View SQL differences for all installed applications $ ./manage.py sqldiff -a :: # View SQL differences for all installed applications using text instead of SQL $ ./manage.py sqldiff -a -t django-extensions-3.1.5/docs/sqldsn.rst000066400000000000000000000014511414177705400201600ustar00rootroot00000000000000sqldsn ====== :synopsis: Prints Data Source Name connection string on stdout Supported Databases ------------------- Currently the following databases are supported: * PostgreSQL (psycopg2 or postgis) * Sqlite3 * MySQL Patches to support other databases are welcome! :-) Exit Codes ---------- Exit status is 0 Example Usage ------------- :: # Prints the DSN for the default database $ ./manage.py sqldsn :: # Prints the DSN for all databases $ ./manage.py sqldsn --all :: # Print the DSN for database named 'slave' $ ./manage.py sqldsn --database=slave :: # Print all DSN styles available for the default database $ ./manage.py sqldsn --style=all :: # Create .pgpass file for default database by using the quiet option $ ./manage.py sqldsn -q --style=pgpass > .pgpass django-extensions-3.1.5/docs/sync_s3.rst000066400000000000000000000040331414177705400202340ustar00rootroot00000000000000sync_s3 ======= :synopsis: sync your MEDIA_ROOT and STATIC_ROOT folders to S3 Django command that scans all files in your settings.MEDIA_ROOT and settings.STATIC_ROOT folders, then uploads them to S3 with the same directory structure. This command can optionally do the following but it is off by default: * gzip compress any CSS and Javascript files it finds and adds the appropriate 'Content-Encoding' header. * set a far future 'Expires' header for optimal caching. * upload only media or static files. * use any other provider compatible with Amazon S3. * set other than 'public-read' ACL. Example Usage ------------- :: # Upload files to S3 into the bucket 'mybucket' $ ./manage.py sync_s3 mybucket :: # Upload files to S3 into the bucket 'mybucket' and enable gzipping CSS/JS files and setting of a far future expires header $ ./manage.py sync_s3 mybucket --gzip --expires :: # Upload only media files to S3 into the bucket 'mybucket' $ ./manage.py sync_s3 mybucket --media-only # or --static-only :: # Upload only media files to a S3 compatible provider into the bucket 'mybucket' and set private file ACLs $ ./manage.py sync_s3 mybucket --media-only --s3host=cs.example.com --acl=private Required libraries and settings ------------------------------- This management command requires the boto library and was tested with version 1.4c: https://github.com/boto/boto It also requires an account with Amazon Web Services (AWS) and the AWS S3 keys. Bucket name is required and cannot be empty. The keys and bucket name are added to your settings.py file, for example:: # settings.py AWS_ACCESS_KEY_ID = '' AWS_SECRET_ACCESS_KEY = '' AWS_BUCKET_NAME = 'bucket' Optional settings ----------------- It is possible to customize sync_s3 directly from django settings file, for example:: # settings.py AWS_S3_HOST = 'cs.example.com' AWS_DEFAULT_ACL = 'private' SYNC_S3_PREFIX = 'some_prefix' FILTER_LIST = 'dir1, dir2' AWS_CLOUDFRONT_DISTRIBUTION = 'E27LVI50CSW06W' SYNC_S3_RENAME_GZIP_EXT = '.gz' django-extensions-3.1.5/docs/syncdata.rst000066400000000000000000000015551414177705400204670ustar00rootroot00000000000000syncdata ======== :synopsis: Makes the current database have the same data as the fixture(s), no more, no less. Introduction ------------- Django command similar to 'loaddata' but also deletes. After 'syncdata' has run, the database will have the same data as the fixture - anything missing will be added, anything different will be updated, and anything extra will be deleted. Usage ----- .. tip:: Command will loop over *fixtures* inside installed apps and pathes defined in ``FIXTURE_DIRS``. Assuming that you've got sample.json under *fixtures* directory in one of your ``INSTALLED_APPS``:: $ python manage.py syncdata sample.json If you want to keep old records use ``--skip-remove`` option:: $ python manage syncdata sample.xml --skip-remove You can provide full path to your fixtures file like:: $ python manage syncdata /var/fixtures/sample.json django-extensions-3.1.5/docs/utilities.rst000066400000000000000000000013431414177705400206670ustar00rootroot00000000000000Utilities ========= :synopsis: Other utility functions or classes InternalIPS ----------- `InternalIPS` allows to specify CIDRs for `INTERNAL_IPS` settings parameter. Example `settings.py`:: from django_extensions.utils import InternalIPS INTERNAL_IPS = InternalIPS([ "127.0.0.1", "172.16.0.0/16", ]) Use `sort_by_size` to sort the lookups to search the largest subnet first. Example `settings.py`:: from django_extensions.utils.internal_ips import InternalIPS INTERNAL_IPS = InternalIPS([ "127.0.0.1", "172.16.0.0/16", ], sort_by_size=True) `InternalIPS` is inspired by `netaddr.IPSet` please consider using it instead as it is more optimized but requires the additional `netaddr` package. django-extensions-3.1.5/docs/validate_templates.rst000066400000000000000000000037071414177705400225310ustar00rootroot00000000000000validate_templates ================== :synopsis: Checks templates on syntax or compile errors. This will catch any invalid Django template syntax, for example:: {% foobar %} {% comment %} This throws this error: TemplateSyntaxError Invalid block tag on line 1: 'foobar'. Did you forget to register or load this tag? {% endcomment %} Note that this will not catch invalid HTML, only errors in the Django template syntax used. Options ------- verbosity ~~~~~~~~~ A higher verbosity level will print out all the files that are processed instead of only the ones that contain errors. break ~~~~~ Do not continue scanning other templates after the first failure. ignore_app ~~~~~~~~~~ Ignore this app (can be used multiple times). includes ~~~~~~~~ Use -i (can be used multiple times) to add directories to the TEMPLATE DIRS. no_apps ~~~~~~~ Do not automatically include app template directories. Settings -------- VALIDATE_TEMPLATES_IGNORE_APPS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Ignore the following apps VALIDATE_TEMPLATES_IGNORES ~~~~~~~~~~~~~~~~~~~~~~~~~~ Ignore file names which matches these patterns. Matching is done via `fnmatch`. VALIDATE_TEMPLATES_EXTRA_TEMPLATE_DIRS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can use `VALIDATE_TEMPLATES_EXTRA_TEMPLATE_DIRS` to include a number of template dirs by default directly from the settings file. This can be useful for situations where TEMPLATE DIRS is dynamically generated or switched in middleware, or when you have other template dirs for external applications like celery, and you want to check those as well. Usage Example ------------- :: ./manage.py validate_templates You can also integrate it with your tests, like this:: import unittest from django.core.management import call_command class MyTests(unittest.TestCase): def test_validate_templates(self): call_command("validate_templates") # This throws an error if it fails to validate django-extensions-3.1.5/docs/validators.rst000066400000000000000000000017711414177705400210310ustar00rootroot00000000000000Validators ========== :synopsis: Validator extensions Usage ----- Example:: from django_extensions.validators import HexValidator class UserKeys(models.Model): user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) public_key = models.CharField(max_length=64, validators=[HexValidator(length=64)]) private_key = models.CharField(max_length=128, validators=[HexValidator(length=128)]) Current Database Model Field Extensions --------------------------------------- ``NoControlCharactersValidator`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Validates that Control Characters like new lines or tabs are not allowed. Can optionally specify `whitelist` of control characters to allow. ``NoWhitespaceValidator`` ~~~~~~~~~~~~~~~~~~~~~~~~~ Validates that leading and trailing whitespace is not allowed. ``HexValidator`` ~~~~~~~~~~~~~~~~ Validates that the string is a valid hex string. Can optionally also specify `length`, `min_length` and `max_length` parameters. django-extensions-3.1.5/manage.py000077500000000000000000000004321414177705400167750ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- import os import sys if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tests.testapp.settings") from django.core.management import execute_from_command_line execute_from_command_line(sys.argv) django-extensions-3.1.5/requirements-dev.txt000066400000000000000000000002521414177705400212300ustar00rootroot00000000000000shortuuid werkzeug django_pdb python-dateutil pytest-django pytest-cov factory-boy requests mock pygments vobject types-pyOpenSSL types-PyYAML types-boto types-requests django-extensions-3.1.5/setup.cfg000066400000000000000000000013311414177705400170100ustar00rootroot00000000000000[flake8] ignore = E128,E731,W503,W504 max-line-length = 512 exclude=venv/ [tool:pytest] DJANGO_SETTINGS_MODULE=tests.testapp.settings norecursedirs=venv* .tox .eggs build dist django_extensions.egg-info django_extensions/mongodb addopts = --doctest-modules --doctest-ignore-import-errors --nomigrations --cov=django_extensions --cov-report html --cov-report term [isort] combine_as_imports = true default_section = THIRDPARTY include_trailing_comma = true known_third_party = django known_first_party = django_extensions multi_line_output = 5 line_length=128 [pycodestyle] count = False max-line-length = 512 statistics = True ignore = D100,D101,D102,D103,D104,D105,D106,D107,D202,D203,D205,D210,D212,D400,D406,D407,D412,D413 django-extensions-3.1.5/setup.py000066400000000000000000000114001414177705400166770ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Based entirely on Django's own ``setup.py``. """ import os import sys import setuptools from distutils.command.install import INSTALL_SCHEMES from distutils.command.install_data import install_data from setuptools import setup class osx_install_data(install_data): # On MacOS, the platform-specific lib dir is at: # /System/Library/Framework/Python/.../ # which is wrong. Python 2.5 supplied with MacOS 10.5 has an Apple-specific # fix for this in distutils.command.install_data#306. It fixes install_lib # but not install_data, which is why we roll our own install_data class. def finalize_options(self): # By the time finalize_options is called, install.install_lib is set to # the fixed directory, so we set the installdir to install_lib. The # install_data class uses ('install_data', 'install_dir') instead. self.set_undefined_options('install', ('install_lib', 'install_dir')) install_data.finalize_options(self) if sys.platform == "darwin": cmdclasses = {'install_data': osx_install_data} else: cmdclasses = {'install_data': install_data} def fullsplit(path, result=None): """ Split a pathname into components (the opposite of os.path.join) in a platform-neutral way. """ if result is None: result = [] head, tail = os.path.split(path) if head == '': return [tail] + result if head == path: return result return fullsplit(head, [tail] + result) # Tell distutils to put the data_files in platform-specific installation # locations. See here for an explanation: # http://groups.google.com/group/comp.lang.python/browse_thread/thread/35ec7b2fed36eaec/2105ee4d9e8042cb for scheme in INSTALL_SCHEMES.values(): scheme['data'] = scheme['purelib'] # Compile the list of packages available, because distutils doesn't have # an easy way to do this. packages, package_data = [], {} root_dir = os.path.dirname(__file__) if root_dir != '': os.chdir(root_dir) extensions_dir = 'django_extensions' for dirpath, dirnames, filenames in os.walk(extensions_dir): # Ignore PEP 3147 cache dirs and those whose names start with '.' dirnames[:] = [d for d in dirnames if not d.startswith('.') and d != '__pycache__'] parts = fullsplit(dirpath) package_name = '.'.join(parts) if '__init__.py' in filenames: packages.append(package_name) elif filenames: relative_path = [] while '.'.join(parts) not in packages: relative_path.append(parts.pop()) relative_path.reverse() path = os.path.join(*relative_path) package_files = package_data.setdefault('.'.join(parts), []) package_files.extend([os.path.join(path, f) for f in filenames]) version = __import__('django_extensions').__version__ if int(setuptools.__version__.split(".", 1)[0]) < 18: assert "bdist_wheel" not in sys.argv, "setuptools 18 or later is required for wheels." long_description = """django-extensions bundles several useful additions for Django projects. See the project page for more information: http://github.com/django-extensions/django-extensions""" if os.path.isfile("README.rst"): with open("README.rst") as f: long_description = f.read() setup( name='django-extensions', version=version, description="Extensions for Django", long_description=long_description, author='Michael Trier', author_email='mtrier@gmail.com', maintainer='Bas van Oostveen', maintainer_email='v.oostveen@gmail.com', url='http://github.com/django-extensions/django-extensions', license='MIT License', platforms=['any'], packages=packages, cmdclass=cmdclasses, package_data=package_data, python_requires=">=3.6", install_requires=["Django>=2.2"], extras_require={}, classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Framework :: Django', 'Framework :: Django :: 2.2', 'Framework :: Django :: 3.0', 'Framework :: Django :: 3.1', 'Framework :: Django :: 3.2', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Utilities', ], ) django-extensions-3.1.5/tests/000077500000000000000000000000001414177705400163335ustar00rootroot00000000000000django-extensions-3.1.5/tests/__init__.py000066400000000000000000000002551414177705400204460ustar00rootroot00000000000000# -*- coding: utf-8 -*- from unittest import mock force_color_support = mock.patch('django.core.management.color.supports_color', autospec=True, side_effect=lambda: True) django-extensions-3.1.5/tests/auth/000077500000000000000000000000001414177705400172745ustar00rootroot00000000000000django-extensions-3.1.5/tests/auth/__init__.py000066400000000000000000000000001414177705400213730ustar00rootroot00000000000000django-extensions-3.1.5/tests/auth/test_mixins.py000066400000000000000000000031341414177705400222150ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django.test import TestCase, RequestFactory from django.http import HttpResponse from django.views.generic import DetailView from django.contrib.auth import get_user_model from django.contrib.auth.models import AnonymousUser from django.core.exceptions import PermissionDenied from django_extensions.auth.mixins import ModelUserFieldPermissionMixin from tests.testapp.models import HasOwnerModel class EmptyResponseView(DetailView): model = HasOwnerModel def get(self, request, *args, **kwargs): return HttpResponse() class OwnerView(ModelUserFieldPermissionMixin, EmptyResponseView): model_permission_user_field = 'owner' class ModelUserFieldPermissionMixinTests(TestCase): factory = RequestFactory() User = get_user_model() @classmethod def setUpTestData(cls): cls.user = cls.User.objects.create(username="Joe", password="pass") cls.ownerModel = HasOwnerModel.objects.create(owner=cls.user) # Test if owner model has access def test_permission_pass(self): request = self.factory.get('/permission-required/' + str(self.ownerModel.id)) request.user = self.user resp = OwnerView.as_view()(request) self.assertEqual(resp.status_code, 200) # # Test if non owner model is redirected def test_permission_denied_and_redirect(self): request = self.factory.get('/permission-required/' + str(self.ownerModel.id)) request.user = AnonymousUser() resp = OwnerView.as_view()(request) self.assertRaises(PermissionDenied) self.assertEqual(resp.status_code, 302) django-extensions-3.1.5/tests/collisions/000077500000000000000000000000001414177705400205115ustar00rootroot00000000000000django-extensions-3.1.5/tests/collisions/__init__.py000066400000000000000000000000001414177705400226100ustar00rootroot00000000000000django-extensions-3.1.5/tests/collisions/apps.py000066400000000000000000000002231414177705400220230ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django.apps import AppConfig class FooConfig(AppConfig): name = 'tests.collisions' app_label = 'collisions' django-extensions-3.1.5/tests/collisions/models.py000066400000000000000000000020531414177705400223460ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django.db import models class Note(models.Model): # name conflict with testapp.Note text = models.CharField(max_length=42) creation_date = models.DateField(auto_now_add=True) class Meta: app_label = 'collisions' class UniqueModel(models.Model): # no conflicts global_hash_id = models.CharField(max_length=32) class Meta: app_label = 'collisions' class Group(models.Model): # conflict with django.contrib.auth name = models.CharField(max_length=10) priority = models.IntegerField() class Name(models.Model): # name conflict with testapp.Name real_name = models.CharField(max_length=50) number_of_users_having_this_name = models.IntegerField() class Meta: app_label = 'collisions' class SystemUser(models.Model): # no conflicts but FK to conflicting models. name = models.ForeignKey(Name, on_delete=models.CASCADE) group = models.ForeignKey(Group, on_delete=models.CASCADE) global_id = models.CharField(unique=True, max_length=32) django-extensions-3.1.5/tests/db/000077500000000000000000000000001414177705400167205ustar00rootroot00000000000000django-extensions-3.1.5/tests/db/fields/000077500000000000000000000000001414177705400201665ustar00rootroot00000000000000django-extensions-3.1.5/tests/db/fields/test_uniq_field_mixin.py000066400000000000000000000123631414177705400251270ustar00rootroot00000000000000# -*- coding: utf-8 -*- from unittest import mock from django.db import models from django.test import TestCase from tests.testapp.models import ( DummyRelationModel, InheritedFromPostWithUniqField, PostWithUniqField, ReverseModel, SecondDummyRelationModel, ThirdDummyRelationModel, ) from django_extensions.db.fields import UniqueFieldMixin class UniqFieldMixinTestCase(TestCase): def setUp(self): class MockField(UniqueFieldMixin): def __init__(self, **kwargs): for key, value in kwargs.items(): setattr(self, key, value) self.uniq_field = MockField( attname='uniq_field', max_length=255, boolean_attr=True, non_boolean_attr='non_boolean_attr' ) f_dummy = DummyRelationModel.objects.create() s_dummy = SecondDummyRelationModel.objects.create() t_dummy = ThirdDummyRelationModel.objects.create() post = PostWithUniqField.objects.create( uniq_field='test_uniq', common_field='first', another_common_field='second', many_to_one_field=f_dummy, one_to_one_field=s_dummy, ) post.many_to_many_field.add(t_dummy) post.save() ReverseModel.objects.create(post_field=post) self.post = post def tearDown(self): PostWithUniqField.objects.all().delete() DummyRelationModel.objects.all().delete() SecondDummyRelationModel.objects.all().delete() ThirdDummyRelationModel.objects.all().delete() ReverseModel.objects.all().delete() def test_check_is_bool_boolean_attr(self): self.assertIsNone(self.uniq_field.check_is_bool('boolean_attr')) def test_check_is_bool_non_boolean_attr(self): with self.assertRaisesMessage( ValueError, "'non_boolean_attr' argument must be True or False", ): self.uniq_field.check_is_bool('non_boolean_attr') def test__get_fields_returns_list_of_tulpes(self): uniq_mixin_fields = UniqueFieldMixin._get_fields(PostWithUniqField) self.assertIsInstance(uniq_mixin_fields, list) for field in uniq_mixin_fields: self.assertIsInstance(field, tuple) def test__get_fields_returns_correct_fields(self): option_fields = PostWithUniqField._meta.get_fields() uniq_mixin_fields = [i[0] for i in UniqueFieldMixin._get_fields(PostWithUniqField)] self.assertEqual(len(option_fields), 9) self.assertEqual(len(uniq_mixin_fields), 7) fields_to_be_excluded_from_uniq_mixin_fields = [ f for f in option_fields if f.is_relation and not f.one_to_one and not (f.many_to_one and f.related_model) ] for field in fields_to_be_excluded_from_uniq_mixin_fields: self.assertNotIn(field, uniq_mixin_fields) def test__get_fields_returns_correct_model(self): post_models = [i[1] for i in UniqueFieldMixin._get_fields(PostWithUniqField)] self.assertTrue(all(model is None for model in post_models)) inherited_post_models = [ i[1] for i in UniqueFieldMixin._get_fields(InheritedFromPostWithUniqField) if i[1] ] self.assertEqual(len(inherited_post_models), 6) self.assertTrue(all(model is PostWithUniqField) for model in inherited_post_models) def test_get_queryset(self): mocked_get_fields = ( (models.CharField, PostWithUniqField), ) with mock.patch( 'django_extensions.db.fields.UniqueFieldMixin._get_fields', return_value=mocked_get_fields ), mock.patch( 'tests.testapp.models.PostWithUniqField._default_manager.all' ) as mocked_qs_all: self.uniq_field.get_queryset(PostWithUniqField, models.CharField) mocked_qs_all.assert_called_with() mocked_get_fields = ( (models.CharField, None), ) with mock.patch( 'django_extensions.db.fields.UniqueFieldMixin._get_fields', return_value=mocked_get_fields ), mock.patch( 'tests.testapp.models.InheritedFromPostWithUniqField._default_manager.all' ) as mocked_qs_all: self.uniq_field.get_queryset(InheritedFromPostWithUniqField, models.CharField) mocked_qs_all.assert_called_with() def test_find_unique(self): def filter_func(*args, **kwargs): uniq_field = kwargs.get('uniq_field') if uniq_field == 'a': return mocked_qs return None mocked_qs = mock.Mock(spec=PostWithUniqField.objects) mocked_qs.filter.side_effect = filter_func mocked_qs.exclude.return_value = mocked_qs field = models.CharField with mock.patch( 'django_extensions.db.fields.UniqueFieldMixin.get_queryset', return_value=mocked_qs ) as get_qs: res = self.uniq_field.find_unique(self.post, field, iter('abcde')) get_qs.assert_called_with(PostWithUniqField, field) mocked_qs.exclude.assert_called_with(pk=self.post.pk) self.assertEqual(res, 'b') self.assertTrue(hasattr(self.post, 'uniq_field')) self.assertEqual(self.post.uniq_field, 'b') django-extensions-3.1.5/tests/db/fields/test_uniq_field_mixin_compat.py000066400000000000000000000127201414177705400264670ustar00rootroot00000000000000# -*- coding: utf-8 -*- try: from unittest import mock except ImportError: import mock import six from django.db import models from django.test import TestCase from tests.testapp.models import ( DummyRelationModel, InheritedFromPostWithUniqFieldCompat, PostWithUniqFieldCompat, ReverseModelCompat, SecondDummyRelationModel, ThirdDummyRelationModel, ) from django_extensions.db.fields import UniqueFieldMixin class UniqFieldMixinCompatTestCase(TestCase): def setUp(self): class MockField(UniqueFieldMixin): def __init__(self, **kwargs): for key, value in six.iteritems(kwargs): setattr(self, key, value) self.uniq_field = MockField( attname='uniq_field', max_length=255, boolean_attr=True, non_boolean_attr='non_boolean_attr' ) f_dummy = DummyRelationModel.objects.create() s_dummy = SecondDummyRelationModel.objects.create() t_dummy = ThirdDummyRelationModel.objects.create() post = PostWithUniqFieldCompat.objects.create( uniq_field='test_uniq', common_field='first', another_common_field='second', many_to_one_field=f_dummy, one_to_one_field=s_dummy, ) post.many_to_many_field.add(t_dummy) post.save() ReverseModelCompat.objects.create(post_field=post) self.post = post def tearDown(self): PostWithUniqFieldCompat.objects.all().delete() DummyRelationModel.objects.all().delete() SecondDummyRelationModel.objects.all().delete() ThirdDummyRelationModel.objects.all().delete() ReverseModelCompat.objects.all().delete() def test_check_is_bool_boolean_attr(self): self.assertIsNone(self.uniq_field.check_is_bool('boolean_attr')) def test_check_is_bool_non_boolean_attr(self): with self.assertRaisesMessage( ValueError, "'non_boolean_attr' argument must be True or False", ): self.uniq_field.check_is_bool('non_boolean_attr') def test__get_fields_returns_list_of_tulpes(self): uniq_mixin_fields = UniqueFieldMixin._get_fields(PostWithUniqFieldCompat) self.assertIsInstance(uniq_mixin_fields, list) for field in uniq_mixin_fields: self.assertIsInstance(field, tuple) def test__get_fields_returns_correct_fields(self): option_fields = PostWithUniqFieldCompat._meta.get_fields() uniq_mixin_fields = [i[0] for i in UniqueFieldMixin._get_fields(PostWithUniqFieldCompat)] self.assertEqual(len(option_fields), 9) self.assertEqual(len(uniq_mixin_fields), 7) fields_to_be_excluded_from_uniq_mixin_fields = [ f for f in option_fields if f.is_relation and not f.one_to_one and not (f.many_to_one and f.related_model) ] for field in fields_to_be_excluded_from_uniq_mixin_fields: self.assertNotIn(field, uniq_mixin_fields) def test__get_fields_returns_correct_model(self): post_models = [i[1] for i in UniqueFieldMixin._get_fields(PostWithUniqFieldCompat)] self.assertTrue(all(model is None for model in post_models)) inherited_post_models = [ i[1] for i in UniqueFieldMixin._get_fields(InheritedFromPostWithUniqFieldCompat) if i[1] ] self.assertEqual(len(inherited_post_models), 6) self.assertTrue(all(model is PostWithUniqFieldCompat) for model in inherited_post_models) def test_get_queryset(self): mocked_get_fields = ( (models.CharField, PostWithUniqFieldCompat), ) with mock.patch( 'django_extensions.db.fields.UniqueFieldMixin._get_fields', return_value=mocked_get_fields ), mock.patch( 'tests.testapp.models.PostWithUniqFieldCompat._default_manager.all' ) as mocked_qs_all: self.uniq_field.get_queryset(PostWithUniqFieldCompat, models.CharField) mocked_qs_all.assert_called_with() mocked_get_fields = ( (models.CharField, None), ) with mock.patch( 'django_extensions.db.fields.UniqueFieldMixin._get_fields', return_value=mocked_get_fields ), mock.patch( 'tests.testapp.models.InheritedFromPostWithUniqFieldCompat._default_manager.all' ) as mocked_qs_all: self.uniq_field.get_queryset(InheritedFromPostWithUniqFieldCompat, models.CharField) mocked_qs_all.assert_called_with() def test_find_unique(self): def filter_func(*args, **kwargs): uniq_field = kwargs.get('uniq_field') if uniq_field == 'a': return mocked_qs return None mocked_qs = mock.Mock(spec=PostWithUniqFieldCompat.objects) mocked_qs.filter.side_effect = filter_func mocked_qs.exclude.return_value = mocked_qs field = models.CharField with mock.patch( 'django_extensions.db.fields.UniqueFieldMixin.get_queryset', return_value=mocked_qs ) as get_qs: res = self.uniq_field.find_unique(self.post, field, iter('abcde')) get_qs.assert_called_with(PostWithUniqFieldCompat, field) mocked_qs.exclude.assert_called_with(pk=self.post.pk) self.assertEqual(res, 'b') self.assertTrue(hasattr(self.post, 'uniq_field')) self.assertEqual(self.post.uniq_field, 'b') django-extensions-3.1.5/tests/jobs/000077500000000000000000000000001414177705400172705ustar00rootroot00000000000000django-extensions-3.1.5/tests/jobs/daily/000077500000000000000000000000001414177705400203725ustar00rootroot00000000000000django-extensions-3.1.5/tests/jobs/daily/test_cache_cleanup.py000066400000000000000000000015761414177705400245660ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django.core.cache import caches from django.core.management import call_command from django.test import TestCase from django.test.utils import override_settings class CacheCleanupTests(TestCase): """Tests for cache_cleanup job.""" def test_should_not_do_anything_if_settings_does_not_have_CACHES_settings(self): call_command('runjob', 'cache_cleanup', verbosity=2) @override_settings(CACHES={ 'test_cache': { 'BACKEND': 'django.core.cache.backends.db.DatabaseCache', 'LOCATION': 'my_cache_table', } }) def test_remove_all_keys_from_DatabaseCache(self): call_command('createcachetable') db_cache = caches['test_cache'] db_cache.set('my_key', 'hello world') call_command('runjob', 'cache_cleanup', verbosity=2) self.assertIsNone(db_cache.get('my_key')) django-extensions-3.1.5/tests/management/000077500000000000000000000000001414177705400204475ustar00rootroot00000000000000django-extensions-3.1.5/tests/management/__init__.py000066400000000000000000000000001414177705400225460ustar00rootroot00000000000000django-extensions-3.1.5/tests/management/commands/000077500000000000000000000000001414177705400222505ustar00rootroot00000000000000django-extensions-3.1.5/tests/management/commands/__init__.py000066400000000000000000000000001414177705400243470ustar00rootroot00000000000000django-extensions-3.1.5/tests/management/commands/error_raising_command.py000066400000000000000000000003451414177705400271670ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django_extensions.management.base import LoggingBaseCommand class Command(LoggingBaseCommand): help = 'Test error' def handle(self, *args, **options): raise Exception("Test Error") django-extensions-3.1.5/tests/management/commands/fixtures/000077500000000000000000000000001414177705400241215ustar00rootroot00000000000000django-extensions-3.1.5/tests/management/commands/fixtures/fixture_with_nonexistent_field.json000066400000000000000000000007131414177705400333370ustar00rootroot00000000000000[ { "pk" : 3, "fields" : { "is_superuser" : false, "first_name" : "John", "date_joined" : "2019-01-19T13:37:26", "email" : "john@doe.us", "is_active" : true, "last_login" : null, "username" : "jdoe", "is_staff" : false, "user_permissions" : [], "groups" : [], "last_name" : "Doe", "password" : "", "non_existent_field": "something" }, "model" : "auth.user" } ] django-extensions-3.1.5/tests/management/commands/fixtures/invalid_fixture.xml000066400000000000000000000000711414177705400300350ustar00rootroot00000000000000 django-extensions-3.1.5/tests/management/commands/fixtures/users.json000066400000000000000000000007201414177705400261540ustar00rootroot00000000000000[ { "pk" : 3, "fields" : { "is_superuser" : false, "first_name" : "John", "date_joined" : "2019-01-19T13:37:26", "email" : "john@doe.us", "is_active" : true, "last_login" : null, "username" : "jdoe", "is_staff" : false, "user_permissions" : [], "groups" : [], "last_name" : "Doe", "password" : "" }, "model" : "auth.user" } ] django-extensions-3.1.5/tests/management/commands/fixtures/users.xml000066400000000000000000000013611414177705400260050ustar00rootroot00000000000000 False jdoe john@doe.us False True django-extensions-3.1.5/tests/management/commands/shell_plus_tests/000077500000000000000000000000001414177705400256445ustar00rootroot00000000000000django-extensions-3.1.5/tests/management/commands/shell_plus_tests/__init__.py000066400000000000000000000000001414177705400277430ustar00rootroot00000000000000django-extensions-3.1.5/tests/management/commands/shell_plus_tests/test_collision_resolver.py000066400000000000000000000372171414177705400332030ustar00rootroot00000000000000# -*- coding: utf-8 -*- import sys from django.contrib.auth.models import Group, Permission from django.test import override_settings from django_extensions.collision_resolvers import AppNameCR, AppsOrderCR, BaseCR, PathBasedCR from tests.collisions.models import Group as Group_Col from tests.collisions.models import Name as Name_Col from tests.collisions.models import Note as Note_Col from tests.collisions.models import SystemUser, UniqueModel from tests.management.commands.shell_plus_tests.test_utils import AutomaticShellPlusImportsTestCase from tests.testapp.models import Name, Note, UniqueTestAppModel from tests.testapp.models import Permission as TAPermission # Bad user defined collision resolvers: class TestAppsOrderCR(AppsOrderCR): pass class TestAppNameCR(AppNameCR): pass def collision_resolver_which_is_not_class(): pass class CRNotExtendingFromBase: pass class CRNoFunction(BaseCR): pass class CRNoArguments(BaseCR): def resolve_collisions(self): pass class CRBadResult(BaseCR): def resolve_collisions(self, namespace): return 1 class CRBadKey(BaseCR): def resolve_collisions(self, namespace): return {1: 'a'} class CRBadValue(BaseCR): def resolve_collisions(self, namespace): return {'a': 1} class CRBadTransformPath(PathBasedCR): def transform_import(self, module_path): return 1 class CRTestCase(AutomaticShellPlusImportsTestCase): def _assert_models_present_under_names(self, group, group_col, name, name_col, note, note_col, system_user, unique_model, permission, test_app_permission, unique_test_app_model): self.run_shell_plus() self.assert_imported_under_names(Group, group) self.assert_imported_under_names(Group_Col, group_col) self.assert_imported_under_names(Name, name) self.assert_imported_under_names(Name_Col, name_col) self.assert_imported_under_names(Note, note) self.assert_imported_under_names(Note_Col, note_col) self.assert_imported_under_names(SystemUser, system_user) self.assert_imported_under_names(UniqueModel, unique_model) self.assert_imported_under_names(Permission, permission) self.assert_imported_under_names(TAPermission, test_app_permission) self.assert_imported_under_names(UniqueTestAppModel, unique_test_app_model) def test_legacy_collision_resolver(self): self._assert_models_present_under_names( set(), {'Group'}, {'Name'}, set(), {'Note'}, set(), {'SystemUser'}, {'UniqueModel'}, set(), {'Permission'}, {'UniqueTestAppModel'}, ) @override_settings( SHELL_PLUS_MODEL_IMPORTS_RESOLVER='django_extensions.collision_resolvers.AppNamePrefixCR', ) def test_app_name_prefix_collision_resolver(self): self._assert_models_present_under_names( {'django_contrib_auth_Group'}, {'tests_collisions_Group', 'Group'}, {'django_extensions_Name', 'Name'}, {'tests_collisions_Name'}, {'django_extensions_Note', 'Note'}, {'tests_collisions_Note'}, {'SystemUser'}, {'UniqueModel'}, {'django_contrib_auth_Permission'}, {'tests_testapp_Permission', 'Permission'}, {'UniqueTestAppModel'}, ) @override_settings( SHELL_PLUS_MODEL_IMPORTS_RESOLVER='django_extensions.collision_resolvers.AppNameSuffixCR', ) def test_app_name_suffix_collision_resolver(self): self._assert_models_present_under_names( {'Group_django_contrib_auth'}, {'Group_tests_collisions', 'Group'}, {'Name_django_extensions', 'Name'}, {'Name_tests_collisions'}, {'Note_django_extensions', 'Note'}, {'Note_tests_collisions'}, {'SystemUser'}, {'UniqueModel'}, {'Permission_django_contrib_auth'}, {'Permission_tests_testapp', 'Permission'}, {'UniqueTestAppModel'}, ) @override_settings( SHELL_PLUS_MODEL_IMPORTS_RESOLVER='django_extensions.collision_resolvers.AppLabelPrefixCR', ) def test_app_label_prefix_collision_resolver(self): self._assert_models_present_under_names( {'auth_Group'}, {'collisions_Group', 'Group'}, {'django_extensions_Name', 'Name'}, {'collisions_Name'}, {'django_extensions_Note', 'Note'}, {'collisions_Note'}, {'SystemUser'}, {'UniqueModel'}, {'auth_Permission'}, {'testapp_Permission', 'Permission'}, {'UniqueTestAppModel'}, ) @override_settings( SHELL_PLUS_MODEL_IMPORTS_RESOLVER='django_extensions.collision_resolvers.AppLabelSuffixCR', ) def test_app_label_suffix_collision_resolver(self): self._assert_models_present_under_names( {'Group_auth'}, {'Group_collisions', 'Group'}, {'Name_django_extensions', 'Name'}, {'Name_collisions'}, {'Note_django_extensions', 'Note'}, {'Note_collisions'}, {'SystemUser'}, {'UniqueModel'}, {'Permission_auth'}, {'Permission_testapp', 'Permission'}, {'UniqueTestAppModel'}, ) @override_settings( SHELL_PLUS_MODEL_IMPORTS_RESOLVER='django_extensions.collision_resolvers.FullPathCR', ) def test_full_path_collision_resolver(self): self._assert_models_present_under_names( {'django_contrib_auth_Group'}, {'tests_collisions_Group', 'Group'}, {'tests_testapp_Name', 'Name'}, {'tests_collisions_Name'}, {'tests_testapp_Note', 'Note'}, {'tests_collisions_Note'}, {'SystemUser'}, {'UniqueModel'}, {'django_contrib_auth_Permission'}, {'tests_testapp_Permission', 'Permission'}, {'UniqueTestAppModel'}, ) @override_settings( SHELL_PLUS_MODEL_IMPORTS_RESOLVER='django_extensions.collision_resolvers.InstalledAppsOrderCR', ) def test_installed_apps_order_collision_resolver(self): self._assert_models_present_under_names( {'Group'}, set(), set(), {'Name'}, set(), {'Note'}, {'SystemUser'}, {'UniqueModel'}, {'Permission'}, set(), {'UniqueTestAppModel'}, ) @override_settings( SHELL_PLUS_MODEL_IMPORTS_RESOLVER='tests.management.commands.shell_plus_tests.test_collision_resolver.TestAppsOrderCR', ) def test_installed_bad_order_collision_resolver(self): with self.assertRaisesRegex(AssertionError, "You must define APP_PRIORITIES in your resolver class!"): self._assert_models_present_under_names( set(), set(), set(), set(), set(), set(), set(), set(), set(), set(), set(), ) @override_settings( SHELL_PLUS_MODEL_IMPORTS_RESOLVER='tests.management.commands.shell_plus_tests.test_collision_resolver.TestAppNameCR', ) def test_installed_apps_bad_name_collision_resolver(self): with self.assertRaisesRegex(AssertionError, "You must define MODIFICATION_STRING in your resolver class!"): self._assert_models_present_under_names( set(), set(), set(), set(), set(), set(), set(), set(), set(), set(), set(), ) def _assert_bad_resolver(self, message): with self.assertRaisesRegex(AssertionError, message): self._assert_models_present_under_names( set(), set(), set(), set(), set(), set(), set(), set(), set(), set(), set(), ) @override_settings( SHELL_PLUS_MODEL_IMPORTS_RESOLVER='tests.management.commands.shell_plus_tests.test_collision_resolver.collision_resolver_which_is_not_class', ) def test_installed_apps_not_class_collision_resolver(self): self._assert_bad_resolver("SHELL_PLUS_MODEL_IMPORTS_RESOLVER must be subclass of BaseCR!") @override_settings( SHELL_PLUS_MODEL_IMPORTS_RESOLVER='tests.management.commands.shell_plus_tests.test_collision_resolver.CRNotExtendingFromBase', ) def test_installed_apps_not_subclass_of_base(self): self._assert_bad_resolver("SHELL_PLUS_MODEL_IMPORTS_RESOLVER must be subclass of BaseCR!") @override_settings( SHELL_PLUS_MODEL_IMPORTS_RESOLVER='tests.management.commands.shell_plus_tests.test_collision_resolver.CRNoFunction', ) def test_installed_apps_no_resolve_conflicts_function(self): exception_msg = "Can't instantiate abstract class CRNoFunction with abstract methods resolve_collisions" if sys.version_info[:2] >= (3, 9): exception_msg = exception_msg.replace('methods', 'method') with self.assertRaisesRegex(TypeError, exception_msg): self._assert_models_present_under_names( set(), set(), set(), set(), set(), set(), set(), set(), set(), set(), set(), ) @override_settings( SHELL_PLUS_MODEL_IMPORTS_RESOLVER='tests.management.commands.shell_plus_tests.test_collision_resolver.CRNoArguments', ) def test_installed_apps_no_arguments_resolve_conflicts(self): self._assert_bad_resolver("resolve_collisions function must take one argument!") @override_settings( SHELL_PLUS_MODEL_IMPORTS_RESOLVER='tests.management.commands.shell_plus_tests.test_collision_resolver.CRBadResult', ) def test_installed_apps_bad_result(self): self._assert_bad_resolver("Result of resolve_collisions function must be a dict!") @override_settings( SHELL_PLUS_MODEL_IMPORTS_RESOLVER='tests.management.commands.shell_plus_tests.test_collision_resolver.CRBadKey', ) def test_installed_apps_bad_key(self): self._assert_bad_resolver("key in collision resolver result should be str not 1") @override_settings( SHELL_PLUS_MODEL_IMPORTS_RESOLVER='tests.management.commands.shell_plus_tests.test_collision_resolver.CRBadValue', ) def test_installed_apps_bad_value(self): self._assert_bad_resolver("value in collision resolver result should be str not 1") @override_settings( SHELL_PLUS_MODEL_IMPORTS_RESOLVER='tests.management.commands.shell_plus_tests.test_collision_resolver.CRBadTransformPath' ) def test_bad_transform_path(self): self._assert_bad_resolver("result of transform_import must be str!") @override_settings( SHELL_PLUS_MODEL_ALIASES={'testapp': {'Note': 'MyFunnyNote'}}, SHELL_PLUS_MODEL_IMPORTS_RESOLVER='django_extensions.collision_resolvers.AppNamePrefixCR', ) def test_app_name_prefix_collision_resolver_with_model_alias(self): self._assert_models_present_under_names( {'django_contrib_auth_Group'}, {'tests_collisions_Group', 'Group'}, {'django_extensions_Name', 'Name'}, {'tests_collisions_Name'}, {'MyFunnyNote'}, {'Note'}, {'SystemUser'}, {'UniqueModel'}, {'django_contrib_auth_Permission'}, {'tests_testapp_Permission', 'Permission'}, {'UniqueTestAppModel'}, ) @override_settings( SHELL_PLUS_MODEL_ALIASES={'testapp': {'Note': 'Name'}}, SHELL_PLUS_MODEL_IMPORTS_RESOLVER='django_extensions.collision_resolvers.AppNamePrefixCR', ) def test_app_name_prefix_collision_resolver_with_clash_because_of_model_alias(self): self._assert_models_present_under_names( {'django_contrib_auth_Group'}, {'tests_collisions_Group', 'Group'}, {'django_extensions_Name'}, {'tests_collisions_Name'}, {'django_extensions_Note', 'Name'}, {'Note'}, {'SystemUser'}, {'UniqueModel'}, {'django_contrib_auth_Permission'}, {'tests_testapp_Permission', 'Permission'}, {'UniqueTestAppModel'}, ) @override_settings( SHELL_PLUS_APP_PREFIXES={'testapp': 'main_app'}, SHELL_PLUS_MODEL_IMPORTS_RESOLVER='django_extensions.collision_resolvers.AppNamePrefixCR', ) def test_app_name_prefix_collision_resolver_with_app_prefixes(self): self._assert_models_present_under_names( {'django_contrib_auth_Group'}, {'tests_collisions_Group', 'Group'}, {'main_app_Name'}, {'Name'}, {'main_app_Note'}, {'Note'}, {'SystemUser'}, {'UniqueModel'}, {'Permission'}, {'main_app_Permission'}, {'main_app_UniqueTestAppModel'}, ) @override_settings( SHELL_PLUS_APP_PREFIXES={'testapp': 'main_app', 'collisions': 'main_app'}, SHELL_PLUS_MODEL_IMPORTS_RESOLVER='django_extensions.collision_resolvers.AppNamePrefixCR', ) def test_app_name_prefix_collision_resolver_with_clashing_app_prefixes(self): self._assert_models_present_under_names( {'Group'}, {'main_app_Group'}, {'django_extensions_Name', 'main_app_Name'}, {'tests_collisions_Name'}, {'django_extensions_Note', 'main_app_Note'}, {'tests_collisions_Note'}, {'main_app_SystemUser'}, {'main_app_UniqueModel'}, {'Permission'}, {'main_app_Permission'}, {'main_app_UniqueTestAppModel'}, ) @override_settings( SHELL_PLUS_APP_PREFIXES={'testapp': 'main_app', 'auth': 'main_app'}, SHELL_PLUS_MODEL_IMPORTS_RESOLVER='django_extensions.collision_resolvers.AppNamePrefixCR', ) def test_app_name_prefix_collision_resolver_clash_with_3rd_party(self): self._assert_models_present_under_names( {'main_app_Group'}, {'Group'}, {'main_app_Name'}, {'Name'}, {'main_app_Note'}, {'Note'}, {'SystemUser'}, {'UniqueModel'}, {'django_contrib_auth_Permission'}, {'main_app_Permission', 'tests_testapp_Permission'}, {'main_app_UniqueTestAppModel'}, ) @override_settings( SHELL_PLUS_APP_PREFIXES={'testapp': 'testapp', 'auth': 'auth', 'collisions': 'collisions'}, SHELL_PLUS_MODEL_IMPORTS_RESOLVER='django_extensions.collision_resolvers.AppNamePrefixCR', ) def test_no_collisions_because_of_app_prefixes(self): self._assert_models_present_under_names( {'auth_Group'}, {'collisions_Group'}, {'testapp_Name'}, {'collisions_Name'}, {'testapp_Note'}, {'collisions_Note'}, {'collisions_SystemUser'}, {'collisions_UniqueModel'}, {'auth_Permission'}, {'testapp_Permission'}, {'testapp_UniqueTestAppModel'}, ) @override_settings( SHELL_PLUS_MODEL_IMPORTS_RESOLVER='django_extensions.collision_resolvers.AppNamePrefixCustomOrderCR', ) def test_app_name_prefix_custom_order_collision_resolver(self): self._assert_models_present_under_names( {'django_contrib_auth_Group', 'Group'}, {'tests_collisions_Group'}, {'django_extensions_Name'}, {'tests_collisions_Name', 'Name'}, {'django_extensions_Note'}, {'tests_collisions_Note', 'Note'}, {'SystemUser'}, {'UniqueModel'}, {'django_contrib_auth_Permission', 'Permission'}, {'tests_testapp_Permission'}, {'UniqueTestAppModel'}, ) @override_settings( SHELL_PLUS_MODEL_IMPORTS_RESOLVER='django_extensions.collision_resolvers.AppNameSuffixCustomOrderCR', ) def test_app_name_suffix_custom_order_collision_resolver(self): self._assert_models_present_under_names( {'Group_django_contrib_auth', 'Group'}, {'Group_tests_collisions'}, {'Name_django_extensions'}, {'Name_tests_collisions', 'Name'}, {'Note_django_extensions'}, {'Note_tests_collisions', 'Note'}, {'SystemUser'}, {'UniqueModel'}, {'Permission_django_contrib_auth', 'Permission'}, {'Permission_tests_testapp'}, {'UniqueTestAppModel'}, ) @override_settings( SHELL_PLUS_MODEL_IMPORTS_RESOLVER='django_extensions.collision_resolvers.FullPathCustomOrderCR', ) def test_full_path_custom_order_collision_resolver(self): self._assert_models_present_under_names( {'django_contrib_auth_Group', 'Group'}, {'tests_collisions_Group'}, {'tests_testapp_Name'}, {'tests_collisions_Name', 'Name'}, {'tests_testapp_Note'}, {'tests_collisions_Note', 'Note'}, {'SystemUser'}, {'UniqueModel'}, {'django_contrib_auth_Permission', 'Permission'}, {'tests_testapp_Permission'}, {'UniqueTestAppModel'}, ) django-extensions-3.1.5/tests/management/commands/shell_plus_tests/test_import_subclasses.py000066400000000000000000000130421414177705400330160ustar00rootroot00000000000000# -*- coding: utf-8 -*- from typing import Optional, Set # noqa from django.conf import settings from django.test.utils import override_settings from tests.management.commands.shell_plus_tests.test_utils import AutomaticShellPlusImportsTestCase from tests.test_module_in_project_dir import FourthDerivedClass from tests.testapp.derived_classes_for_testing import SecondDerivedClass from tests.testapp.derived_classes_for_testing.test_module import ClassWhichShouldNotBeImported, ThirdDerivedClass from tests.testapp.classes_to_include import BaseIncludedClass, FirstDerivedClass, IncludedMixin class ImportSubclassesTestCase(AutomaticShellPlusImportsTestCase): def test_imports_no_subclasses(self): self.assert_imports() @override_settings( SHELL_PLUS_SUBCLASSES_IMPORT=[], ) def test_imports_empty_list(self): self.assert_imports() @override_settings( SHELL_PLUS_SUBCLASSES_IMPORT=[BaseIncludedClass], ) def test_imports_one_base_class(self): self.assert_imports( first={'FirstDerivedClass'}, second={'SecondDerivedClass'}, fourth={'FourthDerivedClass'}, ) @override_settings( SHELL_PLUS_SUBCLASSES_IMPORT=['tests.testapp.classes_to_include.BaseIncludedClass'], ) def test_imports_one_base_class_as_string(self): self.assert_imports( first={'FirstDerivedClass'}, second={'SecondDerivedClass'}, fourth={'FourthDerivedClass'}, ) @override_settings( SHELL_PLUS_SUBCLASSES_IMPORT=[IncludedMixin], ) def test_imports_one_base_mixin(self): self.assert_imports( first={'FirstDerivedClass'}, third={'ThirdDerivedClass'}, ) @override_settings( SHELL_PLUS_SUBCLASSES_IMPORT=[BaseIncludedClass, IncludedMixin], ) def test_imports_two_base_classes(self): self.assert_imports( first={'FirstDerivedClass'}, second={'SecondDerivedClass'}, third={'ThirdDerivedClass'}, fourth={'FourthDerivedClass'}, ) @override_settings( SHELL_PLUS_SUBCLASSES_IMPORT=[BaseIncludedClass, IncludedMixin], SHELL_PLUS_SUBCLASSES_IMPORT_MODULES_BLACKLIST=settings.SHELL_PLUS_SUBCLASSES_IMPORT_MODULES_BLACKLIST + [ 'tests.testapp', ] ) def test_imports_two_base_classes_exclude_testapp(self): self.assert_imports( fourth={'FourthDerivedClass'}, ) @override_settings( SHELL_PLUS_SUBCLASSES_IMPORT=[BaseIncludedClass, IncludedMixin], SHELL_PLUS_SUBCLASSES_IMPORT_MODULES_BLACKLIST=settings.SHELL_PLUS_SUBCLASSES_IMPORT_MODULES_BLACKLIST + [ 'tests.testapp.derived_classes_for_testing', ] ) def test_imports_two_base_classes_exclude_derived_class_for_testing(self): self.assert_imports( first={'FirstDerivedClass'}, fourth={'FourthDerivedClass'}, ) @override_settings( SHELL_PLUS_SUBCLASSES_IMPORT=[BaseIncludedClass, IncludedMixin], SHELL_PLUS_SUBCLASSES_IMPORT_MODULES_BLACKLIST=settings.SHELL_PLUS_SUBCLASSES_IMPORT_MODULES_BLACKLIST + [ 'tests.testapp.derived_classes_for_testing.test_module', ] ) def test_imports_two_base_classes_exclude_test_module(self): self.assert_imports( first={'FirstDerivedClass'}, second={'SecondDerivedClass'}, fourth={'FourthDerivedClass'}, ) @override_settings( SHELL_PLUS_SUBCLASSES_IMPORT=[BaseIncludedClass, IncludedMixin], SHELL_PLUS_SUBCLASSES_IMPORT_MODULES_BLACKLIST=settings.SHELL_PLUS_SUBCLASSES_IMPORT_MODULES_BLACKLIST + [ 'tests.test_module_in_project_dir', ] ) def test_imports_two_base_classes_exclude_classes_in_project_dir(self): self.assert_imports( first={'FirstDerivedClass'}, second={'SecondDerivedClass'}, third={'ThirdDerivedClass'}, ) @override_settings( SHELL_PLUS_SUBCLASSES_IMPORT=[BaseIncludedClass, IncludedMixin], SHELL_PLUS_SUBCLASSES_IMPORT_MODULES_BLACKLIST=settings.SHELL_PLUS_SUBCLASSES_IMPORT_MODULES_BLACKLIST + [ 'tests.testapp.classes_to_include', ] ) def test_imports_two_base_classes_exclude_classes_in_classes_to_include(self): self.assert_imports( second={'SecondDerivedClass'}, third={'ThirdDerivedClass'}, fourth={'FourthDerivedClass'}, ) def assert_imports(self, first=None, second=None, third=None, fourth=None): """ Auxiliary assertion which checks are classes imported under names. :param first: set of expected names under which FirstDerivedClass should be available :param second: set of expected names under which SecondDerivedClass should be available :param third: set of expected names under which ThirdDerivedClass should be available :param fourth: set of expected names under which FourthDerivedClass should be available """ # type: (Optional[Set[str]], Optional[Set[str]], Optional[Set[str]], Optional[Set[str]]) -> () self.run_shell_plus() self.assert_imported_under_names(FirstDerivedClass, first or set()) self.assert_imported_under_names(SecondDerivedClass, second or set()) self.assert_imported_under_names(ThirdDerivedClass, third or set()) self.assert_imported_under_names(FourthDerivedClass, fourth or set()) self.assert_imported_under_names(ClassWhichShouldNotBeImported, set()) django-extensions-3.1.5/tests/management/commands/shell_plus_tests/test_shell_plus.py000066400000000000000000000125741414177705400314400ustar00rootroot00000000000000# -*- coding: utf-8 -*- import os import re import pytest import inspect from textwrap import dedent from django.core.management import call_command from django.db.models import Model from django.test import override_settings from django_extensions.management.commands import shell_plus def test_shell_plus_command(capsys): script = dedent('''\ def _fn(x): return x * 2 print([_fn(i) for i in range(10)]) ''') call_command("shell_plus", "--command=" + script) out, err = capsys.readouterr() assert out.rstrip().endswith(repr([i * 2 for i in range(10)])) @pytest.mark.django_db() @override_settings(SHELL_PLUS_SQLPARSE_ENABLED=False, SHELL_PLUS_PYGMENTS_ENABLED=False) def test_shell_plus_print_sql(capsys): try: from django.db import connection from django.db.backends import utils CursorDebugWrapper = utils.CursorDebugWrapper force_debug_cursor = True if connection.force_debug_cursor else False call_command("shell_plus", "--plain", "--print-sql", "--command=User.objects.all().exists()") finally: utils.CursorDebugWrapper = CursorDebugWrapper connection.force_debug_cursor = force_debug_cursor out, err = capsys.readouterr() assert re.search(r"SELECT\s+.+\s+FROM\s+.auth_user.\s+LIMIT\s+1", out) @pytest.mark.django_db() @override_settings(SHELL_PLUS_SQLPARSE_ENABLED=False, SHELL_PLUS_PYGMENTS_ENABLED=False) def test_shell_plus_print_sql_truncate(capsys): try: from django.db import connection from django.db.backends import utils CursorDebugWrapper = utils.CursorDebugWrapper force_debug_cursor = True if connection.force_debug_cursor else False call_command("shell_plus", "--plain", "--print-sql", "--truncate-sql=0", "--command=User.objects.all().exists()") finally: utils.CursorDebugWrapper = CursorDebugWrapper connection.force_debug_cursor = force_debug_cursor out, err = capsys.readouterr() assert re.search(r"SELECT\s+.+\s+FROM\s+.auth_user.\s+LIMIT\s+1", out) try: from django.db import connection from django.db.backends import utils CursorDebugWrapper = utils.CursorDebugWrapper force_debug_cursor = True if connection.force_debug_cursor else False call_command("shell_plus", "--plain", "--print-sql", "--truncate-sql=4", "--command=User.objects.all().exists()") finally: utils.CursorDebugWrapper = CursorDebugWrapper connection.force_debug_cursor = force_debug_cursor out, err = capsys.readouterr() assert re.search(r"SELE", out) assert not re.search(r"SELEC", out) def test_shell_plus_plain_startup(): command = shell_plus.Command() command.tests_mode = True parser = command.create_parser("test", "shell_plus") args = ["--plain"] options = parser.parse_args(args=args) retcode = command.handle(**vars(options)) assert retcode == 130 def test_shell_plus_plain_startup_with_pythonrc(monkeypatch): command = shell_plus.Command() command.tests_mode = True parser = command.create_parser("test", "shell_plus") args = ["--plain", "--use-pythonrc"] options = parser.parse_args(args=args) tests_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) pythonrc_file = os.path.join(tests_dir, 'pythonrc.py') assert os.path.isfile(pythonrc_file) monkeypatch.setenv('PYTHONSTARTUP', pythonrc_file) retcode = command.handle(**vars(options)) assert retcode == 130 imported_objects = command.tests_imported_objects assert 'pythonrc_test_func' in imported_objects assert imported_objects['pythonrc_test_func']() == "pythonrc was loaded" def test_shell_plus_plain_loading_standard_django_imports(monkeypatch): command = shell_plus.Command() command.tests_mode = True parser = command.create_parser("test", "shell_plus") args = ["--plain"] options = parser.parse_args(args=args) retcode = command.handle(**vars(options)) assert retcode == 130 imported_objects = command.tests_imported_objects assert 'get_user_model' in imported_objects assert 'settings' in imported_objects assert 'timezone' in imported_objects def test_shell_plus_plain_loading_django_extensions_modules(monkeypatch): command = shell_plus.Command() command.tests_mode = True parser = command.create_parser("test", "shell_plus") args = ["--plain"] options = parser.parse_args(args=args) retcode = command.handle(**vars(options)) assert retcode == 130 imported_objects = command.tests_imported_objects assert 'Club' in imported_objects assert 'UniqueTestAppModel' in imported_objects assert 'RandomCharTestModel' in imported_objects def assert_should_models_be_imported(should_be, cli_arguments=None): command = shell_plus.Command() objs = command.get_imported_objects(cli_arguments or {}) imported_models = filter(lambda imported: inspect.isclass(imported) and issubclass(imported, Model), objs.values()) assert bool(list(imported_models)) == should_be def test_shell_plus_loading_models(): assert_should_models_be_imported(True) def test_shell_plus_skipping_models_import_cli(): assert_should_models_be_imported(False, cli_arguments={'dont_load': ['*']}) @override_settings(SHELL_PLUS_DONT_LOAD=['*']) def test_shell_plus_skipping_models_import_settings(): assert_should_models_be_imported(False) django-extensions-3.1.5/tests/management/commands/shell_plus_tests/test_utils.py000066400000000000000000000043361414177705400304230ustar00rootroot00000000000000# -*- coding: utf-8 -*- import sys from io import StringIO from typing import Dict, Set, Type # noqa from django.test import TestCase from django_extensions.management.commands import shell_plus class AutomaticShellPlusImportsTestCase(TestCase): def setUp(self): super().setUp() sys.stdout = StringIO() sys.stderr = StringIO() self.imported_objects = {} # type: Dict[str, Type] self.output = "" def get_all_names_for_class(self, model_to_find_occurrences): # type: (Type) -> Set[str] """ Returns all names under current class is imported. :param model_to_find_occurrences: class to find names :return: set of names under class is imported. """ result = set() for name, model_class in self.imported_objects.items(): if model_class == model_to_find_occurrences: result.add(name) return result def assert_imported_under_names(self, model_class, names_under_model_is_available): # type: (Type, Set[str]) -> () """ Function which asserts that class is available under given names and not available under any other name. :param model_class: class to assert availability. :param names_under_model_is_available: names under which class should be available. """ self.assertSetEqual(self.get_all_names_for_class(model_class), names_under_model_is_available) imports_output = self.output.split("from ") for line in imports_output: if line.startswith(model_class.__module__): for name in names_under_model_is_available: # assert that in print imports this model occurs only under names from parameter if name == model_class.__name__: expected_output = name else: expected_output = "%s (as %s)" % (model_class.__name__, name) line = line.replace(expected_output, '', 1) self.assertNotIn(line, model_class.__name__) def run_shell_plus(self): command = shell_plus.Command() self.imported_objects = command.get_imported_objects({}) self.output = sys.stdout.getvalue() django-extensions-3.1.5/tests/management/commands/test_admin_generator.py000066400000000000000000000033561414177705400270260ustar00rootroot00000000000000# -*- coding: utf-8 -*- from io import StringIO from django.core.management import call_command from django.db import models from django.test import TestCase class AdminGeneratorTests(TestCase): """Tests for admin_generator management command.""" def setUp(self): self.out = StringIO() def test_command(self): call_command('admin_generator', 'django_extensions', stdout=self.out) output = self.out.getvalue() self.assertIn("@admin.register(Secret)", output) self.assertIn("class SecretAdmin(admin.ModelAdmin):", output) self.assertIn("list_display = ('id', 'name', 'text')", output) self.assertIn("search_fields = ('name',)", output) def test_should_print_warning_if_given_app_is_not_installed(self): expected_output = '''This command requires an existing app name as argument Available apps: admin''' call_command('admin_generator', 'invalid_app', stderr=self.out) for expected_line in expected_output.splitlines(): self.assertIn(expected_line, self.out.getvalue()) def test_should_print_admin_class_for_User_model_only(self): call_command('admin_generator', 'auth', 'Group', stdout=self.out) self.assertIn('from .models import Group', self.out.getvalue()) def test_should_print_admin_class_with_date_hierarchy(self): class TestAdminModel(models.Model): created_at = models.DateTimeField(auto_now_add=True) title = models.CharField(max_length=50) class Meta: app_label = 'testapp' call_command('admin_generator', 'testapp', 'TestAdminModel', stdout=self.out) self.assertIn("date_hierarchy = 'created_at'", self.out.getvalue()) django-extensions-3.1.5/tests/management/commands/test_clear_cache.py000066400000000000000000000050321414177705400260720ustar00rootroot00000000000000# -*- coding: utf-8 -*- import mock import os from io import StringIO from django.core.management import call_command from django.core.management.base import CommandError from django.test import TestCase DefaultCacheMock = mock.Mock() OtherCacheMock = mock.Mock() class ClearCacheTests(TestCase): def setUp(self): self.project_root = os.path.join('tests', 'testapp') self._settings = os.environ.get('DJANGO_SETTINGS_MODULE') os.environ['DJANGO_SETTINGS_MODULE'] = 'django_extensions.settings' DefaultCacheMock.reset_mock() OtherCacheMock.reset_mock() self.out = StringIO() def tearDown(self): if self._settings: os.environ['DJANGO_SETTINGS_MODULE'] = self._settings def test_called_with_no_arguments(self): with self.settings(BASE_DIR=self.project_root): call_command('clear_cache') DefaultCacheMock.return_value.clear.assert_called() OtherCacheMock.return_value.clear.assert_not_called() def test_called_with_explicit_other(self): with self.settings(BASE_DIR=self.project_root): call_command('clear_cache', '--cache', 'other') DefaultCacheMock.return_value.clear.assert_not_called() OtherCacheMock.return_value.clear.assert_called() def test_called_with_all_argument(self): with self.settings(BASE_DIR=self.project_root): call_command('clear_cache', '--all') DefaultCacheMock.return_value.clear.assert_called() OtherCacheMock.return_value.clear.assert_called() def test_called_with_explicit_all(self): with self.settings(BASE_DIR=self.project_root): call_command('clear_cache', '--cache', 'default', '--cache', 'other', stdout=self.out) DefaultCacheMock.return_value.clear.assert_called() OtherCacheMock.return_value.clear.assert_called() self.assertIn('Cache "default" has been cleared!', self.out.getvalue()) self.assertIn('Cache "other" has been cleared!', self.out.getvalue()) def test_called_with_invalid_arguments(self): with self.settings(BASE_DIR=self.project_root): with self.assertRaisesRegex(CommandError, 'Using both --all and --cache is not supported'): call_command('clear_cache', '--all', '--cache', 'foo') def test_should_print_that_cache_is_invalid_on_InvalidCacheBackendError(self): call_command('clear_cache', '--cache', 'invalid', stderr=self.out) self.assertIn('Cache "invalid" is invalid!', self.out.getvalue()) django-extensions-3.1.5/tests/management/commands/test_create_command.py000066400000000000000000000074201414177705400266250ustar00rootroot00000000000000# -*- coding: utf-8 -*- import os import shutil from django.conf import settings from django.core.management import call_command from django.test import TestCase from io import StringIO from unittest.mock import patch TEST_APP = 'testapp_with_appconfig' class CreateCommandTests(TestCase): """Tests for create_command command.""" def setUp(self): # noqa self.management_command_path = os.path.join(settings.BASE_DIR, 'tests/{}/management'.format(TEST_APP)) self.command_template_path = os.path.join(settings.BASE_DIR, 'django_extensions/conf/command_template') self.files = [ '__init__.py', 'commands/__init__.py', 'commands/sample.py', ] def tearDown(self): # noqa shutil.rmtree(self.management_command_path, ignore_errors=True) shutil.rmtree(os.path.join(self.command_template_path, '.hidden'), ignore_errors=True) test_pyc_path = os.path.join(self.command_template_path, 'test.pyc') if os.path.isfile(test_pyc_path): os.remove(test_pyc_path) def _create_management_command_with_empty_files(self): os.mkdir(self.management_command_path) os.mkdir(os.path.join(self.management_command_path, 'commands')) for f in self.files: with open(os.path.join(self.management_command_path, f), "a"): pass def _create__pycache__in_command_template_directory(self): with open(os.path.join(self.command_template_path, 'test.pyc'), "a"): pass def _create_hidden_directory_in_command_template_directory(self): os.mkdir(os.path.join(self.command_template_path, '.hidden')) @patch('sys.stdout', new_callable=StringIO) def test_should_print_management_command_files_only_on_dry_run(self, m_stdout): # noqa call_command('create_command', TEST_APP, '--dry-run', verbosity=2) for f in self.files: filepath = os.path.join(self.management_command_path, f) self.assertIn(filepath, m_stdout.getvalue()) self.assertFalse(os.path.isfile(filepath)) @patch('sys.stdout', new_callable=StringIO) def test_should_create_management_command_files_and_print_filepaths(self, m_stdout): # noqa call_command('create_command', TEST_APP, verbosity=2) for f in self.files: filepath = os.path.join(self.management_command_path, f) self.assertIn(filepath, m_stdout.getvalue()) self.assertTrue(os.path.isfile(filepath)) @patch('sys.stdout', new_callable=StringIO) def test_should_print_that_filepaths_already_exists(self, m_stdout): # noqa self._create_management_command_with_empty_files() call_command('create_command', TEST_APP, verbosity=2) for f in self.files: filepath = os.path.join(self.management_command_path, f) self.assertIn('{} already exists'.format(filepath), m_stdout.getvalue()) self.assertTrue(os.path.isfile(filepath)) self.assertEqual(os.path.getsize(filepath), 0) @patch('sys.stderr', new_callable=StringIO) @patch('django_extensions.management.commands.create_command._make_writeable') # noqa def test_should_print_error_on_OSError_exception(self, m__make_writeable, m_stderr): # noqa m__make_writeable.side_effect = OSError self._create__pycache__in_command_template_directory() self._create_hidden_directory_in_command_template_directory() call_command('create_command', TEST_APP) for f in self.files: filepath = os.path.join(self.management_command_path, f) self.assertIn("Notice: Couldn't set permission bits on {}. You're probably using an uncommon filesystem setup. No problem.\n".format(filepath), # noqa m_stderr.getvalue()) django-extensions-3.1.5/tests/management/commands/test_create_jobs.py000066400000000000000000000051201414177705400261370ustar00rootroot00000000000000# -*- coding: utf-8 -*- import os import shutil from django.core.management import call_command from django.test import TestCase from io import StringIO from tests import testapp_with_no_models_file from unittest.mock import patch JOBS_DIR = os.path.join(testapp_with_no_models_file.__path__[0], 'jobs') TIME_PERIODS = ['hourly', 'daily', 'weekly', 'monthly', 'yearly'] class CreateJobsTestsMixin: def tearDown(self): super().tearDown() try: shutil.rmtree(JOBS_DIR) except OSError: pass class CreateJobsExceptionsTests(CreateJobsTestsMixin, TestCase): @patch('sys.stderr', new_callable=StringIO) @patch('django_extensions.management.commands.create_jobs._make_writeable', side_effect=OSError) def test_should_print_error_notice_on_OSError(self, m__make_writeable, m_stderr): call_command('create_jobs', 'testapp_with_no_models_file') self.assertRegex( m_stderr.getvalue(), r"Notice: Couldn't set permission bits on \S+ You're probably using an uncommon filesystem setup. No problem.", ) class CreateJobsTests(CreateJobsTestsMixin, TestCase): def test_should_create_jobs_directory_structure_silently(self): call_command('create_jobs', 'testapp_with_no_models_file') self.assertTrue(os.path.exists(JOBS_DIR)) @patch('sys.stdout', new_callable=StringIO) def test_should_create_jobs_directory_structure_and_print_SUCCESS_message(self, m_stdout): call_command('create_jobs', 'testapp_with_no_models_file', verbosity=2) self.assertTrue(os.path.exists(JOBS_DIR)) for time_period in TIME_PERIODS: self.assertIn( 'testapp_with_no_models_file/jobs/{}/__init__.py'.format(time_period), m_stdout.getvalue()) @patch('sys.stdout', new_callable=StringIO) def test_should_not_override_already_created_jobs_directory_structure_and_print_that_already_exists(self, m_stdout): call_command('create_jobs', 'testapp_with_no_models_file') sample_file_path = os.path.join(JOBS_DIR, 'sample.py') TEST_COMMENT = '# test' with open(sample_file_path, 'a') as f: f.write(TEST_COMMENT) call_command('create_jobs', 'testapp_with_no_models_file', verbosity=2) self.assertTrue(os.path.exists(JOBS_DIR)) self.assertIn(TEST_COMMENT, open(sample_file_path).read()) for time_period in TIME_PERIODS: self.assertIn( 'testapp_with_no_models_file/jobs/{}/__init__.py already exists'.format(time_period), m_stdout.getvalue()) django-extensions-3.1.5/tests/management/commands/test_create_template_tags.py000066400000000000000000000030411414177705400300330ustar00rootroot00000000000000# -*- coding: utf-8 -*- import os import shutil from django.core.management import call_command from django.test import TestCase from io import StringIO from tests import testapp_with_no_models_file from unittest.mock import patch TEMPLATETAGS_DIR = os.path.join(testapp_with_no_models_file.__path__[0], 'templatetags') class CreateTemplateTagsTests(TestCase): """Tests for create_template_tags command.""" def tearDown(self): """Remove templatetags directory after each test.""" try: shutil.rmtree(TEMPLATETAGS_DIR) except OSError: pass def test_should_create_testapp_with_no_models_file_tags_file(self): call_command('create_template_tags', 'testapp_with_no_models_file') self.assertTrue(os.path.isfile(os.path.join(TEMPLATETAGS_DIR, 'testapp_with_no_models_file_tags.py'))) def test_should_create_custom__name_tags_file(self): call_command('create_template_tags', 'testapp_with_no_models_file', '--name', 'custom_name_tags') self.assertTrue(os.path.isfile(os.path.join(TEMPLATETAGS_DIR, 'custom_name_tags.py'))) @patch('sys.stderr', new_callable=StringIO) @patch('shutil.copymode', side_effect=OSError) def test_should_print_error_notice_on_OSError(self, m_copymode, m_stderr): call_command('create_template_tags', 'testapp_with_no_models_file') self.assertRegex( m_stderr.getvalue(), r"Notice: Couldn't set permission bits on \S+ You're probably using an uncommon filesystem setup. No problem.", ) django-extensions-3.1.5/tests/management/commands/test_delete_squashed_migrations.py000066400000000000000000000173541414177705400312660ustar00rootroot00000000000000# -*- coding: utf-8 -*- import os import pytest from unittest.mock import patch from django.core.management import CommandError, call_command from django.db import models from django.test import TestCase, override_settings from tests import testapp_with_appconfig MIGRATIONS_DIR = os.path.join(testapp_with_appconfig.__path__[0], 'migrations') @override_settings(MIGRATION_MODULES={'testapp_with_appconfig': 'tests.testapp_with_appconfig.migrations'}) class BaseDeleteSquashedMigrationsTestCase(TestCase): def migration_exists(self, filename): return os.path.exists(os.path.join(MIGRATIONS_DIR, filename)) def setUp(self): class TitleModel(models.Model): created_at = models.DateTimeField(auto_now_add=True) title = models.CharField(max_length=50) class Meta: app_label = 'testapp_with_appconfig' call_command('makemigrations') def tearDown(self): for root, dirs, files in os.walk(MIGRATIONS_DIR): for filename in files: if filename.startswith('000'): os.remove(os.path.join(root, filename)) @pytest.mark.xfail class DeleteSquashedMigrationsExceptionsTests(BaseDeleteSquashedMigrationsTestCase): """Tests for delete_squashed_migrations command exceptions.""" def test_should_raise_CommandError_if_app_does_not_have_migrations(self): with self.assertRaisesRegex( CommandError, r"App 'testapp_with_no_models_file' does not have migrations \(so delete_squashed_migrations on it makes no sense\)"): call_command('delete_squashed_migrations', 'testapp_with_no_models_file') def test_should_raise_CommandEror_if_migration_is_not_squashed(self): with self.assertRaisesRegex( CommandError, "The migration testapp_with_appconfig 0001_initial is not a squashed migration."): call_command('delete_squashed_migrations', 'testapp_with_appconfig', '0001') def test_should_raise_CommandEror_if_more_than_one_migration_matches_to_given_arg(self): class NameModel(models.Model): created_at = models.DateTimeField(auto_now_add=True) name = models.CharField(max_length=50) class Meta: app_label = 'testapp_with_appconfig' call_command('makemigrations', 'testapp_with_appconfig') call_command('squashmigrations', 'testapp_with_appconfig', '0002', '--noinput') with self.assertRaisesRegex( CommandError, "More than one migration matches '0001' in app 'testapp_with_appconfig'. Please be more specific."): call_command('delete_squashed_migrations', 'testapp_with_appconfig', '0001') def test_should_raise_CommandEror_if_squashed_migration_not_found(self): class NameModel(models.Model): created_at = models.DateTimeField(auto_now_add=True) name = models.CharField(max_length=50) class Meta: app_label = 'testapp_with_appconfig' call_command('makemigrations', 'testapp_with_appconfig') with self.assertRaisesRegex( CommandError, "Cannot find a squashed migration in app 'testapp_with_appconfig'."): call_command('delete_squashed_migrations', 'testapp_with_appconfig') def test_should_raise_CommandEror_if_squashed_migration_not_foundee(self): with self.assertRaisesRegex( CommandError, "Cannot find a migration matching '0002' from app 'testapp_with_appconfig'."): call_command('delete_squashed_migrations', 'testapp_with_appconfig', '0002') def test_should_raise_CommandError_when_database_does_not_exist(self): with self.assertRaisesRegex(CommandError, 'Unknown database non-existing_database'): call_command('delete_squashed_migrations', '--database=non-existing_database') @pytest.mark.xfail class DeleteSquashedMigrationsTests(BaseDeleteSquashedMigrationsTestCase): """Tests for delete_squashed_migrations command.""" @patch('django_extensions.management.commands.delete_squashed_migrations.six.moves.input') def test_should_delete_squashed_migrations(self, m_input): m_input.return_value = 'y' class NameModel(models.Model): created_at = models.DateTimeField(auto_now_add=True) name = models.CharField(max_length=50) class Meta: app_label = 'testapp_with_appconfig' call_command('makemigrations', 'testapp_with_appconfig') call_command('squashmigrations', 'testapp_with_appconfig', '0002', '--noinput') call_command('delete_squashed_migrations', 'testapp_with_appconfig') self.assertFalse(self.migration_exists('0001_initial.py')) self.assertFalse(self.migration_exists('0002_namemodel.py')) self.assertTrue(self.migration_exists('0001_squashed_0002_namemodel.py')) def test_should_delete_squashed_migrations_if_interactive_mode_is_set_to_False(self): class NameModel(models.Model): created_at = models.DateTimeField(auto_now_add=True) name = models.CharField(max_length=50) class Meta: app_label = 'testapp_with_appconfig' call_command('makemigrations', 'testapp_with_appconfig') call_command('squashmigrations', 'testapp_with_appconfig', '0002', '--noinput') call_command('delete_squashed_migrations', 'testapp_with_appconfig', interactive=False) self.assertFalse(self.migration_exists('0001_initial.py')) self.assertFalse(self.migration_exists('0002_namemodel.py')) self.assertTrue(self.migration_exists('0001_squashed_0002_namemodel.py')) @patch('django_extensions.management.commands.delete_squashed_migrations.six.moves.input') def test_should_not_delete_anything(self, m_input): m_input.return_value = None class NameModel(models.Model): created_at = models.DateTimeField(auto_now_add=True) name = models.CharField(max_length=50) class Meta: app_label = 'testapp_with_appconfig' call_command('makemigrations', 'testapp_with_appconfig') call_command('squashmigrations', 'testapp_with_appconfig', '0002', '--noinput') call_command('delete_squashed_migrations', 'testapp_with_appconfig') self.assertTrue(self.migration_exists('0001_initial.py')) self.assertTrue(self.migration_exists('0002_namemodel.py')) self.assertTrue(self.migration_exists('0001_squashed_0002_namemodel.py')) def test_should_not_delete_files_for_given_squashed_migration(self): class NameModel(models.Model): created_at = models.DateTimeField(auto_now_add=True) name = models.CharField(max_length=50) class Meta: app_label = 'testapp_with_appconfig' call_command('makemigrations', 'testapp_with_appconfig') call_command('squashmigrations', 'testapp_with_appconfig', '0002', '--noinput') class FooModel(models.Model): created_at = models.DateTimeField(auto_now_add=True) name = models.CharField(max_length=50) class Meta: app_label = 'testapp_with_appconfig' call_command('makemigrations', 'testapp_with_appconfig') call_command('delete_squashed_migrations', 'testapp_with_appconfig', '0001_squashed_0002_namemodel', interactive=False) self.assertFalse(self.migration_exists('0001_initial.py')) self.assertFalse(self.migration_exists('0002_namemodel.py')) self.assertTrue(self.migration_exists('0001_squashed_0002_namemodel.py')) self.assertTrue(self.migration_exists('0002_foomodel.py')) django-extensions-3.1.5/tests/management/commands/test_describe_form.py000066400000000000000000000042331414177705400264660ustar00rootroot00000000000000# -*- coding: utf-8 -*- from io import StringIO from django.test import TestCase from django.db import models from django.core.management import CommandError, call_command class DescribeFormExceptionsTests(TestCase): """Tests for describe_form command exceptions.""" def test_should_raise_CommandError_if_invalid_arg(self): with self.assertRaisesRegex(CommandError, "Need application and model name in the form: appname.model"): call_command('describe_form', 'testapp') class DescribeFormTests(TestCase): """Tests for describe_form command.""" def setUp(self): self.out = StringIO() class BaseModel(models.Model): title = models.CharField(max_length=50) body = models.TextField() class Meta: app_label = 'testapp' class NonEditableModel(models.Model): created_at = models.DateTimeField(auto_now_add=True) title = models.CharField(max_length=50) class Meta: app_label = 'testapp' def test_should_print_form_definition_for_TestModel(self): expected_result = '''from django import forms from testapp.models import BaseModel class BaseModelForm(forms.Form): title = forms.CharField(label='Title', max_length=50) body = forms.CharField(label='Body')''' call_command('describe_form', 'testapp.BaseModel', stdout=self.out) self.assertIn(expected_result, self.out.getvalue()) def test_should_print_form_definition_for_TestModel_with_non_editable_field(self): expected_result = '''from django import forms from testapp.models import NonEditableModel class NonEditableModelForm(forms.Form): title = forms.CharField(label='Title', max_length=50)''' call_command('describe_form', 'testapp.NonEditableModel', stdout=self.out) self.assertIn(expected_result, self.out.getvalue()) def test_should_print_form_with_fields_for_TestModel(self): not_expected = '''body = forms.CharField(label='Body')''' call_command('describe_form', 'testapp.BaseModel', '--fields=title', stdout=self.out) self.assertNotIn(not_expected, self.out.getvalue()) django-extensions-3.1.5/tests/management/commands/test_drop_test_database.py000066400000000000000000000302061414177705400275110ustar00rootroot00000000000000# -*- coding: utf-8 -*- from io import StringIO from unittest.mock import MagicMock, Mock, PropertyMock, call, patch from django.core.management import CommandError, call_command from django.test import TestCase from django.test.utils import override_settings # Database testing configurations UNKOWN_ENGINE = { 'default': { 'ENGINE': 'django.db.backends.unknown', 'NAME': 'unknown', } } NO_TEST_NAME = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'test', 'TEST': { 'NAME': '', } } } SQLITE = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': 'db.sqlite3', } } MYSQL_HOST_PORT = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'test', 'USER': 'test', 'PASSWORD': 'test', 'HOST': 'localhost', 'PORT': '3306', }, } MYSQL_SOCKET = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'test', 'USER': 'test', 'PASSWORD': 'test', 'HOST': '/var/run/mysqld/mysql.sock', }, } POSTGRES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', 'NAME': 'test', 'USER': 'test', 'PASSWORD': 'test', 'PORT': '5432', 'HOST': 'localhost', }, } class DropTestDatabaseExceptionsTests(TestCase): """Test for drop_test_database command.""" def test_should_raise_CommandError_if_database_is_unknown(self): with self.assertRaisesRegex(CommandError, "Unknown database unknown"): call_command('drop_test_database', '--database=unknown') @override_settings(DATABASES=UNKOWN_ENGINE) @patch('django_extensions.management.commands.drop_test_database.input') def test_should_raise_CommandError_if_unknown_database_engine(self, m_input): m_input.return_value = 'yes' with self.assertRaisesRegex(CommandError, "Unknown database engine django.db.backends.unknown"): call_command('drop_test_database') @override_settings(DATABASES=NO_TEST_NAME) def test_should_raise_CommandError_if_test_database_name_is_empty(self): with self.assertRaisesRegex(CommandError, "You need to specify DATABASE_NAME in your Django settings file."): call_command('drop_test_database') class DropTestDatabaseTests(TestCase): """Test for drop_test_database command.""" @patch('sys.stdout', new_callable=StringIO) @patch('django_extensions.management.commands.drop_test_database.input') def test_should_raise_CommandError_if_database_is_unknown(self, m_input, m_stdout): m_input.return_value = 'no' call_command('drop_test_database') self.assertEqual("Reset cancelled.\n", m_stdout.getvalue()) @override_settings(DATABASES=SQLITE) @patch('sys.stdout', new_callable=StringIO) @patch('os.path.isfile') @patch('os.unlink') def test_sqlite3_should_unlink_primary_test_database(self, m_unlink, m_isfile, m_stdout): # Indicate that no clone databases exist m_isfile.side_effect = (True, False) call_command('drop_test_database', '--noinput', verbosity=2) with self.subTest('Should check for test database names until failure'): self.assertListEqual( m_isfile.call_args_list, # See production code comments regarding double dots [call('test_db.sqlite3'), call('test_db_1..sqlite3')], ) with self.subTest('Should unlink only primary test database'): self.assertListEqual( m_unlink.call_args_list, [call('test_db.sqlite3')], ) with self.subTest('Should report successful message'): self.assertIn("Reset successful.", m_stdout.getvalue()) @override_settings(DATABASES=SQLITE) @patch('os.path.isfile') @patch('os.unlink') def test_sqlite3_should_unlink_all_existing_clone_databases(self, m_unlink, m_isfile): """Test cloned test databases created via 'manage.py test --parallel'.""" # Indicate that clone databases exist up to test_db_2.sqlite3 m_isfile.side_effect = (True, True, True, False) call_command('drop_test_database', '--noinput') with self.subTest('Should check for test database names until failure'): self.assertListEqual( m_isfile.call_args_list, [ call('test_db.sqlite3'), # See production code comments regarding double dots call('test_db_1..sqlite3'), call('test_db_2..sqlite3'), call('test_db_3..sqlite3'), ], ) with self.subTest('Should unlink all existing test databases'): self.assertListEqual( m_unlink.call_args_list, [ call('test_db.sqlite3'), # See production code comments regarding double dots call('test_db_1..sqlite3'), call('test_db_2..sqlite3'), ], ) @override_settings(DATABASES=SQLITE) @patch('sys.stdout', new_callable=StringIO) @patch('os.path.isfile') @patch('os.unlink') def test_sqlite3_should_not_print_Reset_successful_when_OSError_exception(self, m_unlink, m_isfile, m_stdout): m_isfile.return_value = True m_unlink.side_effect = OSError call_command('drop_test_database', '--noinput', verbosity=2) self.assertNotIn("Reset successful.", m_stdout.getvalue()) @override_settings(DATABASES=MYSQL_HOST_PORT) @patch('sys.stdout', new_callable=StringIO) def test_mysql_should_drop_database_with_host_and_port(self, m_stdout): m_database = MagicMock() # Indicate that no clone databases exist # DROP queries return None while SELECT queries return a row count m_database.connect.return_value.cursor.return_value.execute.side_effect = (1, None, 0) with patch.dict("sys.modules", MySQLdb=m_database): call_command('drop_test_database', '--noinput', verbosity=2) with self.subTest('Should check for and remove test database names until failure'): exists_query = "SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME=" self.assertListEqual( m_database.connect.return_value.cursor.return_value.execute.call_args_list, [ call(exists_query + "'test_test';"), call('DROP DATABASE IF EXISTS `test_test`'), call(exists_query + "'test_test_1';"), ], ) with self.subTest('Should report successful message'): self.assertIn("Reset successful.", m_stdout.getvalue()) @override_settings(DATABASES=MYSQL_SOCKET) @patch('sys.stdout', new_callable=StringIO) def test_mysql_should_drop_database_with_unix_socket(self, m_stdout): m_database = MagicMock() # Indicate that no clone databases exist # DROP queries return None while SELECT queries return a row count m_database.connect.return_value.cursor.return_value.execute.side_effect = (1, None, 0) with patch.dict("sys.modules", MySQLdb=m_database): call_command('drop_test_database', '--noinput', verbosity=2) with self.subTest('Should check for and remove test database names until failure'): exists_query = "SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME=" self.assertListEqual( m_database.connect.return_value.cursor.return_value.execute.call_args_list, [ call(exists_query + "'test_test';"), call('DROP DATABASE IF EXISTS `test_test`'), call(exists_query + "'test_test_1';"), ], ) with self.subTest('Should report successful message'): self.assertIn("Reset successful.", m_stdout.getvalue()) @override_settings(DATABASES=MYSQL_HOST_PORT) def test_mysql_should_drop_all_existing_clone_databases(self): """Test cloned test databases created via 'manage.py test --parallel'.""" m_database = MagicMock() # Indicate that clone databases exist up to test_test_2 # DROP queries return None while SELECT queries return a row count m_database.connect.return_value.cursor.return_value.execute.side_effect = \ (1, None, 1, None, 1, None, 0) with patch.dict("sys.modules", MySQLdb=m_database): call_command('drop_test_database', '--noinput') exists_query = "SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME=" self.assertListEqual( m_database.connect.return_value.cursor.return_value.execute.call_args_list, [ call(exists_query + "'test_test';"), call('DROP DATABASE IF EXISTS `test_test`'), call(exists_query + "'test_test_1';"), call('DROP DATABASE IF EXISTS `test_test_1`'), call(exists_query + "'test_test_2';"), call('DROP DATABASE IF EXISTS `test_test_2`'), call(exists_query + "'test_test_3';"), ], ) @override_settings(DATABASES=POSTGRES) @patch('sys.stdout', new_callable=StringIO) def test_postgresql_should_drop_database(self, m_stdout): m_database = MagicMock() m_cursor = Mock() m_database.connect.return_value.cursor.return_value = m_cursor # Indicate that no clone databases exist type(m_cursor).rowcount = PropertyMock(side_effect=(1, 0)) with patch.dict("sys.modules", psycopg2=m_database): call_command('drop_test_database', '--noinput', verbosity=2) with self.subTest('Should check for and remove test database names until failure'): exists_query = "SELECT datname FROM pg_catalog.pg_database WHERE datname=" self.assertListEqual( m_cursor.execute.call_args_list, [ call(exists_query + "'test_test';"), call('DROP DATABASE IF EXISTS "test_test";'), call(exists_query + "'test_test_1';"), ], ) with self.subTest('Should report successful message'): self.assertIn("Reset successful.", m_stdout.getvalue()) @override_settings(DATABASES=POSTGRES) def test_postgresql_should_drop_all_existing_cloned_databases(self): """Test cloned test databases created via 'manage.py test --parallel'.""" m_database = MagicMock() m_cursor = Mock() m_database.connect.return_value.cursor.return_value = m_cursor # Indicate that clone databases exist up to test_test_2 type(m_cursor).rowcount = PropertyMock(side_effect=(1, 1, 1, 0)) with patch.dict("sys.modules", psycopg2=m_database): call_command('drop_test_database', '--noinput') exists_query = "SELECT datname FROM pg_catalog.pg_database WHERE datname=" self.assertListEqual( m_cursor.execute.call_args_list, [ call(exists_query + "'test_test';"), call('DROP DATABASE IF EXISTS "test_test";'), call(exists_query + "'test_test_1';"), call('DROP DATABASE IF EXISTS "test_test_1";'), call(exists_query + "'test_test_2';"), call('DROP DATABASE IF EXISTS "test_test_2";'), call(exists_query + "'test_test_3';"), ], ) @override_settings(DATABASES=POSTGRES) @patch('sys.stdout', new_callable=StringIO) def test_postgresql_should_not_print_Reset_successful_when_exception_occured(self, m_stdout): m_database = MagicMock() m_database.ProgrammingError = Exception m_cursor = Mock() m_cursor.execute.side_effect = m_database.ProgrammingError m_database.connect.return_value.cursor.return_value = m_cursor with patch.dict("sys.modules", psycopg2=m_database): call_command('drop_test_database', '--noinput', verbosity=2) self.assertNotIn("Reset successful.", m_stdout.getvalue()) django-extensions-3.1.5/tests/management/commands/test_export_emails.py000066400000000000000000000070651414177705400265440ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django.core.management import call_command from django_extensions.management.commands.export_emails import full_name import pytest from tests.testapp.settings import DATABASES @pytest.fixture(autouse=True) def custom_djsettings(settings): """Custom django settings to avoid warnings in stdout""" settings.TEMPLATE_DEBUG = False settings.DEBUG = False return settings @pytest.fixture(scope='module') def django_db_setup(): """Select default database for testing""" settings.DATABASES = DATABASES # noqa @pytest.fixture(scope='module') # noqa def django_db_setup(django_db_setup, django_db_blocker): # noqa """Load to database a set of users, create for export emails command testing""" with django_db_blocker.unblock(): call_command('loaddata', 'group.json') call_command('loaddata', 'user.json') @pytest.mark.django_db() def test_do_export_emails_stdout_start(capsys): """Testing export_emails command without args.stdout starts.""" call_command('export_emails') out, err = capsys.readouterr() assert out.startswith('"Mijaíl Bulgakóv') @pytest.mark.django_db() def test_do_export_emails_stdout_end(capsys): """Testing export_emails command without args.stdout end.""" call_command('export_emails') out, err = capsys.readouterr() assert '"Frédéric Mistral" ;\n\n' in out @pytest.mark.django_db() def test_do_export_emails_format_email(capsys): """Testing python manage.py export_emails -f emails""" call_command('export_emails', '--format=emails') out, err = capsys.readouterr() assert 'frederic_mistral@gmail.com' in out @pytest.mark.django_db() def test_do_export_emails_address(capsys): """Testing python manage export_emails -f address""" call_command('export_emails', '--format=address') out, err = capsys.readouterr() assert '"Frédéric Mistral"' in out @pytest.mark.django_db() def test_do_export_emails_format_google(capsys): """Testing python manage export_emails -f google""" call_command('export_emails', '--format=google') out, err = capsys.readouterr() assert out.startswith('Name,Email') @pytest.mark.django_db() def test_do_export_emails_format_linkedin(capsys): """Testing python manage.py export_emails -f linkedin""" call_command('export_emails', '--format=linkedin') out, err = capsys.readouterr() assert out.startswith('First Name,') assert 'Gabriel Garcia,Marquéz' in out @pytest.mark.django_db() def test_do_export_emails_format_outlook(capsys): """Testing python manage.py export_emails -f outlook""" call_command('export_emails', '--format=outlook') out, err = capsys.readouterr() assert out.startswith('Name,E-mail Address') assert 'frederic_mistral@gmail.com' in out @pytest.mark.django_db() def test_do_export_emails_format_vcard_start(capsys): """Testing python manage.py export_emails -f vcard""" call_command('export_emails', '--format=vcard') out, err = capsys.readouterr() assert 'N:Bulgakóv;Mijaíl;;;' in out assert out.startswith('BEGIN:VCARD') @pytest.mark.django_db() def test_full_name(): """Test, getting full name / username""" fake_user = {'first_name': 'Allan', 'last_name': 'Poe', 'username': 'allan_poe'} name = full_name(**fake_user) assert name == "{fn} {ln}".format(fn=fake_user['first_name'], ln=fake_user['last_name']) fake_user2 = {'first_name': '', 'last_name': '', 'username': 'niccolas'} name2 = full_name(**fake_user2) assert name2 == fake_user2['username'] django-extensions-3.1.5/tests/management/commands/test_generate_password.py000066400000000000000000000005731414177705400274020ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django.core.management import call_command def test_without_args(capsys): call_command('generate_password') out, err = capsys.readouterr() assert out def test_with_length_args(capsys): length = 20 call_command('generate_password', length=length) out, err = capsys.readouterr() assert len(out.rstrip('\n')) == length django-extensions-3.1.5/tests/management/commands/test_generate_secret_key.py000066400000000000000000000012151414177705400276670ustar00rootroot00000000000000# -*- coding: utf-8 -*- from io import StringIO from django.core.management import call_command from django.test import TestCase from unittest.mock import patch class GenerateSecretKeyTests(TestCase): """Tests for generate_secret_key command.""" @patch('django_extensions.management.commands.generate_secret_key.get_random_secret_key') @patch('sys.stdout', new_callable=StringIO) def test_should_return_random_secret_key(self, m_stdout, m_get_random_secret): m_get_random_secret.return_value = 'random_secret_key' call_command('generate_secret_key') self.assertIn('random_secret_key', m_stdout.getvalue()) django-extensions-3.1.5/tests/management/commands/test_graph_models.py000066400000000000000000000132401414177705400263250ustar00rootroot00000000000000# -*- coding: utf-8 -*- import json import os import re import tempfile from contextlib import contextmanager from io import StringIO from django.core.management import call_command from django.core.management.base import CommandError from django.test import TestCase def assert_looks_like_dotfile(output): assert output.startswith("digraph model_graph {\n") assert output.endswith("}\n") assert "// Dotfile by Django-Extensions graph_models\n" in output assert "// Labels\n" in output assert "// Relations\n" in output def assert_looks_like_jsonfile(output): assert '"created_at": ' in output assert '"cli_options": ' in output assert '"app_name": "django.contrib.auth"' in output assert "created_at" in json.loads(output) @contextmanager def temp_output_file(extension=""): """Create writable tempfile in filesystem and ensure it gets deleted""" tmpfile = tempfile.NamedTemporaryFile(suffix=extension, delete=False) tmpfile.close() yield tmpfile.name os.unlink(tmpfile.name) class GraphModelsOutputTests(TestCase): def test_graph_models_no_output_options(self): # Given no output-related options, default to output a Dotfile stdout = StringIO() call_command('graph_models', all_applications=True, stdout=stdout) assert_looks_like_dotfile(stdout.getvalue()) def test_graph_models_dot_option_to_stdout(self): # --dot set but --output not set stdout = StringIO() call_command('graph_models', all_applications=True, dot=True, stdout=stdout) assert_looks_like_dotfile(stdout.getvalue()) def test_graph_models_dot_option_to_file(self): # --dot set and --output set stdout = StringIO() with temp_output_file(".dot") as tmpfname: call_command('graph_models', all_applications=True, dot=True, output=tmpfname, stdout=stdout) with open(tmpfname, 'r') as outfile: foutput = outfile.read() assert_looks_like_dotfile(foutput) assert stdout.getvalue() == "" def test_graph_models_dot_extensions_to_file(self): # --dot not set and --output set stdout = StringIO() with temp_output_file(".dot") as tmpfname: call_command('graph_models', all_applications=True, output=tmpfname, stdout=stdout) with open(tmpfname, 'r') as outfile: foutput = outfile.read() assert_looks_like_dotfile(foutput) assert stdout.getvalue() == "" def test_graph_models_dot_option_trumps_json_file_extension(self): # --dot set and --output set to filename ending with .json # assert that --dot option trumps .json file extension stdout = StringIO() with temp_output_file(".json") as tmpfname: call_command('graph_models', all_applications=True, dot=True, output=tmpfname, stdout=stdout) with open(tmpfname, 'r') as outfile: foutput = outfile.read() assert_looks_like_dotfile(foutput) assert stdout.getvalue() == "" def test_graph_models_json_option_to_stdout(self): # --json set but --output not set out = StringIO() call_command('graph_models', all_applications=True, json=True, stdout=out) output = out.getvalue() assert_looks_like_jsonfile(output) def test_graph_models_json_option_to_file(self): # --dot set and --output set stdout = StringIO() with temp_output_file(".json") as tmpfname: call_command('graph_models', all_applications=True, json=True, output=tmpfname, stdout=stdout) with open(tmpfname, 'r') as outfile: foutput = outfile.read() assert_looks_like_jsonfile(foutput) assert stdout.getvalue() == "" def test_graph_models_pydot_without_file(self): # use of --pydot requires specifying output file with self.assertRaises(CommandError): call_command('graph_models', all_applications=True, pydot=True) def test_graph_models_pygraphviz_without_file(self): # use of --pygraphviz requires specifying output file with self.assertRaises(CommandError): call_command('graph_models', all_applications=True, pygraphviz=True) def test_disable_abstract_fields_not_active(): out = StringIO() call_command( 'graph_models', 'django_extensions', include_models=['AbstractInheritanceTestModelChild'], disable_abstract_fields=False, stdout=out, ) output = out.getvalue() assert 'my_field_that_my_child_will_inherit' in output def test_disable_abstract_fields_active(): out = StringIO() call_command( 'graph_models', 'django_extensions', include_models=['AbstractInheritanceTestModelChild'], disable_abstract_fields=True, stdout=out, ) output = out.getvalue() assert 'my_field_that_my_child_will_inherit' not in output def test_exclude_models_hides_relationships(): """ Expose bug #1229 where excluded models appear in relationships. They are replaced with an underscore, but the relationship is still there. """ out = StringIO() call_command( 'graph_models', 'django_extensions', exclude_models=['Personality', 'Note'], stdout=out, ) output = out.getvalue() assert 'tests_testapp_models_Person -> tests_testapp_models_Name' in output assert 'tests_testapp_models_Person -> _' not in output def test_hide_edge_labels(): out = StringIO() call_command('graph_models', 'django_extensions', all_applications=True, hide_edge_labels=True, stdout=out) output = out.getvalue() assert not re.search(r'\[label=\"[a-zA-Z]+"\]', output) django-extensions-3.1.5/tests/management/commands/test_list_signals.py000066400000000000000000000015371414177705400263620ustar00rootroot00000000000000# -*- coding: utf-8 -*- import re from io import StringIO from django.test import TestCase from django.core.management import call_command class ListSignalsTests(TestCase): """Tests for list_signals command.""" def setUp(self): self.out = StringIO() def test_should_print_all_signals(self): expected_result = '''django.contrib.sites.models.Site (site) pre_delete django.contrib.sites.models.clear_site_cache # pre_save django.contrib.sites.models.clear_site_cache # tests.testapp.models.HasOwnerModel (has owner model) pre_save tests.testapp.models.dummy_handler # ''' call_command('list_signals', stdout=self.out) # Strip line numbers to make the test less brittle out = re.sub(r'(?<=#)\d+', '', self.out.getvalue(), re.M) self.assertIn(expected_result, out) django-extensions-3.1.5/tests/management/commands/test_mail_debug.py000066400000000000000000000005141414177705400257510ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django.core.management import call_command from unittest import mock def test_initialize_mail_server(): with mock.patch('django_extensions.management.commands.mail_debug.asyncore.loop') as loop: call_command('mail_debug', '2525') assert loop.called, 'asyncore.loop was not called' django-extensions-3.1.5/tests/management/commands/test_notes.py000066400000000000000000000040761414177705400250200ustar00rootroot00000000000000# -*- coding: utf-8 -*- import os from django.core.management import call_command def test_without_args(capsys, settings): call_command('notes') out, err = capsys.readouterr() assert 'tests/testapp/__init__.py:\n * [ 8] TODO this is a test todo\n\n' in out def test_with_utf8(capsys, settings): call_command('notes') out, err = capsys.readouterr() assert 'tests/testapp/file_with_utf8_notes.py:\n * [ 3] TODO Russian text followed: Это техт на кириллице\n\n' in out def test_with_template_dirs(capsys, settings, tmpdir_factory): templates_dirs_path = tmpdir_factory.getbasetemp().strpath template_path = os.path.join(templates_dirs_path, 'fixme.html') os.mkdir(os.path.join(templates_dirs_path, 'sub.path')) sub_path = os.path.join(templates_dirs_path, 'sub.path', 'todo.html') settings.TEMPLATES[0]['DIRS'] = [templates_dirs_path] with open(template_path, 'w') as f: f.write('''{# FIXME This is a comment. #} {# TODO Do not show this. #}''') with open(sub_path, 'w') as f: f.write('''{# FIXME This is a second comment. #} {# TODO Do not show this. #}''') call_command('notes', '--tag=FIXME') out, err = capsys.readouterr() assert '{}:\n * [ 1] FIXME This is a comment.'.format(template_path) in out assert '{}:\n * [ 1] FIXME This is a second comment.'.format(sub_path) in out assert 'TODO Do not show this.' not in out def test_with_template_sub_dirs(capsys, settings, tmpdir_factory): templates_dirs_path = tmpdir_factory.getbasetemp().strpath os.mkdir(os.path.join(templates_dirs_path, 'test.path')) template_path = os.path.join(templates_dirs_path, 'test.path', 'fixme.html') settings.TEMPLATES[0]['DIRS'] = [templates_dirs_path] with open(template_path, 'w') as f: f.write('''{# FIXME This is a comment. #} {# TODO Do not show this. #}''') call_command('notes', '--tag=FIXME') out, err = capsys.readouterr() assert '{}:\n * [ 1] FIXME This is a comment.'.format(template_path) in out assert 'TODO Do not show this.' not in out django-extensions-3.1.5/tests/management/commands/test_pipchecker.py000066400000000000000000000131201414177705400257730ustar00rootroot00000000000000# -*- coding: utf-8 -*- import importlib import os import subprocess import sys import pip import pkg_resources from django.core.management import call_command from django.test import TestCase from io import StringIO from pip._internal.exceptions import InstallationError class PipCheckerTests(TestCase): def test_pipchecker_when_requirements_file_does_not_exist(self): with self.assertRaises(InstallationError): call_command('pipchecker', '-r', 'not_exist.txt') def test_pipchecker_with_not_installed_requirement(self): requirements_path = './requirements.txt' out = StringIO() f = open(requirements_path, 'wt') f.write('not-installed==1.0.0') f.close() call_command('pipchecker', '-r', requirements_path, stdout=out) value = out.getvalue() subprocess.call([sys.executable, '-m', 'pip', 'uninstall', '--yes', '-r', requirements_path]) os.remove(requirements_path) self.assertTrue(value.endswith('not installed\n')) def test_pipchecker_with_outdated_requirement(self): requirements_path = './requirements.txt' out = StringIO() f = open(requirements_path, 'wt') f.write('djangorestframework==3.0.0') f.close() subprocess.call([sys.executable, '-m', 'pip', 'install', '-r', requirements_path]) pip._vendor.pkg_resources = importlib.reload(pip._vendor.pkg_resources) call_command('pipchecker', '-r', requirements_path, stdout=out) value = out.getvalue() subprocess.call([sys.executable, '-m', 'pip', 'uninstall', '--yes', '-r', requirements_path]) os.remove(requirements_path) self.assertTrue(value.endswith('available\n')) def test_pipchecker_with_up_to_date_requirement(self): requirements_path = './requirements.txt' out = StringIO() f = open(requirements_path, 'wt') f.write('djangorestframework') f.close() subprocess.call([sys.executable, '-m', 'pip', 'install', '-r', requirements_path]) pip._vendor.pkg_resources = importlib.reload(pip._vendor.pkg_resources) call_command('pipchecker', '-r', requirements_path, stdout=out) value = out.getvalue() subprocess.call([sys.executable, '-m', 'pip', 'uninstall', '--yes', '-r', requirements_path]) os.remove(requirements_path) self.assertEqual(value, '') def test_pipchecker_with_github_url_requirement(self): requirements_path = './requirements.txt' out = StringIO() f = open(requirements_path, 'wt') f.write('git+https://github.com/jmrivas86/django-json-widget') f.close() subprocess.call([sys.executable, '-m', 'pip', 'install', 'django-json-widget']) pip._vendor.pkg_resources = importlib.reload(pip._vendor.pkg_resources) call_command('pipchecker', '-r', requirements_path, stdout=out) value = out.getvalue() subprocess.call([sys.executable, '-m', 'pip', 'uninstall', '--yes', '-r', requirements_path]) os.remove(requirements_path) self.assertTrue(value.endswith('repo is not frozen\n'), value) def test_pipchecker_with_outdated_requirement_on_pip20_1(self): subprocess.call([sys.executable, '-m', 'pip', 'install', '-U', 'pip==20.1']) importlib.reload(pip) requirements_path = './requirements.txt' out = StringIO() f = open(requirements_path, 'wt') f.write('djangorestframework==3.0.0') f.close() subprocess.call([sys.executable, '-m', 'pip', 'install', '-r', requirements_path]) importlib.reload(pkg_resources) call_command('pipchecker', '-r', requirements_path, stdout=out) value = out.getvalue() subprocess.call([sys.executable, '-m', 'pip', 'uninstall', '--yes', '-r', requirements_path]) os.remove(requirements_path) self.assertTrue(value.endswith('available\n')) def test_pipchecker_with_long_up_to_date_requirements(self): requirements_path = './requirements.txt' out = StringIO() f = open(requirements_path, 'wt') f.write('appdirs') f.write('asgiref') f.write('attrs') f.write('black') f.write('certifi') f.write('chardet') f.write('click') f.write('distlib') f.write('Django') f.write('django-cors-headers') f.write('django-debug-toolbar') f.write('djangorestframework') f.write('filelock') f.write('idna') f.write('iniconfig') f.write('mypy-extensions') f.write('packaging') f.write('pathspec') f.write('Pillow') f.write('pluggy') f.write('psycopg2-binary') f.write('py') f.write('pyparsing') f.write('pytest') f.write('pytz') f.write('regex') f.write('requests') f.write('sentry-sdk') f.write('shortuuid') f.write('six') f.write('sqlparse') f.write('toml') f.write('typed-ast') f.write('typing-extensions') f.write('urllib3') f.write('whitenoise') f.write('zipp') subprocess.call([sys.executable, '-m', 'pip', 'install', '-r', requirements_path]) pip._vendor.pkg_resources = importlib.reload(pip._vendor.pkg_resources) call_command('pipchecker', '-r', requirements_path, stdout=out) value = out.getvalue() subprocess.call([sys.executable, '-m', 'pip', 'uninstall', '--yes', '-r', requirements_path]) os.remove(requirements_path) self.assertTrue(value.endswith("Retrying in 60 seconds!") or value == '') django-extensions-3.1.5/tests/management/commands/test_print_settings.py000066400000000000000000000032121414177705400267330ustar00rootroot00000000000000# -*- coding: utf-8 -*- import pytest from django.core.management import CommandError, call_command def test_without_args(capsys): call_command('print_settings') out, err = capsys.readouterr() assert 'DEBUG' in out assert 'INSTALLED_APPS' in out def test_with_setting_args(capsys): call_command('print_settings', 'DEBUG') out, err = capsys.readouterr() assert 'DEBUG' in out assert 'INSTALLED_APPS' not in out def test_with_setting_wildcard(capsys): call_command('print_settings', '*_DIRS') out, err = capsys.readouterr() assert 'FIXTURE_DIRS' in out assert 'STATICFILES_DIRS' in out assert 'INSTALLED_APPS' not in out def test_with_setting_fail(capsys): with pytest.raises(CommandError, match='INSTALLED_APPZ not found in settings.'): call_command('print_settings', '-f', 'INSTALLED_APPZ') def test_with_multiple_setting_args(capsys): call_command( 'print_settings', 'SECRET_KEY', 'DATABASES', 'INSTALLED_APPS', ) out, err = capsys.readouterr() assert 'DEBUG' not in out assert 'SECRET_KEY' in out assert 'DATABASES' in out assert 'INSTALLED_APPS' in out def test_format(capsys): call_command( 'print_settings', 'DEBUG', '--format=text', ) out, err = capsys.readouterr() expected = 'DEBUG = False\n' assert expected == out def test_format_json_without_indent(capsys): call_command( 'print_settings', 'DEBUG', '--format=json', '--indent=0', ) expected = '{\n"DEBUG": false\n}\n' out, err = capsys.readouterr() assert expected == out django-extensions-3.1.5/tests/management/commands/test_print_user_for_session.py000066400000000000000000000070111414177705400304630ustar00rootroot00000000000000# -*- coding: utf-8 -*- from importlib import import_module from io import StringIO from django.conf import settings from django.contrib.auth import get_user_model from django.core.management import CommandError, call_command from django.test import TestCase from unittest.mock import patch class PrintUserForSessionExceptionsTests(TestCase): """Test if print_user_for_session command raises exception.""" def test_should_raise_CommandError_if_session_key_contains_exclamination_mark(self): with self.assertRaisesRegex(CommandError, 'malformed session key'): call_command('print_user_for_session', 'l6hxnwblpvrfu8bohelmqjj4soyo2r!?') class PrintUserForSessionTests(TestCase): """Test for print_user_for_session command.""" def setUp(self): self.engine = import_module(settings.SESSION_ENGINE) @patch('sys.stdout', new_callable=StringIO) def test_should_print_Session_Key_does_not_exist_or_expired(self, m_stdout): call_command('print_user_for_session', 'l6hxnwblpvrfu8bohelmqjj4soyo2r12') self.assertIn('Session Key does not exist. Expired?', m_stdout.getvalue()) @patch('sys.stdout', new_callable=StringIO) def test_should_print_that_there_is_no_user_associated_with_given_session(self, m_stdout): session = self.engine.SessionStore() session.update({ '_auth_user_backend': 'django.contrib.auth.backends.ModelBackend', '_auth_user_hash': 'b67352fde8582b12f068c10fd9d29f9fa1af0459', }) session.create() call_command('print_user_for_session', session.session_key) self.assertIn('No user associated with session', m_stdout.getvalue()) @patch('sys.stdout', new_callable=StringIO) def test_should_print_that_there_is_no_backend_associated_with_given_session(self, m_stdout): session = self.engine.SessionStore() session.update({ '_auth_user_id': 1234, '_auth_user_hash': 'b67352fde8582b12f068c10fd9d29f9fa1af0459', }) session.create() call_command('print_user_for_session', session.session_key) self.assertIn('No authentication backend associated with session', m_stdout.getvalue()) @patch('sys.stdout', new_callable=StringIO) def test_should_print_that_there_is_no_user_associated_with_id(self, m_stdout): session = self.engine.SessionStore() session.update({ '_auth_user_id': 1234, '_auth_user_backend': 'django.contrib.auth.backends.ModelBackend', '_auth_user_hash': 'b67352fde8582b12f068c10fd9d29f9fa1af0459', }) session.create() call_command('print_user_for_session', session.session_key) self.assertIn('No user associated with that id.', m_stdout.getvalue()) @patch('sys.stdout', new_callable=StringIO) def test_should_print_user_info_for_session(self, m_stdout): user = get_user_model().objects.create(first_name='John', last_name='Doe', username='foobar', email='foo@bar.com') session = self.engine.SessionStore() session.update({ '_auth_user_id': user.pk, '_auth_user_backend': 'django.contrib.auth.backends.ModelBackend', '_auth_user_hash': 'b67352fde8582b12f068c10fd9d29f9fa1af0459', }) session.create() expected_out = """User id: {} full name: John Doe short name: John username: foobar email: foo@bar.com """.format(user.pk) call_command('print_user_for_session', session.session_key) self.assertIn(expected_out, m_stdout.getvalue()) django-extensions-3.1.5/tests/management/commands/test_reset_db.py000066400000000000000000000177061414177705400254630ustar00rootroot00000000000000# -*- coding: utf-8 -*- import os from io import StringIO from django.core.management import CommandError, call_command from django.test import TestCase from django.test.utils import override_settings from unittest import mock class ResetDbExceptionsTests(TestCase): """Tests if reset_db command raises exceptions.""" def test_should_raise_CommandError_when_database_does_not_exist(self): with self.assertRaisesRegex(CommandError, 'Unknown database non-existing_database'): call_command('reset_db', '--database=non-existing_database') @override_settings(DATABASES={ 'default': { 'ENGINE': 'django.db.backends.UNKNOWN', 'NAME': 'test.db', } }) def test_should_raise_CommandError_when_unknown_database_engine(self): with self.assertRaisesRegex(CommandError, 'Unknown database engine django.db.backends.UNKNOWN'): call_command('reset_db', '--noinput') @override_settings(DATABASES={ 'default': { 'ENGINE': 'django.db.backends.sqlite3', } }) def test_should_raise_CommandError_when_no_db_name_provided(self): with self.assertRaisesRegex(CommandError, 'You need to specify DATABASE_NAME in your Django settings file.'): call_command('reset_db', '--noinput') @override_settings(DATABASES={ 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': 'test_db.sqlite3', } }) class ResetDbSqlite3Tests(TestCase): """Tests for reset_db command and sqlite3 engine.""" @mock.patch('sys.stdout', new_callable=StringIO) @mock.patch('django_extensions.management.commands.reset_db.input') def test_should_cancel_reset_db_if_input_is_different_than_yes(self, m_input, m_stdout): m_input.return_value = 'no' call_command('reset_db') self.assertEqual("Reset cancelled.\n", m_stdout.getvalue()) @mock.patch('sys.stdout', new_callable=StringIO) @mock.patch.object(os, 'unlink') def test_should_unlink_database_and_print_success_message(self, m_unlink, m_stdout): call_command('reset_db', '--noinput', verbosity=2) self.assertEqual("Reset successful.\n", m_stdout.getvalue()) m_unlink.assert_called_once_with('test_db.sqlite3') @mock.patch('sys.stdout', new_callable=StringIO) @mock.patch.object(os, 'unlink', side_effect=[OSError, ]) def test_should_print_successful_message_even_if_unlink_failed(self, m_unlink, m_stdout): call_command('reset_db', '--noinput', verbosity=2) self.assertEqual("Reset successful.\n", m_stdout.getvalue()) m_unlink.assert_called_once_with('test_db.sqlite3') @override_settings(DATABASES={ 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'test_db', 'USER': 'foo', 'PASSWORD': 'bar', 'HOST': '127.0.0.1', 'PORT': '', } }) class ResetDbMysqlTests(TestCase): """Tests for reset_db command and mysql engine.""" @mock.patch('sys.stdout', new_callable=StringIO) @mock.patch('django_extensions.management.commands.reset_db.input') def test_should_cancel_reset_db_if_input_is_different_than_yes(self, m_input, m_stdout): m_input.return_value = 'no' call_command('reset_db') self.assertEqual("Reset cancelled.\n", m_stdout.getvalue()) @mock.patch('sys.stdout', new_callable=StringIO) def test_should_drop_and_create_database_with_characterset_utf8_and_print_success_messsage(self, m_stdout): m_database = mock.MagicMock() m_connection = mock.Mock() m_database.connect.return_value = m_connection expected_calls = [ mock.call('DROP DATABASE IF EXISTS `test_db`'), mock.call('CREATE DATABASE `test_db` CHARACTER SET utf8'), ] with mock.patch.dict("sys.modules", MySQLdb=m_database): call_command('reset_db', '--noinput', verbosity=2) m_database.connect.assert_called_once_with(host='127.0.0.1', passwd='bar', user='foo') m_connection.query.assert_has_calls(expected_calls, any_order=False) self.assertEqual("Reset successful.\n", m_stdout.getvalue()) @override_settings(DATABASES={ 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'test_db', 'USER': 'foo', 'PASSWORD': 'bar', 'HOST': '/var/run/mysqld/mysql.sock', 'PORT': '3306', }, }) @mock.patch('sys.stdout', new_callable=StringIO) def test_should_drop_and_create_database_without_characterset_and_print_success_messsage(self, m_stdout): m_database = mock.MagicMock() m_connection = mock.Mock() m_database.connect.return_value = m_connection expected_calls = [ mock.call('DROP DATABASE IF EXISTS `test_db`'), mock.call('CREATE DATABASE `test_db`'), ] with mock.patch.dict("sys.modules", MySQLdb=m_database): call_command('reset_db', '--noinput', '--no-utf8', verbosity=2) m_database.connect.assert_called_once_with(passwd='bar', port=3306, unix_socket='/var/run/mysqld/mysql.sock', user='foo') m_connection.query.assert_has_calls(expected_calls, any_order=False) self.assertEqual("Reset successful.\n", m_stdout.getvalue()) @override_settings(DATABASES={ 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', 'NAME': 'test_db', 'USER': 'foo', 'PASSWORD': 'bar', 'HOST': '127.0.0.1', 'PORT': '5432', } }) class ResetDbPostgresqlTests(TestCase): """Tests for reset_db command and sqlite3 engine.""" @mock.patch('sys.stdout', new_callable=StringIO) @mock.patch('django_extensions.management.commands.reset_db.input') def test_should_cancel_reset_db_if_input_is_different_than_yes(self, m_input, m_stdout): m_input.return_value = 'no' call_command('reset_db') self.assertEqual("Reset cancelled.\n", m_stdout.getvalue()) @mock.patch('sys.stdout', new_callable=StringIO) def test_should_drop_and_create_database_and_print_success_messsage(self, m_stdout): m_database = mock.MagicMock() m_cursor = mock.Mock() m_database.connect.return_value.cursor.return_value = m_cursor expected_calls = [ mock.call('DROP DATABASE "test_db";'), mock.call('CREATE DATABASE "test_db" WITH OWNER = "foo" ENCODING = \'UTF8\';'), ] with mock.patch.dict("sys.modules", psycopg2=m_database): call_command('reset_db', '--noinput', verbosity=2) m_database.connect.assert_called_once_with(database='template1', host='127.0.0.1', password='bar', port='5432', user='foo') m_cursor.execute.assert_has_calls(expected_calls, any_order=False) self.assertEqual("Reset successful.\n", m_stdout.getvalue()) @override_settings(DEFAULT_TABLESPACE='TEST_TABLESPACE') @mock.patch('sys.stdout', new_callable=StringIO) def test_should_drop_create_database_close_sessions_and_print_success_messsage(self, m_stdout): m_database = mock.MagicMock() m_cursor = mock.Mock() m_database.connect.return_value.cursor.return_value = m_cursor expected_calls = [ mock.call("\n SELECT pg_terminate_backend(pg_stat_activity.pid)\n FROM pg_stat_activity\n WHERE pg_stat_activity.datname = 'test_db';\n "), mock.call('DROP DATABASE "test_db";'), mock.call('CREATE DATABASE "test_db" WITH OWNER = "foo" ENCODING = \'UTF8\' TABLESPACE = TEST_TABLESPACE;'), ] with mock.patch.dict("sys.modules", psycopg2=m_database): call_command('reset_db', '--noinput', '--close-sessions', verbosity=2) m_database.connect.assert_called_once_with(database='template1', host='127.0.0.1', password='bar', port='5432', user='foo') m_cursor.execute.assert_has_calls(expected_calls, any_order=False) self.assertEqual("Reset successful.\n", m_stdout.getvalue()) django-extensions-3.1.5/tests/management/commands/test_reset_schema.py000066400000000000000000000044331414177705400263270ustar00rootroot00000000000000# -*- coding: utf-8 -*- from io import StringIO from django.core.management import CommandError, call_command from django.test import TestCase from django.test.utils import override_settings from unittest import mock class ResetSchemaExceptionsTests(TestCase): """Tests if reset_schema command raises exceptions.""" def test_should_raise_CommandError_when_database_does_not_exist(self): with self.assertRaisesRegex(CommandError, 'Unknown database non-existing_database'): call_command('reset_schema', '--database=non-existing_database') @override_settings(DATABASES={ 'default': { 'ENGINE': 'django.db.backends.mysql', }, }) def test_should_raise_CommandError_when_database_ENGINE_different_thant_postgresql(self): with self.assertRaisesRegex(CommandError, 'This command can be used only with PostgreSQL databases.'): call_command('reset_schema') @override_settings(DATABASES={ 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', 'NAME': 'test', 'USER': 'test', 'PASSWORD': 'test', 'HOST': 'localhost', }, }) class ResetSchemaTests(TestCase): """Tests for reset_chema command.""" def test_should_drop_schema_and_create_new_one(self): m_cursor = mock.Mock() m_router = mock.Mock() m_router.cursor.return_value = mock.Mock( __enter__=mock.Mock(return_value=m_cursor), __exit__=mock.Mock(return_value=False), ) expected_calls = [ mock.call('DROP SCHEMA test_public CASCADE'), mock.call('CREATE SCHEMA test_public'), ] with mock.patch('django_extensions.management.commands.reset_schema.connections', {'default': m_router}): call_command('reset_schema', '--noinput', '--schema=test_public') m_cursor.execute.assert_has_calls(expected_calls, any_order=False) @mock.patch('sys.stdout', new_callable=StringIO) @mock.patch('django_extensions.management.commands.reset_schema.input') def test_should_cancel_reset_schema_and_print_info_if_input_is_different_than_yes(self, m_input, m_stdout): m_input.return_value = 'no' call_command('reset_schema') self.assertEqual("Reset cancelled.\n", m_stdout.getvalue()) django-extensions-3.1.5/tests/management/commands/test_runjob.py000066400000000000000000000060131414177705400251600ustar00rootroot00000000000000# -*- coding: utf-8 -*- import logging import sys from io import StringIO from django.core.management import call_command from django.test import TestCase from unittest.mock import patch class RunJobTests(TestCase): def setUp(self): sys.stdout = StringIO() sys.stderr = StringIO() # Remove old loggers, since utils.setup_logger does not clean up after itself logger = logging.getLogger("django_extensions.management.commands.runjob") for handler in list(logger.handlers): logger.removeHandler(handler) def test_runs(self): # lame test...does it run? call_command('runjob', 'cache_cleanup', verbosity=2) self.assertIn("Executing job: cache_cleanup (app: None)", sys.stdout.getvalue()) def test_sample_job(self): call_command('runjob', 'sample_job', verbosity=2) self.assertIn("Executing job: sample_job (app: None)", sys.stdout.getvalue()) self.assertIn("executing empty sample job", sys.stdout.getvalue()) def test_list_jobs(self): call_command('runjob', '-l', verbosity=2) self.assertRegex(sys.stdout.getvalue(), "tests.testapp +- sample_job +- +- My sample job.\n") def test_list_jobs_appconfig(self): with self.modify_settings(INSTALLED_APPS={ 'append': 'tests.testapp.apps.TestAppConfig', 'remove': 'tests.testapp', }): call_command('runjob', '-l', verbosity=2) self.assertRegex(sys.stdout.getvalue(), "tests.testapp +- sample_job +- +- My sample job.\n") def test_runs_appconfig(self): with self.modify_settings(INSTALLED_APPS={ 'append': 'tests.testapp.apps.TestAppConfig', 'remove': 'tests.testapp', }): call_command('runjob', 'sample_job', verbosity=2) self.assertIn("Executing job: sample_job (app: None)", sys.stdout.getvalue()) self.assertIn("executing empty sample job", sys.stdout.getvalue()) def test_should_print_that_job_not_found(self): call_command('runjob', 'test_job', verbosity=2) self.assertIn("Error: Job test_job not found", sys.stdout.getvalue()) def test_should_print_that_applabel_not_found(self): call_command('runjob', 'test_job', 'test_app', verbosity=2) self.assertIn("Error: Job test_app for applabel test_job not found", sys.stdout.getvalue()) def test_should_always_print_list_option_usage_if_job_or_applabel_not_found(self): call_command('runjob', 'test_job', verbosity=2) self.assertIn("Use -l option to view all the available jobs", sys.stdout.getvalue()) @patch('django_extensions.management.commands.runjob.get_job') def test_should_print_traceback(self, m_get_job): m_get_job.return_value.return_value.execute.side_effect = Exception call_command('runjob', 'test_job', 'test_app') self.assertIn("ERROR OCCURED IN JOB: test_app (APP: test_job)", sys.stdout.getvalue()) self.assertIn("Traceback (most recent call last):", sys.stdout.getvalue()) django-extensions-3.1.5/tests/management/commands/test_runserver_plus.py000066400000000000000000000006041414177705400267570ustar00rootroot00000000000000# -*- coding: utf-8 -*- import pytest from django.core.management import call_command from unittest import mock @pytest.mark.django_db def test_initialize_runserver_plus(): with mock.patch('django_extensions.management.commands.runserver_plus.run_simple') as run_simple: call_command('runserver_plus') assert run_simple.called, 'werkzeug.run_simple was not called' django-extensions-3.1.5/tests/management/commands/test_set_default_site.py000066400000000000000000000076141414177705400272140ustar00rootroot00000000000000# -*- coding: utf-8 -*- from io import StringIO from django.conf import settings from django.contrib.sites.models import Site from django.core.management import call_command from django.core.management.base import CommandError from django.test import TestCase from django.test.utils import override_settings from unittest.mock import patch class SetDefaultSiteTests(TestCase): """Tests for set_default_site command.""" @override_settings(SITE_ID=321) def test_should_raise_CommandError_when_Site_object_does_not_exist(self): with self.assertRaisesRegex(CommandError, "Default site with pk=321 does not exist"): call_command('set_default_site') @patch('django_extensions.management.commands.set_default_site.socket') def test_should_raise_CommandError_if_system_fqdn_return_None(self, m_socket): m_socket.getfqdn.return_value = None with self.assertRaisesRegex(CommandError, "Cannot find systems FQDN"): call_command('set_default_site', '--system-fqdn') def test_should_raise_CommandError_if_both_domain_and_set_as_system_fqdn_are_present(self): with self.assertRaisesRegex(CommandError, "The set_as_system_fqdn cannot be used with domain option."): call_command('set_default_site', '--domain=foo', '--system-fqdn') @override_settings(INSTALLED_APPS=[ app for app in settings.INSTALLED_APPS if app != 'django.contrib.sites']) def test_should_raise_CommandError_Sites_framework_not_installed(self): with self.assertRaisesRegex(CommandError, "The sites framework is not installed."): call_command('set_default_site', '--domain=foo', '--system-fqdn') @patch('sys.stdout', new_callable=StringIO) def test_should_print_Nothing_to_update(self, m_stdout): call_command('set_default_site') self.assertIn("Nothing to update (need --name, --domain and/or --system-fqdn)\n", m_stdout.getvalue()) @patch('django_extensions.management.commands.set_default_site.socket') def test_should_use_domain_as_name_if_system_fqdn_return_domain_and_name_is_not_provided(self, m_socket): m_socket.getfqdn.return_value = 'test.com' call_command('set_default_site', '--system-fqdn') result = Site.objects.get(pk=settings.SITE_ID) self.assertEqual(result.name, 'test.com') self.assertEqual(result.domain, 'test.com') @patch('django_extensions.management.commands.set_default_site.socket') def test_should_set_custom_nameif_system_fqdn_return_domain_and_name_is_provided(self, m_socket): m_socket.getfqdn.return_value = 'test.com' call_command('set_default_site', '--system-fqdn', '--name=foo') result = Site.objects.get(pk=settings.SITE_ID) self.assertEqual(result.name, 'foo') self.assertEqual(result.domain, 'test.com') def test_should_set_name_and_domain_if_provided(self): call_command('set_default_site', '--name=foo', '--domain=bar') result = Site.objects.get(pk=settings.SITE_ID) self.assertEqual(result.name, 'foo') self.assertEqual(result.domain, 'bar') def test_should_set_name_only(self): call_command('set_default_site', '--name=foo') result = Site.objects.get(pk=settings.SITE_ID) self.assertEqual(result.name, 'foo') self.assertEqual(result.domain, 'example.com') def test_should_set_domain_only(self): call_command('set_default_site', '--domain=bar') result = Site.objects.get(pk=settings.SITE_ID) self.assertEqual(result.name, 'example.com') self.assertEqual(result.domain, 'bar') def test_should_not_raise_if_sites_installed_through_appconfig(self): with self.modify_settings(INSTALLED_APPS={ 'append': 'django.contrib.sites.apps.SitesConfig', 'remove': 'django.contrib.sites', }): call_command('set_default_site', '--name=foo', '--domain=foo.bar') django-extensions-3.1.5/tests/management/commands/test_set_fake_emails.py000066400000000000000000000141621414177705400270000ustar00rootroot00000000000000# -*- coding: utf-8 -*- from io import StringIO from django.core.management import call_command, CommandError from django.contrib.auth.models import User from django_extensions.management.commands.set_fake_emails import Command import pytest @pytest.fixture(scope='module') def django_db_setup(django_db_setup, django_db_blocker): """Load to database a set of users, create for export emails command testing""" with django_db_blocker.unblock(): call_command('loaddata', 'group.json') call_command('loaddata', 'user.json') @pytest.mark.django_db() def test_without_args(capsys, settings): settings.DEBUG = True emails = User.objects.values_list('email', flat=True) assert all(email.endswith("@gmail.com") for email in emails) generate_email = Command() call_command(generate_email) out, err = capsys.readouterr() assert 'Changed 3 emails' in out emails = User.objects.values_list('email', flat=True) assert all(email.endswith("@example.com") for email in emails) @pytest.mark.django_db() def test_no_admin(capsys, settings): settings.DEBUG = True emails = User.objects.values_list('email', flat=True) assert all(email.endswith("@gmail.com") for email in emails) generate_email = Command() call_command(generate_email, '-a') out, err = capsys.readouterr() assert 'Changed 2 emails' in out emails = User.objects.filter(is_superuser=False).values_list('email', flat=True) assert all(email.endswith("@example.com") for email in emails) emails = User.objects.filter(is_superuser=True).values_list('email', flat=True) assert all(email.endswith("@gmail.com") for email in emails) @pytest.mark.django_db() def test_no_staff(capsys, settings): settings.DEBUG = True emails = User.objects.values_list('email', flat=True) assert all(email.endswith("@gmail.com") for email in emails) generate_email = Command() call_command(generate_email, '-s') out, err = capsys.readouterr() assert 'Changed 2 emails' in out emails = User.objects.filter(is_staff=False).values_list('email', flat=True) assert all(email.endswith("@example.com") for email in emails) emails = User.objects.filter(is_staff=True).values_list('email', flat=True) assert all(email.endswith("@gmail.com") for email in emails) @pytest.mark.django_db() def test_include_groups(capsys, settings): settings.DEBUG = True emails = User.objects.values_list('email', flat=True) assert all(email.endswith("@gmail.com") for email in emails) generate_email = Command() call_command(generate_email, '--include-groups=Attendees') out, err = capsys.readouterr() assert 'Changed 2 emails' in out emails = User.objects.filter(is_superuser=False).values_list('email', flat=True) assert all(email.endswith("@example.com") for email in emails) emails = User.objects.filter(is_superuser=True).values_list('email', flat=True) assert all(email.endswith("@gmail.com") for email in emails) @pytest.mark.django_db() def test_include_groups_which_does_not_exists(capsys, settings): settings.DEBUG = True emails = User.objects.values_list('email', flat=True) assert all(email.endswith("@gmail.com") for email in emails) with pytest.raises(CommandError, match='No groups matches filter: TEST'): call_command('set_fake_emails', '--include-groups=TEST') assert not User.objects.filter(email__endswith='@example.com').exists() @pytest.mark.django_db() def test_exclude_groups(capsys, settings): settings.DEBUG = True emails = User.objects.values_list('email', flat=True) assert all(email.endswith("@gmail.com") for email in emails) generate_email = Command() call_command(generate_email, '--exclude-groups=Attendees') out, err = capsys.readouterr() assert 'Changed 1 emails' in out emails = User.objects.filter(is_superuser=False).values_list('email', flat=True) assert all(email.endswith("@gmail.com") for email in emails) emails = User.objects.filter(is_superuser=True).values_list('email', flat=True) assert all(email.endswith("@example.com") for email in emails) @pytest.mark.django_db() def test_exclude_groups_which_does_not_exists(capsys, settings): settings.DEBUG = True emails = User.objects.values_list('email', flat=True) assert all(email.endswith("@gmail.com") for email in emails) with pytest.raises(CommandError, match='No groups matches filter: TEST'): call_command('set_fake_emails', '--exclude-groups=TEST') assert not User.objects.filter(email__endswith='@example.com').exists() @pytest.mark.django_db() def test_include_regexp(capsys, settings): settings.DEBUG = True emails = User.objects.values_list('email', flat=True) assert all(email.endswith("@gmail.com") for email in emails) generate_email = Command() call_command(generate_email, '--include=.*briel') out, err = capsys.readouterr() assert 'Changed 1 emails' in out emails = User.objects.exclude(username="Gabriel").values_list('email', flat=True) assert all(email.endswith("@gmail.com") for email in emails) emails = User.objects.filter(username="Gabriel").values_list('email', flat=True) assert all(email.endswith("@example.com") for email in emails) @pytest.mark.django_db() def test_exclude_regexp(capsys, settings): settings.DEBUG = True emails = User.objects.values_list('email', flat=True) assert all(email.endswith("@gmail.com") for email in emails) generate_email = Command() call_command(generate_email, '--exclude=.*briel') out, err = capsys.readouterr() assert 'Changed 2 emails' in out emails = User.objects.filter(username="Gabriel").values_list('email', flat=True) assert all(email.endswith("@gmail.com") for email in emails) emails = User.objects.exclude(username="Gabriel").values_list('email', flat=True) assert all(email.endswith("@example.com") for email in emails) def test_without_debug(settings): settings.DEBUG = False out = StringIO() with pytest.raises(CommandError, match="Only available in debug mode"): call_command('set_fake_emails', verbosity=3, stdout=out, stderr=out) django-extensions-3.1.5/tests/management/commands/test_set_fake_passwords.py000066400000000000000000000046511414177705400275550ustar00rootroot00000000000000# -*- coding: utf-8 -*- from io import StringIO import pytest from django.contrib.auth.models import User from django.core.management import CommandError, call_command from django_extensions.management.commands.set_fake_passwords import DEFAULT_FAKE_PASSWORD from unittest.mock import Mock, patch @pytest.fixture(scope='module') def django_db_setup(django_db_setup, django_db_blocker): """Load to database a set of users, create for export emails command testing""" with django_db_blocker.unblock(): call_command('loaddata', 'group.json') call_command('loaddata', 'user.json') @pytest.mark.django_db() def test_without_args(capsys, settings): settings.DEBUG = True old_passwords = User.objects.values_list('password', flat=True).order_by('id') assert len(set(old_passwords)) == 3 call_command('set_fake_passwords') out, err = capsys.readouterr() assert 'Reset 3 passwords' in out new_passwords = User.objects.values_list('password', flat=True).order_by('id') assert len(set(new_passwords)) == 1 assert old_passwords != new_passwords for user in User.objects.all(): assert user.check_password(DEFAULT_FAKE_PASSWORD) @pytest.mark.django_db() def test_with_password(capsys, settings): settings.DEBUG = True call_command('set_fake_passwords', '--password=helloworld') out, err = capsys.readouterr() assert 'Reset 3 passwords' in out for user in User.objects.all(): assert user.check_password("helloworld") @pytest.mark.django_db() def test_with_prompt(settings): settings.DEBUG = True m_getpass = Mock() m_getpass.getpass.return_value = 'test' with patch.dict('sys.modules', getpass=m_getpass): call_command('set_fake_passwords', '--prompt') assert all([user.check_password("test") for user in User.objects.all()]) @pytest.mark.django_db() def test_with_prompt_with_empty_password(settings): settings.DEBUG = True m_getpass = Mock() m_getpass.getpass.return_value = None with pytest.raises(CommandError, match='You must enter a valid password'): with patch.dict('sys.modules', getpass=m_getpass): call_command('set_fake_passwords', '--prompt') def test_without_debug(settings): settings.DEBUG = False out = StringIO() with pytest.raises(CommandError, match="Only available in debug mode"): call_command('set_fake_passwords', verbosity=3, stdout=out, stderr=out) django-extensions-3.1.5/tests/management/commands/test_show_template_tags.py000066400000000000000000000020251414177705400275510ustar00rootroot00000000000000# -*- coding: utf-8 -*- from io import StringIO from django.core.management import call_command def test_show_template_tags(): out = StringIO() call_command('show_template_tags', '--no-color', stdout=out) output = out.getvalue() assert "App: django.contrib.admin\n" in output assert "App: django_extensions\n" in output assert "load: highlighting\n" in output assert "Tag: highlight\n" in output def test_show_template_tags_testapp(): out = StringIO() call_command('show_template_tags', '--no-color', stdout=out) output = out.getvalue() assert "App: tests.testapp\n" in output assert "load: dummy_tags\n" in output assert "Tag: dummy_tag\n" in output def test_show_template_tags_testapp_with_appconfig(): out = StringIO() call_command('show_template_tags', '--no-color', stdout=out) output = out.getvalue() assert "App: tests.testapp_with_appconfig\n" in output assert "load: dummy_tags_appconfig\n" in output assert "Tag: dummy_tag_appconfig\n" in output django-extensions-3.1.5/tests/management/commands/test_show_urls.py000066400000000000000000000144511414177705400257130ustar00rootroot00000000000000# -*- coding: utf-8 -*- from io import StringIO from django.conf.urls import url from django.core.management import CommandError, call_command from django.http import HttpResponse from django.test import TestCase from django.test.utils import override_settings from django.views.generic.base import View from unittest.mock import Mock, patch def function_based_view(request): pass class ClassView(View): pass urlpatterns = [ url(r'lambda/view', lambda request: HttpResponse('OK')), url(r'function/based/', function_based_view, name='function-based-view'), url(r'class/based/', ClassView.as_view(), name='class-based-view'), ] class ShowUrlsExceptionsTests(TestCase): """Tests if show_urls command raises exceptions.""" def test_should_raise_CommandError_when_format_style_does_not_exists(self): with self.assertRaisesRegex(CommandError, "Format style 'invalid_format' does not exist. Options: aligned, dense, json, pretty-json, table, verbose"): call_command('show_urls', '--format=invalid_format') def test_should_raise_CommandError_when_doesnt_have_urlconf_attr(self): with self.assertRaisesRegex(CommandError, "Settings module does not have the attribute INVALID_URLCONF."): call_command('show_urls', '--urlconf=INVALID_URLCONF') @override_settings(INVALID_URLCONF='') def test_should_raise_CommandError_when_doesnt_have_urlconf_attr_print_exc(self): m_traceback = Mock() with self.assertRaisesRegex(CommandError, 'Error occurred while trying to load : Empty module name'): with patch.dict('sys.modules', traceback=m_traceback): call_command('show_urls', '--urlconf=INVALID_URLCONF', '--traceback') self.assertTrue(m_traceback.print_exc.called) @override_settings(ROOT_URLCONF='tests.management.commands.test_show_urls') class ShowUrlsTests(TestCase): @patch('sys.stdout', new_callable=StringIO) def test_should_show_urls_unsorted_but_same_order_as_found_in_url_patterns(self, m_stdout): call_command('show_urls', '-u', verbosity=3) lines = m_stdout.getvalue().splitlines() self.assertIn('/lambda/view\ttests.management.commands.test_show_urls.', lines[0]) self.assertIn('/function/based/\ttests.management.commands.test_show_urls.function_based_view\tfunction-based-view', lines[1]) self.assertIn('/class/based/\ttests.management.commands.test_show_urls.ClassView\tclass-based-view', lines[2]) @patch('sys.stdout', new_callable=StringIO) def test_should_show_urls_sorted_alphabetically(self, m_stdout): call_command('show_urls', verbosity=3) lines = m_stdout.getvalue().splitlines() self.assertEqual('/class/based/\ttests.management.commands.test_show_urls.ClassView\tclass-based-view', lines[0]) self.assertEqual('/function/based/\ttests.management.commands.test_show_urls.function_based_view\tfunction-based-view', lines[1]) self.assertEqual('/lambda/view\ttests.management.commands.test_show_urls.', lines[2]) @patch('sys.stdout', new_callable=StringIO) def test_should_show_urls_in_json_format(self, m_stdout): call_command('show_urls', '--format=json') self.assertJSONEqual(m_stdout.getvalue(), [ {"url": "/lambda/view", "module": "tests.management.commands.test_show_urls.", "name": "", "decorators": ""}, {"url": "/function/based/", "module": "tests.management.commands.test_show_urls.function_based_view", "name": "function-based-view", "decorators": ""}, {"url": "/class/based/", "module": "tests.management.commands.test_show_urls.ClassView", "name": "class-based-view", "decorators": ""} ]) self.assertEqual(len(m_stdout.getvalue().splitlines()), 1) @patch('sys.stdout', new_callable=StringIO) def test_should_show_urls_in_pretty_json_format(self, m_stdout): call_command('show_urls', '--format=pretty-json') self.assertJSONEqual(m_stdout.getvalue(), [ {"url": "/lambda/view", "module": "tests.management.commands.test_show_urls.", "name": "", "decorators": ""}, {"url": "/function/based/", "module": "tests.management.commands.test_show_urls.function_based_view", "name": "function-based-view", "decorators": ""}, {"url": "/class/based/", "module": "tests.management.commands.test_show_urls.ClassView", "name": "class-based-view", "decorators": ""} ]) self.assertEqual(len(m_stdout.getvalue().splitlines()), 20) @patch('sys.stdout', new_callable=StringIO) def test_should_show_urls_in_table_format(self, m_stdout): call_command('show_urls', '--format=table') self.assertIn('/class/based/ | tests.management.commands.test_show_urls.ClassView | class-based-view |', m_stdout.getvalue()) self.assertIn('/function/based/ | tests.management.commands.test_show_urls.function_based_view | function-based-view |', m_stdout.getvalue()) self.assertIn('/lambda/view | tests.management.commands.test_show_urls. | |', m_stdout.getvalue()) @patch('sys.stdout', new_callable=StringIO) def test_should_show_urls_in_aligned_format(self, m_stdout): call_command('show_urls', '--format=aligned') lines = m_stdout.getvalue().splitlines() self.assertEqual('/class/based/ tests.management.commands.test_show_urls.ClassView class-based-view ', lines[0]) self.assertEqual('/function/based/ tests.management.commands.test_show_urls.function_based_view function-based-view ', lines[1]) self.assertEqual('/lambda/view tests.management.commands.test_show_urls. ', lines[2]) @patch('sys.stdout', new_callable=StringIO) def test_should_show_urls_with_no_color_option(self, m_stdout): call_command('show_urls', '--no-color') lines = m_stdout.getvalue().splitlines() self.assertEqual('/class/based/\ttests.management.commands.test_show_urls.ClassView\tclass-based-view', lines[0]) self.assertEqual('/function/based/\ttests.management.commands.test_show_urls.function_based_view\tfunction-based-view', lines[1]) self.assertEqual('/lambda/view\ttests.management.commands.test_show_urls.', lines[2]) django-extensions-3.1.5/tests/management/commands/test_sqlcreate.py000066400000000000000000000117621414177705400256530ustar00rootroot00000000000000# -*- coding: utf-8 -*- from io import StringIO from django.core.management import CommandError, call_command from django.test import TestCase from django.test.utils import override_settings from unittest.mock import patch MYSQL_DATABASE_SETTINGS = { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'dbatabase', 'USER': 'foo', 'PASSWORD': 'bar', 'HOST': '127.0.0.1', 'PORT': '3306', } SQLITE3_DATABASE_SETTINGS = { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': 'db.sqlite3', } POSTGRESQL_DATABASE_SETTINGS = { 'ENGINE': 'django.db.backends.postgresql', 'NAME': 'database', 'USER': 'foo', 'PASSWORD': 'bar', 'HOST': 'localhost', 'PORT': '5432', } POSTGRESQL_DATABASE_SETTINGS_SOCKET_MODE = { 'ENGINE': 'django.db.backends.postgresql', 'NAME': 'database', 'USER': '', 'PASSWORD': '', 'HOST': '', 'PORT': '', } class SqlcreateExceptionsTests(TestCase): """Test for sqlcreate exception.""" def test_should_raise_CommandError_if_database_is_unknown(self): with self.assertRaisesRegex(CommandError, "Unknown database unknown"): call_command('sqlcreate', '--database=unknown') class SqlCreateTests(TestCase): """Tests for sqlcreate command.""" @override_settings(DATABASES={'default': MYSQL_DATABASE_SETTINGS}) @patch('sys.stderr', new_callable=StringIO) @patch('sys.stdout', new_callable=StringIO) @patch('django_extensions.management.commands.sqlcreate.socket') def test_should_print_SQL_create_database_statement_for_mysql(self, m_socket, m_stdout, m_stderr): m_socket.gethostname.return_value = 'tumbleweed' expected_error = """-- WARNING!: https://docs.djangoproject.com/en/dev/ref/databases/#collation-settings -- Please read this carefully! Collation will be set to utf8_bin to have case-sensitive data. """ expected_statement = """CREATE DATABASE dbatabase CHARACTER SET utf8 COLLATE utf8_bin; GRANT ALL PRIVILEGES ON dbatabase.* to 'foo'@'tumbleweed' identified by 'bar'; """ call_command('sqlcreate') self.assertEqual(expected_statement, m_stdout.getvalue()) self.assertEqual(expected_error, m_stderr.getvalue()) @override_settings(DATABASES={'default': POSTGRESQL_DATABASE_SETTINGS}) @patch('sys.stdout', new_callable=StringIO) def test_should_print_SQL_create_database_statement_for_postgresql(self, m_stdout): expected_statement = """CREATE USER foo WITH ENCRYPTED PASSWORD 'bar' CREATEDB; CREATE DATABASE database WITH ENCODING 'UTF-8' OWNER "foo"; GRANT ALL PRIVILEGES ON DATABASE database TO foo; """ call_command('sqlcreate') self.assertEqual(expected_statement, m_stdout.getvalue()) @override_settings(DATABASES={'default': POSTGRESQL_DATABASE_SETTINGS_SOCKET_MODE}) @patch('sys.stdout', new_callable=StringIO) def test_should_print_SQL_create_database_statement_only_for_postgresql_when_unix_domain_socket_mode_is_used(self, m_stdout): expected_statement = """-- Assuming that unix domain socket connection mode is being used because -- USER or PASSWORD are blank in Django DATABASES configuration. CREATE DATABASE database WITH ENCODING 'UTF-8'; """ call_command('sqlcreate') self.assertEqual(expected_statement, m_stdout.getvalue()) @override_settings(DATABASES={'default': POSTGRESQL_DATABASE_SETTINGS}) @patch('sys.stdout', new_callable=StringIO) def test_should_print_SQL_drop_and_create_database_statement_for_postgresql(self, m_stdout): expected_statement = """DROP DATABASE IF EXISTS database; DROP USER IF EXISTS foo; CREATE USER foo WITH ENCRYPTED PASSWORD 'bar' CREATEDB; CREATE DATABASE database WITH ENCODING 'UTF-8' OWNER "foo"; GRANT ALL PRIVILEGES ON DATABASE database TO foo; """ call_command('sqlcreate', '--drop') self.assertEqual(expected_statement, m_stdout.getvalue()) @override_settings(DATABASES={'default': SQLITE3_DATABASE_SETTINGS}) @patch('sys.stderr', new_callable=StringIO) def test_should_print_stderr_for_sqlite3(self, m_stderr): expected_error = "-- manage.py migrate will automatically create a sqlite3 database file.\n" call_command('sqlcreate') self.assertEqual(expected_error, m_stderr.getvalue()) @override_settings(DATABASES={ 'unknown': { 'ENGINE': 'django.db.backends.unknown', 'NAME': 'database', 'USER': 'foo', } }) @patch('sys.stderr', new_callable=StringIO) @patch('sys.stdout', new_callable=StringIO) def test_should_print_stderr_and_standard_create_database_statement_for_unsupported_engine(self, m_stdout, m_stderr): expected_error = "-- Don't know how to handle 'django.db.backends.unknown' falling back to SQL.\n" expected_statement = """CREATE DATABASE database; GRANT ALL PRIVILEGES ON DATABASE database to foo; """ call_command('sqlcreate', '--database=unknown') self.assertEqual(expected_error, m_stderr.getvalue()) self.assertEqual(expected_statement, m_stdout.getvalue()) django-extensions-3.1.5/tests/management/commands/test_sqldsn.py000066400000000000000000000166061414177705400251760ustar00rootroot00000000000000# -*- coding: utf-8 -*- from io import StringIO from django.core.management import CommandError, call_command from django.test import TestCase from django.test.utils import override_settings from unittest.mock import patch MYSQL_DATABASE_SETTINGS = { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'dbatabase', 'USER': 'foo', 'PASSWORD': 'bar', 'HOST': '127.0.0.1', 'PORT': '3306', } SQLITE3_DATABASE_SETTINGS = { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': 'db.sqlite3', } POSTGRESQL_DATABASE_SETTINGS = { 'ENGINE': 'django.db.backends.postgresql', 'NAME': 'database', 'USER': 'foo', 'PASSWORD': 'bar', 'HOST': 'localhost', 'PORT': '5432', } POSTGRESQL_PSYCOPG2_DATABASE_SETTINGS = { 'ENGINE': 'django.db.backends.postgresql_psycopg2', 'NAME': 'database', 'USER': 'foo', 'PASSWORD': 'bar', 'HOST': 'localhost', 'PORT': '5432', } POSTGIS_DATABASE_SETTINGS = { 'ENGINE': 'django.db.backends.postgis', 'NAME': 'database', 'USER': 'foo', 'PASSWORD': 'bar', 'HOST': 'localhost', } POSTGIS_WITH_PORT_DATABASE_SETTINGS = { 'ENGINE': 'django.db.backends.postgis', 'NAME': 'database', 'USER': 'foo', 'PASSWORD': 'bar', 'HOST': 'localhost', 'PORT': 5432 } class SqlDsnExceptionsTests(TestCase): """Tests for sqldsn management command exceptions.""" @override_settings(DATABASES={}) def test_should_raise_CommandError_if_unknown_database_does_not_exist(self): with self.assertRaisesRegex(CommandError, "Unknown database unknown"): call_command('sqldsn', '--database=unknown') class SqlDsnTests(TestCase): """Tests for sqldsn management command.""" maxDiff = None @override_settings(DATABASES={'default': SQLITE3_DATABASE_SETTINGS}) @patch('sys.stdout', new_callable=StringIO) def test_should_print_info_for_default_sqlite3_database(self, m_stdout): expected_result = """DSN for database 'default' with engine 'django.db.backends.sqlite3': db.sqlite3 """ call_command('sqldsn') self.assertEqual(expected_result, m_stdout.getvalue()) @override_settings(DATABASES={'default': MYSQL_DATABASE_SETTINGS}) @patch('sys.stdout', new_callable=StringIO) def test_should_print_quiet_info_for_mysql_database(self, m_stdout): expected_result = """host="127.0.0.1", db="dbatabase", user="foo", passwd="bar", port="3306" """ call_command('sqldsn', '-q') self.assertEqual(expected_result, m_stdout.getvalue()) @override_settings(DATABASES={'default': POSTGRESQL_DATABASE_SETTINGS}) @patch('sys.stdout', new_callable=StringIO) def test_should_print_all_info_for_postgresql_database(self, m_stdout): expected_result = """DSN for database 'default' with engine 'django.db.backends.postgresql': host='localhost' dbname='database' user='foo' password='bar' port='5432' """ call_command('sqldsn', '-a') self.assertEqual(expected_result, m_stdout.getvalue()) @override_settings(DATABASES={'default': POSTGRESQL_PSYCOPG2_DATABASE_SETTINGS}) @patch('sys.stdout', new_callable=StringIO) def test_should_print_info__with_kwargs_style_for_postgresql_psycopg2_database(self, m_stdout): expected_result = """DSN for database 'default' with engine 'django.db.backends.postgresql_psycopg2': host='localhost', database='database', user='foo', password='bar', port='5432' """ call_command('sqldsn', '--style=kwargs') self.assertEqual(expected_result, m_stdout.getvalue()) @override_settings(DATABASES={'default': POSTGIS_WITH_PORT_DATABASE_SETTINGS}) @patch('sys.stdout', new_callable=StringIO) def test_should_print_info__with_uri_style_for_postgis_database(self, m_stdout): expected_result = """DSN for database 'default' with engine 'django.db.backends.postgis': postgresql://foo:bar@localhost:5432/database """ call_command('sqldsn', '--style=uri') self.assertEqual(expected_result, m_stdout.getvalue()) @override_settings(DATABASES={'default': POSTGIS_DATABASE_SETTINGS}) @patch('sys.stdout', new_callable=StringIO) def test_should_print_info__with_uri_style_without_port_for_postgis_database(self, m_stdout): expected_result = """DSN for database 'default' with engine 'django.db.backends.postgis': postgresql://foo:bar@localhost/database """ call_command('sqldsn', '--style=uri') self.assertEqual(expected_result, m_stdout.getvalue()) @override_settings(DATABASES={'default': POSTGRESQL_DATABASE_SETTINGS}) @patch('sys.stdout', new_callable=StringIO) def test_should_print_info_with_pgpass_style_and_quiet_option_for_postgresql_database(self, m_stdout): expected_result = "localhost:5432:database:foo:bar\n" call_command('sqldsn', '--style=pgpass', '-q') self.assertEqual(expected_result, m_stdout.getvalue()) @override_settings(DATABASES={ 'default': POSTGRESQL_DATABASE_SETTINGS, 'slave': MYSQL_DATABASE_SETTINGS, 'test': SQLITE3_DATABASE_SETTINGS, 'unknown': { 'ENGINE': 'django.db.backends.unknown', } }) @patch('sys.stdout', new_callable=StringIO) def test_should_print_info_for_all_databases(self, m_stdout): default_postgresql = """DSN for database 'default' with engine 'django.db.backends.postgresql': host='localhost' dbname='database' user='foo' password='bar' port='5432'""" slave_mysql = '''DSN for database 'slave' with engine 'django.db.backends.mysql': host="127.0.0.1", db="dbatabase", user="foo", passwd="bar", port="3306"''' test_sqlite3 = """DSN for database 'test' with engine 'django.db.backends.sqlite3': db.sqlite3""" unknown = """DSN for database 'unknown' with engine 'django.db.backends.unknown': Unknown database, cant generate DSN""" call_command('sqldsn', '--all') self.assertIn(default_postgresql, m_stdout.getvalue()) self.assertIn(slave_mysql, m_stdout.getvalue()) self.assertIn(test_sqlite3, m_stdout.getvalue()) self.assertIn(unknown, m_stdout.getvalue()) @override_settings(DATABASES={ 'default': POSTGRESQL_DATABASE_SETTINGS, 'slave': MYSQL_DATABASE_SETTINGS, 'test': SQLITE3_DATABASE_SETTINGS, 'unknown': { 'ENGINE': 'django.db.backends.unknown', } }) @patch('sys.stdout', new_callable=StringIO) def test_should_print_info_with_all_style_for_all_databases(self, m_stdout): default_postgresql = """DSN for database 'default' with engine 'django.db.backends.postgresql': host='localhost' dbname='database' user='foo' password='bar' port='5432' host='localhost', database='database', user='foo', password='bar', port='5432' postgresql://foo:bar@localhost:5432/database localhost:5432:database:foo:bar""" slave_mysql = '''DSN for database 'slave' with engine 'django.db.backends.mysql': host="127.0.0.1", db="dbatabase", user="foo", passwd="bar", port="3306"''' test_sqlite3 = """DSN for database 'test' with engine 'django.db.backends.sqlite3': db.sqlite3""" unknown = """DSN for database 'unknown' with engine 'django.db.backends.unknown': Unknown database, cant generate DSN""" call_command('sqldsn', '--all', '--style=all') self.assertIn(default_postgresql, m_stdout.getvalue()) self.assertIn(slave_mysql, m_stdout.getvalue()) self.assertIn(test_sqlite3, m_stdout.getvalue()) self.assertIn(unknown, m_stdout.getvalue()) django-extensions-3.1.5/tests/management/commands/test_sync_s3.py000066400000000000000000000204741414177705400252510ustar00rootroot00000000000000# -*- coding: utf-8 -*- import os import shutil from django.conf import settings from django.core.management import CommandError, call_command from django.test import TestCase from django.test.utils import override_settings from io import StringIO from django_extensions.management.commands.sync_s3 import Command from unittest.mock import Mock, patch class SyncS3TestsMixin(): def setUp(self): self.m_boto = Mock() self.boto_patch = patch('django_extensions.management.commands.sync_s3.boto', create=True, boto=self.m_boto) self.boto_patch.start() def tearDown(self): super().tearDown() self.boto_patch.stop() @patch('django_extensions.management.commands.sync_s3.HAS_BOTO', True) class SyncS3ExceptionsTests(SyncS3TestsMixin, TestCase): def test_should_raise_ImportError_if_boto_is_not_installed(self): with patch('django_extensions.management.commands.sync_s3.HAS_BOTO', False): with self.assertRaisesRegex(CommandError, r"Please install the 'boto' Python library. \(\$ pip install boto\)"): call_command('sync_s3') @override_settings(AWS_ACCESS_KEY_ID=None) def test_should_raise_CommandError_if_AWS_ACCESS_KEY_ID_is_not_set_or_is_set_to_None(self): with self.assertRaisesRegex(CommandError, "Missing AWS keys from settings file. Please supply both AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY"): call_command('sync_s3') @override_settings(AWS_SECRET_ACCESS_KEY=None) def test_should_raise_CommandError_if_AWS_SECRET_ACCESS_KEY_is_not_set_or_is_set_to_None(self): with self.assertRaisesRegex(CommandError, "Missing AWS keys from settings file. Please supply both AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY"): call_command('sync_s3') @override_settings( AWS_ACCESS_KEY_ID='access_key_id', AWS_SECRET_ACCESS_KEY='secret_access_key', AWS_BUCKET_NAME='' ) def test_should_raise_CommandError_if_AWS_BUCKET_NAME_is_set_to_None(self): with self.assertRaisesRegex(CommandError, "AWS_BUCKET_NAME cannot be empty"): call_command('sync_s3') @override_settings( AWS_ACCESS_KEY_ID='access_key_id', AWS_SECRET_ACCESS_KEY='secret_access_key', ) def test_should_raise_CommandError_if_AWS_BUCKET_NAME_does_not_have_AWS_BUCKET_NAME_attr(self): del settings.AWS_BUCKET_NAME with self.assertRaisesRegex(CommandError, "Missing bucket name from settings file. Please add the AWS_BUCKET_NAME to your settings file."): call_command('sync_s3') @override_settings( AWS_ACCESS_KEY_ID='access_key_id', AWS_SECRET_ACCESS_KEY='secret_access_key', AWS_BUCKET_NAME='bucket_name', MEDIA_ROOT=None ) def test_should_raise_CommandError_if_MEDIA_ROOT_is_None(self): with self.assertRaisesRegex(CommandError, "MEDIA_ROOT must be set in your settings."): call_command('sync_s3') @override_settings( AWS_ACCESS_KEY_ID='access_key_id', AWS_SECRET_ACCESS_KEY='secret_access_key', AWS_BUCKET_NAME='bucket_name', ) def test_should_raise_CommandError_if_settings_does_not_have_MEDIA_ROOT_attr(self): del settings.MEDIA_ROOT with self.assertRaisesRegex(CommandError, "MEDIA_ROOT must be set in your settings."): call_command('sync_s3') @override_settings( AWS_ACCESS_KEY_ID='access_key_id', AWS_SECRET_ACCESS_KEY='secret_access_key', AWS_BUCKET_NAME='bucket_name', ) def test_should_raise_CommandError_when_media_only_and_static_only_options_together(self): with self.assertRaisesRegex(CommandError, "Can't use --media-only and --static-only together. Better not use anything..."): call_command('sync_s3', '--media-only', '--static-only') @override_settings( AWS_ACCESS_KEY_ID='access_key_id', AWS_SECRET_ACCESS_KEY='secret_access_key', AWS_BUCKET_NAME='bucket_name', ) def test_should_raise_CommandError_when_medi(self): m_bucket = Mock() del settings.AWS_CLOUDFRONT_DISTRIBUTION self.m_boto.connect_s3.return_value.get_bucket.return_value = m_bucket self.m_boto.s3.key.Key.return_value = 'bucket_key' with self.assertRaisesRegex(CommandError, "An object invalidation was requested but the variable AWS_CLOUDFRONT_DISTRIBUTION is not present in your settings."): call_command('sync_s3', '--media-only', '--invalidate') @patch('django_extensions.management.commands.sync_s3.HAS_BOTO', Mock(side_effect=True)) class SyncS3Tests(TestCase): def setUp(self): current_dir = os.path.dirname(os.path.abspath(__file__)) media_root = os.path.join(current_dir, "media") if os.path.isdir(media_root): shutil.rmtree(media_root) os.mkdir(media_root) test_dirs = [ os.path.join(media_root, "testdir1"), os.path.join(media_root, "testdir2"), os.path.join(media_root, "testdir3"), os.path.join(media_root, "testdir4"), os.path.join(media_root, "testsamenamedir"), ] for dir_ in test_dirs: os.mkdir(dir_) test_sub_dir = os.path.join(media_root, "testdir2", "testsubdir1") os.mkdir(test_sub_dir) test_sub_dir_base = os.path.join(media_root, "testdir1") test_sub_dirs = [ os.path.join(test_sub_dir_base, "testsubdir1"), os.path.join(test_sub_dir_base, "testsubdir2"), os.path.join(test_sub_dir_base, "testsubdir3"), os.path.join(test_sub_dir_base, "testsubdir4"), ] for dir_ in test_sub_dirs: os.mkdir(dir_) test_sub_dir_base2 = os.path.join(media_root, "testdir3") test_sub_dirs_2 = [ os.path.join(test_sub_dir_base2, "testsubdir1"), os.path.join(test_sub_dir_base2, "testsubdir2"), os.path.join(test_sub_dir_base2, "testsubdir3"), os.path.join(test_sub_dir_base2, "testsubdir4"), os.path.join(test_sub_dir_base2, "testsamenamedir"), ] for dir_ in test_sub_dirs_2: os.mkdir(dir_) def tearDown(self): current_dir = os.path.dirname(os.path.abspath(__file__)) media_root = os.path.join(current_dir, "media") shutil.rmtree(media_root) @override_settings(MEDIA_ROOT=os.path.join(os.path.dirname(os.path.abspath(__file__)), "media")) @override_settings(STATIC_ROOT=os.path.join(os.path.dirname(os.path.abspath(__file__)), "static")) @override_settings(AWS_ACCESS_KEY_ID="FAKE_KEY") @override_settings(AWS_SECRET_ACCESS_KEY="FAKE_SECRET_KEY") @override_settings(AWS_BUCKET_NAME="FAKE_BUCKET_NAME") def test_sync_s3_dir_exclusions(self): sync_s3_command = Command() try: call_command( "sync_s3", "--acl=authenticated-read", "--filter-list=testsamenamedir,testdir1", ) except Exception: # Exception is expected, we're not actually attempting to connect to S3 self.assertEqual(0, 0) for directory in sync_s3_command.DIRECTORIES: for root, dirs, files in os.walk(directory): sync_s3_command.upload_s3(("FAKE_BUCKET", "FAKE_KEY", "FAKE_BUCKET_NAME", directory), root, files, dirs) dir_name = os.path.basename(root) if dir_name == "testdir1": self.assertEqual(len(dirs), 0) elif dir_name == "testdir2": self.assertEqual(len(dirs), 1) elif dir_name == "testdir3": self.assertEqual(len(dirs), 5) @patch('django_extensions.management.commands.sync_s3.HAS_BOTO', Mock(side_effect=True)) class SyncS3CommandTests(SyncS3TestsMixin, TestCase): @override_settings( AWS_ACCESS_KEY_ID='access_key_id', AWS_SECRET_ACCESS_KEY='secret_access_key', AWS_BUCKET_NAME='bucket_name', ) @patch('sys.stdout', new_callable=StringIO) def test_should_raise_CommandError_when_medi(self, m_stdout): self.m_boto.connect_s3.return_value.get_bucket.return_value = Mock() self.m_boto.s3.key.Key.return_value = 'bucket_key' call_command('sync_s3', '--media-only') self.assertIn("0 files uploaded.", m_stdout.getvalue()) self.assertIn("0 files skipped.", m_stdout.getvalue()) django-extensions-3.1.5/tests/management/commands/test_syncdata.py000066400000000000000000000064671414177705400255040ustar00rootroot00000000000000# -*- coding: utf-8 -*- import os import pytest from django.contrib.auth.models import User from django.core.management import call_command, CommandError from django.test import TestCase from django.test.utils import override_settings from io import StringIO from unittest.mock import patch TEST_FIXTURE_DIR = os.path.join(os.path.dirname(__file__), 'fixtures') @override_settings(FIXTURE_DIRS=[TEST_FIXTURE_DIR]) class SyncDataExceptionsTests(TestCase): """Tests for SyncData command exceptions.""" def test_should_return_SyncDataError_when_unknown_fixture_format(self): with pytest.raises(CommandError, match="Problem installing fixture 'foo': jpeg is not a known serialization format."): call_command('syncdata', 'foo.jpeg', verbosity=2) def test_should_return_SyncDataError_when_file_not_contains_valid_fixture_data(self): with pytest.raises(CommandError, match=r"No fixture data found for 'invalid_fixture'. \(File format may be invalid.\)"): call_command('syncdata', 'invalid_fixture.xml', verbosity=2) def test_should_return_SyncDataError_when_file_has_non_existent_field_in_fixture_data(self): with pytest.raises(CommandError, match=r"Problem installing fixture '.+fixture_with_nonexistent_field.json'"): call_command('syncdata', 'fixture_with_nonexistent_field.json', verbosity=1) with pytest.raises(CommandError, match="django.core.exceptions.FieldDoesNotExist: User has no field named 'non_existent_field'"): call_command('syncdata', 'fixture_with_nonexistent_field.json', verbosity=1) def test_should_return_SyncDataError_when_multiple_fixtures(self): with pytest.raises(CommandError, match="Multiple fixtures named 'users' in '{}'. Aborting.".format(TEST_FIXTURE_DIR)): call_command('syncdata', 'users', verbosity=2) @override_settings(FIXTURE_DIRS=[TEST_FIXTURE_DIR]) class SyncDataTests(TestCase): """Tests for syncdata command.""" @patch('sys.stdout', new_callable=StringIO) def test_should_print_No_fixtures_found_if_fixture_labels_not_provided(self, m_stdout): call_command('syncdata', verbosity=2) self.assertEqual('No fixtures found.\n', m_stdout.getvalue()) @patch('sys.stdout', new_callable=StringIO) def test_should_print_No_fixtures_found_if_fixtures_not_found(self, m_stdout): call_command('syncdata', 'foo', verbosity=2) self.assertIn('No fixtures found.\n', m_stdout.getvalue()) def test_should_keep_old_objects_and_load_data_from_json_fixture(self): User.objects.all().delete() User.objects.create(username='foo') call_command('syncdata', '--skip-remove', os.path.join(TEST_FIXTURE_DIR, 'users.json'), verbosity=2) self.assertTrue(User.objects.filter(username='jdoe').exists()) self.assertTrue(User.objects.filter(username='foo').exists()) @patch('sys.stdout', new_callable=StringIO) def test_should_delete_old_objects_and_load_data_from_json_fixture(self, m_stdout): User.objects.all().delete() User.objects.create(username='foo') call_command('syncdata', 'users.json', verbosity=2) self.assertTrue(User.objects.filter(username='jdoe').exists()) self.assertEqual(User.objects.count(), 1) self.assertIn('Installed 1 object from 1 fixture', m_stdout.getvalue()) django-extensions-3.1.5/tests/management/commands/test_unreferenced_files.py000066400000000000000000000035231414177705400275130ustar00rootroot00000000000000# -*- coding: utf-8 -*- import os import shutil from io import StringIO from tempfile import mkdtemp from django.core.management import CommandError, call_command from django.test import TestCase from django.test.utils import override_settings from ...testapp.models import Photo from unittest.mock import patch class UnreferencedFilesExceptionsTests(TestCase): @override_settings(MEDIA_ROOT=None) def test_should_raise_ComandError_if_MEDIA_ROOT_is_not_set(self): with self.assertRaisesRegex(CommandError, 'MEDIA_ROOT is not set, nothing to do'): call_command('unreferenced_files') class UnreferencedFilesTests(TestCase): def setUp(self): self.media_root_dir = mkdtemp() def tearDown(self): super().tearDown() shutil.rmtree(self.media_root_dir) @patch('sys.stdout', new_callable=StringIO) def test_should_not_print_any_output(self, m_stdout): with override_settings(MEDIA_ROOT=self.media_root_dir): call_command('unreferenced_files') self.assertIs('', m_stdout.getvalue()) @patch('sys.stdout', new_callable=StringIO) def test_should_print_unreferenced_hello_txt_file(self, m_stdout): fn = os.path.join(self.media_root_dir, 'hello.txt') open(fn, 'a').close() with override_settings(MEDIA_ROOT=self.media_root_dir): call_command('unreferenced_files') self.assertIn(fn, m_stdout.getvalue()) @patch('sys.stdout', new_callable=StringIO) def test_should_not_print_referenced_image_jpg_file(self, m_stdout): fn = os.path.join(self.media_root_dir, 'image.jpg') open(fn, 'a').close() Photo.objects.create(photo='image.jpg') with override_settings(MEDIA_ROOT=self.media_root_dir): call_command('unreferenced_files') self.assertNotIn(fn, m_stdout.getvalue()) django-extensions-3.1.5/tests/management/commands/test_update_permissions.py000066400000000000000000000047261414177705400276070ustar00rootroot00000000000000# -*- coding: utf-8 -*- import sys from io import StringIO from django.contrib.auth.models import Permission from django.core.management import call_command from django.db import models from django.test import TestCase class UpdatePermissionsTests(TestCase): def setUp(self): class PermModel(models.Model): class Meta: app_label = 'django_extensions' permissions = (('test_permission', 'test_permission'),) class TestModel(models.Model): class Meta: app_label = 'testapp' permissions = (('testapp_permission', 'testapp_permission'),) def test_works(self): original_stdout = sys.stdout out = sys.stdout = StringIO() call_command('update_permissions', stdout=out, verbosity=3) sys.stdout = original_stdout self.assertIn("Can change perm model", out.getvalue()) def test_should_reload_permission_only_for_specified_apps(self): original_stdout = sys.stdout out = sys.stdout = StringIO() call_command('update_permissions', '--apps=testapp', stdout=out, verbosity=3) sys.stdout = original_stdout self.assertNotIn('django_extensions | perm model | Can add perm model', out.getvalue()) self.assertIn('testapp | test model | Can add test model', out.getvalue()) def test_should_reload_permission_only_for_all_apps(self): original_stdout = sys.stdout out = sys.stdout = StringIO() call_command('update_permissions', verbosity=3) sys.stdout = original_stdout self.assertIn('django_extensions | perm model | Can add perm model', out.getvalue()) self.assertIn('testapp | test model | Can add test model', out.getvalue()) def test_should_update_permission_if_name_changed(self): original_stdout = sys.stdout out = sys.stdout = StringIO() call_command('update_permissions', verbosity=3, create_only=True) self.assertIn('testapp | test model | testapp_permission', out.getvalue()) testapp_permission = Permission.objects.get(name="testapp_permission") testapp_permission.name = "testapp_permission_wrong" testapp_permission.save() call_command('update_permissions', verbosity=3, update_only=True) sys.stdout = original_stdout self.assertIn("'testapp | test model | testapp_permission_wrong' to 'testapp | test model | testapp_permission'", out.getvalue()) django-extensions-3.1.5/tests/management/commands/test_validate_templates.py000066400000000000000000000044771414177705400275440ustar00rootroot00000000000000# -*- coding: utf-8 -*- import os import shutil from io import StringIO from tempfile import mkdtemp from django.conf import settings from django.core.management import CommandError, call_command from django.test import TestCase from django.test.utils import override_settings def test_validate_templates(): out = StringIO() try: call_command('validate_templates', verbosity=3, stdout=out, stderr=out) except CommandError: print(out.getvalue()) raise output = out.getvalue() assert "0 errors found\n" in output class ValidateTemplatesTests(TestCase): def setUp(self): self.tempdir = mkdtemp() def tearDown(self): shutil.rmtree(self.tempdir) def test_should_print_that_there_is_error(self): with override_settings(INSTALLED_APPS=settings.INSTALLED_APPS + ['tests.testapp_with_template_errors']): with self.assertRaisesRegex(CommandError, '1 errors found'): call_command('validate_templates', verbosity=3) def test_should_not_print_any_errors_if_template_in_VALIDATE_TEMPLATES_IGNORES(self): out = StringIO() with override_settings( INSTALLED_APPS=settings.INSTALLED_APPS + ['tests.testapp_with_template_errors'], VALIDATE_TEMPLATES_IGNORES=['template_with_error.html']): call_command('validate_templates', verbosity=3, stdout=out, stderr=out) output = out.getvalue() self.assertIn('0 errors found\n', output) def test_should_not_print_any_errors_if_app_in_VALIDATE_TEMPLATES_IGNORE_APPS(self): out = StringIO() with override_settings( INSTALLED_APPS=settings.INSTALLED_APPS + ['tests.testapp_with_template_errors'], VALIDATE_TEMPLATES_IGNORE_APPS=['tests.testapp_with_template_errors']): call_command('validate_templates', verbosity=3, stdout=out, stderr=out) output = out.getvalue() self.assertIn('0 errors found\n', output) def test_should_break_when_first_error_occur(self): fn = os.path.join(self.tempdir, 'template_with_error.html') with open(fn, 'w') as f: f.write("""{% invalid_tag %}""") with self.assertRaisesRegex(CommandError, 'Errors found'): call_command('validate_templates', '-i', self.tempdir, '-b', verbosity=3) django-extensions-3.1.5/tests/management/test_email_notifications.py000066400000000000000000000063371414177705400261110ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django.core import mail from django.core.management import call_command from django.test import TestCase from django.test.utils import override_settings from io import StringIO from unittest.mock import patch class EmailNotificationCommandTests(TestCase): """Tests for EmailNotificationCommand class.""" @override_settings(ADMINS=[]) @patch('sys.stdout', new_callable=StringIO) def test_should_print_that_no_email_recipients_available(self, m_stdout): with self.assertRaises(Exception): call_command('test_email_notification_command', '--email-exception', verbosity=2) self.assertIn('No email recipients available', m_stdout.getvalue()) self.assertListEqual(mail.outbox, []) @override_settings(ADMINS=['foo@bar.com', 'bar@foo.com'], DEFAULT_FROM_EMAIL='webmaster@foo.bar') def test_should_send_email_with_command_name_and_full_traceback_if_command_fail(self): expected_lines = '''Reporting execution of command: 'test_email_notification_command' Traceback: raise Exception()''' with self.assertRaises(Exception): call_command('test_email_notification_command', '--email-exception', verbosity=2) self.assertIsNot(mail.outbox, []) self.assertEqual(mail.outbox[0].subject, 'Django extensions email notification.') self.assertEqual(mail.outbox[0].from_email, 'webmaster@foo.bar') self.assertListEqual(mail.outbox[0].to, ['foo@bar.com', 'bar@foo.com']) for expected_line in expected_lines.splitlines(): self.assertIn(expected_line, mail.outbox[0].body) @patch('sys.stdout', new_callable=StringIO) def test_should_not_notify_if_notification_level_is_not_set(self, m_stdout): call_command('runscript', 'sample_script', '--email-notifications', verbosity=2) self.assertIn("Exiting, not in 'notify always' mode", m_stdout.getvalue()) self.assertListEqual(mail.outbox, []) @override_settings(ADMINS=['foo@bar.com'], DEFAULT_FROM_EMAIL='webmaster@foo.bar', EMAIL_NOTIFICATIONS={ 'tests.testapp.scripts.sample_script': { 'subject': 'my_script subject', 'body': 'my_script body', 'from_email': 'from_email@example.com', 'recipients': ('recipient0@example.com',), 'no_admins': False, 'no_traceback': False, 'notification_level': 1, 'fail_silently': False }}) def test_should_notify_if_notification_level_is_greater_than_0(self): call_command('runscript', 'sample_script', '--email-notifications', verbosity=2) self.assertIsNot(mail.outbox, []) self.assertEqual(mail.outbox[0].subject, 'my_script subject') self.assertEqual(mail.outbox[0].body, 'my_script body') self.assertEqual(mail.outbox[0].from_email, 'from_email@example.com') self.assertListEqual(mail.outbox[0].to, ['recipient0@example.com', 'foo@bar.com']) django-extensions-3.1.5/tests/management/test_modelviz.py000066400000000000000000000020641414177705400237130ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django.test import SimpleTestCase from django_extensions.management.modelviz import generate_graph_data class ModelVizTests(SimpleTestCase): def test_generate_graph_data_can_render_label(self): app_labels = ['auth'] data = generate_graph_data(app_labels) models = data['graphs'][0]['models'] user_data = [x for x in models if x['name'] == 'User'][0] relation_labels = [x['label'] for x in user_data['relations']] self.assertIn("groups (user)", relation_labels) def test_render_unicode_field_label(self): app_labels = ['django_extensions'] data = generate_graph_data(app_labels, verbose_names=True) models = data['graphs'][0]['models'] model = [x for x in models if x['name'] == 'UnicodeVerboseNameModel'][0] fields = dict((_f['name'], _f['label']) for _f in model['fields']) expected = { 'id': u'ID', 'cafe': u'Café', 'parent_cafe': u'Café latte', } self.assertEqual(expected, fields) django-extensions-3.1.5/tests/pythonrc.py000066400000000000000000000001231414177705400205470ustar00rootroot00000000000000# -*- coding: utf-8 -*- def pythonrc_test_func(): return "pythonrc was loaded" django-extensions-3.1.5/tests/runner.py000066400000000000000000000015661414177705400202260ustar00rootroot00000000000000# -*- coding: utf-8 -*- class PytestTestRunner: """Runs pytest to discover and run tests.""" def __init__(self, verbosity=1, failfast=False, keepdb=False, **kwargs): self.verbosity = verbosity self.failfast = failfast self.keepdb = keepdb def run_tests(self, test_labels): """Run pytest and return the exitcode. It translates some of Django's test command option to pytest's. """ import pytest # NOQA argv = [] if self.verbosity == 0: argv.append('--quiet') if self.verbosity == 2: argv.append('--verbose') if self.verbosity == 3: argv.append('-vv') if self.failfast: argv.append('--exitfirst') if self.keepdb: argv.append('--reuse-db') argv.extend(test_labels) return pytest.main(argv) django-extensions-3.1.5/tests/templatetags/000077500000000000000000000000001414177705400210255ustar00rootroot00000000000000django-extensions-3.1.5/tests/templatetags/__init__.py000066400000000000000000000000001414177705400231240ustar00rootroot00000000000000django-extensions-3.1.5/tests/templatetags/test_highlighting.py000066400000000000000000000045071414177705400251110ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django.template import Context, Template, TemplateSyntaxError from django.test import TestCase class HighlightTagExceptionTests(TestCase): """Tests for highlight tag exceptions.""" def setUp(self): self.ctx = Context() def test_should_raise_TemplateSyntaxError(self): content = """{% load highlighting %} {% highlight %} {% endhighlight %} """ with self.assertRaisesRegex( TemplateSyntaxError, "'highlight' statement requires an argument"): Template(content).render(self.ctx) class HighlightTagTests(TestCase): """Tests for highlight tag.""" def setUp(self): self.ctx = Context() def test_should_highlight_python_syntax_with_name(self): content = """{% load highlighting %} {% highlight 'python' 'Excerpt: blah.py' %} def need_food(self): print("Love is colder than death") {% endhighlight %}""" expected_result = '''
Excerpt: blah.py
def need_food(self):
    print("Love is colder than death")
''' result = Template(content).render(self.ctx) self.assertHTMLEqual(result, expected_result) def test_should_highlight_bash_syntax_without_name(self): content = """{% load highlighting %} {% highlight 'bash' %} echo "Hello $1" {% endhighlight %}""" expected_result = '''
echo "Hello $1"
''' result = Template(content).render(self.ctx) self.assertHTMLEqual(result, expected_result) class ParseTemplateTests(TestCase): """Tests for parse_teplate filter.""" def test_should_mark_html_as_safe(self): ctx = Context({'value': '

Hello World

'}) content = """{% load highlighting %} {{ value|parse_template }} """ expected_result = '''

Hello World

''' result = Template(content).render(ctx) self.assertHTMLEqual(result, expected_result) django-extensions-3.1.5/tests/templatetags/test_indent_text.py000066400000000000000000000031751414177705400247710ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django.test import TestCase from django.template import Context, Template, TemplateSyntaxError class IndentByTagExceptions(TestCase): """Test for indentby exceptions.""" def test_should_raise_TemplateSyntaxError_if_args_lenght_not_in_2_4(self): content = """{% load indent_text %} {% indentby %} Hello World {% endindentby %}""" with self.assertRaisesRegex(TemplateSyntaxError, "indentby tag requires 1 or 3 arguments"): Template(content).render(Context()) class IndentByTagTests(TestCase): """Tests for indentby tag.""" def test_should_add_4_spaces_indent_before_given_text(self): content = """{% load indent_text %} {% indentby 4 %} Hello World {% endindentby %}""" expected_result = '\n \n Hello World\n' result = Template(content).render(Context()) self.assertEqual(result, expected_result) def test_should_add_2_spaces_indent_before_given_text_if_statement_True(self): content = """{% load indent_text %} {% indentby 2 if test_statement %} Hello World {% endindentby %}""" expected_result = '\n \n Hello World\n' result = Template(content).render(Context({'test_statement': True})) self.assertEqual(result, expected_result) def test_should_not_add_any_spaces_indent_before_given_text_if_statement_variable_does_not_exist(self): content = """{% load indent_text %} {% indentby 2 if test_statement %} Hello World {% endindentby %}""" expected_result = '\n\nHello World\n' result = Template(content).render(Context()) self.assertEqual(result, expected_result) django-extensions-3.1.5/tests/templatetags/test_syntax_color.py000066400000000000000000000071061414177705400251660ustar00rootroot00000000000000# -*- coding: utf-8 -*- import os import html import shutil from tempfile import mkdtemp from django.template import Context, Template from django.test import TestCase from django_extensions.templatetags.syntax_color import generate_pygments_css class SyntaxColorTagTests(TestCase): """Tests for syntax_color tags.""" @classmethod def setUpClass(cls): cls.tmpdir = mkdtemp() @classmethod def tearDownClass(cls): shutil.rmtree(cls.tmpdir) def test_should_generate_pygments_css_file_in_temp_directory(self): generate_pygments_css(self.tmpdir) self.assertTrue(os.path.exists( os.path.join(self.tmpdir, 'pygments.css'))) def test_pygments_css_should_return_highlight_css(self): content = """{% load syntax_color %} {% pygments_css %} """ result = Template(content).render(Context()) self.assertIn('.highlight .hll', result) def test_should_colorize_with_default_lexer(self): ctx = Context({'code_string': '

TEST

'}) content = """{% load syntax_color %} {{ code_string|colorize }} """ expected_result = '''
<h1>TEST</h1>
''' result = Template(content).render(ctx) self.assertHTMLEqual(result, expected_result) def test_colorize_should_return_value_if_lexer_class_not_found(self): ctx = Context({'code_string': '

TEST

'}) content = """{% load syntax_color %} {{ code_string|colorize:'invalid_lexer' }} """ expected_result = '

TEST

' result = Template(content).render(ctx) self.assertHTMLEqual(html.unescape(result), expected_result) def test_should_colorize_table_with_default_lexer(self): ctx = Context({'code_string': '

TEST

'}) content = """{% load syntax_color %} {{ code_string|colorize_table }} """ result = Template(content).render(ctx) self.assertIn('', result) self.assertIn('
', result) self.assertIn('>1<h1>TEST</h1>', result) def test_colorize_table_should_return_value_if_lexer_class_not_found(self): ctx = Context({'code_string': '

TEST

'}) content = """{% load syntax_color %} {{ code_string|colorize_table:'invalid_lexer' }} """ expected_result = '

TEST

' result = Template(content).render(ctx) self.assertHTMLEqual(html.unescape(result), expected_result) def test_should_colorize_noclasses_with_default_lexer(self): ctx = Context({'code_string': '

TEST

'}) content = """{% load syntax_color %} {{ code_string|colorize_noclasses }} """ expected_result = '''
<h1>TEST</h1>
''' result = Template(content).render(ctx) self.assertHTMLEqual(result, expected_result) def test_colorize_noclasses_should_return_value_if_lexer_class_not_found(self): ctx = Context({'code_string': '

TEST

'}) content = """{% load syntax_color %} {{ code_string|colorize_noclasses:'invalid_lexer' }} """ expected_result = '

TEST

' result = Template(content).render(ctx) self.assertHTMLEqual(html.unescape(result), expected_result) django-extensions-3.1.5/tests/test_admin_filter.py000066400000000000000000000061471414177705400224110ustar00rootroot00000000000000# -*- coding: utf-8 -*- from unittest.mock import Mock from django.test import RequestFactory, TestCase from factory import Iterator from django_extensions.admin.filter import NotNullFieldListFilter, NullFieldListFilter from .testapp.factories import SecretFactory from .testapp.models import Secret class BaseFieldFilter(TestCase): """Base class for filter test cases.""" @classmethod def setUpClass(cls): SecretFactory.create_batch(5, text=Iterator([None, None, 'foo', 'bar', None])) cls.request = RequestFactory().get('/admin/testapp/secret') cls.field = Secret._meta.get_field('text') cls.field_path = 'text' cls.qs = Secret.objects.all() @classmethod def tearDownClass(cls): Secret.objects.all().delete() class NullFieldListFilterTests(BaseFieldFilter): """Tests for NullFieldListFilter.""" def test_should_not_filter_qs_if_all_lookup_selected(self): params = {} filter_spec = NullFieldListFilter(self.field, self.request, params, Secret, Mock(), self.field_path) result = filter_spec.queryset(self.request, self.qs) self.assertQuerysetEqual(self.qs, map(repr, result), ordered=False) def test_should_return_objects_with_empty_text_if_yes_lookup_selected(self): expected_result = Secret.objects.filter(text__isnull=True) params = {'text__isnull': '1'} filter_spec = NullFieldListFilter(self.field, self.request, params, Secret, Mock(), self.field_path) result = filter_spec.queryset(self.request, self.qs) self.assertQuerysetEqual(expected_result, map(repr, result), ordered=False) def test_should_return_objects_with_not_empty_text_value_if_no_lookup_selected(self): expected_result = Secret.objects.filter(text__isnull=False) params = {'text__isnull': '0'} filter_spec = NullFieldListFilter(self.field, self.request, params, Secret, Mock(), self.field_path) result = filter_spec.queryset(self.request, self.qs) self.assertQuerysetEqual(expected_result, map(repr, result), ordered=False) def test_choices(self): expected_result = [ {'selected': True, 'query_string': '?', 'display': 'All'}, {'selected': False, 'query_string': '?active__isnull=1', 'display': 'Yes'}, {'selected': False, 'query_string': '?active__isnull=0', 'display': 'No'}, ] m_cl = Mock() m_cl.get_query_string.side_effect = ['?', '?active__isnull=1', '?active__isnull=0'] filter_spec = NullFieldListFilter(self.field, self.request, {}, Secret, Mock(), self.field_path) result = filter_spec.choices(m_cl) self.assertEqual(list(result), expected_result) class NotNullFieldListFilterTests(BaseFieldFilter): """Tests for NotNullFieldListFilter.""" def test_should_not_filter_qs_if_all_lookup_selected(self): params = {} filter_spec = NotNullFieldListFilter(self.field, self.request, params, Secret, Mock(), self.field_path) result = filter_spec.queryset(self.request, self.qs) self.assertQuerysetEqual(self.qs, map(repr, result), ordered=False) django-extensions-3.1.5/tests/test_admin_widgets.py000066400000000000000000000030601414177705400225610ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django.test import TestCase from django.utils.text import Truncator from django_extensions.admin import widgets from .testapp import models class ForeignKeySearchInputTestCase(TestCase): def test_widget_works(self): name = models.Name.objects.create(name="Name") person = models.Person.objects.create( name=name, age=30, ) club = models.Club.objects.create( name='Club', ) membership = models.Membership.objects.create(club=club, person=person) widget = widgets.ForeignKeySearchInput( models.Membership._meta.get_field('person').remote_field, ['person__name'], ) label = widget.label_for_value(membership.pk) self.assertEqual( Truncator(person).words(14, truncate='...'), label, ) # Just making sure rendering the widget doesn't cause any issue widget.render('person', person.pk) widget.render('person', None) # Check media to make sure rendering media doesn't cause any issue self.assertListEqual( [ '/static/django_extensions/js/jquery.bgiframe.js', '/static/django_extensions/js/jquery.ajaxQueue.js', '/static/django_extensions/js/jquery.autocomplete.js', ], widget.media._js, ) self.assertListEqual( ['/static/django_extensions/css/jquery.autocomplete.css'], list(widget.media._css['all']), ) django-extensions-3.1.5/tests/test_autoslug_fields.py000066400000000000000000000231621414177705400231410ustar00rootroot00000000000000# -*- coding: utf-8 -*- import pytest from django.db import migrations, models from django.db.migrations.writer import MigrationWriter from django.test import TestCase from django.utils.encoding import force_bytes import django_extensions # noqa from django_extensions.db.fields import AutoSlugField from .testapp.models import ( ChildSluggedTestModel, CustomFuncPrecedenceSluggedTestModel, CustomFuncSluggedTestModel, FKSluggedTestModel, FKSluggedTestModelCallable, FunctionSluggedTestModel, ModelMethodSluggedTestModel, SluggedTestModel, SluggedTestNoOverwriteOnAddModel, OverridedFindUniqueModel, SluggedWithConstraintsTestModel, SluggedWithUniqueTogetherTestModel, ) @pytest.mark.usefixtures("admin_user") class AutoSlugFieldTest(TestCase): def tearDown(self): super().tearDown() SluggedTestModel.objects.all().delete() CustomFuncSluggedTestModel.objects.all().delete() CustomFuncPrecedenceSluggedTestModel.objects.all().delete() def test_auto_create_slug(self): m = SluggedTestModel(title='foo') m.save() self.assertEqual(m.slug, 'foo') def test_auto_create_next_slug(self): m = SluggedTestModel(title='foo') m.save() m = SluggedTestModel(title='foo') m.save() self.assertEqual(m.slug, 'foo-2') def test_auto_create_slug_with_number(self): m = SluggedTestModel(title='foo 2012') m.save() self.assertEqual(m.slug, 'foo-2012') def test_auto_update_slug_with_number(self): m = SluggedTestModel(title='foo 2012') m.save() m.save() self.assertEqual(m.slug, 'foo-2012') def test_update_slug(self): m = SluggedTestModel(title='foo') m.save() self.assertEqual(m.slug, 'foo') # update m instance without using `save' SluggedTestModel.objects.filter(pk=m.pk).update(slug='foo-2012') # update m instance with new data from the db m = SluggedTestModel.objects.get(pk=m.pk) self.assertEqual(m.slug, 'foo-2012') m.save() self.assertEqual(m.title, 'foo') self.assertEqual(m.slug, 'foo-2012') # Check slug is not overwrite m.title = 'bar' m.save() self.assertEqual(m.title, 'bar') self.assertEqual(m.slug, 'foo-2012') def test_simple_slug_source(self): m = SluggedTestModel(title='-foo') m.save() self.assertEqual(m.slug, 'foo') n = SluggedTestModel(title='-foo') n.save() self.assertEqual(n.slug, 'foo-2') n.save() self.assertEqual(n.slug, 'foo-2') def test_empty_slug_source(self): # regression test m = SluggedTestModel(title='') m.save() self.assertEqual(m.slug, '-2') n = SluggedTestModel(title='') n.save() self.assertEqual(n.slug, '-3') n.save() self.assertEqual(n.slug, '-3') def test_callable_method_slug_source(self): m = ModelMethodSluggedTestModel(title='-foo') m.save() self.assertEqual(m.slug, 'the-title-is-foo') n = ModelMethodSluggedTestModel(title='-foo') n.save() self.assertEqual(n.slug, 'the-title-is-foo-2') n.save() self.assertEqual(n.slug, 'the-title-is-foo-2') def test_callable_function_slug_source(self): m = FunctionSluggedTestModel(title='-foo') m.save() self.assertEqual(m.slug, 'the-title-is-foo') n = FunctionSluggedTestModel(title='-foo') n.save() self.assertEqual(n.slug, 'the-title-is-foo-2') n.save() self.assertEqual(n.slug, 'the-title-is-foo-2') def test_inheritance_creates_next_slug(self): m = SluggedTestModel(title='foo') m.save() n = ChildSluggedTestModel(title='foo') n.save() self.assertEqual(n.slug, 'foo-2') o = SluggedTestModel(title='foo') o.save() self.assertEqual(o.slug, 'foo-3') def test_foreign_key_populate_from_field(self): m_fk = SluggedTestModel(title='foo') m_fk.save() m = FKSluggedTestModel(related_field=m_fk) m.save() self.assertEqual(m.slug, 'foo') def test_foreign_key_populate_from_callable(self): m_fk = ModelMethodSluggedTestModel(title='foo') m_fk.save() m = FKSluggedTestModelCallable(related_field=m_fk) m.save() self.assertEqual(m.slug, 'the-title-is-foo') def test_copy_model_generates_new_slug(self): m = SluggedTestModel(title='foo') m.save() self.assertEqual(m.slug, 'foo') m.pk = None m.save() self.assertEqual(m.slug, 'foo-2') def test_copy_model_generates_new_slug_no_overwrite_on_add(self): m = SluggedTestNoOverwriteOnAddModel(title='foo') m.save() self.assertEqual(m.slug, 'foo') m.pk = None m.slug = None m.save() self.assertEqual(m.slug, 'foo-2') def test_populate_from_does_not_allow_bytes(self): with pytest.raises(TypeError): AutoSlugField(populate_from=b'bytes') with pytest.raises(TypeError): AutoSlugField(populate_from=[b'bytes']) def test_populate_from_must_allow_string_or_list_str_or_tuple_str(self): AutoSlugField(populate_from='str') AutoSlugField(populate_from=['str']) AutoSlugField(populate_from=('str', )) def test_slug_argument_priority(self): m = SluggedTestModel(slug='slug', title='title') m.save() self.assertEqual(m.slug, 'title') def test_slug_argument_priority_no_overwrite_on_add(self): m = SluggedTestNoOverwriteOnAddModel(slug='slug', title='title') m.save() self.assertEqual(m.slug, 'slug') def test_overrided_find_unique_autoslug_field(self): m = OverridedFindUniqueModel(title='foo') slug_field = m._meta.fields[2] self.assertFalse(hasattr(slug_field, 'overrided')) m.save() slug_field = m._meta.fields[2] self.assertTrue(slug_field.overrided) def test_slugify_func(self): to_upper = lambda c: c.upper() to_lower = lambda c: c.lower() content_n_func_n_expected = ( ('test', to_upper, 'TEST'), ('', to_upper, ''), ('TEST', to_lower, 'test'), ) for content, slugify_function, expected in content_n_func_n_expected: self.assertEqual( AutoSlugField.slugify_func(content, slugify_function), expected ) def test_use_custom_slug_function(self): m = CustomFuncSluggedTestModel(title='test') m.save() self.assertEqual(m.slug, 'TEST') def test_precedence_custom_slug_function(self): m = CustomFuncPrecedenceSluggedTestModel(title='test') m.save() self.assertEqual(m.slug, 'TEST') self.assertTrue(hasattr(m._meta.get_field('slug'), 'slugify_function')) self.assertEqual(m._meta.get_field('slug').slugify_function('TEST'), 'test') def test_auto_create_slug_with_unique_together(self): m = SluggedWithUniqueTogetherTestModel(title='foo', category='self-introduction') m.save() self.assertEqual(m.slug, 'foo') m = SluggedWithUniqueTogetherTestModel(title='foo', category='review') m.save() self.assertEqual(m.slug, 'foo') # check if satisfy database integrity m = SluggedWithUniqueTogetherTestModel(title='foo', category='review') m.save() self.assertEqual(m.slug, 'foo-2') def test_auto_create_slug_with_constraints(self): m = SluggedWithConstraintsTestModel(title='foo', category='self-introduction') m.save() self.assertEqual(m.slug, 'foo') m = SluggedWithConstraintsTestModel(title='foo', category='review') m.save() self.assertEqual(m.slug, 'foo') # check if satisfy database integrity m = SluggedWithConstraintsTestModel(title='foo', category='review') m.save() self.assertEqual(m.slug, 'foo-2') class MigrationTest(TestCase): def safe_exec(self, string, value=None): dct = {} try: exec(force_bytes(string), globals(), dct) except Exception as e: if value: self.fail("Could not exec %r (from value %r): %s" % (string.strip(), value, e)) else: self.fail("Could not exec %r: %s" % (string.strip(), e)) return dct def test_17_migration(self): """ Tests making migrations with Django's migration framework """ fields = { 'autoslugfield': AutoSlugField(populate_from='otherfield'), } migration = type(str("Migration"), (migrations.Migration,), { "operations": [ migrations.CreateModel( "MyModel", tuple(fields.items()), {'populate_from': 'otherfield'}, (models.Model,) ), ], }) writer = MigrationWriter(migration) output = writer.as_string() self.assertIsInstance(output, str, "Migration as_string returned bytes") # We don't test the output formatting - that's too fragile. # Just make sure it runs for now, and that things look alright. result = self.safe_exec(output) self.assertIn("Migration", result) def test_stable_deconstruct(self): slug_field = SluggedTestModel._meta.get_field('slug') construction_values = slug_field.deconstruct() m = SluggedTestModel(title='foo') m.save() self.assertEqual(slug_field.deconstruct(), construction_values) django-extensions-3.1.5/tests/test_clean_pyc.py000066400000000000000000000045301414177705400217030ustar00rootroot00000000000000# -*- coding: utf-8 -*- import fnmatch import os import shutil from io import StringIO from django.core.management import call_command from django.test import TestCase class CleanPycTests(TestCase): def setUp(self): self.project_root = os.path.join('tests', 'testapp') self._settings = os.environ.get('DJANGO_SETTINGS_MODULE') os.environ['DJANGO_SETTINGS_MODULE'] = 'django_extensions.settings' def tearDown(self): if self._settings: os.environ['DJANGO_SETTINGS_MODULE'] = self._settings def _find_pyc(self, path): pyc_glob = [] for root, dirnames, filenames in os.walk(path): for filename in fnmatch.filter(filenames, '*.pyc'): pyc_glob.append(os.path.join(root, filename)) return pyc_glob def test_removes_pyc_files(self): with self.settings(BASE_DIR=self.project_root): call_command('compile_pyc') pyc_glob = self._find_pyc(self.project_root) self.assertTrue(len(pyc_glob) > 0) with self.settings(BASE_DIR=self.project_root): call_command('clean_pyc') pyc_glob = self._find_pyc(self.project_root) self.assertEqual(len(pyc_glob), 0) def test_takes_path(self): out = StringIO() project_root = os.path.join('tests', 'testapp') call_command('compile_pyc', path=project_root) pyc_glob = self._find_pyc(project_root) self.assertTrue(len(pyc_glob) > 0) call_command('clean_pyc', verbosity=2, path=project_root, stdout=out) output = out.getvalue().splitlines() self.assertEqual(sorted(pyc_glob), sorted(output)) def test_removes_pyo_files(self): out = StringIO() project_root = os.path.join('tests', 'testapp') call_command('compile_pyc', path=project_root) pyc_glob = self._find_pyc(project_root) self.assertTrue(len(pyc_glob) > 0) # Create some fake .pyo files since we can't force them to be created. pyo_glob = [] for fn in pyc_glob: pyo = '%s.pyo' % os.path.splitext(fn)[0] shutil.copyfile(fn, pyo) pyo_glob.append(pyo) call_command('clean_pyc', verbosity=2, path=project_root, optimize=True, stdout=out) output = out.getvalue().splitlines() self.assertEqual(sorted(pyc_glob + pyo_glob), sorted(output)) django-extensions-3.1.5/tests/test_color.py000066400000000000000000000012601414177705400210610ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django.test import SimpleTestCase from django_extensions.management import color from . import force_color_support class ColorTest(SimpleTestCase): def test_no_style(self): with force_color_support: style = color.no_style().MODULE_NAME text = 'csv' styled_text = style(text) self.assertEqual(text, styled_text) def test_color_style(self): with force_color_support: style = color.color_style().MODULE_NAME text = 'antigravity' styled_text = style(text) self.assertIn(text, styled_text) self.assertNotEqual(text, styled_text) django-extensions-3.1.5/tests/test_compat.py000066400000000000000000000016621414177705400212340ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django.test import TestCase from django.test.utils import override_settings from django_extensions.compat import get_template_setting class CompatTests(TestCase): @override_settings(TEMPLATES=None) def test_should_return_None_by_default_if_TEMPLATES_setting_is_none(self): self.assertIsNone(get_template_setting('template_key')) @override_settings(TEMPLATES=None) def test_should_return_default_if_TEMPLATES_setting_is_none(self): self.assertEqual(get_template_setting('template_key', 'test'), 'test') @override_settings(TEMPLATES=[ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': ['templates'], 'APP_DIRS': True }]) def test_should_return_value_for_key(self): self.assertEqual(get_template_setting('BACKEND'), 'django.template.backends.django.DjangoTemplates') django-extensions-3.1.5/tests/test_compatibility.py000066400000000000000000000011531414177705400226150ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Test Compatility between different django versions""" import django import pytest import django_extensions class TestDefaultAppConfigDefinition: @pytest.mark.skipif(django.VERSION < (3, 2), reason='app config is automatically defined by django') def test_app_config_not_defined(self): assert hasattr(django_extensions, 'default_app_config') is False @pytest.mark.skipif(django.VERSION >= (3, 2), reason='app config is not automatically defined by django') def test_app_config_defined(self): assert hasattr(django_extensions, 'default_app_config') is True django-extensions-3.1.5/tests/test_compile_pyc.py000066400000000000000000000037311414177705400222530ustar00rootroot00000000000000# -*- coding: utf-8 -*- import fnmatch import os from io import StringIO from django.core.management import call_command from django.test import TestCase class CompilePycTests(TestCase): def setUp(self): self.project_root = os.path.join('tests', 'testapp') self._settings = os.environ.get('DJANGO_SETTINGS_MODULE') os.environ['DJANGO_SETTINGS_MODULE'] = 'django_extensions.settings' def tearDown(self): if self._settings: os.environ['DJANGO_SETTINGS_MODULE'] = self._settings def _find_pyc(self, path, mask='*.pyc'): pyc_glob = [] for root, dirs, filenames in os.walk(path): for filename in fnmatch.filter(filenames, mask): pyc_glob.append(os.path.join(root, filename)) return pyc_glob def test_compiles_pyc_files(self): with self.settings(BASE_DIR=self.project_root): call_command('clean_pyc') pyc_glob = self._find_pyc(self.project_root) self.assertEqual(len(pyc_glob), 0) with self.settings(BASE_DIR=self.project_root): call_command('compile_pyc') pyc_glob = self._find_pyc(self.project_root) self.assertTrue(len(pyc_glob) > 0) with self.settings(BASE_DIR=self.project_root): call_command('clean_pyc') def test_takes_path(self): out = StringIO() with self.settings(BASE_DIR=""): call_command('clean_pyc', path=self.project_root) pyc_glob = self._find_pyc(self.project_root) self.assertEqual(len(pyc_glob), 0) with self.settings(BASE_DIR=""): call_command('compile_pyc', verbosity=2, path=self.project_root, stdout=out) expected = ['Compiling %s...' % fn for fn in sorted(self._find_pyc(self.project_root, mask='*.py'))] output = out.getvalue().splitlines() self.assertEqual(expected, sorted(output)) with self.settings(BASE_DIR=""): call_command('clean_pyc', path=self.project_root) django-extensions-3.1.5/tests/test_dumpscript.py000066400000000000000000000066231414177705400221450ustar00rootroot00000000000000# -*- coding: utf-8 -*- import ast import os import shutil import sys from io import StringIO from django.core.management import call_command from django.test import TestCase, override_settings from .testapp.models import Name, Note, Person, Club class DumpScriptTests(TestCase): def setUp(self): sys.stdout = StringIO() sys.stderr = StringIO() def test_runs(self): # lame test...does it run? n = Name(name='Gabriel') n.save() call_command('dumpscript', 'django_extensions') self.assertTrue('Gabriel' in sys.stdout.getvalue()) def test_replaced_stdout(self): # check if stdout can be replaced sys.stdout = StringIO() n = Name(name='Mike') n.save() tmp_out = StringIO() call_command('dumpscript', 'django_extensions', stdout=tmp_out) self.assertTrue('Mike' in tmp_out.getvalue()) # script should go to tmp_out self.assertEqual(0, len(sys.stdout.getvalue())) # there should not be any output to sys.stdout tmp_out.close() def test_replaced_stderr(self): # check if stderr can be replaced, without changing stdout n = Name(name='Fred') n.save() tmp_err = StringIO() sys.stderr = StringIO() call_command('dumpscript', 'django_extensions', stderr=tmp_err) self.assertTrue('Fred' in sys.stdout.getvalue()) # script should still go to stdout self.assertTrue('Name' in tmp_err.getvalue()) # error output should go to tmp_err self.assertEqual(0, len(sys.stderr.getvalue())) # there should not be any output to sys.stderr tmp_err.close() def test_valid_syntax(self): n1 = Name(name='John') n1.save() p1 = Person(name=n1, age=40) p1.save() n2 = Name(name='Jane') n2.save() p2 = Person(name=n2, age=18) p2.save() p2.children.add(p1) note1 = Note(note="This is the first note.") note1.save() note2 = Note(note="This is the second note.") note2.save() p2.notes.add(note1, note2) tmp_out = StringIO() call_command('dumpscript', 'django_extensions', stdout=tmp_out) ast_syntax_tree = ast.parse(tmp_out.getvalue()) if hasattr(ast_syntax_tree, 'body'): self.assertTrue(len(ast_syntax_tree.body) > 1) else: self.assertTrue(len(ast_syntax_tree.asList()) > 1) tmp_out.close() @override_settings(TIME_ZONE='Asia/Seoul') def test_with_datetimefield(self): django = Club.objects.create(name='Club Django') Note.objects.create( note='Django Tips', club=django, ) dumpscript_path = './django_extensions/scripts' os.mkdir(dumpscript_path) open(dumpscript_path + '/__init__.py', 'w').close() # for python 2.7 # This script will have a dateutil codes. # e.g. importer.locate_object(..., # 'date_joined': dateutil.parser.parse("2019-05-20T03:32:27.144586+09:00") with open(dumpscript_path + '/test.py', 'wt') as test: call_command('dumpscript', 'django_extensions', stdout=test) # Check dumpscript without exception call_command('runscript', 'test') # Delete dumpscript shutil.rmtree(dumpscript_path) # Check if Note is duplicated self.assertEqual(Note.objects.filter(note='Django Tips').count(), 2) django-extensions-3.1.5/tests/test_find_template.py000066400000000000000000000012511414177705400225560ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django.core.management import call_command from django.test import TestCase from io import StringIO from unittest.mock import patch class FindTemplateTests(TestCase): @patch('sys.stdout', new_callable=StringIO) def test_finding_template(self, m_stdout): call_command('find_template', 'admin/change_form.html') self.assertIn('admin/change_form.html', m_stdout.getvalue()) @patch('sys.stderr', new_callable=StringIO) def test_should_print_error_when_template_not_found(self, m_stderr): call_command('find_template', 'not_found_template.html') self.assertIn('No template found', m_stderr.getvalue()) django-extensions-3.1.5/tests/test_json_field.py000066400000000000000000000102441414177705400220610ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django.test import TestCase from .testapp.models import JSONFieldTestModel from django_extensions.db.fields.json import ( dumps, loads, JSONField, JSONDict, JSONList ) class JsonFieldTest(TestCase): def test_char_field_create(self): j = JSONFieldTestModel.objects.create(a=6, j_field=dict(foo='bar')) self.assertEqual(j.a, 6) self.assertEqual(j.j_field, {'foo': 'bar'}) def test_char_field_get_or_create(self): j, created = JSONFieldTestModel.objects.get_or_create( a=6, j_field=dict(foo='bar')) self.assertTrue(created) self.assertEqual(j.a, 6) self.assertEqual(j.j_field, {'foo': 'bar'}) j, created = JSONFieldTestModel.objects.get_or_create(a=6, j_field=dict(foo='bar')) self.assertFalse(created) self.assertEqual(j.a, 6) self.assertEqual(j.j_field, {'foo': 'bar'}) def test_default(self): j = JSONFieldTestModel.objects.create(a=1) self.assertEqual(j.j_field, {}) def test_default_mutable(self): j1 = JSONFieldTestModel.objects.create(a=1) self.assertEqual(j1.j_field, {}) j2 = JSONFieldTestModel.objects.create(a=1) self.assertEqual(j2.j_field, {}) self.assertIsNot(j1.j_field, j2.j_field) def test_get_default(self): j_field = JSONField() value = j_field.get_default() self.assertEqual(value, {}) self.assertIsInstance(value, JSONDict) j_field = JSONField(default={}) value = j_field.get_default() self.assertEqual(value, {}) self.assertIsInstance(value, JSONDict) j_field = JSONField(default='{}') value = j_field.get_default() self.assertEqual(value, {}) self.assertIsInstance(value, JSONDict) j_field = JSONField(default=[{}]) value = j_field.get_default() self.assertEqual(value, [{}]) self.assertIsInstance(value, JSONList) j_field = JSONField(default='[{}]') value = j_field.get_default() self.assertEqual(value, [{}]) self.assertIsInstance(value, JSONList) j_field = JSONField(default=lambda: '{}') value = j_field.get_default() self.assertEqual(value, {}) self.assertIsInstance(value, JSONDict) def test_empty_list(self): j = JSONFieldTestModel.objects.create(a=6, j_field=[]) self.assertIsInstance(j.j_field, list) self.assertEqual(j.j_field, []) def test_float_values(self): """Tests that float values in JSONFields are correctly serialized over repeated saves. Regression test for c382398b, which fixes floats being returned as strings after a second save.""" test_instance = JSONFieldTestModel(a=6, j_field={'test': 0.1}) test_instance.save() test_instance = JSONFieldTestModel.objects.get() test_instance.save() test_instance = JSONFieldTestModel.objects.get() self.assertEqual(test_instance.j_field['test'], 0.1) def test_get_prep_value(self): j_field = JSONField() self.assertEqual( str(dumps([{'a': 'a'}])), j_field.get_prep_value(value=[{'a': 'a'}]), ) self.assertEqual( str(dumps([{'a': 'a'}])), j_field.get_prep_value(value='[{"a": "a"}]'), ) def test_get_db_prep_save(self): j_field = JSONField() self.assertEqual( str(dumps([{'a': 'a'}])), j_field.get_db_prep_save(value=[{'a': 'a'}], connection=None), ) self.assertEqual( str('[{"a": "a"}]'), j_field.get_db_prep_save(value='[{"a": "a"}]', connection=None), ) def test_to_python(self): j_field = JSONField() self.assertEqual( loads('1'), j_field.to_python('1') ) self.assertEqual( loads('"1"'), j_field.to_python('"1"') ) self.assertEqual( loads('[{"a": 1}]'), j_field.to_python('[{"a": 1}]') ) self.assertEqual( loads('[{"a": "1"}]'), j_field.to_python('[{"a": "1"}]') ) django-extensions-3.1.5/tests/test_logging_filters.py000066400000000000000000000047741414177705400231360ustar00rootroot00000000000000# -*- coding: utf-8 -*- import time from django.test import TestCase from django.test.utils import override_settings from django_extensions.logging.filters import RateLimiterFilter from unittest import mock TEST_SUBJECT = 'test_subect' class RateLimiterFilterTests(TestCase): """Tests for RateLimiterFilter.""" def setUp(self): self.rate_limiter_filter = RateLimiterFilter() self.record = mock.Mock(msg=TEST_SUBJECT) self.record.getMessage.return_value = TEST_SUBJECT.encode() self.time_patch = mock.patch.object(time, 'time', return_value='test_time') self.time_patch.start() def tearDown(self): self.time_patch.stop() @override_settings(RATE_LIMITER_FILTER_PREFIX='test_prefix') @mock.patch('django.core.cache.cache') def test_should_incr_cache_with_custom_prefix_and_return_False(self, m_cache): m_cache.get_many.return_value = { 'test_prefix:114392702498ad1d75c1829b9519b8c7': 10, 'test_prefix:114392702498ad1d75c1829b9519b8c7:count': 1, } result = self.rate_limiter_filter.filter(self.record) self.assertIs(m_cache.set.called, False) m_cache.incr.assert_called_once_with('test_prefix:114392702498ad1d75c1829b9519b8c7:count') self.assertIs(result, False) @override_settings(RATE_LIMITER_FILTER_RATE=1) @mock.patch('django.core.cache.cache') def test_should_set_cache_key_with_custom_rate_and_return_True(self, m_cache): m_cache.get_many.return_value = {} expected_calls = [ mock.call('ratelimiterfilter:114392702498ad1d75c1829b9519b8c7:count', 1, 61), mock.call('ratelimiterfilter:114392702498ad1d75c1829b9519b8c7', 'test_time', 1), ] result = self.rate_limiter_filter.filter(self.record) self.assertEqual(self.record.msg, '[1x] test_subect') m_cache.set.assert_has_calls(expected_calls, any_order=False) self.assertIs(result, True) @mock.patch('django.core.cache.cache') def test_should_modify_record_msg_and_return_True(self, m_cache): """Default rate and prefix values.""" m_cache.get_many.return_value = { 'ratelimiterfilter:114392702498ad1d75c1829b9519b8c7:count': 999, } result = self.rate_limiter_filter.filter(self.record) self.assertEqual(self.record.msg, '[999x] test_subect') m_cache.set.assert_called_once_with('ratelimiterfilter:114392702498ad1d75c1829b9519b8c7', 'test_time', 10) self.assertIs(result, True) django-extensions-3.1.5/tests/test_management_command.py000066400000000000000000000471421414177705400235660ustar00rootroot00000000000000# -*- coding: utf-8 -*- import os import mock import logging import importlib from django.conf import settings from django.core.management import call_command, find_commands, load_command_class from django.test import TestCase from io import StringIO from django_extensions.management.modelviz import use_model, generate_graph_data from django_extensions.management.commands.merge_model_instances import get_model_to_deduplicate, get_field_names, keep_first_or_last_instance from . import force_color_support from .testapp.models import Person, Name, Note, Personality, Club, Membership, Permission from .testapp.jobs.hourly.test_hourly_job import HOURLY_JOB_MOCK from .testapp.jobs.daily.test_daily_job import DAILY_JOB_MOCK from .testapp.jobs.weekly.test_weekly_job import WEEKLY_JOB_MOCK from .testapp.jobs.monthly.test_monthly_job import MONTHLY_JOB_MOCK from .testapp.jobs.yearly.test_yearly_job import YEARLY_JOB_MOCK class MockLoggingHandler(logging.Handler): """ Mock logging handler to check for expected logs. """ def __init__(self, *args, **kwargs): self.reset() logging.Handler.__init__(self, *args, **kwargs) def emit(self, record): self.messages[record.levelname.lower()].append(record.getMessage()) def reset(self): self.messages = { 'debug': [], 'info': [], 'warning': [], 'error': [], 'critical': [], } class CommandTest(TestCase): def test_error_logging(self): # Ensure command errors are properly logged and reraised from django_extensions.management.base import logger logger.addHandler(MockLoggingHandler()) module_path = "tests.management.commands.error_raising_command" module = importlib.import_module(module_path) error_raising_command = module.Command() self.assertRaises(Exception, error_raising_command.execute) handler = logger.handlers[0] self.assertEqual(len(handler.messages['error']), 1) class ShowTemplateTagsTests(TestCase): def test_some_output(self): out = StringIO() call_command('show_template_tags', stdout=out) output = out.getvalue() # Once django_extension is installed during tests it should appear with # its templatetags self.assertIn('django_extensions', output) # let's check at least one self.assertIn('syntax_color', output) class DescribeFormTests(TestCase): def test_command(self): out = StringIO() call_command('describe_form', 'django_extensions.Secret', stdout=out) output = out.getvalue() self.assertIn("class SecretForm(forms.Form):", output) self.assertRegex(output, r"name = forms.CharField\(.*max_length=255") self.assertRegex(output, r"name = forms.CharField\(.*required=False") self.assertRegex(output, r"name = forms.CharField\(.*label=u?'Name'") self.assertRegex(output, r"text = forms.CharField\(.*required=False") self.assertRegex(output, r"text = forms.CharField\(.*label=u?'Text'") class CommandSignalTests(TestCase): pre = None post = None def test_works(self): from django_extensions.management.signals import post_command, pre_command from django_extensions.management.commands.show_template_tags import Command def pre(sender, **kwargs): CommandSignalTests.pre = dict(**kwargs) def post(sender, **kwargs): CommandSignalTests.post = dict(**kwargs) pre_command.connect(pre, Command) post_command.connect(post, Command) out = StringIO() call_command('show_template_tags', stdout=out) self.assertIn('args', CommandSignalTests.pre) self.assertIn('kwargs', CommandSignalTests.pre) self.assertIn('args', CommandSignalTests.post) self.assertIn('kwargs', CommandSignalTests.post) self.assertIn('outcome', CommandSignalTests.post) class CommandClassTests(TestCase): def setUp(self): management_dir = os.path.join('django_extensions', 'management') self.commands = find_commands(management_dir) def test_load_commands(self): """Try to load every management command to catch exceptions.""" try: for command in self.commands: load_command_class('django_extensions', command) except Exception as e: self.fail("Can't load command class of {0}\n{1}".format(command, e)) class GraphModelsTests(TestCase): """ Tests for the `graph_models` management command. """ def test_use_model(self): include_models = [ 'NoWildcardInclude', 'Wildcard*InsideInclude', '*WildcardPrefixInclude', 'WildcardSuffixInclude*', '*WildcardBothInclude*', ] exclude_models = [ 'NoWildcardExclude', 'Wildcard*InsideExclude', '*WildcardPrefixExclude', 'WildcardSuffixExclude*', '*WildcardBothExclude*', '*Include', ] # Any model name should be used if neither include or exclude # are defined. self.assertTrue(use_model( 'SomeModel', None, None, )) # Any model name should be allowed if `*` is in `include_models`. self.assertTrue(use_model( 'SomeModel', ['OtherModel', '*', 'Wildcard*Model'], None, )) # No model name should be allowed if `*` is in `exclude_models`. self.assertFalse(use_model( 'SomeModel', None, ['OtherModel', '*', 'Wildcard*Model'], )) # Some tests with the `include_models` defined above. self.assertFalse(use_model( 'SomeModel', include_models, None, )) self.assertTrue(use_model( 'NoWildcardInclude', include_models, None, )) self.assertTrue(use_model( 'WildcardSomewhereInsideInclude', include_models, None, )) self.assertTrue(use_model( 'MyWildcardPrefixInclude', include_models, None, )) self.assertTrue(use_model( 'WildcardSuffixIncludeModel', include_models, None, )) self.assertTrue(use_model( 'MyWildcardBothIncludeModel', include_models, None, )) # Some tests with the `exclude_models` defined above. self.assertTrue(use_model( 'SomeModel', None, exclude_models, )) self.assertFalse(use_model( 'NoWildcardExclude', None, exclude_models, )) self.assertFalse(use_model( 'WildcardSomewhereInsideExclude', None, exclude_models, )) self.assertFalse(use_model( 'MyWildcardPrefixExclude', None, exclude_models, )) self.assertFalse(use_model( 'WildcardSuffixExcludeModel', None, exclude_models, )) self.assertFalse(use_model( 'MyWildcardBothExcludeModel', None, exclude_models, )) # Test with `exclude_models` and `include_models` combined # where the user wants to exclude some models through a wildcard # while still being able to include given models self.assertTrue(use_model( 'MyWildcardPrefixInclude', include_models, exclude_models )) self.assertFalse(use_model( 'MyInclude', include_models, exclude_models )) def test_no_models_dot_py(self): data = generate_graph_data(['testapp_with_no_models_file']) self.assertEqual(len(data['graphs']), 1) model_name = data['graphs'][0]['models'][0]['name'] self.assertEqual(model_name, 'TeslaCar') class ShowUrlsTests(TestCase): """ Tests for the `show_urls` management command. """ def test_color(self): with force_color_support: out = StringIO() call_command('show_urls', stdout=out) self.output = out.getvalue() self.assertIn('\x1b', self.output) def test_no_color(self): with force_color_support: out = StringIO() call_command('show_urls', '--no-color', stdout=out) self.output = out.getvalue() self.assertNotIn('\x1b', self.output) class ListModelInfoTests(TestCase): """ Tests for the `list_model_info` management command. """ def test_plain(self): out = StringIO() call_command('list_model_info', '--model', 'django_extensions.MultipleFieldsAndMethods', stdout=out) self.output = out.getvalue() self.assertIn('id', self.output) self.assertIn('char_field', self.output) self.assertIn('integer_field', self.output) self.assertIn('foreign_key_field', self.output) self.assertIn('has_self_only()', self.output) self.assertIn('has_one_extra_argument()', self.output) self.assertIn('has_two_extra_arguments()', self.output) self.assertIn('has_args_kwargs()', self.output) self.assertIn('has_defaults()', self.output) self.assertNotIn('__class__()', self.output) self.assertNotIn('validate_unique()', self.output) def test_all(self): out = StringIO() call_command('list_model_info', '--model', 'django_extensions.MultipleFieldsAndMethods', '--all', stdout=out) self.output = out.getvalue() self.assertIn('id', self.output) self.assertIn('__class__()', self.output) self.assertIn('validate_unique()', self.output) def test_signature(self): out = StringIO() call_command('list_model_info', '--model', 'django_extensions.MultipleFieldsAndMethods', '--signature', stdout=out) self.output = out.getvalue() self.assertIn('has_self_only(self)', self.output) self.assertIn('has_one_extra_argument(self, arg_one)', self.output) self.assertIn('has_two_extra_arguments(self, arg_one, arg_two)', self.output) self.assertIn('has_args_kwargs(self, *args, **kwargs)', self.output) self.assertIn("has_defaults(self, one=1, two='Two', true=True, false=False, none=None)", self.output) def test_db_type(self): if settings.DATABASES['default']['ENGINE'] == 'django.db.backends.postgresql': id_type = 'serial' else: id_type = 'integer' out = StringIO() call_command('list_model_info', '--model', 'django_extensions.MultipleFieldsAndMethods', '--db-type', stdout=out) self.output = out.getvalue() self.assertIn('id - %s' % id_type, self.output) self.assertIn('char_field - varchar(10)', self.output) self.assertIn('integer_field - integer', self.output) self.assertIn('foreign_key_field - integer', self.output) def test_field_class(self): out = StringIO() call_command('list_model_info', '--model', 'django_extensions.MultipleFieldsAndMethods', '--field-class', stdout=out) self.output = out.getvalue() self.assertIn('id - AutoField', self.output) self.assertIn('char_field - CharField', self.output) self.assertIn('integer_field - IntegerField', self.output) self.assertIn('foreign_key_field - ForeignKey', self.output) class MergeModelInstancesTests(TestCase): """ Tests for the `merge_model_instances` management command. """ @mock.patch('django_extensions.management.commands.merge_model_instances.apps.get_models') @mock.patch('django_extensions.management.commands.merge_model_instances.input') def test_get_model_to_merge(self, test_input, get_models): class Model: __name__ = "" return_value = [] for v in ["one", "two", "three"]: instance = Model() instance.__name__ = v return_value.append(instance) get_models.return_value = return_value test_input.return_value = 2 model_to_deduplicate = get_model_to_deduplicate() self.assertEqual(model_to_deduplicate.__name__, "two") @mock.patch('django_extensions.management.commands.merge_model_instances.input') def test_get_field_names(self, test_input): class Field: name = "" def __init__(self, name): self.name = name class Model: __name__ = "" one = Field(name="one") two = Field(name="two") three = Field(name="three") return_value = [Model().__getattribute__(field) for field in dir(Model()) if not field.startswith("__")] Model._meta = mock.MagicMock() Model._meta.get_fields = mock.MagicMock(return_value=return_value) # Choose the second return_value test_input.side_effect = [2, "C"] field_names = get_field_names(Model()) # Test that the second return_value returned self.assertEqual(field_names, [return_value[1].name]) @mock.patch('django_extensions.management.commands.merge_model_instances.input') def test_keep_first_or_last_instance(self, test_input): test_input.side_effect = ["xxxx", "first", "last"] first_or_last = keep_first_or_last_instance() self.assertEqual(first_or_last, "first") first_or_last = keep_first_or_last_instance() self.assertEqual(first_or_last, "last") @mock.patch('django_extensions.management.commands.merge_model_instances.get_model_to_deduplicate') @mock.patch('django_extensions.management.commands.merge_model_instances.get_field_names') @mock.patch('django_extensions.management.commands.merge_model_instances.keep_first_or_last_instance') def test_merge_model_instances(self, keep_first_or_last_instance, get_field_names, get_model_to_deduplicate): get_model_to_deduplicate.return_value = Person get_field_names.return_value = ["name"] keep_first_or_last_instance.return_value = "first" name = Name.objects.create(name="Name") note = Note.objects.create(note="This is a note.") personality_1 = Personality.objects.create(description="Child 1's personality.") personality_2 = Personality.objects.create(description="Child 2's personality.") child_1 = Person.objects.create( name=Name.objects.create(name="Child1"), age=10, personality=personality_1, ) child_1.notes.add(note) child_2 = Person.objects.create( name=Name.objects.create(name="Child2"), age=10, personality=personality_2, ) child_2.notes.add(note) club1 = Club.objects.create(name="Club one") club2 = Club.objects.create(name="Club two") person_1 = Person.objects.create( name=name, age=50, personality=Personality.objects.create(description="First personality"), ) person_1.children.add(child_1) person_1.notes.add(note) Permission.objects.create(text="Permission", person=person_1) person_2 = Person.objects.create( name=name, age=50, personality=Personality.objects.create(description="Second personality"), ) person_2.children.add(child_2) new_note = Note.objects.create(note="This is a new note") person_2.notes.add(new_note) Membership.objects.create(club=club1, person=person_2) Membership.objects.create(club=club1, person=person_2) Permission.objects.create(text="Permission", person=person_2) person_3 = Person.objects.create( name=name, age=50, personality=Personality.objects.create(description="Third personality"), ) person_3.children.add(child_2) person_3.notes.add(new_note) Membership.objects.create(club=club2, person=person_3) Membership.objects.create(club=club2, person=person_3) Permission.objects.create(text="Permission", person=person_3) self.assertEqual(Person.objects.count(), 5) self.assertEqual(Membership.objects.count(), 4) out = StringIO() call_command('merge_model_instances', stdout=out) self.ouptput = out.getvalue() self.assertEqual(Person.objects.count(), 3) person = Person.objects.get(name__name="Name") self.assertRaises( Person.DoesNotExist, lambda: Person.objects.get(personality__description="Second personality"), ) self.assertEqual(person.notes.count(), 2) self.assertEqual(person.clubs.distinct().count(), 2) self.assertEqual(person.permission_set.count(), 3) self.assertRaises( Personality.DoesNotExist, lambda: Personality.objects.get(description="Second personality"), ) class RunJobsTests(TestCase): """ Tests for the `runjobs` management command. """ @mock.patch('django_extensions.management.commands.runjobs.Command.runjobs_by_signals') @mock.patch('django_extensions.management.commands.runjobs.Command.runjobs') @mock.patch('django_extensions.management.commands.runjobs.Command.usage_msg') def test_runjobs_management_command( self, usage_msg, runjobs, runjobs_by_signals): when = 'daily' call_command('runjobs', when) usage_msg.assert_not_called() runjobs.assert_called_once() runjobs_by_signals.assert_called_once() self.assertEqual(runjobs.call_args[0][0], when) @mock.patch('django_extensions.management.commands.runjobs.Command.runjobs_by_signals') @mock.patch('django_extensions.management.commands.runjobs.Command.runjobs') @mock.patch('django_extensions.management.commands.runjobs.Command.usage_msg') def test_runjobs_management_command_invalid_when( self, usage_msg, runjobs, runjobs_by_signals): when = 'invalid' call_command('runjobs', when) usage_msg.assert_called_once_with() runjobs.assert_not_called() runjobs_by_signals.assert_not_called() def test_runjobs_integration_test(self): jobs = [ ("hourly", HOURLY_JOB_MOCK), ("daily", DAILY_JOB_MOCK), ("monthly", MONTHLY_JOB_MOCK), ("weekly", WEEKLY_JOB_MOCK), ("yearly", YEARLY_JOB_MOCK), ] # Reset all mocks in case they have been called elsewhere. for job in jobs: job[1].reset_mock() counter = 1 for job in jobs: call_command('runjobs', job[0], verbosity=2) for already_called in jobs[:counter]: already_called[1].assert_called_once_with() for not_yet_called in jobs[counter:]: not_yet_called[1].assert_not_called() counter += 1 def test_runjob_integration_test(self): jobs = [ ("test_hourly_job", HOURLY_JOB_MOCK), ("test_daily_job", DAILY_JOB_MOCK), ("test_monthly_job", MONTHLY_JOB_MOCK), ("test_weekly_job", WEEKLY_JOB_MOCK), ("test_yearly_job", YEARLY_JOB_MOCK), ] # Reset all mocks in case they have been called elsewhere. for job in jobs: job[1].reset_mock() counter = 1 for job in jobs: call_command('runjob', job[0], verbosity=2) for already_called in jobs[:counter]: already_called[1].assert_called_once_with() for not_yet_called in jobs[counter:]: not_yet_called[1].assert_not_called() counter += 1 django-extensions-3.1.5/tests/test_models.py000066400000000000000000000030661414177705400212340ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django.test import TestCase from django_extensions.db.models import ActivatorModel from .testapp.models import Post class ActivatorModelTestCase(TestCase): def test_active_includes_active(self): post = Post.objects.create(status=ActivatorModel.ACTIVE_STATUS) active = Post.objects.active() self.assertIn(post, active) post.delete() def test_active_excludes_inactive(self): post = Post.objects.create(status=ActivatorModel.INACTIVE_STATUS) active = Post.objects.active() self.assertNotIn(post, active) post.delete() def test_inactive_includes_inactive(self): post = Post.objects.create(status=ActivatorModel.INACTIVE_STATUS) inactive = Post.objects.inactive() self.assertIn(post, inactive) post.delete() def test_inactive_excludes_active(self): post = Post.objects.create(status=ActivatorModel.ACTIVE_STATUS) inactive = Post.objects.inactive() self.assertNotIn(post, inactive) post.delete() def test_active_is_chainable(self): post = Post.objects.create(title='Foo', status=ActivatorModel.ACTIVE_STATUS) specific_post = Post.objects.filter(title='Foo').active() self.assertIn(post, specific_post) post.delete() def test_inactive_is_chainable(self): post = Post.objects.create(title='Foo', status=ActivatorModel.INACTIVE_STATUS) specific_post = Post.objects.filter(title='Foo').inactive() self.assertIn(post, specific_post) post.delete() django-extensions-3.1.5/tests/test_modificationdatetime_fields.py000066400000000000000000000022121414177705400254510ustar00rootroot00000000000000# -*- coding: utf-8 -*- from datetime import datetime from django.test import TestCase from .testapp.models import ( ModelModificationDateTimeField, CustomModelModificationDateTimeField, DisabledUpdateModelModificationDateTimeField, ) class ModificationDatetimeFieldTest(TestCase): def test_update_model_with_modification_field(self): m = ModelModificationDateTimeField.objects.create() current_updated_time = m.modified m.field_to_update = False m.save() self.assertNotEqual(m.modified, current_updated_time) def test_custom_modification_field_name(self): m = CustomModelModificationDateTimeField.objects.create() current_updated_time = m.custom_modified m.field_to_update = False m.save() self.assertNotEqual(m.custom_modified, current_updated_time) def test_disabled_update_modification_field(self): m = DisabledUpdateModelModificationDateTimeField.objects.create(modified=datetime.now()) current_updated_time = m.modified m.field_to_update = False m.save() self.assertEqual(m.modified, current_updated_time) django-extensions-3.1.5/tests/test_module_in_project_dir.py000066400000000000000000000002171414177705400243030ustar00rootroot00000000000000# -*- coding: utf-8 -*- from tests.testapp.classes_to_include import BaseIncludedClass class FourthDerivedClass(BaseIncludedClass): pass django-extensions-3.1.5/tests/test_parse_mysql_cnf.py000066400000000000000000000034101414177705400231270ustar00rootroot00000000000000# -*- coding: utf-8 -*- import os import shutil from tempfile import mkdtemp from django.test import TestCase from django_extensions.management.mysql import parse_mysql_cnf class ParseMysqlCnfTests(TestCase): """Tests for parse_mysql_cnf.""" @classmethod def setUpClass(cls): cls.tmpdir = mkdtemp() @classmethod def tearDownClass(cls): shutil.rmtree(cls.tmpdir) def test_should_return_empty_strings_if_read_default_file_option_is_missing(self): dbinfo = {} result = parse_mysql_cnf(dbinfo) self.assertEqual(result, ('', '', '', '', '')) def test_should_parse_my_cnf_and_retun_connection_settings(self): my_cnf_path = os.path.join(self.tmpdir, 'my.cnf') with open(my_cnf_path, 'w') as f: f.write("""[client] database = test_name user = test_user password = test_password host = localhost port = 3306 socket = /var/lib/mysqld/mysql.sock """) dbinfo = { 'ENGINE': 'django.db.backends.mysql', 'OPTIONS': { 'read_default_file': my_cnf_path, } } result = parse_mysql_cnf(dbinfo) self.assertEqual(result, ('test_user', 'test_password', 'test_name', '/var/lib/mysqld/mysql.sock', '3306')) def test_should_return_empty_strings_if_NoSectionError_exception_occured(self): my_cnf_path = os.path.join(self.tmpdir, 'my.cnf') with open(my_cnf_path, 'w') as f: f.write("") dbinfo = { 'ENGINE': 'django.db.backends.mysql', 'OPTIONS': { 'read_default_file': my_cnf_path, } } result = parse_mysql_cnf(dbinfo) self.assertEqual(result, ('', '', '', '', '')) django-extensions-3.1.5/tests/test_randomchar_field.py000066400000000000000000000067671414177705400232450ustar00rootroot00000000000000# -*- coding: utf-8 -*- import string import pytest from django.test import TestCase from .testapp.models import RandomCharTestModel, RandomCharTestModelUnique, RandomCharTestModelLowercase from .testapp.models import RandomCharTestModelUppercase, RandomCharTestModelAlpha, RandomCharTestModelDigits from .testapp.models import RandomCharTestModelPunctuation, RandomCharTestModelLowercaseAlphaDigits, RandomCharTestModelUppercaseAlphaDigits from .testapp.models import RandomCharTestModelUniqueTogether from unittest import mock class RandomCharFieldTest(TestCase): def testRandomCharField(self): m = RandomCharTestModel() m.save() assert len(m.random_char_field) == 8, m.random_char_field def testRandomCharFieldUnique(self): m = RandomCharTestModelUnique() m.save() assert len(m.random_char_field) == 8, m.random_char_field def testRandomCharFieldLowercase(self): m = RandomCharTestModelLowercase() m.save() for c in m.random_char_field: assert c.islower(), m.random_char_field def testRandomCharFieldUppercase(self): m = RandomCharTestModelUppercase() m.save() for c in m.random_char_field: assert c.isupper(), m.random_char_field def testRandomCharFieldAlpha(self): m = RandomCharTestModelAlpha() m.save() for c in m.random_char_field: assert c.isalpha(), m.random_char_field def testRandomCharFieldDigits(self): m = RandomCharTestModelDigits() m.save() for c in m.random_char_field: assert c.isdigit(), m.random_char_field def testRandomCharFieldPunctuation(self): m = RandomCharTestModelPunctuation() m.save() for c in m.random_char_field: assert c in string.punctuation, m.random_char_field def testRandomCharTestModelLowercaseAlphaDigits(self): m = RandomCharTestModelLowercaseAlphaDigits() m.save() for c in m.random_char_field: assert c.isdigit() or (c.isalpha() and c.islower()), m.random_char_field def testRandomCharTestModelUppercaseAlphaDigits(self): m = RandomCharTestModelUppercaseAlphaDigits() m.save() for c in m.random_char_field: assert c.isdigit() or (c.isalpha() and c.isupper()), m.random_char_field def testRandomCharTestModelDuplicate(self): m = RandomCharTestModelUnique() m.save() with mock.patch('django_extensions.db.fields.RandomCharField.random_char_generator') as func: func.return_value = iter([m.random_char_field, 'aaa']) m = RandomCharTestModelUnique() m.save() assert m.random_char_field == 'aaa' def testRandomCharTestModelAsserts(self): with mock.patch('django_extensions.db.fields.get_random_string') as mock_sample: mock_sample.return_value = 'aaa' m = RandomCharTestModelUnique() m.save() m = RandomCharTestModelUnique() with pytest.raises(RuntimeError): m.save() def testRandomCharTestModelUniqueTogether(self): with mock.patch('django_extensions.db.fields.get_random_string') as mock_sample: mock_sample.return_value = 'aaa' m = RandomCharTestModelUniqueTogether() m.common_field = 'bbb' m.save() m = RandomCharTestModelUniqueTogether() m.common_field = 'bbb' with pytest.raises(RuntimeError): m.save() django-extensions-3.1.5/tests/test_runscript.py000066400000000000000000000237071414177705400220060ustar00rootroot00000000000000# -*- coding: utf-8 -*- import os import sys import importlib from io import StringIO from django.core.management import call_command from django.core.management.base import CommandError from django.test import TestCase, override_settings from django_extensions.management.commands.runscript import Command, BadCustomDirectoryException, DirPolicyChoices class RunScriptTests(TestCase): def setUp(self): sys.stdout = StringIO() sys.stderr = StringIO() def get_command(self): cmd = Command() cmd.running_tests = True return cmd def test_runs(self): # lame test...does it run? call_command('runscript', 'sample_script', verbosity=2) self.assertIn("Found script 'tests.testapp.scripts.sample_script'", sys.stdout.getvalue()) self.assertIn("Running script 'tests.testapp.scripts.sample_script'", sys.stdout.getvalue()) def test_runs_appconfig(self): with self.modify_settings(INSTALLED_APPS={ 'append': 'tests.testapp.apps.TestAppConfig', 'remove': 'tests.testapp', }): call_command('runscript', 'sample_script', verbosity=2) self.assertIn("Found script 'tests.testapp.scripts.sample_script'", sys.stdout.getvalue()) self.assertIn("Running script 'tests.testapp.scripts.sample_script'", sys.stdout.getvalue()) class NonExistentScriptsTests(RunScriptTests): def test_prints_error_on_nonexistent_script(self): cmd = self.get_command() with self.assertRaises(CommandError): call_command(cmd, 'non_existent_script', verbosity=2) self.assertIn("No (valid) module for script 'non_existent_script' found", sys.stdout.getvalue()) self.assertEqual(cmd.last_exit_code, 1) def test_prints_nothing_for_nonexistent_script_when_silent(self): cmd = self.get_command() call_command(cmd, 'non_existent_script', silent=True) self.assertEqual("", sys.stdout.getvalue()) self.assertEqual(cmd.last_exit_code, 1) def test_doesnt_print_exception_for_nonexistent_script_when_no_traceback(self): cmd = self.get_command() with self.assertRaises(CommandError): call_command(cmd, 'non_existent_script', no_traceback=True) self.assertEqual("", sys.stderr.getvalue()) self.assertEqual(cmd.last_exit_code, 1) class InvalidImportScriptsTests(RunScriptTests): def test_prints_additional_info_on_nonexistent_script_by_default(self): cmd = self.get_command() with self.assertRaises(CommandError): call_command(cmd, 'non_existent_script') self.assertIn("No (valid) module for script 'non_existent_script' found", sys.stdout.getvalue()) self.assertIn("Try running with a higher verbosity level like: -v2 or -v3", sys.stdout.getvalue()) self.assertEqual(cmd.last_exit_code, 1) def test_prints_import_error_on_script_with_invalid_imports_by_default(self): cmd = self.get_command() with self.assertRaises(CommandError): call_command(cmd, 'invalid_import_script') self.assertIn("Cannot import module 'tests.testapp.scripts.invalid_import_script'", sys.stdout.getvalue()) self.assertRegex(sys.stdout.getvalue(), 'No module named (\')?(invalidpackage)\1?') self.assertEqual(cmd.last_exit_code, 1) def test_prints_import_error_on_script_with_invalid_imports_reliably(self): cmd = self.get_command() if hasattr(importlib, 'util') and hasattr(importlib.util, 'find_spec'): with self.settings(BASE_DIR=os.path.dirname(os.path.abspath(__file__))): with self.assertRaises(CommandError): call_command(cmd, 'invalid_import_script') self.assertIn("Cannot import module 'tests.testapp.scripts.invalid_import_script'", sys.stdout.getvalue()) self.assertRegex(sys.stdout.getvalue(), 'No module named (\')?(invalidpackage)\1?') self.assertEqual(cmd.last_exit_code, 1) class InvalidScriptsTests(RunScriptTests): def test_raises_error_message_on_invalid_script_by_default(self): cmd = self.get_command() with self.assertRaises(Exception): call_command(cmd, 'error_script') self.assertIn("Exception while running run() in", sys.stdout.getvalue()) def test_prints_nothing_for_invalid_script_when_silent(self): cmd = self.get_command() call_command(cmd, 'error_script', silent=True) self.assertEqual(cmd.last_exit_code, 1) self.assertEqual("", sys.stdout.getvalue()) def test_doesnt_print_exception_for_nonexistent_script_when_no_traceback(self): cmd = self.get_command() with self.assertRaises(CommandError): call_command(cmd, 'error_script', no_traceback=True) self.assertEqual("", sys.stderr.getvalue()) self.assertIn("Exception while running run() in", sys.stdout.getvalue()) self.assertEqual(cmd.last_exit_code, 1) class RunFunctionTests(RunScriptTests): def test_prints_error_message_for_script_without_run(self): cmd = self.get_command() with self.assertRaises(CommandError): call_command(cmd, 'script_no_run_function') self.assertIn("No (valid) module for script 'script_no_run_function' found", sys.stdout.getvalue()) self.assertIn("Try running with a higher verbosity level like: -v2 or -v3", sys.stdout.getvalue()) self.assertEqual(cmd.last_exit_code, 1) def test_prints_additional_info_for_script__run_extra_verbosity(self): cmd = self.get_command() with self.assertRaises(CommandError): call_command(cmd, 'script_no_run_function', verbosity=2) self.assertIn("No (valid) module for script 'script_no_run_function' found", sys.stdout.getvalue()) self.assertIn("Found script", sys.stdout.getvalue()) self.assertEqual(cmd.last_exit_code, 1) def test_prints_nothing_for_script_without_run(self): cmd = self.get_command() call_command(cmd, 'script_no_run_function', silent=True) self.assertEqual("", sys.stdout.getvalue()) project_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) class ChangingDirectoryTests(RunScriptTests): def setUp(self): super().setUp() self.curwd = os.getcwd() os.chdir(project_path) def tearDown(self): super().setUp() os.chdir(self.curwd) def _execute_script_with_chdir(self, dir_policy, start_path, expected_path, chdir=None): os.chdir(os.path.join(project_path, *start_path)) expected_path = os.path.join(project_path, *expected_path) call_command('runscript', 'directory_checker_script', dir_policy=dir_policy, chdir=chdir) output = sys.stdout.getvalue().split('Script called from: ')[1] self.assertEqual(output, expected_path + '\n') def test_none_policy_command_run(self): self._execute_script_with_chdir(DirPolicyChoices.NONE, [], []) def test_none_policy_command_run_with_chdir(self): self._execute_script_with_chdir(DirPolicyChoices.NONE, ['tests'], ['tests']) def test_none_policy_freezing_start_directory(self): self._execute_script_with_chdir(DirPolicyChoices.NONE, ['tests'], ['tests']) self._execute_script_with_chdir(DirPolicyChoices.NONE, ['tests'], ['tests']) def test_root_policy_command_run(self): self._execute_script_with_chdir(DirPolicyChoices.ROOT, ['tests'], []) def test_each_policy_command_run(self): os.chdir(os.path.join(project_path, 'tests')) call_command('runscript', 'directory_checker_script', 'other_directory_checker_script', dir_policy=DirPolicyChoices.EACH) output = sys.stdout.getvalue() first_output = output.split('Script called from: ')[1].split('Cannot import module ')[0] self.assertEqual(first_output, os.path.join(project_path, 'tests', 'testapp', 'scripts') + '\n') second_output = output.split('Script called from: ')[2].split('Cannot import module ')[0] self.assertEqual(second_output, os.path.join(project_path, 'tests', 'testapp_with_no_models_file', 'scripts') + '\n') def test_chdir_specified(self): execution_path = os.path.join(project_path, 'django_extensions', 'management') self._execute_script_with_chdir(DirPolicyChoices.ROOT, ['tests'], ['django_extensions', 'management'], chdir=execution_path) @override_settings(RUNSCRIPT_CHDIR=os.path.join(project_path, 'tests')) def test_policy_from_cli_and_chdir_from_settings(self): self._execute_script_with_chdir(DirPolicyChoices.ROOT, ['tests'], []) @override_settings( RUNSCRIPT_CHDIR=os.path.join(project_path, 'django_extensions', 'management'), RUNSCRIPT_CHDIR_POLICY=DirPolicyChoices.ROOT, ) def test_chdir_from_settings_and_policy_from_settings(self): self._execute_script_with_chdir(None, ['tests'], ['django_extensions', 'management']) @override_settings(RUNSCRIPT_CHDIR_POLICY=DirPolicyChoices.EACH) def test_policy_from_settings(self): self._execute_script_with_chdir(None, ['tests'], ['tests', 'testapp', 'scripts']) @override_settings(RUNSCRIPT_CHDIR=os.path.join(project_path, 'tests')) def test_chdir_django_settings(self): self._execute_script_with_chdir(None, [], ['tests']) @override_settings(RUNSCRIPT_CHDIR='bad path') def test_custom_policy_django_settings_bad_path(self): with self.assertRaisesRegex( BadCustomDirectoryException, 'bad path is not a directory! If --dir-policy is custom than you must set ' 'correct directory in --dir option or in settings.RUNSCRIPT_CHDIR' ): self._execute_script_with_chdir(None, [], ['tests']) def test_skip_printing_modules_which_does_not_exist(self): call_command('runscript', 'directory_checker_script') self.assertNotIn('No module named', sys.stdout.getvalue()) self.assertNotIn('No module named', sys.stderr.getvalue()) django-extensions-3.1.5/tests/test_runserver.py000066400000000000000000000056531414177705400220100ustar00rootroot00000000000000# -*- coding: utf-8 -*- import pytest from os.path import join from django_extensions.management.commands.runserver_plus import Command as RunServerCommand from unittest import mock location = join('some', 'strange', 'path') different_path = join('some', 'other', 'path') @pytest.mark.parametrize("cert_option, key_file_option, expected_cert_path, expected_key_path", [ ['hello', None, join(location, 'hello.crt'), join(location, 'hello.key')], ['hello.crt', None, join(location, 'hello.crt'), join(location, 'hello.key')], [None, 'hello', join(location, 'hello.crt'), join(location, 'hello.key')], [None, 'hello.key', join(location, 'hello.crt'), join(location, 'hello.key')], ['first', 'second', join(location, 'first.crt'), join(location, 'second.key')], ['first.pem', 'second.pem', join(location, 'first.pem'), join(location, 'second.pem')], ['cert.pem', 'key.pem', join(location, 'cert.pem'), join(location, 'key.pem')], [join(location, 'hello'), None, join(location, 'hello.crt'), join(location, 'hello.key')], [None, join(location, 'hello'), join(location, 'hello.crt'), join(location, 'hello.key')], [join(location, 'hello'), join(location, 'hello'), join(location, 'hello.crt'), join(location, 'hello.key')], [join(location, 'hello.crt'), join(location, 'hello.key'), join(location, 'hello.crt'), join(location, 'hello.key')], [join(location, 'hello.key'), join(location, 'hello.crt'), join(location, 'hello.key'), join(location, 'hello.crt')], [join(location, 'cert.pem'), join(location, 'key.pem'), join(location, 'cert.pem'), join(location, 'key.pem')], [join(location, 'hello.crt'), join(location, 'hello.key'), join(location, 'hello.crt'), join(location, 'hello.key')], [join(location, 'other'), join(location, 'hello.key'), join(location, 'other.crt'), join(location, 'hello.key')], [join(location, 'hello.key'), join(location, 'hello.crt'), join(location, 'hello.key'), join(location, 'hello.crt')], [join(different_path, 'hello'), None, join(different_path, 'hello.crt'), join(different_path, 'hello.key')], [None, join(different_path, 'hello'), join(different_path, 'hello.crt'), join(different_path, 'hello.key')], [join(location, 'hello.crt'), join(different_path, 'hello.key'), join(location, 'hello.crt'), join(different_path, 'hello.key')], [join(different_path, 'hello.crt'), join(location, 'hello.key'), join(different_path, 'hello.crt'), join(location, 'hello.key')], ]) def test_determining_paths(cert_option, key_file_option, expected_cert_path, expected_key_path): with mock.patch('django_extensions.management.commands.runserver_plus.os.getcwd', return_value=location): options = {'cert_path': cert_option, 'key_file_path': key_file_option} result_cert_path, result_key_path = RunServerCommand.determine_ssl_files_paths(options) assert expected_cert_path == result_cert_path assert expected_key_path == result_key_path django-extensions-3.1.5/tests/test_shortuuid_field.py000066400000000000000000000022341414177705400231360ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django.test import TestCase from .testapp.models import ( ShortUUIDTestAgregateModel, ShortUUIDTestManyToManyModel, ShortUUIDTestModel_field, ShortUUIDTestModel_pk, ) class ShortUUIDFieldTest(TestCase): def test_UUID_field_create(self): j = ShortUUIDTestModel_field.objects.create(a=6, uuid_field='vytxeTZskVKR7C7WgdSP3d') self.assertEqual(j.uuid_field, 'vytxeTZskVKR7C7WgdSP3d') def test_UUID_field_pk_create(self): j = ShortUUIDTestModel_pk.objects.create(uuid_field='vytxeTZskVKR7C7WgdSP3d') self.assertEqual(j.uuid_field, 'vytxeTZskVKR7C7WgdSP3d') self.assertEqual(j.pk, 'vytxeTZskVKR7C7WgdSP3d') def test_UUID_field_pk_agregate_create(self): j = ShortUUIDTestAgregateModel.objects.create(a=6) self.assertEqual(j.a, 6) self.assertIsInstance(j.pk, str) self.assertTrue(len(j.pk) < 23) def test_UUID_field_manytomany_create(self): j = ShortUUIDTestManyToManyModel.objects.create(uuid_field='vytxeTZskVKR7C7WgdSP3e') self.assertEqual(j.uuid_field, 'vytxeTZskVKR7C7WgdSP3e') self.assertEqual(j.pk, 'vytxeTZskVKR7C7WgdSP3e') django-extensions-3.1.5/tests/test_sqldiff.py000066400000000000000000000112661414177705400214020ustar00rootroot00000000000000# -*- coding: utf-8 -*- import mock import pytest from io import StringIO from django.conf import settings from django.apps import apps from django.test import TestCase # from django.core.management import call_command from django_extensions.management.commands.sqldiff import SqliteSQLDiff, Command, MySQLDiff, PostgresqlSQLDiff class SqlDiffTests(TestCase): def setUp(self): self.parser = Command().create_parser("test", "sqldiff") self.args = ["-a"] self.options = self.parser.parse_args(args=self.args) self.tmp_out = StringIO() self.tmp_err = StringIO() def _include_proxy_models_testing(self, should_include_proxy_models): # type: (bool) -> () if should_include_proxy_models: self.args.append("--include-proxy-models") self.options = self.parser.parse_args(args=self.args) instance = SqliteSQLDiff( apps.get_models(include_auto_created=True), vars(self.options), stdout=self.tmp_out, stderr=self.tmp_err, ) instance.load() instance.find_differences() checked_models = {"%s.%s" % (app_label, model_name) for app_label, model_name, _ in instance.differences} self.assertEqual(should_include_proxy_models, "testapp.PostWithTitleOrdering" in checked_models) @pytest.mark.skipif(settings.DATABASES['default']['ENGINE'] != 'django.db.backends.sqlite3', reason="Test can only run on sqlite3") def test_sql_diff_without_proxy_models(self): self._include_proxy_models_testing(False) @pytest.mark.skipif(settings.DATABASES['default']['ENGINE'] != 'django.db.backends.sqlite3', reason="Test can only run on sqlite3") def test_sql_diff_with_proxy_models(self): self._include_proxy_models_testing(True) def test_format_field_names(self): instance = MySQLDiff( apps.get_models(include_auto_created=True), vars(self.options), stdout=self.tmp_out, stderr=self.tmp_err, ) expected_field_name = ['name', 'email', 'address'] self.assertEqual(instance.format_field_names(['Name', 'EMAIL', 'aDDress']), expected_field_name) @pytest.mark.skipif(settings.DATABASES['default']['ENGINE'] != 'django.db.backends.mysql', reason="Test can only run on mysql") def test_mysql_to_dict(self): mysql_instance = MySQLDiff( apps.get_models(include_auto_created=True), vars(self.options), stdout=self.tmp_out, stderr=self.tmp_err, ) mysql_dict = mysql_instance.sql_to_dict("""select 1 as "foo", 1 + 1 as "BAR";""", []) self.assertEqual(mysql_dict, [{'bar': 2, 'foo': 1}]) @pytest.mark.skipif(settings.DATABASES['default']['ENGINE'] != 'django.db.backends.mysql', reason="Test can only run on mysql") @mock.patch('django_extensions.management.commands.sqldiff.MySQLDiff.format_field_names') def test_invalid_mysql_to_dict(self, format_field_names): format_field_names.side_effect = lambda x: x mysql_instance = MySQLDiff( apps.get_models(include_auto_created=True), vars(self.options), stdout=self.tmp_out, stderr=self.tmp_err, ) mysql_dict = mysql_instance.sql_to_dict("""select 1 as "foo", 1 + 1 as "BAR";""", []) self.assertNotEquals(mysql_dict, [{'bar': 2, 'foo': 1}]) @pytest.mark.skipif(settings.DATABASES['default']['ENGINE'] != 'django.db.backends.sqlite3', reason="Test can only run on sqlite3") def test_sqlite_to_dict(self): sqlite_instance = SqliteSQLDiff( apps.get_models(include_auto_created=True), vars(self.options), stdout=self.tmp_out, stderr=self.tmp_err, ) sqlite_dict = sqlite_instance.sql_to_dict("""select 1 as "foo", 1 + 1 as "BAR";""", []) self.assertEqual(sqlite_dict, [{'BAR': 2, 'foo': 1}]) @pytest.mark.skipif(settings.DATABASES['default']['ENGINE'] != 'django.db.backends.postgresql', reason="Test can only run on postgresql") def test_postgresql_to_dict(self): postgresql_instance = PostgresqlSQLDiff( apps.get_models(include_auto_created=True), vars(self.options), stdout=self.tmp_out, stderr=self.tmp_err, ) postgresql_dict = postgresql_instance.sql_to_dict("""select 1 as "foo", 1 + 1 as "BAR";""", []) self.assertEqual(postgresql_dict, [{'BAR': 2, 'foo': 1}]) # def test_sql_diff_run(self): # tmp_out = StringIO() # call_command("sqldiff", all_applications=True, migrate_for_tests=True, stdout=tmp_out) # self.assertEqual('-- No differences', tmp_out.getvalue()) # tmp_out.close() django-extensions-3.1.5/tests/test_template_rendering.py000066400000000000000000000007451414177705400236220ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django.test import TestCase from django.template import Context, Template class TemplateRenderingTests(TestCase): def setUp(self): self.ctx = Context({ 'worldvar': 'world', }) def test_simple_template(self): self.assertEqual(Template("hello world").render(self.ctx), "hello world") def test_simple_context(self): self.assertEqual(Template("hello {{ worldvar }}").render(self.ctx), "hello world") django-extensions-3.1.5/tests/test_templatetags.py000066400000000000000000000022461414177705400224420ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django.template import engines from django.test import TestCase from django_extensions.templatetags.widont import widont, widont_html from unittest.mock import MagicMock # TODO: these tests are far from having decent test coverage class TemplateTagsTests(TestCase): def test_widont(self): self.assertEqual(widont('Test Value'), 'Test Value') self.assertEqual(widont(str('Test Value')), str('Test Value')) def test_widont_html(self): self.assertEqual(widont_html('Test Value'), 'Test Value') self.assertEqual(widont_html(str('Test Value')), str('Test Value')) class DebuggerTagsTests(TestCase): """Test class for DebuggerTags.""" def setUp(self): self.engine = engines['django'] def test_pdb_filter(self): """Test for pdb filter.""" import pdb pdb.set_trace = MagicMock(return_value=None) template = self.engine.from_string( ''' {% load debugger_tags %} {{ test_object|pdb }} ''' ) template.render({'test_object': 'test_value'}) self.assertTrue(pdb.set_trace.called) django-extensions-3.1.5/tests/test_timestamped_model.py000066400000000000000000000011071414177705400234370ustar00rootroot00000000000000# -*- coding: utf-8 -*- import time from django.test import TestCase from .testapp.models import TimestampedTestModel class ModifiedFieldTest(TestCase): def test_update(self): t = TimestampedTestModel.objects.create() modified = t.modified time.sleep(1) t.save() self.assertNotEqual(modified, t.modified) def test_update_no_modified(self): t = TimestampedTestModel.objects.create() modified = t.modified time.sleep(1) t.save(update_modified=False) self.assertEqual(modified, t.modified) django-extensions-3.1.5/tests/test_validators.py000066400000000000000000000063521414177705400221220ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django.core.exceptions import ValidationError from django.test import TestCase from django_extensions.validators import NoControlCharactersValidator, NoWhitespaceValidator class NoControlCharactersValidatorTests(TestCase): """Tests for NoControlCharactersValidator.""" def test_should_raise_default_message_and_code_if_value_contains_new_line(self): self.validator = NoControlCharactersValidator() value_with_new_line = 'test\nvalue' with self.assertRaises(ValidationError) as cm: self.validator(value_with_new_line) self.assertEqual(cm.exception.message, 'Control Characters like new lines or tabs are not allowed.') self.assertEqual(cm.exception.code, 'no_control_characters') self.assertDictEqual(cm.exception.params, {'value': value_with_new_line, 'whitelist': None}) def test_should_raise_custom_message_and_code_if_value_contains_tabs(self): self.validator = NoControlCharactersValidator(message='custom message', code='custom code') value_with_tabs = 'test\tvalue' with self.assertRaises(ValidationError) as cm: self.validator(value_with_tabs) self.assertEqual(cm.exception.message, 'custom message') self.assertEqual(cm.exception.code, 'custom code') self.assertDictEqual(cm.exception.params, {'value': value_with_tabs, 'whitelist': None}) def test_should_not_raise_if_value_contains_characters_which_is_on_whitelist(self): self.validator = NoControlCharactersValidator(message='custom message', code='custom code', whitelist=['\n']) value_with_new_line = 'test\nvalue' result = self.validator(value_with_new_line) self.assertIsNone(result) class NoWhiteSpaceValidatorTests(TestCase): """Tests for NoWhitespaceValidator.""" def test_should_raise_default_message_and_code_if_value_has_leading_whitespace(self): self.validator = NoWhitespaceValidator() value_with_leading_whitespace = ' test_value' with self.assertRaises(ValidationError) as cm: self.validator(value_with_leading_whitespace) self.assertEqual(cm.exception.message, 'Leading and Trailing whitespaces are not allowed.') self.assertEqual(cm.exception.code, 'no_whitespace') self.assertDictEqual(cm.exception.params, {'value': value_with_leading_whitespace}) def test_should_raise_custom_message_and_code_if_value_has_trailing_whitespace(self): self.validator = NoWhitespaceValidator(message='custom message', code='custom code') value_with_trailing_whitespace = 'test value ' with self.assertRaises(ValidationError) as cm: self.validator(value_with_trailing_whitespace) self.assertEqual(cm.exception.message, 'custom message') self.assertEqual(cm.exception.code, 'custom code') self.assertDictEqual(cm.exception.params, {'value': value_with_trailing_whitespace}) def test_should_not_raise_if_value_doesnt_have_leading_or_trailing_whitespaces(self): self.validator = NoWhitespaceValidator() value_without_leading_or_trailing_whitespaces = 'test value' result = self.validator(value_without_leading_or_trailing_whitespaces) self.assertIsNone(result) django-extensions-3.1.5/tests/test_version.py000066400000000000000000000010051414177705400214250ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django.test import TestCase from django_extensions import get_version class DjangoExtensionsVersionTests(TestCase): def test_patch_number_is_str(self): result = get_version((1, 2, 'pre')) self.assertEqual(result, '1.2_pre') def test_patch_number_is_int(self): result = get_version((1, 2, 3)) self.assertEqual(result, '1.2.3') def test_no_patch_number(self): result = get_version((1, 2)) self.assertEqual(result, '1.2') django-extensions-3.1.5/tests/testapp/000077500000000000000000000000001414177705400200135ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp/__init__.py000066400000000000000000000002351414177705400221240ustar00rootroot00000000000000# -*- coding: utf-8 -*- import django if django.VERSION < (3, 2): default_app_config = 'tests.testapp.apps.TestAppConfig' # TODO: this is a test todo django-extensions-3.1.5/tests/testapp/admin.py000066400000000000000000000001631414177705400214550ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django.contrib import admin from . import models admin.site.register(models.Person) django-extensions-3.1.5/tests/testapp/apps.py000066400000000000000000000001671414177705400213340ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django.apps import AppConfig class TestAppConfig(AppConfig): name = 'tests.testapp' django-extensions-3.1.5/tests/testapp/classes_to_include.py000066400000000000000000000002401414177705400242230ustar00rootroot00000000000000# -*- coding: utf-8 -*- class BaseIncludedClass: pass class IncludedMixin: pass class FirstDerivedClass(BaseIncludedClass, IncludedMixin): pass django-extensions-3.1.5/tests/testapp/derived_classes_for_testing/000077500000000000000000000000001414177705400255555ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp/derived_classes_for_testing/__init__.py000066400000000000000000000002171414177705400276660ustar00rootroot00000000000000# -*- coding: utf-8 -*- from tests.testapp.classes_to_include import BaseIncludedClass class SecondDerivedClass(BaseIncludedClass): pass django-extensions-3.1.5/tests/testapp/derived_classes_for_testing/test_module.py000066400000000000000000000002661414177705400304570ustar00rootroot00000000000000# -*- coding: utf-8 -*- from tests.testapp.classes_to_include import IncludedMixin class ClassWhichShouldNotBeImported: pass class ThirdDerivedClass(IncludedMixin): pass django-extensions-3.1.5/tests/testapp/factories.py000066400000000000000000000004651414177705400223510ustar00rootroot00000000000000# -*- coding: utf-8 -*- import factory from factory.django import DjangoModelFactory from .models import Secret class SecretFactory(DjangoModelFactory): """DjangoModelFactory for object Secret.""" name = factory.Faker('name') text = factory.Faker('bs') class Meta: model = Secret django-extensions-3.1.5/tests/testapp/fields.py000066400000000000000000000006131414177705400216330ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django.db import models from django_extensions.db.fields import UniqueFieldMixin class UniqField(UniqueFieldMixin, models.CharField): def __init__(self, *args, **kwargs): self.boolean_attr = kwargs.pop('boolean_attr', False) self.non_boolean_attr = kwargs.pop('non_boolean_attr', 'non_boolean_attr') super().__init__(*args, **kwargs) django-extensions-3.1.5/tests/testapp/file_with_utf8_notes.py000066400000000000000000000001401414177705400245100ustar00rootroot00000000000000# -*- coding: utf-8 -*- # TODO: Russian text followed: Это техт на кириллице django-extensions-3.1.5/tests/testapp/fixtures/000077500000000000000000000000001414177705400216645ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp/fixtures/group.json000066400000000000000000000001551414177705400237140ustar00rootroot00000000000000[ { "model": "auth.group", "pk": 1, "fields": { "name": "Attendees", "permissions": [] } } ] django-extensions-3.1.5/tests/testapp/fixtures/user.json000066400000000000000000000024211414177705400235340ustar00rootroot00000000000000[ { "model": "auth.user", "fields": { "password": "pbkdf2_sha256$36000$90bEBUbbgBEO$L8mP32NfNI8IYyrFFUejJ1J8Mb7nWXAXfc4o40NZcVw=", "last_login": null, "is_superuser": false, "username": "Frederic", "first_name": "Fr\u00e9d\u00e9ric", "last_name": "Mistral", "email": "frederic_mistral@gmail.com", "is_staff": false, "is_active": true, "groups": [ 1 ], "user_permissions": [] } }, { "model": "auth.user", "fields": { "password": "pbkdf2_sha256$36000$DQjI1J5Sb0y7$qcHMl08/2DTYusG4s42c28EBZIzl0inoWmB3znGBLfI=", "last_login": null, "is_superuser": false, "username": "Gabriel", "first_name": "Gabriel Garcia", "last_name": "Marqu\u00e9z", "email": "gabriel_marquez@gmail.com", "is_staff": false, "is_active": true, "groups": [ 1 ], "user_permissions": [] } }, { "model": "auth.user", "fields": { "password": "pbkdf2_sha256$36000$PnIDqW2Atwye$ZKCDEzn+KsGMHrAb4Cq7MDpWVrW4smFl0MK/7Ehxp6g=", "last_login": null, "is_superuser": true, "username": "Mijail", "first_name": "Mija\u00edl", "last_name": "Bulgak\u00f3v", "email": "mijail_bulgakov@gmail.com", "is_staff": true, "is_active": true, "groups": [], "user_permissions": [] } } ] django-extensions-3.1.5/tests/testapp/jobs/000077500000000000000000000000001414177705400207505ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp/jobs/__init__.py000066400000000000000000000000001414177705400230470ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp/jobs/daily/000077500000000000000000000000001414177705400220525ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp/jobs/daily/__init__.py000066400000000000000000000000001414177705400241510ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp/jobs/daily/test_daily_job.py000066400000000000000000000003701414177705400254170ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django_extensions.management.jobs import DailyJob from unittest import mock DAILY_JOB_MOCK = mock.MagicMock() class Job(DailyJob): help = "My sample daily job." def execute(self): DAILY_JOB_MOCK() django-extensions-3.1.5/tests/testapp/jobs/hourly/000077500000000000000000000000001414177705400222725ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp/jobs/hourly/__init__.py000066400000000000000000000000001414177705400243710ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp/jobs/hourly/test_hourly_job.py000066400000000000000000000003751414177705400260640ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django_extensions.management.jobs import HourlyJob from unittest import mock HOURLY_JOB_MOCK = mock.MagicMock() class Job(HourlyJob): help = "My sample hourly job." def execute(self): HOURLY_JOB_MOCK() django-extensions-3.1.5/tests/testapp/jobs/monthly/000077500000000000000000000000001414177705400224425ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp/jobs/monthly/__init__.py000066400000000000000000000000001414177705400245410ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp/jobs/monthly/test_monthly_job.py000066400000000000000000000004021414177705400263730ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django_extensions.management.jobs import MonthlyJob from unittest import mock MONTHLY_JOB_MOCK = mock.MagicMock() class Job(MonthlyJob): help = "My sample monthly job." def execute(self): MONTHLY_JOB_MOCK() django-extensions-3.1.5/tests/testapp/jobs/sample.py000066400000000000000000000003121414177705400225770ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django_extensions.management.jobs import BaseJob class Job(BaseJob): help = "My sample job." def execute(self): # executing empty sample job pass django-extensions-3.1.5/tests/testapp/jobs/sample_job.py000066400000000000000000000003041414177705400234320ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django_extensions.management.jobs import BaseJob class Job(BaseJob): help = "My sample job." def execute(self): print("executing empty sample job") django-extensions-3.1.5/tests/testapp/jobs/weekly/000077500000000000000000000000001414177705400222505ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp/jobs/weekly/__init__.py000066400000000000000000000000001414177705400243470ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp/jobs/weekly/test_weekly_job.py000066400000000000000000000003751414177705400260200ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django_extensions.management.jobs import WeeklyJob from unittest import mock WEEKLY_JOB_MOCK = mock.MagicMock() class Job(WeeklyJob): help = "My sample weekly job." def execute(self): WEEKLY_JOB_MOCK() django-extensions-3.1.5/tests/testapp/jobs/yearly/000077500000000000000000000000001414177705400222555ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp/jobs/yearly/__init__.py000066400000000000000000000000001414177705400243540ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp/jobs/yearly/test_yearly_job.py000066400000000000000000000003751414177705400260320ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django_extensions.management.jobs import YearlyJob from unittest import mock YEARLY_JOB_MOCK = mock.MagicMock() class Job(YearlyJob): help = "My sample yearly job." def execute(self): YEARLY_JOB_MOCK() django-extensions-3.1.5/tests/testapp/management/000077500000000000000000000000001414177705400221275ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp/management/__init__.py000066400000000000000000000000001414177705400242260ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp/management/commands/000077500000000000000000000000001414177705400237305ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp/management/commands/__init__.py000066400000000000000000000000001414177705400260270ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp/management/commands/test_email_notification_command.py000066400000000000000000000004251414177705400326750ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django_extensions.management.email_notifications import EmailNotificationCommand class Command(EmailNotificationCommand): help = 'Just for email_notifications testing purpose' def handle(self, *args, **kwargs): raise Exception() django-extensions-3.1.5/tests/testapp/models.py000066400000000000000000000345121414177705400216550ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django.db import models from django.contrib.auth import get_user_model from django.db.models import UniqueConstraint from django.db.models.signals import pre_save from django_extensions.db.fields import AutoSlugField, ModificationDateTimeField, RandomCharField, ShortUUIDField from django_extensions.db.fields.json import JSONField from django_extensions.db.models import ActivatorModel, TimeStampedModel from .fields import UniqField class Secret(models.Model): name = models.CharField(blank=True, max_length=255, null=True) text = models.TextField(blank=True, null=True) class Meta: app_label = 'django_extensions' class Name(models.Model): name = models.CharField(max_length=50) class Meta: app_label = 'django_extensions' class Personality(models.Model): description = models.CharField(max_length=50) class Club(models.Model): name = models.CharField(max_length=50) created = models.DateTimeField(auto_now=True) class Note(models.Model): note = models.TextField() club = models.ForeignKey(Club, null=True, on_delete=models.CASCADE) class Meta: app_label = 'django_extensions' class Person(models.Model): name = models.ForeignKey(Name, on_delete=models.CASCADE) age = models.PositiveIntegerField() children = models.ManyToManyField('self') notes = models.ManyToManyField(Note) personality = models.OneToOneField( Personality, null=True, on_delete=models.CASCADE, ) clubs = models.ManyToManyField(Club, through='testapp.Membership') class Meta: app_label = 'django_extensions' class Membership(models.Model): person = models.ForeignKey(Person, on_delete=models.CASCADE) club = models.ForeignKey(Club, on_delete=models.CASCADE) created = models.DateTimeField(auto_now=True) class Post(ActivatorModel): title = models.CharField(max_length=255) class Meta: app_label = 'django_extensions' class PostWithTitleOrdering(Post): class Meta: proxy = True ordering = ['title'] class DummyRelationModel(models.Model): class Meta: app_label = 'django_extensions' class SecondDummyRelationModel(models.Model): class Meta: app_label = 'django_extensions' class ThirdDummyRelationModel(models.Model): class Meta: app_label = 'django_extensions' class PostWithUniqFieldCompat(models.Model): """ django-extensions from version 3.0 officially supports only Django 2.2+ which introduced Meta.constraints and suggests using it instead of Meta.unique_together this is left only to ensure compatibility with Meta.unique_together """ uniq_field = UniqField( max_length=255, boolean_attr=True, non_boolean_attr='non_boolean_attr' ) common_field = models.CharField(max_length=10) another_common_field = models.CharField(max_length=10) many_to_one_field = models.ForeignKey(DummyRelationModel, on_delete=models.CASCADE) one_to_one_field = models.OneToOneField(SecondDummyRelationModel, on_delete=models.CASCADE) many_to_many_field = models.ManyToManyField(ThirdDummyRelationModel, related_name='posts_with_uniq') class Meta: app_label = 'django_extensions' unique_together = ('common_field', 'uniq_field',) class ReverseModelCompat(models.Model): post_field = models.ForeignKey(PostWithUniqFieldCompat, related_name='reverse_models', on_delete=models.CASCADE) class Meta: app_label = 'django_extensions' class InheritedFromPostWithUniqFieldCompat(PostWithUniqFieldCompat): new_field = models.CharField(max_length=10) class Meta: app_label = 'django_extensions' class PostWithUniqField(models.Model): uniq_field = UniqField( max_length=255, boolean_attr=True, non_boolean_attr='non_boolean_attr' ) common_field = models.CharField(max_length=10) another_common_field = models.CharField(max_length=10) many_to_one_field = models.ForeignKey(DummyRelationModel, on_delete=models.CASCADE) one_to_one_field = models.OneToOneField(SecondDummyRelationModel, on_delete=models.CASCADE) many_to_many_field = models.ManyToManyField(ThirdDummyRelationModel, related_name='posts22_with_uniq') class Meta: app_label = 'django_extensions' constraints = [ models.UniqueConstraint(fields=('common_field', 'uniq_field'), name='unique_common_uniq_pair'), models.CheckConstraint(check=~models.Q(common_field=models.F("another_common_field")), name='common_and_another_common_differ'), ] class ReverseModel(models.Model): post_field = models.ForeignKey(PostWithUniqField, related_name='reverse_models', on_delete=models.CASCADE) class Meta: app_label = 'django_extensions' class InheritedFromPostWithUniqField(PostWithUniqField): new_field = models.CharField(max_length=10) class Meta: app_label = 'django_extensions' class AbstractInheritanceTestModelParent(models.Model): my_field_that_my_child_will_inherit = models.BooleanField() class Meta: app_label = 'django_extensions' abstract = True class AbstractInheritanceTestModelChild(AbstractInheritanceTestModelParent): class Meta: app_label = 'django_extensions' class SluggedTestModel(models.Model): title = models.CharField(max_length=42) slug = AutoSlugField(populate_from='title') class Meta: app_label = 'django_extensions' class SluggedWithConstraintsTestModel(models.Model): title = models.CharField(max_length=42) slug = AutoSlugField(populate_from='title') category = models.CharField(max_length=20, null=True) class Meta: app_label = 'django_extensions' constraints = [ UniqueConstraint( fields=['slug', 'category'], name="unique_slug_and_category", ), ] class SluggedWithUniqueTogetherTestModel(models.Model): title = models.CharField(max_length=42) slug = AutoSlugField(populate_from='title') category = models.CharField(max_length=20, null=True) class Meta: app_label = 'django_extensions' unique_together = ['slug', 'category'] class OverridedFindUniqueAutoSlugField(AutoSlugField): def find_unique(self, model_instance, field, iterator, *args): self.overrided = True return super().find_unique(model_instance, field, iterator, *args) class OverridedFindUniqueModel(models.Model): title = models.CharField(max_length=42) slug = OverridedFindUniqueAutoSlugField(populate_from='title') class CustomFuncSluggedTestModel(models.Model): def slugify_function(self, content): return content.upper() title = models.CharField(max_length=42) slug = AutoSlugField(populate_from='title') class Meta: app_label = 'django_extensions' class CustomFuncPrecedenceSluggedTestModel(models.Model): def custom_slug_one(self, content): return content.upper() def custom_slug_two(content): return content.lower() slugify_function = custom_slug_one title = models.CharField(max_length=42) slug = AutoSlugField(populate_from='title', slugify_function=custom_slug_two) class Meta: app_label = 'django_extensions' class ChildSluggedTestModel(SluggedTestModel): class Meta: app_label = 'django_extensions' class SluggedTestNoOverwriteOnAddModel(models.Model): title = models.CharField(max_length=42) slug = AutoSlugField(populate_from='title', overwrite_on_add=False) class Meta: app_label = 'django_extensions' def get_readable_title(instance): return "The title is {}".format(instance.title) class ModelMethodSluggedTestModel(models.Model): title = models.CharField(max_length=42) slug = AutoSlugField(populate_from='get_readable_title') class Meta: app_label = 'django_extensions' def get_readable_title(self): return get_readable_title(self) class FunctionSluggedTestModel(models.Model): title = models.CharField(max_length=42) slug = AutoSlugField(populate_from=get_readable_title) class Meta: app_label = 'django_extensions' class FKSluggedTestModel(models.Model): related_field = models.ForeignKey(SluggedTestModel, on_delete=models.CASCADE) slug = AutoSlugField(populate_from="related_field__title") class Meta: app_label = 'django_extensions' class FKSluggedTestModelCallable(models.Model): related_field = models.ForeignKey(ModelMethodSluggedTestModel, on_delete=models.CASCADE) slug = AutoSlugField(populate_from="related_field__get_readable_title") class Meta: app_label = 'django_extensions' class JSONFieldTestModel(models.Model): a = models.IntegerField() j_field = JSONField() class Meta: app_label = 'django_extensions' class ShortUUIDTestModel_field(models.Model): a = models.IntegerField() uuid_field = ShortUUIDField() class Meta: app_label = 'django_extensions' class ShortUUIDTestModel_pk(models.Model): uuid_field = ShortUUIDField(primary_key=True) class Meta: app_label = 'django_extensions' class ShortUUIDTestAgregateModel(ShortUUIDTestModel_pk): a = models.IntegerField() class Meta: app_label = 'django_extensions' class ShortUUIDTestManyToManyModel(ShortUUIDTestModel_pk): many = models.ManyToManyField(ShortUUIDTestModel_field) class Meta: app_label = 'django_extensions' class RandomCharTestModel(models.Model): random_char_field = RandomCharField(length=8, unique=False) class Meta: app_label = 'django_extensions' class RandomCharTestModelUnique(models.Model): random_char_field = RandomCharField(length=8, unique=True) class Meta: app_label = 'django_extensions' class RandomCharTestModelAlphaDigits(models.Model): random_char_field = RandomCharField(length=8, unique=True) class Meta: app_label = 'django_extensions' class RandomCharTestModelLowercaseAlphaDigits(models.Model): random_char_field = RandomCharField(length=8, lowercase=True) class Meta: app_label = 'django_extensions' verbose_name = 'lowercase alpha digits' class RandomCharTestModelUppercaseAlphaDigits(models.Model): random_char_field = RandomCharField(length=8, uppercase=True) class Meta: app_label = 'django_extensions' verbose_name = 'uppercase alpha digits' class RandomCharTestModelLowercase(models.Model): random_char_field = RandomCharField(length=8, lowercase=True, include_digits=False) class Meta: app_label = 'django_extensions' class RandomCharTestModelUppercase(models.Model): random_char_field = RandomCharField(length=8, uppercase=True, include_digits=False) class Meta: app_label = 'django_extensions' class RandomCharTestModelAlpha(models.Model): random_char_field = RandomCharField(length=8, include_digits=False) class Meta: app_label = 'django_extensions' class RandomCharTestModelDigits(models.Model): random_char_field = RandomCharField(length=8, include_alpha=False) class Meta: app_label = 'django_extensions' class RandomCharTestModelPunctuation(models.Model): random_char_field = RandomCharField( length=8, include_punctuation=True, include_digits=False, include_alpha=False, ) class Meta: app_label = 'django_extensions' class RandomCharTestModelUniqueTogether(models.Model): random_char_field = RandomCharField(length=8) common_field = models.CharField(max_length=10) class Meta: app_label = 'django_extensions' unique_together = ('random_char_field', 'common_field') class TimestampedTestModel(TimeStampedModel): class Meta: app_label = 'django_extensions' class UnicodeVerboseNameModel(models.Model): cafe = models.IntegerField(verbose_name=u'café') parent_cafe = models.ForeignKey( 'self', related_name='children', on_delete=models.CASCADE, verbose_name='café latte', ) class Meta: app_label = 'django_extensions' verbose_name = u'café unicode model' class Permission(models.Model): text = models.CharField(max_length=32) person = models.ForeignKey(Person, on_delete=models.CASCADE) class UniqueTestAppModel(models.Model): global_id = models.CharField(max_length=32, unique=True) class SqlDiff(models.Model): number = models.CharField(max_length=40, null=True, verbose_name='Chargennummer') creator = models.CharField(max_length=20, null=True, blank=True) class SqlDiffUniqueTogether(models.Model): aaa = models.CharField(max_length=20) bbb = models.CharField(max_length=20) class Meta: unique_together = ['aaa', 'bbb'] class Photo(models.Model): photo = models.FileField() class CustomModelModificationDateTimeField(models.Model): field_to_update = models.BooleanField(default=True) custom_modified = ModificationDateTimeField() class Meta: app_label = 'django_extensions' class ModelModificationDateTimeField(models.Model): field_to_update = models.BooleanField(default=True) modified = ModificationDateTimeField() class Meta: app_label = 'django_extensions' class DisabledUpdateModelModificationDateTimeField(models.Model): field_to_update = models.BooleanField(default=True) modified = ModificationDateTimeField() update_modified = False class Meta: app_label = 'django_extensions' class HasOwnerModel(models.Model): content = models.TextField(default="") owner = models.ForeignKey(get_user_model(), on_delete=models.CASCADE) class Meta: app_label = 'django_extensions' class MultipleFieldsAndMethods(models.Model): char_field = models.CharField(max_length=10) integer_field = models.IntegerField() foreign_key_field = models.ForeignKey(get_user_model(), on_delete=models.CASCADE) def has_self_only(self): pass def has_one_extra_argument(self, arg_one): pass def has_two_extra_arguments(self, arg_one, arg_two): pass def has_args_kwargs(self, *args, **kwargs): pass def has_defaults(self, one=1, two='Two', true=True, false=False, none=None): pass class Meta: app_label = 'django_extensions' def dummy_handler(sender, instance, **kwargs): pass pre_save.connect(dummy_handler, sender=HasOwnerModel) django-extensions-3.1.5/tests/testapp/scripts/000077500000000000000000000000001414177705400215025ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp/scripts/__init__.py000066400000000000000000000000001414177705400236010ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp/scripts/directory_checker_script.py000066400000000000000000000003041414177705400271250ustar00rootroot00000000000000# -*- coding: utf-8 -*- import os def run(*args): print('Script called from: %s' % os.getcwd()) os.chdir(os.path.dirname(os.getcwd())) # to test is NONE option freezing start directory django-extensions-3.1.5/tests/testapp/scripts/error_script.py000066400000000000000000000000671414177705400245740ustar00rootroot00000000000000# -*- coding: utf-8 -*- def run(): raise Exception django-extensions-3.1.5/tests/testapp/scripts/invalid_import_script.py000066400000000000000000000001141414177705400264540ustar00rootroot00000000000000# -*- coding: utf-8 -*- import invalidpackage # NOQA def run(): pass django-extensions-3.1.5/tests/testapp/scripts/sample_script.py000066400000000000000000000000541414177705400247200ustar00rootroot00000000000000# -*- coding: utf-8 -*- def run(): pass django-extensions-3.1.5/tests/testapp/scripts/script_no_run_function.py000066400000000000000000000000001414177705400266330ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp/settings.py000066400000000000000000000056731414177705400222400ustar00rootroot00000000000000# -*- coding: utf-8 -*- import os SECRET_KEY = 'dummy' TEST_RUNNER = 'tests.runner.PytestTestRunner' INSTALLED_APPS = [ 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.admin', 'django.contrib.sessions', 'django.contrib.staticfiles', 'django.contrib.messages', 'django.contrib.sites', 'tests.collisions', 'tests.testapp', 'tests.testapp_with_no_models_file', 'tests.testapp_with_appconfig.apps.TestappWithAppConfigConfig', 'django_extensions', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] DATABASES = { 'default': { 'ENGINE': os.environ.get('DJANGO_EXTENSIONS_DATABASE_ENGINE', 'django.db.backends.sqlite3'), 'NAME': os.environ.get('DJANGO_EXTENSIONS_DATABASE_NAME', ':memory:'), 'USER': os.environ.get("DJANGO_EXTENSIONS_DATABASE_USER"), 'PASSWORD': os.environ.get("DJANGO_EXTENSIONS_DATABASE_PASSWORD"), 'HOST': os.environ.get('DJANGO_EXTENSIONS_DATABASE_HOST'), 'PORT': os.environ.get('DJANGO_EXTENSIONS_DATABASE_PORT'), } } SITE_ID = 1 MEDIA_ROOT = '/tmp/django_extensions_test_media/' MEDIA_PATH = '/media/' ROOT_URLCONF = 'tests.testapp.urls' DEBUG = True TEMPLATE_DEBUG = True TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'debug': TEMPLATE_DEBUG, 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) STATIC_URL = "/static/" SHELL_PLUS_SUBCLASSES_IMPORT_MODULES_BLACKLIST = [ 'django_extensions.mongodb.fields', 'django_extensions.mongodb.models', 'tests.testapp.scripts.invalid_import_script', 'setup', ] CACHES = { 'default': { 'BACKEND': 'tests.management.commands.test_clear_cache.DefaultCacheMock', }, 'other': { 'BACKEND': 'tests.management.commands.test_clear_cache.OtherCacheMock', }, } SHELL_PLUS_PRE_IMPORTS = [ 'import sys, os', ] SHELL_PLUS_IMPORTS = [ 'from django_extensions import settings as django_extensions_settings', ] SHELL_PLUS_POST_IMPORTS = [ 'import traceback', 'import pprint', 'import os as test_os', 'from django_extensions.utils import *', ] SILENCED_SYSTEM_CHECKS = ["models.W027", "models.W042"] django-extensions-3.1.5/tests/testapp/templates/000077500000000000000000000000001414177705400220115ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp/templates/hello_world.html000077700000000000000000000000001414177705400346742../templates_symlinked/hello_world.htmlustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp/templates/valid_template.html000066400000000000000000000000711414177705400256670ustar00rootroot00000000000000{% load i18n %} {% trans "myvar" noop %} django-extensions-3.1.5/tests/testapp/templates_symlinked/000077500000000000000000000000001414177705400240705ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp/templates_symlinked/hello_world.html000066400000000000000000000000231414177705400272630ustar00rootroot00000000000000

Hello World

django-extensions-3.1.5/tests/testapp/templatetags/000077500000000000000000000000001414177705400225055ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp/templatetags/__init__.py000066400000000000000000000000001414177705400246040ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp/templatetags/dummy_tags.py000066400000000000000000000002221414177705400252240ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django import template register = template.Library() @register.simple_tag def dummy_tag(): return "dummy_tag" django-extensions-3.1.5/tests/testapp/urls.py000066400000000000000000000022261414177705400213540ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/1.9/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') Including another URLconf 1. Import the include() function: from django.conf.urls import url, include 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) """ from django.conf.urls import url from django.contrib import admin from django.contrib.auth import views as auth_views login_view = auth_views.LoginView.as_view() if hasattr(auth_views, 'LoginView') else auth_views.login logout_view = auth_views.LogoutView.as_view() if hasattr(auth_views, 'LogoutView') else auth_views.logout urlpatterns = [ url(r'^login/$', login_view, {'template_name': 'login.html'}, name="login"), url(r'^logout/$', logout_view, name="logout"), url(r'^admin/', admin.site.urls), ] django-extensions-3.1.5/tests/testapp_with_appconfig/000077500000000000000000000000001414177705400230745ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp_with_appconfig/__init__.py000066400000000000000000000000001414177705400251730ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp_with_appconfig/admin.py000066400000000000000000000001311414177705400245310ustar00rootroot00000000000000# -*- coding: utf-8 -*- # from django.contrib import admin # Register your models here. django-extensions-3.1.5/tests/testapp_with_appconfig/apps.py000066400000000000000000000002231414177705400244060ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django.apps import AppConfig class TestappWithAppConfigConfig(AppConfig): name = 'tests.testapp_with_appconfig' django-extensions-3.1.5/tests/testapp_with_appconfig/migrations/000077500000000000000000000000001414177705400252505ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp_with_appconfig/migrations/__init__.py000066400000000000000000000000001414177705400273470ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp_with_appconfig/models.py000066400000000000000000000001231414177705400247250ustar00rootroot00000000000000# -*- coding: utf-8 -*- # from django.db import models # Create your models here. django-extensions-3.1.5/tests/testapp_with_appconfig/templatetags/000077500000000000000000000000001414177705400255665ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp_with_appconfig/templatetags/__init__.py000066400000000000000000000000001414177705400276650ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp_with_appconfig/templatetags/dummy_tags_appconfig.py000066400000000000000000000002461414177705400323410ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django import template register = template.Library() @register.simple_tag def dummy_tag_appconfig(): return "dummy_tag_appconfig" django-extensions-3.1.5/tests/testapp_with_appconfig/tests.py000066400000000000000000000001261414177705400246070ustar00rootroot00000000000000# -*- coding: utf-8 -*- # from django.test import TestCase # Create your tests here. django-extensions-3.1.5/tests/testapp_with_appconfig/views.py000066400000000000000000000001311414177705400245760ustar00rootroot00000000000000# -*- coding: utf-8 -*- # from django.shortcuts import render # Create your views here. django-extensions-3.1.5/tests/testapp_with_no_models_file/000077500000000000000000000000001414177705400241045ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp_with_no_models_file/__init__.py000066400000000000000000000000001414177705400262030ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp_with_no_models_file/scripts/000077500000000000000000000000001414177705400255735ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp_with_no_models_file/scripts/__init__.py000066400000000000000000000000001414177705400276720ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp_with_no_models_file/scripts/other_directory_checker_script.py000066400000000000000000000001461414177705400344230ustar00rootroot00000000000000# -*- coding: utf-8 -*- import os def run(*args): print('Script called from: %s' % os.getcwd()) django-extensions-3.1.5/tests/testapp_with_no_models_file/teslacar.py000066400000000000000000000002701414177705400262530ustar00rootroot00000000000000# -*- coding: utf-8 -*- # An app without a models.py file for issue #936 from django.db import models class TeslaCar(models.Model): sentient = models.BooleanField(default=False) django-extensions-3.1.5/tests/testapp_with_template_errors/000077500000000000000000000000001414177705400243355ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp_with_template_errors/__init__.py000066400000000000000000000000001414177705400264340ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp_with_template_errors/templates/000077500000000000000000000000001414177705400263335ustar00rootroot00000000000000django-extensions-3.1.5/tests/testapp_with_template_errors/templates/template_with_error.html000066400000000000000000000000221414177705400332720ustar00rootroot00000000000000{% invalid_tag %} django-extensions-3.1.5/tox.ini000066400000000000000000000046461414177705400165160ustar00rootroot00000000000000# Tox (http://tox.testrun.org/) is a tool for running tests # in multiple virtualenvs. This configuration file will run the # test suite on all supported python versions. To use it, "pip install tox" # and then run "tox" from this directory. [tox] envlist = precommit safety mypy {py37,py38,py39,py310}-flake8 {py36,py37,py38,py39,py310,pypy}-dj22 {py36,py37,py38,py39,py310,pypy}-dj30 {py36,py37,py38,py39,py310,pypy}-dj31 {py36,py37,py38,py39,py310,pypy}-dj32 {py38,py39,py310,pypy}-djmaster py310-dj32-postgres py310-dj32-mysql py310-djmaster-postgres [testenv] commands = make test whitelist_externals = make passenv = DJANGO_EXTENSIONS_DATABASE_ENGINE DJANGO_EXTENSIONS_DATABASE_NAME DJANGO_EXTENSIONS_DATABASE_USER DJANGO_EXTENSIONS_DATABASE_PASSWORD DJANGO_EXTENSIONS_DATABASE_HOST DJANGO_EXTENSIONS_DATABASE_PORT setenv = postgres: DJANGO_EXTENSIONS_DATABASE_ENGINE = {env:DJANGO_EXTENSIONS_DATABASE_ENGINE:django.db.backends.postgresql} postgres: DJANGO_EXTENSIONS_DATABASE_NAME = {env:DJANGO_EXTENSIONS_DATABASE_NAME:django_extensions_test} mysql: DJANGO_EXTENSIONS_DATABASE_ENGINE = {env:DJANGO_EXTENSIONS_DATABASE_ENGINE:django.db.backends.mysql} mysql: DJANGO_EXTENSIONS_DATABASE_NAME = {env:DJANGO_EXTENSIONS_DATABASE_NAME:django_extensions_test} deps = pip >= 21.1 -rrequirements-dev.txt dj22: Django==2.2 dj30: Django>=3.0,<3.1 dj31: Django>=3.1,<3.2 dj32: Django>=3.2,<4.0 djmaster: https://github.com/django/django/archive/refs/heads/main.zip postgres: psycopg2-binary mysql: mysqlclient [testenv:precommit] deps = pip >= 21.1 pre-commit commands = pre-commit run -a [testenv:safety] deps = pip >= 21.1 safety commands = safety check --full-report [testenv:mypy] deps = pip >= 21.1 mypy -rrequirements-dev.txt commands = mypy --ignore-missing-imports django_extensions [testenv:compile-catalog] whitelist_externals = make deps = pip >= 21.1 Babel commands = make compile-catalog [testenv:py37-flake8] deps = pip >= 21.1 flake8 commands = flake8 django_extensions tests [testenv:py38-flake8] deps = pip >= 21.1 flake8 commands = flake8 django_extensions tests [testenv:py39-flake8] deps = pip >= 21.1 flake8 commands = flake8 django_extensions tests [testenv:py310-flake8] deps = pip >= 21.1 flake8 commands = flake8 django_extensions tests