Как связать несколько многоразовых приложений Django вместе?

Я стараюсь писать многоразовые приложения Django. Теперь я озадачен тем, как собрать все вместе, чтобы получить окончательный проект.

Вот пример того, что я имею в виду: У меня есть приложение для картинок, которое хранит, изменяет размеры и отображает изображения. Также у меня есть приложение weblog, которое хранит, редактирует и отображает тексты. Теперь я хочу объединить эти два, чтобы показывать сообщения в блогах с изображениями.

Чтобы сделать это, я мог бы помещать поля внешнего ключа в блоге, чтобы указывать на фотографии. Но тогда блог не мог использоваться без приложения для картинок. Также я мог бы создать третье приложение, которое отвечает за подключение обоих.

Какова наилучшая практика?

EDIT: Спасибо за ваши очень хорошие ответы, но я все еще ищу более практичный пример того, как решить эту проблему. Чтобы завершить мой пример: иногда было бы неплохо использовать приложение для блога без приложения для картинок. Но если я жестко кодирую зависимость, это уже невозможно. Итак, как насчет 3-го приложения объединить оба?

Ответ 1

Вступительный разговор в нижней части ответа (более прямой ответ). Я предполагаю, что у вас есть одно приложение для обработки текста под названием "Текст" и одно приложение для обработки изображений "Картинки" и третье приложение для ведения блога под названием "Блог".

Большое изображение

Вам нужно будет изучить руководство по языку шаблона для программистов python. Идея состоит в том, что каждая вещь находится в собственном приложении и что у вас есть третье приложение, которое соединяет все. Затем приложения должны предоставлять свои модели и представления, как вам нравится (просто не забудьте сосредоточиться на том, что должно делать приложение), а также предоставить набор templatetags.

Как сделать теги включения

Сделайте теги включения, и это очень просто! Это напомнит вам о написании нормальных представлений.

Создайте каталог templatetags в папке приложения. Также создайте файл __init__.py в этом templatetags (поэтому каталог становится пакетом python).

Затем создайте файл python. Имя важно, вы будете использовать его в {% load xyz %} в шаблонах, которые будут использовать ваше приложение. Например, если вы вызываете файл picturestags.py, вы вызываете {% load picturestags %} во всех шаблонах, которые будут использовать его.

Сначала в файле добавьте некоторую политику, о которой вам не нужно много думать, просто включите ее прежде всего:

from django.template import Library
register = Library()

Затем добавьте теги, определив функции с тем же именем, что и ваш тег. Я назову его display_picture в примере, и это займет один аргумент url. Функция должна создать словарь, который вы будете использовать в шаблоне. В моем примере будет отображаться только изображение, на которое указывает URL.

@register.inclusion_tag('pictures/display_picture.html')
def display_picture(url):
    return {'picture': url}

Создайте шаблоны/рисунки пути в своем приложении и создайте файл display_picture.html внутри, содержащий:

<img src="{{ picture }}" />

Как вы, вероятно, понимаете, @register делает это тегом, то, что находится внутри словаря display_picture, - это то, что вы можете использовать в display_picture.html. Очень похож на обычные функции просмотра.

В итоге вы получите эти файлы:

pictures/
    __init__.py
    models.py
    views.py
    tests.py
    templates/
        pictures/
            display_picture.html
    templatetags/
        picturetags.py

Это все, что вам нужно добавить в приложение для картинок. Чтобы использовать это в своем приложении в блоге, вам нужно добавить Картинки на ваш INSTALLED_APPS. Затем в шаблонах, где вам нужно использовать свой собственный недавно испеченный тег, сначала загрузите его: {% load picturestags %}, затем просто добавьте тег {% display_picture https://www.google.com/intl/sv_ALL/images/logos/images_logo_lg.gif %} следующим образом:

{% load picturestags %}
<html>
    <body>
        {% display_picture https://www.google.com/intl/sv_ALL/images/logos/images_logo_lg.gif %}
    </body>
</html>

Результаты

Это всего лишь небольшой пример, но вы можете видеть, что это очень просто расширить. Ваш блог может подключать приложение "Текст и рисунки", импортируя свои модели и внешний ключ. Существует сообщение "Текст и картинки" для определенного сообщения в блоге. Ваш шаблон blog_post.html может выглядеть (упрощен):

{% load picturestags %}
{% load texttags %}
<html>
    <body>
        <h1>{{ subject }}</h1>
        <div class="article">{% article_to_html articleid %}</div>
        <div class="article_picture">{% display_picture %}</div>
    </body>
</html>

Обратите внимание, что только у Blog есть зависимости, и это зависимости, которые он должен иметь (без блога без текста и изображений... но картинки могут жить без текста). Взгляд и размещение должны контролироваться CSS и DIV/SPAN-тегами. Таким образом, вы можете принять приложение "Картинка" и передать его тому, кто понятия не имеет о приложении "Текст", и использовать его, отображая изображения по-разному, возможно, даже не прикасаясь к вашему коду!

Теги включения - это единственное, что я знаю, так как я только что узнал об этом вчера. Я думаю, что это удобство Django, чтобы сделать жизнь простой. На странице документации есть намного больше (в том числе, как сделать "реальные" теги трудным путем без "ярлыков" ). Поэтому, если вы обнаружите, что этот метод ограничен, прочитайте документацию... в нем много примеров. В нем также обсуждается, как создавать фильтры, simple_tags, обсуждения потоков и другие продвинутые материалы.

Введение

У меня была эта именно эта проблема, так как вы и я также хотели что-то другое, чем ответы, которые я читал (я не говорю, что ответы были плохими, они помогли мне многому научиться и дали мне идеи, но я хотел этого, я писать сейчас). Мне удалось понять что-то, что не очень очевидно, благодаря вашему вопросу и, безусловно, благодаря Stack Overflow, так что это мой вклад обратно даже на полтора года вопрос, который, вероятно, заброшен (может помочь googler или два)!

Я также получил много вдохновения от Google Tech Talk многоразовых приложений. В конце (43 минуты) он упоминает несколько хороших примеров, таких как django-tagging, что он говорит о модели для написания многоразовых приложений. Это дало мне идею для всего этого, потому что это то, как django-tagging решает эту проблему, которую мы имели/имели.

Теперь, после того как я написал все это (занял час), я впервые чувствую, что могу внести вклад, а не просто google и следить за тем, что делают другие, или жалуются, что другие не пишут, как мне нужно что-то делать. Впервые я беру на себя ответственность писать свое мнение, чтобы другие могли это сделать (просто нужно было написать этот абзац:-), потому что он чувствует себя действительно здорово, даже если его можно разбить на части или игнорировать и забыть).

Ответ 2

Подумайте об этом так же, как использовать любое стороннее приложение в своем проекте. "Повторно использовать" не означает "без зависимостей". Напротив, вам будет трудно найти приложение, которое не имеет хотя бы одной зависимости, даже если оно просто зависит от Django или основных библиотек Python. (В то время как базовые библиотеки Python обычно считаются "безопасными" зависимостями, то есть каждый будет иметь его, вещи иногда меняются между версиями Python, поэтому вы по-прежнему блокируете свое приложение в определенный момент времени).

Цель повторного использования такая же, как и DRY: вы не хотите писать один и тот же код снова и снова. В результате имеет смысл вырвать функциональность, например, приложение для картинок, потому что вы можете использовать его снова и снова в других приложениях и проектах, но ваше приложение для картинок будет иметь зависимости, а другие пакеты будут зависеть от него, пока нет круговых зависимостей, вы хороши (круговая зависимость означает, что вы фактически не разделяли функциональность).

Ответ 3

Это хороший вопрос, и мне тоже сложно справиться. Но... представляете ли вы, что эти приложения публикуются публично или вы сами используете их сами? Если вы не выпускаете, я бы не стал слишком беспокоиться об этом.

Другое дело, зависимости могут быть хорошими. Приложение для картинок в вашем примере кажется хорошим кандидатом, чтобы быть "многоразовым" приложением. Это просто, делает одно и может быть использовано другими приложениями.

С другой стороны, приложение для блогов обычно требует использования других приложений, таких как приложение для картинок или приложение для тегов. Я нахожу эту зависимость, чтобы иметь. Вы можете попытаться немного отвлечь его, просто связавшись с медиа-ресурсом, который был помещен в ваше приложение для картинок.

Это всего лишь немного здравого смысла. Можете ли вы сделать ваши приложения стройными? Если да, попробуйте создать их, чтобы их можно было повторно использовать. Но не бойтесь брать зависимости, когда они имеют смысл. Кроме того, попробуйте разрешить точки расширения, чтобы вы могли поменять зависимости для других. Прямой иностранный ключ здесь не поможет, но возможно что-то вроде сигналов или Restful APIs.

Ответ 4

Я новичок в Django, и я столкнулся с той же проблемой. Позорно, как в сообществе Django так много шумов о многоразовых приложениях, но ни одной авторитетной справки о том, как их подключать в проекте без жестко закодированных зависимостей.

Я работаю над новым проектом Django, и я хотел настроить свои модели, чтобы как можно больше избежать жесткого кодирования. Это образец, который я использовал.

Разметка

project_root/
    core/
        models/
            mixinmodels1.py
            mixinmodels2.py
            ...
        utils.py
        ...
    app1/
        models/
            __init__.py
            base.py
            basemixins.py
            mixins.py
            concrete.py
        /signals
            __init__.py
            handlers.py
        utils.py
        ...
    app2/
        ...
    ...

Модели приложений

base.py: Этот модуль реализует только "причину существования" этого приложения в абстрактных классах. Правила:

  • Этот модуль обычно импортирует только из приложения core. Обычно он ничего не импортирует из других приложений в одном проекте.

  • Исключение из вышеприведенного правила - это когда "причина существования" предполагает существование другого приложения. Например, приложение group предполагает, что есть приложение user. В этом случае способ их соединения:

    # project_root/settings.py
    
    AUTH_USER_MODEL = 'app_label.UserModel'
    
    # project_root/groups/models/base.py
    
    from django.conf import settings
    

    а затем используя settings.AUTH_USER_MODEL, чтобы обратиться к модели user

  • Используйте этот шаблон для всех приложений, а не только для приложения user. Например, вы также должны делать

    # project_root/settings.py
    
    GROUP_MODEL = 'app_label.GroupModel'
    
  • Если вы используете вышеуказанный шаблон, предположите только функциональность, предоставляемую base.py другого приложения к которому вы связываетесь. Не предполагайте функциональность сложных конкретных классов (я расскажу, где быстро положить конкретные классы)

  • Конечно, допускается импорт из django, сторонних приложений и пакетов python.

  • Удостоверьтесь, что предположения, которые вы делаете в base.py любого приложения, прочны и не будут значительно изменится в будущем. Хорошим примером может служить Джеймс Беннетт django-registration. Это старое приложение, но его призыв не ослабел, потому что он сделал твердые предположения. Поскольку хорошие многоразовые приложения хорошо справляются, нетрудно найти этот набор предположений.

basemixins.py: Этот модуль должен внедрять пробки в конкретные модели этого приложения. "Plug" для модели M - любая модель, которая содержит внешний ключ модели M. Например:

# project_root/groups/models/basemixins.py

from django.conf import settings
from django.db import models

class BaseOwnedByGroup(models.Model):
    """
    This is a plug to the group model. Use this
    to implement ownership like relations with 
    the group model
    """
    owner = models.ForeignKey(settings.GROUP_MODEL,
        related_name = '%(app_label)s_%(class)s_owner',
        verbose_name = 'owner')

    # functionality and manager definitions go here.

    class Meta:
        abstract = True
        app_label = 'groups'

BaseOwnedByGroup является "подключением" к модели group. Правила здесь такие же, как "base.py`

  • При определении "подключений" в basemixins.py используйте только функции base.py.
  • Импортировать только из core, django, сторонних приложений и пакетов python.

mixins.py: Этот модуль должен использоваться для двух целей

  • Определить сложные "пробки", которые предполагают функциональность сложных конкретных классов, но не взаимоотношения с другими приложениями. Разработанные разъемы должны идеально наследовать один из "базовых разъемов", определенных в basemixins.py.

  • Определить модели mixin (которые не являются "plug-ыми" ), которые могут использоваться конкретными классами этого приложения.

concrete.py: этот модуль должен использоваться для определения (как вы предполагали) конкретных классов этих приложений и для установления отношений с другими приложениями. Короче говоря, этот модуль предполагает ваш проект и все функциональность, которую вы хотите предоставить в ней.

Отношения с другими приложениями должны быть настроены следующим образом:

  • Чтобы установить связь one to one или many to one с моделью M приложения another_app, выполните следующие действия:

    # project_root/another_app/utils.py
    
    def plug_to_M_factory(version_label):
        """
        This is a factory method which returns
        the plug to model M specified by 
        version_label
        """
        if version_label == 'first_version':
            from another_app.models.basemixins import BasePlugToM
            return BasePlugToM
        if version_label == 'second_version':
            from another_app.models.mixins import PlugToM
            return PlugToM
        ...
    
    # project_root/groups/models/concrete.py
    
    from groups.models.base import BaseGroup
    from another_app.utils import plug_to_M_factory
    
    PlugToMClass = plug_to_M_factory(version_label = 'second_version')
    
    class ConcreteGroup(BaseGroup, PlugToMClass):
        # define your concrete model
    
        class Meta:
            app_label = 'groups'
    
  • Чтобы установить связь many to many, рекомендуется использовать модель through. Наследовать правильный штекер в модели through точно так же (как это было в модели ConcreteGroup)

signals: При настройке отношений часто приходится выполнять операции над моделью M приложения app1, когда изменяется модель N приложения app2. Вы можете использовать сигналы для их обработки. Ваши обработчики могут использовать функциональность конкретного отправителя, но часто им это не нужно. Допущения базовой версии отправителя в base.py достаточно. Вот почему рекомендуется использовать settings.ModelName для обозначения конкретной модели. Вы можете извлечь класс модели из строки settings.ModelName либо с помощью ContentType, либо с помощью функции get_model_for_settings с расширенным проектом:

# project_root/project/utils.py

from django.db.models import get_model
from django.core.exceptions import ImproperlyConfigured

def get_model_from_settings(model_string):
    """
    Takes a string of the form 'app_label.model' as input, returns the 
    appropriate model class if it can find it.
    """
    try:
        app_label, model_name = model_string.split('.')
    except ValueError:
        raise ImproperlyConfigured("function argument must be of the " 
            "form 'app_label.model_name', got '%s'" % model_string)

    model = get_model(app_label, model_name)

    if model is None:
        raise ImproperlyConfigured("function argument refers to model "
            "'%s' that has not been installed" % model_string)

    return model

core: Основное приложение - это специальное приложение, в котором хранятся расширенные функции mixin проекта.

  • Эти mixins не должны принимать ничего о каком-либо другом приложении. Единственным исключением из этого правила являются mixins, которые полагаются на базовую функциональность настроек .AUTH_USER_MODEL. Это связано с тем, что вы можете с уверенностью предположить, что большинство проектов будут иметь модель user.

  • Конечно, импорт из пакетов django, сторонних и python по-прежнему разрешен

  • Помните, что всем base.py и basemixins.py модулям разрешено импортировать из core.

Наконец, для того, чтобы все работало по назначению, импортируйте свои модели в models/__init__.py каждого приложения.

Преимущества, которые я нахожу из следующей схемы:

  • Модели могут использоваться повторно. Любой может использовать абстрактные базовые модели и микшины для разработки своей собственной конкретной модели. base.py, basemixins.py и связанные с ним методы factory могут быть объединены вместе с бетонной моделью из голых костей и отправлены в многоразовое приложение.

  • Приложения расширяемы. Все миксины имеют версию и есть четкая схема наследования.

  • Приложения слабо связаны. Доступ к внешним микшинам осуществляется с помощью методов factory, а внешние модели - с помощью django.conf.settings.

  • Приложения автономны. Любые изменения в приложении, скорее всего, нарушат это приложение и это приложение. Другие приложения, скорее всего, останутся невредимыми. Даже если внешние приложения сломаются, место, где это может произойти четко.

Я использую эту схему для уменьшения связи между моими приложениями. Я не опытный программист Django, и у меня есть чему поучиться, поэтому любые отзывы приветствуются.