大廳裝潢完還有房間呢,接續昨天的首頁套版,我們將取用之前建立的首頁資料與套用商品列表頁面、商品詳細頁面,現在馬上就開始!
套用了新頁面後可不能忘了取用我們之前建立的的資料,新首頁我們不只顯示商品列表還多了商品分類區塊,這代表我們在首頁對應的視圖 HomeView 需要取得商品分類的資料
在 context 新增了 left_product_categories 來儲存商品分類的左側的兩筆資料,right_product_category 來儲存商品分類的右側的一筆資料,以下為更新後的 HomeView :
# products/views.py
from django.views.generic import TemplateView, ListView, DetailView
from products.models import Product, ProductCategory
class HomeView(TemplateView):
template_name = "products/home.html"
def get(self, request, *args, **kwargs):
products = Product.objects.all()
left_product_categories = ProductCategory.objects.all()[0:2]
right_product_categories = ProductCategory.objects.all()[2:3]
right_product_category = right_product_categories.first() if right_product_categories else None
context = self.get_context_data(**kwargs)
context['items'] = products
context['left_product_categories'] = left_product_categories
context['right_product_category'] = right_product_category
return self.render_to_response(context)
在 home.html 使用 left_product_categories 與 right_product_category 來呈現商品分類資料,
使用 DTL 的 . 語法對查找 ImageField 物件的 url 時,需要先判斷 image 是否存在,否則沒有圖片時直接取用 url 會出現錯誤。
<!-- products/templates/products/home.html -->
{% extends "base.html" %}
{% load static %}
{% block content %}
<section class="product-category section">
<div class="container">
<div class="row">
<div class="col-md-12">
<div class="title text-center">
<h2>以商品分類尋找屬於您的商品</h2>
</div>
</div>
<div class="col-md-6">
{% for left_product_category in left_product_categories %}
<div class="category-box">
<a href="#!">
{% if left_product_category.image %}
<img src="{{ left_product_category.image.url }}" alt="" />
{% endif %}
<div class="content">
<h3 style="color: white;">{{ left_product_category.name }}</h3>
<p style="color: white;">{{ left_product_category.description }}</p>
</div>
</a>
</div>
{% endfor %}
</div>
<div class="col-md-6">
<div class="category-box category-box-2">
<a href="#!">
{% if right_product_category.image %}
<img src="{{ right_product_category.image.url }}" alt="" />
{% endif %}
<div class="content">
<h3 style="color: white;">{{ right_product_category.name }}</h3>
<p style="color: white;">{{ right_product_category.description }}</p>
</div>
</a>
</div>
</div>
</div>
</div>
</section>
<section class="products section bg-gray">
<!-- ... -->
</section>
{% endblock content %}
商品分類區塊展示:
下方的商品列表與原先一樣使用 items 取得所有商品資料,商品圖片因為關聯到 ProductImage 模型,使用 related_name 取得所有關聯物件,再使用 .0 取用第一張圖片,另外要注意 html 使用了 modal 互動視窗,因此要透過 item.id 進行對應
<!-- products/templates/products/home.html -->
{% extends "base.html" %}
{% load static %}
{% block content %}
<section class="product-category section">
<!-- ... -->
</section>
<section class="products section bg-gray">
<div class="container">
<div class="row">
<div class="title text-center">
<h2>所有商品</h2>
</div>
</div>
<div class="row">
{% for item in items %}
<div class="col-md-4">
<div class="product-item">
<div class="product-thumb">
{% if item.product_image_set.all.0.image %}
<img class="img-responsive" src="{{ item.product_image_set.all.0.image.url }}" alt="product-img" />
{% endif %}
<div class="preview-meta">
<ul>
<li>
<span data-toggle="modal" data-target="#product-modal-{{ item.id }}">
<i class="tf-ion-ios-search-strong"></i>
</span>
</li>
<li>
<a href="#" ><i class="tf-ion-ios-heart"></i></a>
</li>
<li>
<a href="#!"><i class="tf-ion-android-cart"></i></a>
</li>
</ul>
</div>
</div>
<div class="product-content">
<h4><a href="product-single.html">{{ item.name }}</a></h4>
<p class="price">${{ item.price }}</p>
</div>
</div>
</div>
<!-- Modal -->
<div class="modal product-modal fade" id="product-modal-{{ item.id }}">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<i class="tf-ion-close"></i>
</button>
<div class="modal-dialog " role="document">
<div class="modal-content">
<div class="modal-body">
<div class="row">
<div class="col-md-8 col-sm-6 col-xs-12">
<div class="modal-image">
{% if item.product_image_set.all.0.image %}
<img class="img-responsive" src="{{ item.product_image_set.all.0.image.url }}" alt="product-img" />
{% endif %}
</div>
</div>
<div class="col-md-4 col-sm-6 col-xs-12">
<div class="product-short-details">
<h2 class="product-title">{{ item.name }}</h2>
<p class="product-price">${{ item.price }}</p>
<p class="product-short-description">
{{ item.description }}
</p>
<a href="cart.html" class="btn btn-main">加入購物車</a>
<a href="{% url 'products:detail' item.id %}" class="btn btn-transparent">檢視商品詳細資料</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div><!-- /.modal -->
{% endfor %}
</div>
</div>
</section>
{% endblock content %}
商品列表區塊展示:
點選檢視 modal 互動視窗:
將 aviato/shop.html 更名為 list.html 並取代 products 應用程式目錄中的 template/products/list.html。
商品列表頁面與首頁的商品列表區塊大同小異,在上方新增了 page-header 來呈現 breadcrumb 麵包屑,而商品列表區塊則是移除了 title 。
<!-- products/templates/products/list.html -->
{% extends "base.html" %}
{% load static %}
{% block content %}
<section class="page-header">
<div class="container">
<div class="row">
<div class="col-md-12">
<div class="content">
<h1 class="page-name">全部商品</h1>
<ol class="breadcrumb">
<li><a href="{% url 'products:home' %}">首頁</a></li>
<li class="active"><a href="{% url 'products:list' %}">商品列表</a></li>
</ol>
</div>
</div>
</div>
</div>
</section>
<section class="products section">
<div class="container">
<div class="row">
{% for item in items %}
<div class="col-md-4">
<div class="product-item">
<div class="product-thumb">
{% if item.product_image_set.all.0.image %}
<img class="img-responsive" src="{{ item.product_image_set.all.0.image.url }}" alt="product-img" />
{% endif %}
<div class="preview-meta">
<ul>
<li>
<span data-toggle="modal" data-target="#product-modal-{{ item.id }}">
<i class="tf-ion-ios-search-strong"></i>
</span>
</li>
<li>
<a href="#" ><i class="tf-ion-ios-heart"></i></a>
</li>
<li>
<a href="#!"><i class="tf-ion-android-cart"></i></a>
</li>
</ul>
</div>
</div>
<div class="product-content">
<h4><a href="product-single.html">{{ item.name }}</a></h4>
<p class="price">${{ item.price }}</p>
</div>
</div>
</div>
<!-- Modal -->
<div class="modal product-modal fade" id="product-modal-{{ item.id }}">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<i class="tf-ion-close"></i>
</button>
<div class="modal-dialog " role="document">
<div class="modal-content">
<div class="modal-body">
<div class="row">
<div class="col-md-8 col-sm-6 col-xs-12">
<div class="modal-image">
{% if item.product_image_set.all.0.image %}
<img class="img-responsive" src="{{ item.product_image_set.all.0.image.url }}" alt="product-img" />
{% endif %}
</div>
</div>
<div class="col-md-4 col-sm-6 col-xs-12">
<div class="product-short-details">
<h2 class="product-title">{{ item.name }}</h2>
<p class="product-price">${{ item.price }}</p>
<p class="product-short-description">
{{ item.description }}
</p>
<a href="cart.html" class="btn btn-main">加入購物車</a>
<a href="{% url 'products:detail' item.id %}" class="btn btn-transparent">檢視商品詳細資料</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div><!-- /.modal -->
{% endfor %}
</div>
</div>
</section>
{% endblock content %}
商品列表頁面展示:
將 aviato/product-single.html 更名為 detail.html 並取代 products 應用程式目錄中的 template/products/detail.html。
這裡會使用到 DTL for 語法的 forloop.counter 進行迴圈計數,當第一次迴圈時將 class 加上 active 屬性,而 add 則是 DTL 的加法運算,用於計算縮圖的對應的位置
<!-- products/templates/products/detail.html -->
{% extends "base.html" %}
{% load static %}
{% block content %}
<section class="single-product">
<div class="container">
<div class="row">
<div class="col-md-6">
<ol class="breadcrumb">
<li><a href="{% url 'products:home' %}">首頁</a></li>
<li><a href="{% url 'products:list' %}">商品列表</a></li>
<li class="active">商品詳細資料</li>
</ol>
</div>
</div>
<div class="row mt-20">
<div class="col-md-5">
<div class="single-product-slider">
<div id='carousel-custom' class='carousel slide' data-ride='carousel'>
<div class='carousel-outer'>
<!-- me art lab slider -->
<div class='carousel-inner '>
{% for product_image in item.product_image_set.all %}
{% if forloop.counter == 1 %}
<div class='item active'>
{% else %}
<div class='item'>
{% endif %}
{% if product_image.image %}
<img src='{{ product_image.image.url }}' alt='' data-zoom-image="{{ product_image.image.url }}" />
{% endif %}
</div>
{% endfor %}
</div>
<!-- sag sol -->
<a class='left carousel-control' href='#carousel-custom' data-slide='prev'>
<i class="tf-ion-ios-arrow-left"></i>
</a>
<a class='right carousel-control' href='#carousel-custom' data-slide='next'>
<i class="tf-ion-ios-arrow-right"></i>
</a>
</div>
<!-- thumb -->
<ol class='carousel-indicators mCustomScrollbar meartlab'>
{% for product_image in item.product_image_set.all %}
{% if forloop.counter == 1 %}
<li data-target='#carousel-custom' data-slide-to='0' class='active'>
{% else %}
<li data-target='#carousel-custom' data-slide-to='{{ forloop.counter|add:-1 }}'>
{% endif %}
{% if product_image.image %}
<img src='{{ product_image.image.url }}' alt='' />
{% endif %}
</li>
{% endfor %}
</ol>
</div>
</div>
</div>
<div class="col-md-7">
<div class="single-product-details">
<h2>{{ item.name }}</h2>
<p class="product-price">${{ item.price }}</p>
<p class="product-description mt-20">
{{ item.description }}
</p>
<div class="product-quantity">
<span>數量:</span>
<div class="product-quantity-slider">
<input id="product-quantity" type="text" value="0" name="product-quantity">
</div>
</div>
<div class="product-category">
<span>商品分類:</span>
<ul>
<li><a href="product-single.html">{{ item.category.name }}</a></li>
</ul>
</div>
<a href="cart.html" class="btn btn-main mt-20">加入購物車</a>
</div>
</div>
</div>
</div>
</section>
{% endblock content %}
商品詳細頁面展示:
一次完整的套版流程完成了,是不是開始有電商網站的感覺了呢?
在多租戶的架構下,每個租戶不會只有商品資料不同,網站的 logo 、title 等商家資訊甚至是模板都可能要做出不同的調整,下一回『個人化,Django 多租戶網站設定』走出自己的路!