You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django中如何引用继承模型?基于GET参数筛选子类模型遇阻

问题描述

我想在Django中根据继承的模型类型筛选产品,尝试用返回模型名称的model_name属性实现,但筛选未生效。需求是通过GET请求传入的分类值,在get_queryset()方法里过滤出对应类型的产品。

现有代码

models.py

class Product(PolymorphicModel):
    title = models.CharField(max_length=100, blank=True)
    image = models.ImageField(upload_to='product', default=None)
    quantity = models.IntegerField(null=False)
    is_available = models.BooleanField(default=True, null=False)
    price = models.IntegerField(null=False, blank=False, default=15)
    popularity = models.IntegerField(default=0)

    def __str__(self):
        return str(self.title)

    def get_absolute_url(self):
        return reverse("ProductDetail", args=[str(self.pk)])

    @property
    def model_name(self):
        return self._meta.model_name

class CD(Product):
    GENRE_CHOICES = (
        ('Disco', 'Disco'),
        ('Electronic music', 'Electronic music'),
        ('Rap', 'Rap'),
        ('Reggae', 'Reggae'),
        ('Rock', 'Rock'),
        ('Pop', 'Pop'),
    )
    band = models.CharField(max_length=100, null=False, blank=False)
    tracklist = models.TextField(max_length=500, null=False, blank=False)
    genre = models.CharField(max_length=100, choices=GENRE_CHOICES, null=False, blank=False)

class Book(Product):
    GENRE_CHOICES = (
        ('Biography', 'Biography'),
        ('Criminal', 'Criminal'),
        ('Fantasy', 'Fantasy'),
        ('Historical Novel', 'Historical Novel'),
        ('Horror', 'Horror'),
        ('Romance', 'Romance'),
        ('Sci-Fi', 'Sci-Fi'),
    )
    author = models.CharField(max_length=100, null=False, blank=False)
    isbn = models.CharField(max_length=100, null=False, blank=False, unique=True)
    genre = models.CharField(max_length=100, choices=GENRE_CHOICES, null=False, blank=False)

class Film(Product):
    GENRE_CHOICES = (
        ('Adventure', 'Adventure'),
        ('Animated', 'Animated'),
        ('Comedy', 'Comedy'),
        ('Horror', 'Horror'),
        ('Thriller', 'Thriller'),
        ('Romance', 'Romance'),
    )
    director = models.CharField(max_length=100, null=False, blank=False)
    duration = models.IntegerField(null=False, blank=False)
    genre = models.CharField(max_length=100, choices=GENRE_CHOICES, null=False, blank=False)

views.py

class Statistics(ListView):
    model = Product
    template_name = 'orders/statistics.html'

    def get_queryset(self):
        qs = super(Statistics, self).get_queryset()
        if self.request.GET:
            qs = qs.filter(model_name=self.request.GET['category'])
        return qs

    def get_context_data(self, **kwargs):
        data = super().get_context_data(**kwargs)
        data['form'] = CategoryForm
        return data

forms.py

from django import forms

class CategoryForm(forms.Form):
    CATEGORY_CHOICES = (
        ('book', 'Book'),
        ('cd', 'CD'),
        ('film', 'Film'),
    )

    category = forms.ChoiceField(choices=CATEGORY_CHOICES)

模板文件

{% extends 'base.html' %}
{% load static %}
{% block content %}
{% for item in object_list %}
    <form method="get">
        {{ form }}
        <input type="submit" value="Search">
    </form>

    <table>
        <tr>
            <th>Title</th>
            <th>Category</th>
            <th>Genre</th>
            <th>Popularity</th>
        </tr>
        <tr>
            <td>{{ item.title }}</td>
            <td>{{ item.model_name}}</td>
            <td>{{ item.genre }}</td>
            <td>{{item.popularity}}</td>
        </tr>
    </table>
{% endfor %}
{% endblock %}

问题原因

  1. 使用filter(model_name=...)是错误的:model_name是你定义的实例属性,并非数据库字段,Django ORM无法直接用它构建数据库查询语句。
  2. 模板中表单放在{% for %}循环内部,会导致每个产品都渲染一次表单,属于冗余操作。

解决方案

1. 修改视图的筛选逻辑

由于使用了PolymorphicModel,它自带instance_of()方法可直接筛选指定子类的实例。我们可以建立分类值到模型类的映射,再用该方法过滤:

修改views.py的get_queryset方法:

from .models import Book, CD, Film  # 导入子类模型

class Statistics(ListView):
    model = Product
    template_name = 'orders/statistics.html'

    def get_queryset(self):
        qs = super().get_queryset()
        category = self.request.GET.get('category')
        # 建立分类值与对应模型的映射
        category_model_map = {
            'book': Book,
            'cd': CD,
            'film': Film,
        }
        if category in category_model_map:
            # 筛选对应子类的实例
            qs = qs.instance_of(category_model_map[category])
        return qs

    def get_context_data(self, **kwargs):
        data = super().get_context_data(**kwargs)
        data['form'] = CategoryForm
        return data

2. 修复模板的表单位置

将表单移到循环外面,避免重复渲染:

{% extends 'base.html' %}
{% load static %}
{% block content %}
<!-- 表单移至循环外部 -->
<form method="get">
    {{ form }}
    <input type="submit" value="Search">
</form>

{% for item in object_list %}
    <table>
        <tr>
            <th>Title</th>
            <th>Category</th>
            <th>Genre</th>
            <th>Popularity</th>
        </tr>
        <tr>
            <td>{{ item.title }}</td>
            <td>{{ item.model_name}}</td>
            <td>{{ item.genre }}</td>
            <td>{{item.popularity}}</td>
        </tr>
    </table>
{% empty %}
    <p>暂无相关产品</p>
{% endfor %}
{% endblock %}

额外优化方案

如果不想依赖PolymorphicModel的instance_of方法,也可以直接通过子类模型的objects.all()获取数据:

def get_queryset(self):
    category = self.request.GET.get('category')
    category_model_map = {
        'book': Book,
        'cd': CD,
        'film': Film,
    }
    if category in category_model_map:
        return category_model_map[category].objects.all()
    # 默认返回所有产品
    return Product.objects.all()

内容的提问来源于stack exchange,提问作者Tylzan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 00:08:00