Django中通过标签获取相关对象

Getting related objects via tags in Django

我的目标是在访问单个产品页面时在我的电子商务网站中建立一个 "Recommended Products" 部分。

我有一系列产品,在管理员中有几个用户定义的标签。标签系统是 django-taggitmodelcluster 的组合,详见 Wagtail-CMS docs.

我试图做到这一点,以便在访问产品页面时,Django 会查看带有 same/similar 标签的所有其他产品,并根据数量将它们列在 "Recommended Products" 部分中相同的标签。根据 their docs.

django-taggit 文档似乎通过 get_related() 函数在 API 中解决了这一需求

我正在努力让它工作,但是因为我不断遇到错误,最新的错误是 Exception Type: KeyError at /categories/test-category/test-product/ Exception Value: (15,)。到目前为止,这是我的代码:

class ProductTag(TaggedItemBase):
    content_object = ParentalKey('Product', related_name='tagged_items')

class Product(Page):
    ...

    tags = ClusterTaggableManager(through=ProductTag, blank=True)

def get_context(self, request):
    context = super(Product, self).get_context(request)

    current_tags = self.tags
    related_products = Product.objects.filter(current_tags.similar_objects())

    context['related_products'] = related_products
    return context

编辑 |完整的错误回溯如下:

Environment:


Request Method: GET
Request URL: http://127.0.0.1:8000/categories/test-category/test-product/

Django Version: 1.11.5
Python Version: 3.5.2
Installed Applications:
['home',
 'search',
 'products',
 'wagtail.wagtailforms',
 'wagtail.wagtailredirects',
 'wagtail.wagtailembeds',
 'wagtail.wagtailsites',
 'wagtail.wagtailusers',
 'wagtail.wagtailsnippets',
 'wagtail.wagtaildocs',
 'wagtail.wagtailimages',
 'wagtail.wagtailsearch',
 'wagtail.wagtailadmin',
 'wagtail.wagtailcore',
 'modelcluster',
 'taggit',
 'django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles']
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',
 'wagtail.wagtailcore.middleware.SiteMiddleware',
 'wagtail.wagtailredirects.middleware.RedirectMiddleware']



Traceback:

File "C:\Users\ddl_9\Envs\fstvl\lib\site-packages\django\core\handlers\exception.py" in inner
  41.             response = get_response(request)

File "C:\Users\ddl_9\Envs\fstvl\lib\site-packages\django\core\handlers\base.py" in _get_response
  187.                 response = self.process_exception_by_middleware(e, request)

File "C:\Users\ddl_9\Envs\fstvl\lib\site-packages\django\core\handlers\base.py" in _get_response
  185.                 response = wrapped_callback(request, *callback_args, **callback_kwargs)

File "C:\Users\ddl_9\Envs\fstvl\lib\site-packages\wagtail\wagtailcore\views.py" in serve
  26.     return page.serve(request, *args, **kwargs)

File "C:\Users\ddl_9\Envs\fstvl\lib\site-packages\wagtail\wagtailcore\models.py" in serve
  773.             self.get_context(request, *args, **kwargs)

File "C:\Users\ddl_9\Desktop\fstvl\products\models.py" in get_context
  143.      related_products = current_tags.similar_objects()

File "C:\Users\ddl_9\Envs\fstvl\lib\site-packages\taggit\utils.py" in inner
  146.         return func(self, *args, **kwargs)

File "C:\Users\ddl_9\Envs\fstvl\lib\site-packages\taggit\managers.py" in similar_objects
  350.                 tuple(result[k] for k in lookup_keys)

Exception Type: KeyError at /categories/test-category/test-product/
Exception Value: (15,)

如果我尝试访问其他产品页面,我会遇到同样的错误,只是异常值不同。该函数需要来自字典的键,但由于某种原因,它被赋予了值...这可能是代码的兼容性问题吗?

好的,经过大量的研究和修改,我找到了一个解决方案,但是这个 只适用于 Django >= 1.9.

Github 用户 nickhudkins 运行 遇到了同样的问题并编辑了 similar_objects() 函数来解决 KeyError,详见他的 ticket.

解决方法是进入 taggit managers.py 并按如下方式编辑函数(+ 表示添加行,- 表示删除它们)。

             objs = rel_model._default_manager.filter(**{
                  "%s__in" % remote_field.field_name: [r["content_object"] for r in qs]
              })
 +            actual_remote_field_name = remote_field.field_name
 +            if VERSION > (1, 9):
 +                actual_remote_field_name = f.target_field.get_attname()
 +            else:
 +                actual_remote_field_name = f.related_field.get_attname()
              for obj in objs:
 -                items[(getattr(obj, remote_field.field_name),)] = obj
 +                items[(getattr(obj, actual_remote_field_name),)] = obj
          else:
              preload = {}
              for result in qs:

如果您不想修补 django-taggit,其他可能的解决方案是使用 raw sql 查询。类似于:

ArticlePage.objects.raw('select a.page_ptr_id, p.title, count(at.tag_id) as tag_count from article_articlepage a join wagtailcore_page p on a.page_ptr_id = p.id join article_articletag at on at.content_object_id = a.page_ptr_id join taggit_tag t on t.id = at.tag_id where tag_id in (1, 2, 3) group by (page_ptr_id, p.id) order by tag_count desc');

ArticlePage - 我的模型

ArticleTag - 我的标签的 m2m 模型(继承自 TaggedItemBase

wagtailcore_page - 基本 Wagtail table 页面

taggit_tag - Taggit 标签 table

(1, 2, 3) - 只是示例标记 ID

希望对您有所帮助!请随意根据您的需要进行调整。