pythondjangodjango-modelsdjango-viewsweb-frameworks

How to render all data related to object id in views.py


I am trying to create a webapp for a restaurant to display the different menus available, Breakfast, Lunch, evening etc. I have created models for Menu, Menu Category and Menu Items. I have a menu.html page that displays the available menus to view image of menu list on site . What I would like to happen when a user clicks the menu they wish to view is a menu_detail page to load and populated with required categories and items for that particular menu. Currently when clicked it will load the correct menu based on id but then only load a single category with the same id not all categories related to that menu and wont render any items

models.py

from django.db import models


"""
A Menu represents a collection of categories of food items. For example,
a restaurant may have a Lunch menu, and a Dinner menu.
"""


class Menu(models.Model):
    name = models.CharField(max_length=246, unique=True,)
    slug = models.SlugField(max_length=24, unique=True, help_text='The slug is the URL friendly version of the menu name, so that this can be accessed at a URL like mysite.com/menus/dinner/.')
    additional_text = models.CharField(max_length=128, null=True, blank=True, help_text='Any additional text that the menu might need, i.e. Served between 11:00am and 4:00pm.')
    order = models.PositiveSmallIntegerField(default=0, help_text='The order of the menu determines where this menu appears alongside other menus.')

    class Meta:
        verbose_name = 'menu name'

    def __str__(self):
        return self.name


"""
A Menu Category represents a grouping of items within a specific Menu.
An example of a Menu Category would be Appetizers, or Pasta.
"""


class MenuCategory(models.Model):
    menu = models.ForeignKey(Menu, null=True, help_text='The menus that this category belongs to, i.e. \'Lunch\'.', on_delete=models.SET_NULL)
    name = models.CharField(max_length=32, verbose_name='menu category name')
    additional_text = models.CharField(max_length=128, null=True, blank=True, help_text='The additional text is any bit of related information to go along with a menu category, i.e. the \'Pasta\' category might have details that say \'All entrees come with salad and bread\'.')
    order = models.IntegerField(default=0, help_text='The order is the order that this category should appear in when rendered on the templates.')

    class Meta:
        verbose_name = 'menu category'
        verbose_name_plural = 'menu categories'

    def __str__(self):
        return self.name


"""
A Menu Item is an item of food that the restaurant makes. A Menu Item can
belong to multiple Menu Categories to facilitate menus that have the same item
across multiple menus.
"""


class MenuItem(models.Model):
    CLASSIFICATION_CHOICES = (
        ('neither', 'Neither'),
        ('vegan', 'Vegan'),
        ('vegetarian', 'Vegetarian'),
    )

    name = models.CharField(max_length=48, help_text='Name of the item on the menu.')
    menu = models.ForeignKey(Menu, null=True, blank=True, help_text='The menus that this category belongs to, i.e. \'Lunch\'.', on_delete=models.SET_NULL)
    description = models.CharField(max_length=256, null=True, blank=True, help_text='The description is a simple text description of the menu item.')
    category = models.ManyToManyField(MenuCategory, blank=True, verbose_name='menu category', help_text='Category is the menu category that this menu item belongs to, i.e. \'Appetizers\'.')
    order = models.IntegerField(default=0, verbose_name='order', help_text='The order is to specify the order in which items show up on the menu.')
    price = models.DecimalField(max_digits=6, decimal_places=2, help_text='The price is the cost of the item.')

    classification = models.CharField(max_length=10, choices=CLASSIFICATION_CHOICES, default=0, verbose_name='classification', help_text='Select if this item classifies as Vegetarian, Vegan, or Neither.')
    spicy = models.BooleanField(default=False, verbose_name='spicy?', help_text='Is this item spicy?')
    contains_peanuts = models.BooleanField(default=True, verbose_name='contain peanuts?', help_text='Does this item contain peanuts?')
    gluten_free = models.BooleanField(default=False, verbose_name='gluten free?', help_text='Is this item Gluten Free?')

    def menu_name(self):
        return ",".join([str(p) for p in self.category.all()])

    class Meta:
        verbose_name = 'menu item'
        verbose_name_plural = 'menu items'
        verbose_name = 'menu name'

    def __str__(self):
        return self.name

views.py

from django.shortcuts import render, redirect, reverse, get_object_or_404
from django.views.generic import ListView
from .models import Menu, MenuCategory, MenuItem


# Create your views here.


def listMenus(request):
    menu = Menu.objects.all()

    context = {
        'menu': menu,
    }

    return render(request, 'menu/menu.html', context)


def get_menu_detail(request, menu_id):
    """ A view to show individual product details """

    detail = get_object_or_404(Menu, id=menu_id)
    menuField = MenuCategory.objects.filter(id=menu_id)
    getItem = MenuItem.objects.all()

    context = {
        'detail': detail,
        'menuField': menuField,
        'getItem': getItem
    }

    return render(request, 'menu/menu_detail.html', context)

I am still studying python and frameworks so please excuse and stupidity but if anyone is able to point me in the right direction it would be very much appreciated.


Solution

  • For getting the categories and items that belong to the menu, you can do:

    menuField = MenuCategory.objects.filter(menu=detail) # detail is an instance of Menu
    getItem = MenuItem.objects.filter(menu=detail) 
    

    Also, since detail is the instance of the menu, you can use the backward lookup manager like:

    menuField = detail.menucategory_set.all()
    getItem = detail.menuitem_set.all()
    

    You can change the backward lookup manager name with the related_name parameter in the ForeignKey, by default it will be the lowercase model's name plus _set.

    -Edited-

    In your specific case, first you want to show all menu categories that belong to a menu, then list menu items that belong to each category and also belong to the menu, to accomplish that you can do something like:

    view:

    from django.db.models import Prefetch
    
    ...
    def get_menu_detail(request, menu_id):
        menu = get_object_or_404(Menu, id=menu_id)
        categories = (
            MenuCategory.objects.prefetch_related(
                Prefetch(
                    "menuitem_set",
                    queryset=MenuItem.objects.filter(
                        menu=menu
                    ),  # prefetch only items that belong to the menu
                )
            )
            .filter(menuitem__menu=menu)  # categories with items belong to the menu
            .distinct()
        )
        context = {
            "menu": menu,
            "categories": categories,
        }
        return render(request, "menu/menu_detail.html", context)
    

    Then, you can include in your template something like:

    <h1>{{ menu.name }}</h1>
    {% for category in categories %}
        <h2>{{ category.name }}</h2>
        <ul>
            {% for menu_item in category.menuitem_set.all %}
            <li>{{ menu_item.name }}</li>
            {% endfor %}
        </ul>
    {% endfor %}