django - CBV - передать несколько значений из URL - PullRequest
1 голос
/ 06 февраля 2020

Я в полном замешательстве и огорчен 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 отображал несколько путей URL в зависимости от того, какое значение было передано в CBV ... Я могу это сделать ( передать несколько значений) в 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.

URL-адреса работают нормально, а фильтрация функции get_queryset работает нормально, однако,

Вопрос 1 : Интересно, этого может быть недостаточно DRY ?! Тем не менее, это работает. тогда спасибо!! Но может ли это быть более сухим ??

Вопрос 2 : при запуске ItemDetailView 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'),
]
  • Всегда используйте <pk> в качестве значения, передаваемого в DetailView, поскольку это значение по умолчанию. Я использовал item_<int:item_pk> в качестве URL-адреса пути. Вот почему мне пришлось использовать get_object(), чтобы вручную получить объект элемента (чтобы переопределить значение по умолчанию get_object()). Поскольку ответ @bluegrounds говорит о том, что причина, по которой представления на основе классов работают хорошо, заключается в том, что они экономят время и используют функции по умолчанию.
  • Если вы хотите sh использовать item_<int:item_pk>, CBV также предлагает гибкость. : просто переопределить pk_url_kwargs = 'item_pk' в классе View - не стесняйтесь проверять мой другой вопрос: DetailView - get_object функция путаницы для уточнения. Ответ @neverwalkaloner очень простой.

Ответы [ 2 ]

1 голос
/ 06 февраля 2020

Итак, обо всем по порядку, 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 о ListViews, что context_object_name = None со значением по умолчанию object_list, как упомянуто выше. Вы можете изменить это, например, context_object_name = 'my_articles'. Вы также можете установить template_name = 'my_articles.html', и это заменит шаблон имени шаблона по умолчанию <<em> модель > _ список. 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() по умолчанию, и мы переопределили этот метод. Смотрите CCBV для получения дополнительной информации о реализации по умолчанию get_queryset().

Теперь шаблон будет поставляться с объектами из get_queryset() под именем categories, потому что это то, что мы установили context_object_name ' Значение s должно быть.

ПРИМЕЧАНИЕ. Переменная model используется в других местах, кроме get_queryset(), таких как значение по умолчанию template_name. Имя шаблона по умолчанию получено из названия модели и template_name_suffix. Поэтому, если вы не установите переменную model, убедитесь, что вы установили template_name вручную.

Я не уверен в логике вашего приложения c, но я думаю, что вы должны изменить Category модель должна иметь только одно логическое поле, обозначающее пол. Например, мужчины, если это правда, и женщины, если это ложь. Таким образом, категория не может быть как для мужчин, так и для женщин одновременно (если это не то, что вам нужно), и также не может быть ни для одного, потому что в настоящее время вы можете иметь категорию, ложную для обоих половых полей. , что на самом деле не имеет смысла.

Я бы на самом деле предложил совершенно другое решение, включающее ВЫБОР, как таковое:

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

Это сохранит в базе данных число, обозначающее пола, и в вашем приложении вы увидите только коррелирующую строку (пол) с этим числом.


Я не пробовал этот код на своей машине, поэтому я мог пропустить несколько вещей, но я надеюсь, я уточнил общую работу CBV.

1 голос
/ 06 февраля 2020

Чтобы ответить на ваш главный вопрос, ваш ItemList должен установить model=models.Item, как указано в сообщении об ошибке, поскольку он должен быть списком элементов.

Я бы настроил ваш urls.py так, чтобы / items / или / item_list / перешел к ItemListView.as_view (). Если вы хотите отфильтровать список элементов, я бы не стал делать это, изменив URL на / items / women /. Вместо этого я бы использовал строку URL-запроса, например / items /? Sex = 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
...