如何使用django-compressor离线压缩表单媒体

How to use django-compressor to compress form media offline

包含如下代码片段的 Django 模板(包含带有媒体的表单)在将 django-compressor 与 COMPRESS_OFFLINE=True 一起使用时会抛出错误,因为 form 在离线时不可用执行压缩:

# Template snippet
{% compress js %}
{{ form.media.js }}
{% endcompress %}

一般来说,django-compressor 提供了 COMPRESS_OFFLINE_CONTEXT 设置来处理类似的情况。但是,如果站点包含许多此类表单或带有媒体的小部件,则此解决方案并不理想。

例如,目前,我在 settings.py 中执行类似的操作(针对每个小部件的媒体):

# settings.py
...
from my_app1.widgets import Widget1
from my_app1.widgets import Widget2
from my_app1.widgets import Widget3
...
widgets = {
    'my_widget1': Widget1(),
    'my_widget2': Widget2(),
    'my_widget3': Widget3(),
    ...
}
for name, widget in widgets.items():
    COMPRESS_OFFLINE_CONTEXT['{}_css'.format(name)] = widget.media['css']
    COMPRESS_OFFLINE_CONTEXT['{}_js'.format(name)] = widget.media['js']

然后在模板中,我这样做:

{% compress js %}
{{ my_widget1_js }}
{% endcompress %}

有没有办法以更类似于 Django 的 {{ form.media }} 方法的方式来处理这种情况,或者可能不需要为站点中的每个小部件或每个表单(包含媒体)枚举特定媒体?

您可以通过使用一些带有 __getattr__ 方法的自定义 class 来简化您的设置。这样,如果您将该对象传递到模板并尝试调用

{{ obj.my_widget1_js }}

__getattr__ 中的逻辑可以在您的项目中搜索指定的小部件,return 它是媒体。

将自动发现每个小部件的 class 示例:

class WidgetImporter(object):

    def __getattr__(self, name):
        path = name.split('__') # this will split path on double underscore, so you can define submodule here
        path = [(node, "".join(part.capitalize() for part in node.split('_'))) for node in path] # this will convert underscore_names to CamelCase names.

        # determining for each module if it's name is CamelCased or underscored
        real_path = []
        for underscored, cameled in path:
            try:
                __import__(".".join(real_path + [underscored])) # first trying to import umderscored module
            except ImportError:
                try:
                    __import__(".".join(real_path + [cameled])) # now try with cameled
                except ImportError:
                    return "" # or raise some exception here if you like
                else:
                    real_path.append(cameled)
            else:
                real_path.append(underscored)

        last_node = real_path[len(real_path) - 1]
        return getattr(__import__(".".join(real_path), fromlist=[last_node]), last_node)() # returning actual class instance

这样你就可以在你的 settings.py 文件中使用它了:

COMPRESS_OFFLINE_CONTEXT = {
    'widgets': WidgetImporter(),
}

并在您的模板中使用:

{{ widgets.my_app1__widgets__widget1.media.css }}

Class 将尝试将 my_app1__widgets__widget1 解析为您的 class 的实际路径并尝试将其导入。如果成功,将打印 media['css']。它不是 100% 最佳代码和 100% 安全(如果有人可以访问您的模板,可能会有一些漏洞),但它可以完成工作。