Django Rest + Jinja2: ValueError: dictionary update sequence element #0 has length 0; 2 is required

Django Rest + Jinja2: ValueError: dictionary update sequence element #0 has length 0; 2 is required

我目前正在使用 django-jinja package. As the website will need a JSON api I thought of using the Django Rest Framework (DRF) 使用 Jinja2 模板引擎开发 Django 网站。

在使用 DRF 之前一切都很好,使用从 django 的基于 class 的视图(TemplateView、ListView 等)继承的基于 class 的视图。

Django Rest 框架部分

所以我开始通过创建序列化程序和路由器将 DRF 包含到网站中。我的 DRF 设置如下:

REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': (
        'rest_framework.renderers.TemplateHTMLRenderer',
        'rest_framework.renderers.JSONRenderer',
    )
}

我的序列化程序如下

class ItemCategorySerializer(serializers.ModelSerializer):

    class Meta:
        model = ItemCategory
        fields = ('id', 'name', 'slug')

class ItemImageSerializer(serializers.ModelSerializer):

    class Meta:
        model = ItemImage
        fields = ('id', 'file',)

class ItemSerializer(serializers.ModelSerializer):

    owner = UserSerializer(read_only=True)
    categories = ItemCategorySerializer(many=True, read_only=True)
    photos = ItemImageSerializer(many=True, read_only=True)

    class Meta:
        model = Item
        fields = (
            'id', 'owner', 'name', 'categories', 'photos', 'price', 'quantity',
            'urgent', 'available', 'condition', 'boxing', 'description',
            'start_date', 'end_date', 'created_at', 'modified_at'
        )

我的urls.py如下:

router = routers.SimpleRouter()
router.register(r'', ItemViewSet)

urlpatters = router.urls

我创建了一个视图来测试系统:

class ItemViewSet(viewsets.ModelViewSet):

    queryset = Item.objects.all().select_related('owner').prefetch_related('categories', 'photos')
    serializer_class = ItemSerializer
    permission_classes = (
        permissions.IsAuthenticatedOrReadOnly,
        IsOwnerOrReadOnly,
    )

    def list(self, request):
        self.template_name = 'item/list.jinja'
        return super(ItemViewSet, self).list(request)

django-jinja 部分

我的模板设置是这样的:

TEMPLATES = [

    {
        'BACKEND': 'django_jinja.backend.Jinja2',
        'APP_DIRS': False,
        'DIRS': [
            os.path.join(BASE_DIR, 'templates'),
        ],
        'OPTIONS': {
            #'match_regex': "*\.jinja$",
            'match_extension': '.jinja',
            'newstyle_gettext': True,
            'autoescape': True,
            'auto_reload': DEBUG,
            'constants': {
                'STATIC_URL': STATIC_URL,
                'timezone': timezone, # timezone is django.utils.timezone
            },
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
                'django.template.context_processors.tz',
                'django.template.context_processors.media',
            ],
            'extensions': [
                'jinja2.ext.do',
                'jinja2.ext.loopcontrols',
                'jinja2.ext.with_',
                'jinja2.ext.i18n',
                'jinja2.ext.autoescape',
                'django_jinja.builtins.extensions.CsrfExtension',
                'django_jinja.builtins.extensions.CacheExtension',
                'django_jinja.builtins.extensions.TimezoneExtension',
                'django_jinja.builtins.extensions.UrlsExtension',
                'django_jinja.builtins.extensions.StaticFilesExtension',
                'django_jinja.builtins.extensions.DjangoFiltersExtension',

                'jdj_tags.extensions.DjangoStatic',
                'jdj_tags.extensions.DjangoI18n',
                'jdj_tags.extensions.DjangoStatic',
                'jdj_tags.extensions.DjangoUrl',
            ],
        }
    },

    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [
            os.path.join(BASE_DIR, 'templates'),
        ],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

设置部分

在我的 settings.py 中,INSTALLED_APPSMIDDLEWARE_CLASSES 看起来像这样:

INSTALLED_APPS = (
    'grappelli',

    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'pytz',
    'django_extensions',
    'easy_thumbnails',
    'django_jinja',
    'django_jinja.contrib._humanize',
    'django_jinja.contrib._easy_thumbnails',

    'rest_framework',

    'mysite.item',
)

MIDDLEWARE_CLASSES = (
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'django.middleware.security.SecurityMiddleware',

)

既然您可以看到整个系统,我遇到的问题是当我在 /items/ 加载页面时调用 ItemViewSet.list(request) 方法,我得到以下错误:

Environment:


Request Method: GET
Request URL: http://localhost:8000/items/

Django Version: 1.8.1
Python Version: 2.7.8
Installed Applications:
('grappelli',
 'django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'pytz',
 'django_extensions',
 'easy_thumbnails',
 'django_jinja',
 'django_jinja.contrib._humanize',
 'django_jinja.contrib._easy_thumbnails',
 'rest_framework',
 'mysite.item',
 'mysite.person')
Installed Middleware:
('django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware',
 'django.middleware.security.SecurityMiddleware')


Traceback:
File "/usr/local/lib/python2.7/site-packages/django/core/handlers/base.py" in get_response
  164.                 response = response.render()
File "/usr/local/lib/python2.7/site-packages/django/template/response.py" in render
  158.             self.content = self.rendered_content
File "/usr/local/lib/python2.7/site-packages/rest_framework/response.py" in rendered_content
  59.         ret = renderer.render(self.data, media_type, context)
File "/usr/local/lib/python2.7/site-packages/rest_framework/renderers.py" in render
  169.         return template.render(context)
File "/usr/local/lib/python2.7/site-packages/django_jinja/backend.py" in render
  66.         return self.template.render(context)
File "/usr/local/lib/python2.7/site-packages/jinja2/environment.py" in render
  972.         vars = dict(*args, **kwargs)

Exception Type: ValueError at /items/
Exception Value: dictionary update sequence element #0 has length 0; 2 is required

所以我在 /usr/local/lib/python2.7/site-packages/jinja2/environment.py 处编辑了文件并打印了 argskwargs 以便更好地理解发生了什么:

使用 print 打印 args 时:

([{
        'False': False,
        'None': None,
        'True': True
    },
    [OrderedDict(  ... here were all serialized objects returned  )], {}
], )

并且在打印 kwargs 时它只是返回 {}

所以基本上如果有人知道发生了什么或者我可以做些什么来解决这个问题。

提前谢谢你,托马斯

编辑 1

第一个主要发现是,在进行正常请求时,args 是一个包含平面 dict 的元组,而在使用 DRF 的请求中,args 是一个包含平面的元组RequestContext 对象。

所以很明显我尝试调用 args.flatten() 来获得一个普通的 dict 对象。现在这个方法抛出一个类似的错误:ValueError: dictionary update sequence element #0 has length 16; 2 is required.

此错误发生在 django.template.context.RequestContext.flatten() 中的代码中,即

def flatten(self):
        """
        Returns self.dicts as one dictionary
        """
        flat = {}
        for d in self.dicts:
            flat.update(d)  # <-- Exception occurs at this point
        return flat

我将尝试收集更多信息以解决此问题

编辑 2

我意识到 REST 框架在 TemplateHTMLRenderer 的 render 方法中实例化了 RequestContext,很明显这会破坏系统,因为 Jinja2 需要一个字典而不是 RequestContext。现在尝试编写修复程序

编辑 3:已修复

好吧,我能看到的解决这个问题的唯一方法是编写我自己的 REST 渲染器,所以我写了一个 JinjaTemplateRenderer,我将 post 在 github 上尝试合并该项目。我很快就会post

我可以通过编写自己的渲染器来解决这个问题,这里是 JinjaTemplateRenderer

虽然这最终并没有真​​正起作用,但它有很多问题,我将返回使用基于 class 的普通视图而不是 REST 框架,因为它不能满足我的需求。