I am utterly confused and mortified by CBVs, seeking for help.
So I've designed the model structure and decided the url patterns as following, but simply can't write a valid CBV to facilitate the 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>
Essentially, as you can see, I'd like the ItemListView
to render multiple url paths depending on what value passed into the CBV... I can do it (pass multiple values) in a FBV, however, utterly confused by the mechanism of the CBVs...
So if anyone could write an example ItemListView
and according anchor url template tags (if mine are incorrect), it would be tremendulent!!! Thanks!!!
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
After FiddleStix' answer (I haven't gone thu bluegrounds thread yet), I tried and managed to make everything work except ItemDetailView
.
The urls are working fine and the filtering of get_queryset
function is working fine, however,
Question 1 : I wonder this might not be DRY enough?! Nevertheless, it's working. so thanks!! But could it be dryer??
Question 2 : when ItemDetailView
runs, the urls appear to be correct, however, the page redirect to a page rendering all items from all categories...
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
Neither is the object nor the context being printed out... and it doesn't prompt any error, either. I must have made a silly mistake somewhere!!
view.py
class ItemDetailView(DetailView):
'''display an individual item'''
model = Item
template_name = 'boutique/item.html'
urlpatterns
:url.py
urlpatterns = [
path('item_<int:pk>', view.ItemDetailView.as_view(), name='item'),
]
<pk>
as the value passed in DetailView as it's the default. I used item_<int:item_pk>
as the path url. That's why I had to use get_object()
to manually get the item object (to override the default get_object()
). As @bluegrounds answer suggests that the reason Class-based Views work well is they save time, for the default functions they possess.item_<int:item_pk>
, CBV offers the flexibility as well: simply override pk_url_kwargs = 'item_pk'
in the View class - Feel free to check my other question: DetailView - get_object
function confusion for clarification. @neverwalkaloner 's answer is very straightforward. To answer you main question, your ItemList
should set model=models.Item
, as alluded to in the error message, because it is meant to be a list of items.
I would set up your urls.py so that /items/ or /item_list/ goes to ItemListView.as_view(). If you then want to filter your list of items, I would not do it by changing the URL to /items/women/. Instead, I would use a URL query string like /items/?gender=women. How to do that is explained here but basically:
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
So first things first, CBVs...
They work by having "default" behaviours built into each one of them. For the ListView class, take this example view:
class ArticleListView(ListView):
model = Article
along with an example urlpattern:
path('all-articles/', ArticleListView.as_view())
and that's it. This is all that is essential for a ListView to work. This ArticleListView
view would look for a template called article_list.html
and in it you can use the context variable object_list
to access all the Article objects that the class gets for you without you having to write the QuerySet explicitly.
Of course you can change these values, and customize the QuerySet, and do all kinds of things, but for that you'll have to study the docs. I personally find ccbv much easier to read than the docs. So for example you can see in ccbv's page about ListViews that the context_object_name = None
which defaults to object_list
, as mentioned above. You can change that to, for example context_object_name = 'my_articles'
. You could also set the template_name = 'my_articles.html'
and that will override the default template name pattern of < model >_list.html.
Now, about your code,
If you're sure that you want your URL structure to stay like it is, you could have your class view as follows to get the functionality you need:
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
As you can see, we didn't set many things in this class view for it to work. Namely, we didn't set the model
variable as we did previously. That's because we wouldn't need it. The part that uses that variable was in the default get_queryset()
method and we've overridden that method. See CCBV for more info on the default implementation of get_queryset()
.
Now the template will be supplied with the objects from get_queryset()
, under the name categories
, because that's what we set context_object_name
's value to be.
NOTE: The variable model
is used in other places other than get_queryset()
such as the default template_name
. The default template name is derived from the model name and the template_name_suffix
. So if you don't set the model
variable, make sure to set the template_name
manually.
I'm not sure about your application's logic but I think you should change the Category
model to have only one Boolean field that denotes gender. For example men if it is True and women if it's False. That way a category can't be for both men and women at the same time (unless that is something you need), and it also can't be for neither, because currently you can have a category be false for the both gender fields, which doesn't really make sense.
I would actually suggest a completely different solution that involves CHOICES, as such:
gender = models.IntegerField(null=False, CHOICES=[(1,'Men'), (2,'Women'), (3,'Other'), (4,'Unisex')], default=3)
This would store a number in the database that denotes the gender, and in your app you'll only see the correlating string (gender) to that number.
I have not tried this code on my machine so I might have missed a few things, but I hope I clarified the overall workings of CBVs.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.