Вопрос: Как организовать разработку reusable app для Django?

Лучший ответ:
Мы активно используем собственные батарейки для Django. Вот типичная структура одной:scr — корень Python пакета (как для PyPi)dvhb_docs — сама батарейкаtestproject — минимальный проект Django для тестирования батарейки
Пример setup.py:# -*- coding: utf-8 -*-

import os

from distutils.core import setup

__version__ = "0.1.0"

README = open(os.path.join(os.path.dirname(__file__), 'README.md')).read()
os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir)))

tests_require = [
    'Django>=1.5',
    'model-mommy',
    'south',
    'pyelasticsearch',
    'elasticsearch',
    'django-haystack>=2.1.0',
    'djangorestframework',
    'django-appconf',
    'webtest',
    'django-webtest',
    'django-celery',
    'django-mptt'
]

setup(
    name='dvhb_docs',
    version=__version__,
    packages=['dvhb_docs'],
    include_package_data=True,
    license='private',
    description='Django application for document store and management.',
    long_description=README,
    url='https://github.com/dvhb/dvhb_docs',
    author='Vadim Lopatyuk',
    author_email='qnub@example.com',
    classifiers=[
        'Environment :: Web Environment',
        'Framework :: Django',
        'Intended Audience :: Developers',
        'Operating System :: OS Independent',
        'Programming Language :: Python',
        'Programming Language :: Python :: 2.7',
        'Topic :: Internet :: WWW/HTTP',
        'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
    ],
    install_requires=[
        'Django>=1.5',
        'south',
        'pyelasticsearch',
        'elasticsearch',
        'django-haystack>=2.1.0',
        'djangorestframework',
        'django-appconf',
        'django-celery',
        'django-mptt'
    ],
)
MANIFEST.inrecursive-include dvhb_docs *.py *.html *.css *.js *.gif *.png *.jpg *.xap *.swfrequirements.txt файл зависимостей для виртуального окружения — по желанию (там можно включать что-то для разработки пакета, что нет смысла указывать в setup.py).
К слову новая (после 1.4) структура проекта Django способствует удобному отделению батареек. Т.е. testproject может быть обычным Вашим проектом и на том же уровне каталога будут лежать разрабатываемые модули, которые потом удаляются и ставятся уже из репозитория.
В миграции South надо вносить правки в слепок таблиц, чтобы не было конфликтов с кастомными моделями пользователей. Если нет каких-то хитрых действий с юзером то типичную версию:{
…
        u'auth.user': {
            'Meta': {'object_name': 'User'},
            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
        }
}
мы меняем на:from django.conf import settings
…
{
…
        settings.AUTH_USER_MODEL.lower(): {
            'Meta': {'object_name': settings.AUTH_USER_MODEL.split('.')[-1]},
            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
        }
}
А всякие указания на неё в ключах других моделей вроде:'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'author'", 'blank': 'True', 'to': "orm['auth.User']"}),
на:'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'author'", 'blank': 'True', 'to': u"orm['{0}']".format(settings.AUTH_USER_MODEL)}),
Ну и последнее, ставим мы их таким образом (теперь мы работаем со своим GitLab, но для примера приведу GitHub):pip install git+ssh://git@github.com/dvhb/dvhb_docs.git@0.1.0#egg=dvhb_docs==0.1.0
Тут есть пара нюансов:git+ssh://git@github.com/dvhb/dvhb_docs.git — линк для установки пакета с помощью pip из git (отличается от обычного линка на репу для самого git: git@github.com:dvhb/dvhb_docs.git).@0.1.0 — это обычный tag в git репозитории. Так можно устанавливать необходимую версию пакета.#egg=dvhb_docs==0.1.0 — это то, как пакет будет предствален в окружении python. Т.е. в этом случае он будет записан как dvhb_docs версии 0.1.0.
В начале мы пытались и юзерский фронтенд засовывать в батарейку, но он очень сильно меняется от случая к случаю в итоге с этой затеей завязали и стали просто делать общие библиотеки на JS, которые ставятся через Bower (по аналогии с питоноим). Остальные нюансы уже дописываются в самом проекте.

Худший ответ:
Мы активно используем собственные батарейки для Django. Вот типичная структура одной:scr — корень Python пакета (как для PyPi)dvhb_docs — сама батарейкаtestproject — минимальный проект Django для тестирования батарейки
Пример setup.py:# -*- coding: utf-8 -*-

import os

from distutils.core import setup

__version__ = "0.1.0"

README = open(os.path.join(os.path.dirname(__file__), 'README.md')).read()
os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir)))

tests_require = [
    'Django>=1.5',
    'model-mommy',
    'south',
    'pyelasticsearch',
    'elasticsearch',
    'django-haystack>=2.1.0',
    'djangorestframework',
    'django-appconf',
    'webtest',
    'django-webtest',
    'django-celery',
    'django-mptt'
]

setup(
    name='dvhb_docs',
    version=__version__,
    packages=['dvhb_docs'],
    include_package_data=True,
    license='private',
    description='Django application for document store and management.',
    long_description=README,
    url='https://github.com/dvhb/dvhb_docs',
    author='Vadim Lopatyuk',
    author_email='qnub@example.com',
    classifiers=[
        'Environment :: Web Environment',
        'Framework :: Django',
        'Intended Audience :: Developers',
        'Operating System :: OS Independent',
        'Programming Language :: Python',
        'Programming Language :: Python :: 2.7',
        'Topic :: Internet :: WWW/HTTP',
        'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
    ],
    install_requires=[
        'Django>=1.5',
        'south',
        'pyelasticsearch',
        'elasticsearch',
        'django-haystack>=2.1.0',
        'djangorestframework',
        'django-appconf',
        'django-celery',
        'django-mptt'
    ],
)
MANIFEST.inrecursive-include dvhb_docs *.py *.html *.css *.js *.gif *.png *.jpg *.xap *.swfrequirements.txt файл зависимостей для виртуального окружения — по желанию (там можно включать что-то для разработки пакета, что нет смысла указывать в setup.py).
К слову новая (после 1.4) структура проекта Django способствует удобному отделению батареек. Т.е. testproject может быть обычным Вашим проектом и на том же уровне каталога будут лежать разрабатываемые модули, которые потом удаляются и ставятся уже из репозитория.
В миграции South надо вносить правки в слепок таблиц, чтобы не было конфликтов с кастомными моделями пользователей. Если нет каких-то хитрых действий с юзером то типичную версию:{
…
        u'auth.user': {
            'Meta': {'object_name': 'User'},
            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
        }
}
мы меняем на:from django.conf import settings
…
{
…
        settings.AUTH_USER_MODEL.lower(): {
            'Meta': {'object_name': settings.AUTH_USER_MODEL.split('.')[-1]},
            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
        }
}
А всякие указания на неё в ключах других моделей вроде:'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'author'", 'blank': 'True', 'to': "orm['auth.User']"}),
на:'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'author'", 'blank': 'True', 'to': u"orm['{0}']".format(settings.AUTH_USER_MODEL)}),
Ну и последнее, ставим мы их таким образом (теперь мы работаем со своим GitLab, но для примера приведу GitHub):pip install git+ssh://git@github.com/dvhb/dvhb_docs.git@0.1.0#egg=dvhb_docs==0.1.0
Тут есть пара нюансов:git+ssh://git@github.com/dvhb/dvhb_docs.git — линк для установки пакета с помощью pip из git (отличается от обычного линка на репу для самого git: git@github.com:dvhb/dvhb_docs.git).@0.1.0 — это обычный tag в git репозитории. Так можно устанавливать необходимую версию пакета.#egg=dvhb_docs==0.1.0 — это то, как пакет будет предствален в окружении python. Т.е. в этом случае он будет записан как dvhb_docs версии 0.1.0.
В начале мы пытались и юзерский фронтенд засовывать в батарейку, но он очень сильно меняется от случая к случаю в итоге с этой затеей завязали и стали просто делать общие библиотеки на JS, которые ставятся через Bower (по аналогии с питоноим). Остальные нюансы уже дописываются в самом проекте.