【问题标题】:django - CBV - pass in multiple values from urldjango - CBV - 从 url 传入多个值
【发布时间】:2020-05-22 16:44:41
【问题描述】:

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

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

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 之外的所有东西都能正常工作。

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

问题 1:我想知道这可能不够 DRY?!尽管如此,它仍然有效。那谢谢啦!!但是可以烘干吗??

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

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类应该很简单,问题出在urlpatterns

url.py

urlpatterns = [
    path('item_<int:pk>', view.ItemDetailView.as_view(), name='item'),
]
  • 始终使用&lt;pk&gt; 作为在DetailView 中传递的值,因为它是默认值。我使用item_&lt;int:item_pk&gt; 作为路径url。这就是为什么我不得不使用get_object() 来手动获取项目对象(以覆盖默认的get_object())。正如@bluegrounds 的回答所暗示的那样,基于类的视图运行良好的原因是它们可以节省时间,因为它们拥有的默认功能。
  • 如果您希望使用item_&lt;int:item_pk&gt;,CBV 也提供了灵活性:只需在 View 类中覆盖 pk_url_kwargs = 'item_pk' - 请随时查看 my other question: DetailView - get_object function confusion 以获取说明。 @neverwalkaloner 的回答非常直接。

【问题讨论】:

  • 你的问题不是很清楚。我假设您的 urls.py 正在运行,您可以转到 /women/1/ 或 /women/1/3 以获取要显示的页面。但是,我认为您想访问 CBV 中的“女性”、“1”和“3”,以便可以将它们传递回模板以呈现更多 URL?如果是这样,您可以在您的get_context_data() 中执行gender = self.kwargs['gender']。 category_pk 和 subcategory_pk 也是如此。
  • @FiddleStix 抱歉,我应该说得更清楚一点:ImproperlyConfigured at /women/ ItemListView is missing a QuerySet. Define ItemListView.model, ItemListView.queryset, or override ItemListView.get_queryset(). 除了索引之外,所有网址都不起作用...
  • 确切地说,我不知道 url.py 中的 是如何传递到 ItemListView 中的,更不用说显示子类别列表视图时的 3 个 了。它不像FBV,一切都是在定义函数时以参数的形式传入的。但是,CBV 如何从 url 中捕获 3 个值让我感到困惑......

标签: python django django-class-based-views


【解决方案1】:

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

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

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

【讨论】:

  • 感谢您的及时答复!在摆弄了几个小时后,我设法让它工作,上面附加了代码,我想知道它是否可以是 DRYer?我会很感激一些反馈的欢呼~
【解决方案2】:

首先,CBV...

它们通过将“默认”行为内置到每一个中来工作。对于 ListView 类,以这个示例视图为例:

class ArticleListView(ListView):
    model = Article

连同一个示例 urlpattern:

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

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

当然,您可以更改这些值、自定义 QuerySet 以及做各种事情,但为此您必须研究文档。我个人觉得ccbv 比文档更容易阅读。例如,您可以在 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 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

如您所见,我们没有在这个类视图中设置很多东西来让它工作。也就是说,我们没有像以前那样设置model 变量。那是因为我们不需要它。使用该变量的部分在默认的get_queryset() 方法中,我们已经覆盖了该方法。有关 get_queryset() 的默认实现的更多信息,请参阅 CCBV。

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

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

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

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

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

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


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

【讨论】:

  • 非常感谢!!答案非常彻底。感谢您向我介绍 CCBV。它帮助很大! @bluegrounds
  • @DL8,我很高兴。至于您更新的第二个问题,您解决了吗?我认为 urls.py 有问题,因为 DetailView 无法将项目列表发送到模板,除非你明确地弄乱get_context_data()
  • 另外,我发现打印到控制台并不是调试 django 应用程序的一种非常有效的方法。如果您不想使用 IDE,我建议您使用 pyCharm 或“Werkzeug”之类的 IDE。
  • 是的!我已经解决了这个问题并附上了上面的答案。正如你所说,这是urlpatterns 的问题。我正在使用 Visual Studio,我确实对插件感到不知所措......你会推荐 'Werkzeug' 而不是 IDE 吗?
  • 没有 Visual Studio 很好。我没有用它来开发 python 应用程序,但我知道它是一个很好的 IDE。研究使用断点。我认为你不需要配置任何东西来使用它们。
猜你喜欢
  • 1970-01-01
  • 2017-08-31
  • 2013-06-29
  • 2015-01-16
  • 2017-09-24
  • 1970-01-01
  • 2013-12-05
  • 1970-01-01
  • 2018-07-29
相关资源
最近更新 更多