├── .coveragerc ├── .editorconfig ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── AUTHORS.rst ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── bar ├── __init__.py ├── admin.py ├── apps.py └── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20170511_0515.py │ └── __init__.py ├── foo ├── __init__.py ├── admin.py ├── apps.py └── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20170511_0514.py │ ├── 0003_auto_20170511_0514.py │ └── __init__.py ├── manage.py ├── migraph ├── __init__.py ├── apps.py └── management │ ├── __init__.py │ └── commands │ ├── __init__.py │ └── migration_dependencies.py ├── requirements.txt ├── requirements_dev.txt ├── requirements_test.txt ├── runtests.py ├── screenshot.png ├── setup.cfg ├── setup.py ├── tests ├── __init__.py ├── settings.py └── test_migration_dependencies_command.py └── tox.ini /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = true 3 | 4 | [report] 5 | omit = 6 | */apps.py 7 | *site-packages* 8 | *tests* 9 | *.tox* 10 | show_missing = True 11 | exclude_lines = 12 | raise NotImplementedError 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.{py,rst,ini}] 12 | indent_style = space 13 | indent_size = 4 14 | 15 | [*.{html,css,scss,json,yml}] 16 | indent_style = space 17 | indent_size = 2 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | 22 | [Makefile] 23 | indent_style = tab 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * django-migrations-graph version: 2 | * Django version: 3 | * Python version: 4 | * Operating System: 5 | 6 | ### Description 7 | 8 | Describe what you were trying to get done. 9 | Tell us what happened, what went wrong, and what you expected to happen. 10 | 11 | ### What I Did 12 | 13 | ``` 14 | Paste the command(s) you ran and the output. 15 | If there was a crash, please include the traceback here. 16 | ``` 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | __pycache__ 3 | 4 | # C extensions 5 | *.so 6 | 7 | # Packages 8 | *.egg 9 | *.egg-info 10 | dist 11 | build 12 | eggs 13 | parts 14 | bin 15 | var 16 | sdist 17 | develop-eggs 18 | .installed.cfg 19 | lib 20 | lib64 21 | 22 | # Installer logs 23 | pip-log.txt 24 | 25 | # Unit test / coverage reports 26 | .coverage 27 | .tox 28 | nosetests.xml 29 | htmlcov 30 | 31 | # Translations 32 | *.mo 33 | 34 | # Mr Developer 35 | .mr.developer.cfg 36 | .project 37 | .pydevproject 38 | 39 | # Pycharm/Intellij 40 | .idea 41 | 42 | # Complexity 43 | output/*.html 44 | output/*/index.html 45 | *.swp 46 | venv 47 | .DS_Store 48 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Config file for automatic testing at travis-ci.org 2 | 3 | language: python 4 | 5 | python: 6 | - "3.5" 7 | 8 | env: 9 | - TOX_ENV=py35-django-18 10 | - TOX_ENV=py34-django-18 11 | - TOX_ENV=py27-django-18 12 | - TOX_ENV=py35-django-19 13 | - TOX_ENV=py34-django-19 14 | - TOX_ENV=py27-django-19 15 | - TOX_ENV=py35-django-110 16 | - TOX_ENV=py34-django-110 17 | - TOX_ENV=py27-django-110 18 | - TOX_ENV=py35-django-111 19 | - TOX_ENV=py34-django-111 20 | - TOX_ENV=py27-django-111 21 | 22 | matrix: 23 | fast_finish: true 24 | 25 | # command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors 26 | install: pip install -r requirements_test.txt 27 | 28 | # command to run tests using coverage, e.g. python setup.py test 29 | script: tox -e $TOX_ENV 30 | 31 | after_success: 32 | - codecov -e TOX_ENV 33 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Credits 3 | ======= 4 | 5 | Development Lead 6 | ---------------- 7 | 8 | * Iurii Shikanov 9 | 10 | Contributors 11 | ------------ 12 | 13 | None yet. Why not be the first? 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | MIT License 3 | 4 | Copyright (c) 2017, Iurii Shikanov 5 | 6 | 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: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | 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. 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS.rst 2 | include LICENSE 3 | include README.rst 4 | recursive-include migraph *.html *.png *.gif *js *.css *jpg *jpeg *svg *py 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean-pyc clean-build help 2 | .DEFAULT_GOAL := help 3 | define BROWSER_PYSCRIPT 4 | import os, webbrowser, sys 5 | try: 6 | from urllib import pathname2url 7 | except: 8 | from urllib.request import pathname2url 9 | 10 | webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) 11 | endef 12 | export BROWSER_PYSCRIPT 13 | BROWSER := python -c "$$BROWSER_PYSCRIPT" 14 | 15 | help: 16 | @perl -nle'print $& if m{^[a-zA-Z_-]+:.*?## .*$$}' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-25s\033[0m %s\n", $$1, $$2}' 17 | 18 | clean: clean-build clean-pyc 19 | 20 | clean-build: ## remove build artifacts 21 | rm -fr build/ 22 | rm -fr dist/ 23 | rm -fr *.egg-info 24 | 25 | clean-pyc: ## remove Python file artifacts 26 | find . -name '*.pyc' -exec rm -f {} + 27 | find . -name '*.pyo' -exec rm -f {} + 28 | find . -name '*~' -exec rm -f {} + 29 | 30 | test: ## run tests quickly with the default Python 31 | python runtests.py tests 32 | 33 | test-all: ## run tests on every Python version with tox 34 | tox 35 | 36 | coverage: ## check code coverage quickly with the default Python 37 | coverage run --source migraph runtests.py tests 38 | coverage report -m 39 | coverage html 40 | open htmlcov/index.html 41 | 42 | release: clean ## package and upload a release 43 | python setup.py sdist upload 44 | python setup.py bdist_wheel upload 45 | 46 | sdist: clean ## package 47 | python setup.py sdist 48 | ls -l dist 49 | 50 | pep8: 51 | pep8 --exclude=*migrations*,*settings_local.py*,venv/* --max-line-length=119 --show-source migraph tests 52 | 53 | pyflakes: 54 | pylama --skip=*migrations*,venv/* -l pyflakes migraph tests 55 | 56 | lint: pep8 pyflakes 57 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ============================= 2 | django-migrations-graph 3 | ============================= 4 | 5 | .. image:: https://badge.fury.io/py/django-migrations-graph.svg 6 | :target: https://badge.fury.io/py/django-migrations-graph 7 | 8 | .. image:: https://travis-ci.org/dizballanze/django-migrations-graph.svg?branch=master 9 | :target: https://travis-ci.org/dizballanze/django-migrations-graph 10 | 11 | .. image:: https://codecov.io/gh/dizballanze/django-migrations-graph/branch/master/graph/badge.svg 12 | :target: https://codecov.io/gh/dizballanze/django-migrations-graph 13 | 14 | .. image:: https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg 15 | :target: https://saythanks.io/to/dizballanze 16 | 17 | Django-admin command to display migrations with dependencies. 18 | 19 | Documentation 20 | ------------- 21 | 22 | Requirements 23 | ----------- 24 | 25 | - Python 2.7, 3.4+ 26 | - Django 1.8+ 27 | 28 | Quickstart 29 | ---------- 30 | 31 | Install django-migrations-graph:: 32 | 33 | pip install django-migrations-graph 34 | 35 | Add it to your `INSTALLED_APPS`: 36 | 37 | .. code-block:: python 38 | 39 | INSTALLED_APPS = ( 40 | ... 41 | 'migraph', 42 | ... 43 | ) 44 | 45 | Screenshot 46 | -------- 47 | 48 | .. image:: screenshot.png 49 | 50 | Running Tests 51 | ------------- 52 | 53 | Does the code actually work? 54 | 55 | :: 56 | 57 | source /bin/activate 58 | (myenv) $ pip install tox 59 | (myenv) $ tox 60 | 61 | Credits 62 | ------- 63 | 64 | Tools used in rendering this package: 65 | 66 | * Cookiecutter_ 67 | * `cookiecutter-djangopackage`_ 68 | 69 | .. _Cookiecutter: https://github.com/audreyr/cookiecutter 70 | .. _`cookiecutter-djangopackage`: https://github.com/pydanny/cookiecutter-djangopackage 71 | 72 | License 73 | ----- 74 | 75 | MIT 76 | -------------------------------------------------------------------------------- /bar/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dizballanze/django-migrations-graph/2b5f75016c093cdac1d6cc9e7fb24fb0f343607e/bar/__init__.py -------------------------------------------------------------------------------- /bar/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /bar/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class BarConfig(AppConfig): 5 | name = 'bar' 6 | -------------------------------------------------------------------------------- /bar/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.1 on 2017-05-11 10:15 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('foo', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | ] 16 | -------------------------------------------------------------------------------- /bar/migrations/0002_auto_20170511_0515.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.1 on 2017-05-11 10:15 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('bar', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | ] 16 | -------------------------------------------------------------------------------- /bar/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dizballanze/django-migrations-graph/2b5f75016c093cdac1d6cc9e7fb24fb0f343607e/bar/migrations/__init__.py -------------------------------------------------------------------------------- /foo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dizballanze/django-migrations-graph/2b5f75016c093cdac1d6cc9e7fb24fb0f343607e/foo/__init__.py -------------------------------------------------------------------------------- /foo/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /foo/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class FooConfig(AppConfig): 5 | name = 'foo' 6 | -------------------------------------------------------------------------------- /foo/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.1 on 2017-05-11 10:14 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | ] 15 | -------------------------------------------------------------------------------- /foo/migrations/0002_auto_20170511_0514.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.1 on 2017-05-11 10:14 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('foo', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | ] 16 | -------------------------------------------------------------------------------- /foo/migrations/0003_auto_20170511_0514.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.1 on 2017-05-11 10:14 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('foo', '0002_auto_20170511_0514'), 12 | ('bar', '0002_auto_20170511_0515'), 13 | ] 14 | 15 | operations = [ 16 | ] 17 | -------------------------------------------------------------------------------- /foo/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dizballanze/django-migrations-graph/2b5f75016c093cdac1d6cc9e7fb24fb0f343607e/foo/migrations/__init__.py -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from __future__ import unicode_literals, absolute_import 4 | 5 | import os 6 | import sys 7 | 8 | if __name__ == "__main__": 9 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tests.settings") 10 | from django.core.management import execute_from_command_line 11 | 12 | execute_from_command_line(sys.argv) 13 | -------------------------------------------------------------------------------- /migraph/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.1.1' 2 | -------------------------------------------------------------------------------- /migraph/apps.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 2 | from django.apps import AppConfig 3 | 4 | 5 | class MigraphConfig(AppConfig): 6 | name = 'migraph' 7 | -------------------------------------------------------------------------------- /migraph/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dizballanze/django-migrations-graph/2b5f75016c093cdac1d6cc9e7fb24fb0f343607e/migraph/management/__init__.py -------------------------------------------------------------------------------- /migraph/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dizballanze/django-migrations-graph/2b5f75016c093cdac1d6cc9e7fb24fb0f343607e/migraph/management/commands/__init__.py -------------------------------------------------------------------------------- /migraph/management/commands/migration_dependencies.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import AppCommand 2 | from django.db.migrations.loader import MigrationLoader 3 | 4 | 5 | class Command(AppCommand): 6 | 7 | help = "Show migrations with dependencies for provided applications " 8 | 9 | def handle(self, *apps, **options): 10 | self.loader = MigrationLoader(None) 11 | for app in apps: 12 | self._print_success("[{}]".format(app)) 13 | self._print_app_migrations_graph(app) 14 | if app != apps[-1]: 15 | self.stdout.write('\n') 16 | 17 | def _print_app_migrations_graph(self, app): 18 | try: 19 | root_key = self.loader.graph.root_nodes(app)[0] 20 | except IndexError: 21 | self._print_error("Migrations for `{}` application were not found".format(app)) 22 | return 23 | root_node = self.loader.graph.node_map[root_key] 24 | nodes_to_process = [root_node] 25 | processed = [] 26 | while nodes_to_process: 27 | curr_node = nodes_to_process.pop(0) 28 | processed.append(curr_node) 29 | self._print_node(curr_node) 30 | depending_nodes = [] 31 | for child in curr_node.children: 32 | if (child.key[0] == curr_node.key[0]) and (child not in nodes_to_process) and (child not in processed): 33 | nodes_to_process.append(child) 34 | elif child.key[0] != curr_node.key[0]: 35 | depending_nodes.append(child) 36 | self._print_depending_nodes(depending_nodes) 37 | self._print_depends_on(curr_node, self.loader.graph.nodes[curr_node.key]) 38 | 39 | def _print_node(self, node): 40 | self._print_label("{}/{}".format(*node.key)) 41 | 42 | def _print_depending_nodes(self, depending_nodes): 43 | if depending_nodes: 44 | self._print_title("\tDepending:") 45 | for depending in depending_nodes: 46 | self._print_warn("\t\t{}/{}".format(*depending.key)) 47 | 48 | def _print_depends_on(self, node, migration): 49 | deps = [dep for dep in migration.dependencies if dep[0] != node.key[0]] 50 | if deps: 51 | self._print_title("\tDepends on:") 52 | for dep in deps: 53 | self._print_notice("\t\t{}/{}".format(*dep)) 54 | 55 | def _print_styled(self, style, text): 56 | self.stdout.write(style(text) + '\n') 57 | 58 | def _print_label(self, text): 59 | self._print_styled(self.style.MIGRATE_LABEL, text) 60 | 61 | def _print_warn(self, text): 62 | self._print_styled(self.style.WARNING, text) 63 | 64 | def _print_title(self, text): 65 | self._print_styled(self.style.MIGRATE_HEADING, text) 66 | 67 | def _print_notice(self, text): 68 | self._print_styled(self.style.NOTICE, text) 69 | 70 | def _print_error(self, text): 71 | self._print_styled(self.style.ERROR, text) 72 | 73 | def _print_success(self, text): 74 | try: 75 | style = self.style.SUCCESS 76 | except AttributeError: 77 | style = self.style.MIGRATE_SUCCESS 78 | self._print_styled(self.style.ERROR, text) 79 | 80 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django>=1.7 2 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | bumpversion==0.5.3 2 | wheel==0.29.0 3 | Django>=1.7 4 | pep8==1.7.0 5 | pylama==7.0.7 6 | -------------------------------------------------------------------------------- /requirements_test.txt: -------------------------------------------------------------------------------- 1 | coverage==4.3.4 2 | mock>=1.0.1 3 | flake8>=2.1.0 4 | tox>=1.7.0 5 | codecov>=2.0.0 6 | -------------------------------------------------------------------------------- /runtests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 3 | from __future__ import unicode_literals, absolute_import 4 | 5 | import os 6 | import sys 7 | 8 | import django 9 | from django.conf import settings 10 | from django.test.utils import get_runner 11 | 12 | 13 | def run_tests(*test_args): 14 | if not test_args: 15 | test_args = ['tests'] 16 | 17 | os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.settings' 18 | django.setup() 19 | TestRunner = get_runner(settings) 20 | test_runner = TestRunner() 21 | failures = test_runner.run_tests(test_args) 22 | sys.exit(bool(failures)) 23 | 24 | 25 | if __name__ == '__main__': 26 | run_tests(*sys.argv[1:]) 27 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dizballanze/django-migrations-graph/2b5f75016c093cdac1d6cc9e7fb24fb0f343607e/screenshot.png -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.1.0 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:setup.py] 7 | 8 | [bumpversion:file:migraph/__init__.py] 9 | 10 | [wheel] 11 | universal = 1 12 | 13 | [flake8] 14 | ignore = D203 15 | exclude = 16 | migraph/migrations, 17 | .git, 18 | .tox, 19 | build, 20 | dist 21 | max-line-length = 119 22 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import os 4 | import re 5 | import sys 6 | 7 | try: 8 | from setuptools import setup 9 | except ImportError: 10 | from distutils.core import setup 11 | 12 | 13 | def get_version(*file_paths): 14 | """Retrieves the version from migraph/__init__.py""" 15 | filename = os.path.join(os.path.dirname(__file__), *file_paths) 16 | version_file = open(filename).read() 17 | version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", 18 | version_file, re.M) 19 | if version_match: 20 | return version_match.group(1) 21 | raise RuntimeError('Unable to find version string.') 22 | 23 | 24 | version = get_version("migraph", "__init__.py") 25 | 26 | 27 | if sys.argv[-1] == 'publish': 28 | try: 29 | import wheel 30 | print("Wheel version: ", wheel.__version__) 31 | except ImportError: 32 | print('Wheel library missing. Please run "pip install wheel"') 33 | sys.exit() 34 | os.system('python setup.py sdist upload') 35 | os.system('python setup.py bdist_wheel upload') 36 | sys.exit() 37 | 38 | if sys.argv[-1] == 'tag': 39 | print("Tagging the version on git:") 40 | os.system("git tag -a %s -m 'version %s'" % (version, version)) 41 | os.system("git push --tags") 42 | sys.exit() 43 | 44 | readme = open('README.rst').read() 45 | 46 | setup( 47 | name='django-migrations-graph', 48 | version=version, 49 | description="""Django-admin command to display migrations with dependencies.""", 50 | long_description=readme, 51 | author='Iurii Shikanov', 52 | author_email='dizballanze@gmail.com', 53 | url='https://github.com/dizballanze/django-migrations-graph', 54 | packages=[ 55 | 'migraph', 56 | ], 57 | include_package_data=True, 58 | install_requires=['Django>=1.7'], 59 | license="MIT", 60 | zip_safe=False, 61 | keywords='django-migrations-graph', 62 | classifiers=[ 63 | 'Development Status :: 3 - Alpha', 64 | 'Framework :: Django', 65 | 'Framework :: Django :: 1.8', 66 | 'Framework :: Django :: 1.9', 67 | 'Framework :: Django :: 1.10', 68 | 'Framework :: Django :: 1.11', 69 | 'Intended Audience :: Developers', 70 | 'License :: OSI Approved :: BSD License', 71 | 'Natural Language :: English', 72 | 'Programming Language :: Python :: 2', 73 | 'Programming Language :: Python :: 2.7', 74 | 'Programming Language :: Python :: 3', 75 | 'Programming Language :: Python :: 3.4', 76 | 'Programming Language :: Python :: 3.5', 77 | ], 78 | ) 79 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dizballanze/django-migrations-graph/2b5f75016c093cdac1d6cc9e7fb24fb0f343607e/tests/__init__.py -------------------------------------------------------------------------------- /tests/settings.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 2 | from __future__ import unicode_literals, absolute_import 3 | 4 | import django 5 | 6 | DEBUG = True 7 | USE_TZ = True 8 | 9 | # SECURITY WARNING: keep the secret key used in production secret! 10 | SECRET_KEY = "@9%&4^0vb5iz%fhb%eqhib&-a84*3w8h9bn_n$dzqy8z920mv1" 11 | 12 | DATABASES = { 13 | "default": { 14 | "ENGINE": "django.db.backends.sqlite3", 15 | "NAME": ":memory:", 16 | } 17 | } 18 | 19 | INSTALLED_APPS = [ 20 | "migraph", 21 | "foo", 22 | "bar" 23 | ] 24 | 25 | SITE_ID = 1 26 | 27 | if django.VERSION >= (1, 10): 28 | MIDDLEWARE = () 29 | else: 30 | MIDDLEWARE_CLASSES = () 31 | -------------------------------------------------------------------------------- /tests/test_migration_dependencies_command.py: -------------------------------------------------------------------------------- 1 | import sys 2 | if sys.version_info[0] == 2: 3 | from StringIO import StringIO 4 | else: 5 | from io import StringIO 6 | 7 | from django.test import TestCase 8 | from django.core.management import call_command, CommandError 9 | 10 | 11 | class MigrationDependenciesCommandTestCase(TestCase): 12 | 13 | COMMAND_NAME = 'migration_dependencies' 14 | 15 | FOO_MIGRATIONS = [ 16 | '0001_initial', 17 | '0002_auto_20170511_0514', 18 | '0003_auto_20170511_0514', 19 | ] 20 | 21 | BAR_MIGRATIONS = [ 22 | '0001_initial', 23 | '0002_auto_20170511_0515', 24 | ] 25 | 26 | def setUp(self): 27 | self.out = StringIO() 28 | 29 | def test_call_without_apps_arguments_raise_command_error(self): 30 | """ Command call without applications lists should return usage info """ 31 | with self.assertRaises(CommandError): 32 | call_command(self.COMMAND_NAME) 33 | 34 | def test_single_app_output_has_app_name(self): 35 | """ Should print application name """ 36 | call_command(self.COMMAND_NAME, 'foo', stdout=self.out) 37 | self.assertIn('[foo]', self.out.getvalue()) 38 | 39 | def test_print_all_migrations(self): 40 | """ Should print all migrations names """ 41 | call_command(self.COMMAND_NAME, 'foo', stdout=self.out) 42 | output = self.out.getvalue() 43 | for migration in self.FOO_MIGRATIONS: 44 | self.assertIn('foo/{}'.format(migration), output) 45 | 46 | def test_print_depending_migrations(self): 47 | """ Should print depending migrations from another apps """ 48 | call_command(self.COMMAND_NAME, 'foo', stdout=self.out) 49 | output = self.out.getvalue() 50 | self.assertIn('bar/0001_initial', output) 51 | 52 | def test_print_depends_on_migrations(self): 53 | call_command(self.COMMAND_NAME, 'foo', stdout=self.out) 54 | output = self.out.getvalue() 55 | self.assertIn('bar/0002_auto_20170511_0515', output) 56 | 57 | def test_multiple_apps_names(self): 58 | """ Should display multiple apps names """ 59 | call_command(self.COMMAND_NAME, 'foo', 'bar', stdout=self.out) 60 | self.assertIn('[foo]', self.out.getvalue()) 61 | self.assertIn('[bar]', self.out.getvalue()) 62 | 63 | def test_display_all_provided_apps_migrations(self): 64 | """ Should display all migrations of all provided apps """ 65 | call_command(self.COMMAND_NAME, 'foo', 'bar', stdout=self.out) 66 | output = self.out.getvalue() 67 | for migration in self.FOO_MIGRATIONS: 68 | self.assertIn('foo/{}'.format(migration), output) 69 | for migration in self.BAR_MIGRATIONS: 70 | self.assertIn('bar/{}'.format(migration), output) 71 | 72 | def test_not_existing_app(self): 73 | """ Should show error message if wrong application name was provided """ 74 | call_command(self.COMMAND_NAME, 'wrong', stdout=self.out) 75 | self.assertIn('Migrations for `wrong` application were not found', self.out.getvalue()) 76 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | {py27,py34,py35,py36}-django-18 4 | {py27,py34,py35,py36}-django-19 5 | {py27,py34,py35,py36}-django-110 6 | {py27,py34,py35,py36}-django-111 7 | 8 | [testenv] 9 | setenv = 10 | PYTHONPATH = {toxinidir}:{toxinidir}/migraph 11 | commands = coverage run --source migraph runtests.py 12 | deps = 13 | django-18: Django>=1.8.3,<1.9 14 | django-19: Django>=1.9,<1.10 15 | django-110: Django>=1.10 16 | django-111: Django>=1.11 17 | -r{toxinidir}/requirements_test.txt 18 | basepython = 19 | py36: python3.6 20 | py35: python3.5 21 | py34: python3.4 22 | py27: python2.7 23 | --------------------------------------------------------------------------------