22

我尽力编写可重用的 Django 应用程序。现在我很困惑如何将它们放在一起以获得最终项目。

这是我的意思的一个例子:我有一个图片应用程序,可以存储、调整大小和显示图像。我还有一个用于存储、编辑和显示文本的博客应用程序。现在我想将这两者结合起来以显示带有图像的博客文章。

为此,我可以在博客中放置外键字段以指向图片。但是没有图片应用程序就无法使用博客。我还可以创建第三个应用程序,负责连接两者。

什么是“最佳实践”的做法?

编辑:感谢您提供非常好的答案,但我仍在寻找如何解决此问题的更实际示例。完成我的示例:有时使用没有图片应用程序的博客应用程序会很好。但是,如果我对依赖项进行硬编码,则不再可能。那么第三个应用程序如何结合两者呢?

4

4 回答 4

16

答案底部的介绍谈话(更直接的答案)。我假设您有一个用于文本处理的应用程序,称为文本,一个用于处理图片的应用程序,称为图片,以及第三个用于博客的应用程序,称为博客。

大图

您将需要学习有关 python 程序员模板语言的手册。这个想法是每件事都在它自己的应用程序中,并且你有一个连接一切的第三个应用程序。然后,应用程序应该提供您喜欢的模型和视图(只要记住让您专注于应用程序应该做什么)并提供一组模板标签。

如何制作包含标签

制作包含标签,这真的很容易!它会提醒您编写普通视图。

在您的应用程序文件夹中创建目录模板标签。__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-template 可能看起来像(简化):

{% 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>

请注意,只有博客有依赖关系,它应该有依赖关系(没有文字和图片的博客......但图片可以没有文字)。外观和位置应该并且可以由 CSS 和 DIV/SPAN 标记控制。通过这种方式,您可以将您的图片应用程序提供给不了解文本应用程序的人并使用它,以不同的方式显示图片,可能无需接触您的代码!

包含标签是我唯一知道的,因为我昨天才知道这一点。我认为这是 Django 提供的一种便利,让生活变得简单。在文档页面上还有更多内容(包括如何在没有“快捷方式”的情况下艰难地制作“真实”标签)。因此,如果您发现此方法有限,请阅读文档...它有很多示例。它还讨论了如何制作过滤器、simple_tags、线程注意事项和其他高级内容。

介绍

我和你一样遇到了这个问题,我也想要一些与我读到的答案不同的东西(我并不是说答案很糟糕,它们帮助我学到了很多东西并给了我见解,但我想要这个我现在正在写) . 我设法弄清楚了一些不是很明显的事情,这要归功于您的问题,并且绝对要感谢 Stack Overflow,所以这是我对可能被放弃的半年前问题的贡献(可能会帮助一两个谷歌人)!

我还从Google Tech Talk Reusable Apps中获得了很多灵感。最后(43 分钟)他提到了一些很好的例子,比如django-tagging,这是他所说的如何编写可重用应用程序的模型。这给了我所有这一切的想法,因为这就是 django-tagging 解决我们曾经/拥有的这个问题的方式。

现在,在我写完所有这些(花了一个小时)之后,我第一次觉得我可能会做出贡献,而不仅仅是谷歌,关注别人在做什么,或者抱怨别人没有写出我需要做的事情。这是我第一次承担起写下我的观点的责任,这样其他人就可以在谷歌上搜索这个(只需要写下这段:-),因为它感觉真的很棒,即使它可能被撕成碎片或被忽视和遗忘)。

于 2013-01-14T22:01:17.153 回答
6

以您在项目中使用任何 3rd 方应用程序的方式来考虑它。“可重用”并不意味着“没有依赖关系”。相反,你很难找到一个没有至少一个依赖项的应用程序,即使它只依赖于 Django 或核心 Python 库。(虽然核心 Python 库通常被认为是“安全的”依赖项,即每个人都会拥有它,但有时在 Python 版本之间会发生变化,因此您仍将应用程序锁定到特定时间点)。

可重用的目标与 DRY 的目标相同:您不想一遍又一遍地编写相同的代码。因此,像图片应用一样拆分功能是有意义的,因为您可以在其他应用和项目中一遍又一遍地使用它,但是您的图片应用将具有依赖项,其他包将依赖它,只要没有循环依赖,你很好(循环依赖意味着你实际上没有分离功能)。

于 2012-07-20T15:19:23.717 回答
5

这是一个很好的问题,我也觉得很难处理。但是 - 您是否认为这些应用程序会公开发布,或者您只是自己使用它们?如果你不释放,我不会担心。

另一件事是,依赖关系很好。您示例中的图片应用程序听起来很适合成为“可重用”应用程序。它很简单,只做一件事,并且可以被其他应用程序使用。

另一方面,博客应用程序通常需要使用其他应用程序,例如图片应用程序或标记应用程序。我发现这种依赖很好。您可以通过简单地链接到您的图片应用程序放置的媒体资源来尝试将其抽象一点。

这只是一点点常识。你能让你的应用程序变得苗条吗?如果是,则尝试创建它们以便可以重复使用。但是,当它们有意义时,不要害怕接受依赖。此外,请尝试允许扩展点,以便您可以将依赖项换成其他的。直接外键在这里没有帮助,但也许像信号或 Restful API 之类的东西可以。

于 2012-07-20T12:27:51.723 回答
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-registrationJames Bennett就是一个很好的例子。它是一个旧应用程序,但它的吸引力并没有减弱,因为它做出了坚如磐石的假设。由于好的可重用应用程序可以很好地完成一件事,因此不难找到这组假设。

basemixins.py:这个模块应该实现对该应用程序具体模型的插件。模型 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'

BaseOwnedByGroupgroup模型的“插头”。这里的规则和'base.py'一样

  • 在定义 'plugs' 时basemixins.py,仅假定base.py.
  • 仅从core、django、第三方应用程序和 python 包导入。

mixins.py: 这个模块应该用于两个目的

  • 定义精细的“插件”,它假定精细的具体类的功能,而不是与其他应用程序的关系。精心设计的插件应该理想地继承basemixins.py.

  • 定义可由该应用程序的具体类使用的混合模型(不是“插件”)。

concrete.py:这个模块应该用于定义(你猜对了)该应用程序的具体类并建立与其他应用程序的关系。简而言之,该模块假定您的项目以及您想要在其中提供的所有功能。

与其他应用程序的关系应设置如下:

  • 要与 app 的模型 M 建立one to oneormany to one关系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: 在建立关系的过程中app1,当app的模型Napp2发生变化时,我们经常要对app的模型M进行操作。您可以使用信号来处理它。您的处理程序可以承担具体发送者的功能,但通常他们不需要。假设发件人的基本版本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 不应该假设任何其他应用程序。此规则的唯一例外是依赖于 settings.AUTH_USER_MODEL 基本功能的 mixin。这是因为您可以放心地假设大多数项目都会有一个user模型。

  • 当然仍然允许从 django、第三方和 python 包导入

  • 请记住,所有base.pybasemixins.py模块都允许从core.

最后,为了让一切按预期工作,将您的模型导入models/__init__.py每个应用程序。

我发现遵循此方案的优点是:

  • 这些模型是可重复使用的。任何人都可以使用抽象基础模型和 mixin 来设计自己的具体模型。base.pybasemixins.py并且相关的工厂方法可以与一个基本的具体模型捆绑在一起,并在一个可重用的应用程序中发布。

  • 这些应用程序是可扩展的。所有的 mixin 都是版本化的,并且有一个明确的继承方案。

  • 这些应用程序是松散耦合的。通过工厂方法访问外部 mixin,并使用 django.conf.settings 引用外部模型。

  • 这些应用程序是自包含的。应用程序中的任何更改很可能只会破坏该应用程序和该应用程序。其他应用程序很可能会毫发无损。即使外部应用程序出现故障,也会清楚地标记可能发生这种情况的位置。

我一直在使用这个方案来减少我的应用程序之间的耦合。我不是一个经验丰富的 Django 程序员,我有很多东西要学,所以任何反馈都是值得的。

于 2014-09-25T20:46:57.687 回答