[英]django - CBV - pass in multiple values from url
我对 CBV 感到非常困惑和羞愧,寻求帮助。
因此,我设计了模型结构并确定了以下 url 模式,但根本无法编写有效的 CBV 来简化 url :
模型.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)
网址.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'),
]
视图.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
项目.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 模板标签(如果我的不正确),那就太棒了!!! 谢谢!!!
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 的回答之后(我还没有去过 thu bluegrounds 线程),我尝试并设法使除ItemDetailView
之外的所有内容都能正常工作。
url 工作正常, get_queryset
函数的过滤工作正常,但是,
问题 1 :我想知道这可能还不够 DRY?! 尽管如此,它仍然有效。 那谢谢啦!! 但它会更干燥吗??
问题 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
对象和上下文都没有被打印出来......它也不会提示任何错误。 我一定是在某个地方犯了一个愚蠢的错误!!
查看.py
class ItemDetailView(DetailView):
'''display an individual item'''
model = Item
template_name = 'boutique/item.html'
urlpatterns
:网址.py
urlpatterns = [
path('item_<int:pk>', view.ItemDetailView.as_view(), name='item'),
]
<pk>
作为 DetailView 中传递的值,因为它是默认值。 我使用item_<int:item_pk>
作为路径 url。 这就是为什么我必须使用get_object()
手动获取项目对象(覆盖默认的get_object()
)。 正如@bluegrounds 的回答所暗示的那样,基于类的视图运行良好的原因是它们为它们拥有的默认功能节省了时间。item_<int:item_pk>
,CBV 也提供了灵活性:只需在 View 类中覆盖pk_url_kwargs = 'item_pk'
- 随意检查我的另一个问题:DetailView - get_object
函数混淆以进行澄清。 @neverwalkaloner 的回答非常简单。要回答您的主要问题,您的ItemList
应该设置model=models.Item
,正如错误消息中提到的那样,因为它是一个项目列表。
我会设置您的 urls.py,以便 /items/ 或 /item_list/ 转到 ItemListView.as_view()。 如果您想过滤您的项目列表,我不会通过将 URL 更改为 /items/women/ 来实现。 相反,我会使用像 /items/?gender=women 这样的 URL 查询字符串。 这里解释了如何做到这一点,但基本上是:
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
所以首先,CBVs...
它们的工作原理是将“默认”行为内置到它们中的每一个中。 对于 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 的关于 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 get_queryset()
的默认实现的更多信息,请参阅 CCBV。
现在模板将与来自get_queryset()
的对象一起提供,在名称categories
,因为这是我们设置的context_object_name
的值。
注意:变量model
用于get_queryset()
以外的其他地方,例如默认的template_name
。 默认模板名称源自模型名称和template_name_suffix
。 因此,如果您不设置model
变量,请确保手动设置template_name
。
我不确定您的应用程序的逻辑,但我认为您应该将Category
模型更改为只有一个表示性别的布尔字段。 例如男人如果是真,女人如果是假。 这样一个类别不能同时适用于男性和女性(除非这是您需要的东西),也不能同时适用于两者,因为目前您可以将两个性别字段的类别设为 false ,这真的没有意义。
我实际上会建议一个涉及选择的完全不同的解决方案,例如:
gender = models.IntegerField(null=False, CHOICES=[(1,'Men'), (2,'Women'), (3,'Other'), (4,'Unisex')], default=3)
这将在数据库中存储一个表示性别的数字,而在您的应用程序中,您只会看到与该数字相关的字符串(性别)。
我没有在我的机器上尝试过这段代码,所以我可能错过了一些东西,但我希望我澄清了 CBV 的整体工作原理。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.