django - CBV - 从 url 传入多个值

django - CBV - pass in multiple values from url

我对 CBV 感到非常困惑和羞愧,寻求帮助。

所以我设计了模型结构并确定 url 模式如下,但根本无法编写有效的 CBV 来促进 urls:

models.py

class Category(models.Model):
    '''Category for men's and women's items'''
    men = models.BooleanField()
    women = models.BooleanField()
    name = models.CharField(max_length=100)
    description = models.CharField(max_length=300, blank=True)
    uploaded_date = models.DateTimeField(
        auto_now_add=True, null=True, blank=True)

    class Meta():
        verbose_name_plural = 'Categories'

    def __str__(self):
        return ("Men's " + self.name) if self.men else ("Women's " + self.name)


class SubCategory(models.Model):
    '''Sub-category for the categories (not mandatory)'''
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    name = models.CharField(max_length=100)
    description = models.CharField(max_length=300, blank=True)
    uploaded_date = models.DateTimeField(
        auto_now_add=True, null=True, blank=True)

    class Meta():
        verbose_name = 'Sub-category'
        verbose_name_plural = 'Sub-categories'

    def __str__(self):
        return ("Men's " + self.name) if self.category.men else ("Women's " + self.name)


class Item(models.Model):
    '''Each item represents a product'''
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    subcategory = models.ForeignKey(
        SubCategory, on_delete=models.CASCADE, null=True, blank=True)
    name = models.CharField(max_length=100)
    description = models.TextField(blank=True)
    price = models.IntegerField(default='0')
    discount = models.IntegerField(null=True, blank=True)
    uploaded_date = models.DateTimeField(
        auto_now_add=True, null=True, blank=True)

    class Meta:
        ordering = ['-uploaded_date']

    def __str__(self):
        return self.name

    def discounted_price(self):
        '''to calculate the price after discount'''
        return int(self.price * (100 - self.discount) * 0.01)


class ItemImage(models.Model):
    item = models.ForeignKey(Item, on_delete=models.CASCADE)
    image = models.ImageField(upload_to='itemimages', null=True, blank=True)


urls.py

app_name = 'boutique'
urlpatterns = [
    # show index page
    path('', views.IndexView.as_view(), name='index'),

    # show categories of products for men or women
    path('<slug:gender>/', views.ItemListView.as_view(), name='show-all'),

    # show a specific category for men or women
    path('<slug:gender>/cat_<int:category_pk>/', views.ItemListView.as_view(), name='category'),

    # show a specific subcategory under a specific category for men or women
    path('<slug:gender>/cat_<int:category_pk>/subcat_<int:subcategory_pk>/', views.ItemListView.as_view(), name='subcategory'),

    # show a specific item
    path('item_<int:item_pk>/', views.ItemDetailView.as_view(), name='item'),
]

views.py

class IndexView(ListView):
    '''landing page'''
    model = Category
    template_name = 'boutique/index.html'
    context_object_name = 'categories'


class ItemListView(ListView):
    '''display a list of items'''
    # model = Category ??? what's the point of declaring model when get_context_data() ???
    template_name = 'boutique/items.html'
    context_object_name = 'categories'
    paginate_by = 12

    def get_object(self):
        obj = get_object_or_404(Category, pk=self.kwargs.get('category_pk'))
        return obj

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['categories'] = Category.objects.all() # for rendering nav bar data
        return context

class ItemDetailView(DetailView):
    '''display an individual item'''
    # model = Item
    template_name = 'boutique/item.html'
    context_object_name = 'item'

    def get_object(self):
        return get_object_or_404(Item, pk=self.kwargs['item_pk'])

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['categories'] = Category.objects.all()
        return context

items.html

<a href="{% url 'boutique:show-all' 'women' %}> link to categories of product for women </a>
<a href="{% url 'boutique:category' 'women' category.pk %}> link to a cat for women </a>
<a href="{% url 'boutique:subcategory' 'women' category.pk subcategory.pk %}> link to a subcat under a specific cat for women </a>

基本上,如您所见,我希望 ItemListView 根据传入 CBV 的值呈现多个 url 路径...我可以做到(传递多个值) 在 FBV 中,然而,完全被 CBV 的机制搞糊涂了...

所以如果有人能写一个例子ItemListView并根据锚点url模板标签(如果我的不正确),那将是巨大的!!!谢谢!!!

编辑 1

class ItemListView(ListView):
    '''display a list of items'''
    model = Item
    template_name = 'boutique/items.html'
    # paginate_by = 12

    def get_queryset(self):
        # get original queryset: Item.objects.all()
        qs = super().get_queryset()

        # filter items: men/women
        if self.kwargs['gender'] == 'women':
            qs = qs.filter(category__women=True)
        elif self.kwargs['gender'] == 'men':
            qs = qs.filter(category__men=True)

        if self.kwargs.get('category_pk'):
            qs = qs.filter(category=self.kwargs.get('category_pk'))
            if self.kwargs.get('subcategory_pk'):
                qs = qs.filter(subcategory=self.kwargs.get('subcategory_pk'))

        # print(qs)
        return qs

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        # add categories for navbar link texts
        context['categories'] = Category.objects.all()

        if self.kwargs.get('gender') == 'women':
            context['category_shown'] = Category.objects.filter(women=True)
        if self.kwargs.get('gender') == 'men':
            context['category_shown'] = Category.objects.filter(men=True)

        if self.kwargs.get('category_pk'):
            context['category_shown']=get_object_or_404(Category, pk=self.kwargs.get('category_pk'))
            if self.kwargs.get('subcategory_pk'):
                context['subcategory_shown']=get_object_or_404(SubCategory, pk=self.kwargs.get('subcategory_pk'))

        # print(context)
        return context

在 FiddleStix 的回答之后(我还没有进入 bluegrounds 线程),我尝试并设法使除 ItemDetailView 之外的一切正常。

urls 工作正常,get_queryset 函数的过滤工作正常,但是,

问题 1:我想知道这可能还不够干?!尽管如此,它的工作。那谢谢啦!!但它可以更干燥吗??

问题 2:当 ItemDetailView 运行时,urls 似乎是正确的,但是,页面重定向到一个呈现所有项目的页面所有类别...

class ItemDetailView(DetailView):
    '''display an individual item'''
    # model = Item
    template_name = 'boutique/item.html'

    def get_object(self):
        print(get_object_or_404(Item, pk=self.kwargs.get('item_pk')))
        return get_object_or_404(Item, pk=self.kwargs.get('item_pk'))

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        # add categories for navbar link texts
        # context['categories'] = Category.objects.all()
        print(context)
        return context

对象和上下文都没有被打印出来...而且它也没有提示任何错误。我一定是在什么地方犯了一个愚蠢的错误!!


问题 2 的答案:

view.py

class ItemDetailView(DetailView):
    '''display an individual item'''
    model = Item
    template_name = 'boutique/item.html'

DetailView class 如果不需要自定义,应该很简单,问题出在urlpatterns:

url.py

urlpatterns = [
    path('item_<int:pk>', view.ItemDetailView.as_view(), name='item'),
]

要回答您的主要问题,您的 ItemList 应该设置 model=models.Item,如错误消息中所提到的,因为它应该是一个项目列表。

我会设置您的 urls.py,以便 /items/ 或 /item_list/ 转到 ItemListView.as_view()。如果您随后想过滤您的项目列表,我不会通过将 URL 更改为 /items/women/ 来实现。相反,我会使用 URL 查询字符串,例如 /items/?gender=women。 解释了如何做到这一点,但基本上:

class ItemListView(ListView):
    model = Item
    template_name = "item_list.html"
    paginate_by = 100

    def get_queryset(self):
        filter_val = self.request.GET.get('gender', 'some_default')

        queryset = Item.objects.filter(
            ...  # You'll have to work this bit out
        )
        return queryset 

    def get_context_data(self, **kwargs):
        context = super(MyView, self).get_context_data(**kwargs)
        context['gender'] = self.request.GET.get('gender', 'some_default')
        return context

首先,CBV...

它们的工作原理是将 "default" 行为内置到每一个中。对于 ListView class,以这个示例视图为例:

class ArticleListView(ListView):
    model = Article

连同 urlpattern 示例:

path('all-articles/', ArticleListView.as_view())

就是这样。这就是 ListView 工作所必需的。此 ArticleListView 视图将查找名为 article_list.html 的模板,您可以在其中使用上下文变量 object_list 访问 class 为您获取的所有 Article 对象,而无需您必须显式编写 QuerySet。

当然,您可以更改这些值,自定义 QuerySet 以及执行各种操作,但为此您必须研究文档。我个人发现 ccbv much easier to read than the docs. So for example you can see in ccbv's page 关于 ListViews 的 context_object_name = None 默认为 object_list,如上所述。您可以将其更改为,例如 context_object_name = 'my_articles'。您还可以设置 template_name = 'my_articles.html',这将覆盖 <model>_list.html.

的默认模板名称模式

现在,关于您的代码,

如果您确定希望 URL 结构保持原样,您可以按如下方式设置 class 视图以获得所需的功能:

class ItemListView(ListView):
    template_name = 'boutique/items.html'
    context_object_name = 'categories'
    paginate_by = 12

    def get_queryset(self):
        # This method should return a queryset that represents the items to be listed in the view.
        # I think you intend on listing categories in your view, in which case consider changing the view's name to CategoryListView. Just sayin'...
        # An instance of this view has a dictionary called `kwargs` that has the url parameters, so you can do the following:

        # You need some null assertions here because of the way you've setup your URLs
        qs = Categories.objects.filter(men=self.kwargs['gender'], pk=self.kwargs['category_pk'])
        return qs

如您所见,我们并未在此 class 视图中设置太多内容以使其正常工作。也就是说,我们没有像之前那样设置 model 变量。那是因为我们不需要它。使用该变量的部分在默认的 get_queryset() 方法中,我们已经覆盖了该方法。有关 get_queryset().

默认实现的更多信息,请参阅 CCBV

现在模板将以 categories 的名称提供来自 get_queryset() 的对象,因为这就是我们设置的 context_object_name 的值。

注意:变量model用于除get_queryset()以外的其他地方,例如默认的template_name。默认模板名称源自模型名称和 template_name_suffix。因此,如果您不设置 model 变量,请确保手动设置 template_name

我不确定您的应用程序的逻辑,但我认为您应该将 Category 模型更改为只有一个表示性别的布尔字段。例如,如果是 True 则为男性,如果为 False 则为女性。这样一个类别就不能同时适用于男性和女性(除非这是你需要的),而且它也不能同时适用于男性和女性,因为目前你可以让一个类别对于两个性别字段都是错误的, 这真的没有意义。

我实际上会建议一个涉及 CHOICES 的完全不同的解决方案,例如:

gender = models.IntegerField(null=False, CHOICES=[(1,'Men'), (2,'Women'), (3,'Other'), (4,'Unisex')], default=3)

这会在数据库中存储一个表示性别的数字,而在您的应用中,您只会看到与该数字相关的字符串(性别)。


我没有在我的机器上尝试过这段代码,所以我可能遗漏了一些东西,但我希望我阐明了 CBV 的整体工作原理。