Product Homepage View
The product homepage displays categories, promotional banners, advertisements, and category-specific product listings. The backend must provide the following data to the frontend:
- All product category information
- Banner carousel data
- Promotional advertisement details
- Categorry display titles and images
- User shopping cart information
IndexView Implementation
from django.shortcuts import render
from django.views.generic import View
from .models import GoodsCategory, IndexGoodsBanner, IndexPromotionBanner
from .models import IndexCategoryGoodsBanner
class IndexView(View):
def get(self, request):
# Fetch all product categories
goods_cate = GoodsCategory.objects.all()
# Retrieve banner information
index_banner = IndexGoodsBanner.objects.all().order_by("index")
# Retrieve promotional advertisements
promotion_banners = IndexPromotionBanner.objects.all().order_by("index")
for category in goods_cate:
# Category titles for homepage display
category_title = IndexCategoryGoodsBanner.objects.filter(
category=category, display_type=0
)[:4]
category.title = category_title
# Category images for homepage display
category_image = IndexCategoryGoodsBanner.objects.filter(
category=category, display_type=1
)[:4]
category.img = category_image
# Initialize cart count
cart_num = 0
context = {
"goods_cate": goods_cate,
"index_banners": index_banner,
"promotion_banners": promotion_banners,
"cart_num": cart_num
}
return render(request, 'index.html', context)
Frontend Template: Product Categories
<ul class="subnav fl">
{% for cate in goods_cate %}
<li><a href="#model0{{ forloop.counter }}" class="{{ cate.logo }}">{{ cate.name }}</a></li>
{% endfor %}
</ul>
Frontend Template: Banner Carousel
<ul class="slide_pics">
{% for slide in index_banners %}
<li><a href="#"><img src="{{ slide.image.url }}" alt="Slide"></a></li>
{% endfor %}
</ul>
Frontend Template: Promotional Advertisements
<div class="adv fl">
{% for ad in promotion_banners %}
<a href="{{ ad.url }}"><img src="{{ ad.image.url }}"></a>
{% endfor %}
</div>
Frontend Template: Category Product Display
{% for cate in goods_cate %}
<div class="list_model">
<div class="list_title clearfix">
<h3 class="fl" id="model01">{{ cate.name }}</h3>
<div class="subtitle fl">
<span>|</span>
{% for title in cate.title %}
<a href="#">{{ title.sku.title }}</a>
{% endfor %}
</div>
<a href="#" class="goods_more fr" id="fruit_more">View More ></a>
</div>
<div class="goods_con clearfix">
<div class="goods_banner fl"><img src="{{ cate.image.url }}"></div>
<ul class="goods_list fl">
{% for img in cate.img %}
<li>
<h4><a href="#">{{ img.sku.name }}</a></h4>
<a href="#"><img src="{{ img.sku.default_image.url }}"></a>
<div class="prize">{{ img.sku.price }}</div>
</li>
{% endfor %}
</ul>
</div>
</div>
{% endfor %}
User Authentication Display
{% if user.is_authenticated %}
<div class="login_btn fl">
Welcome: <em>{{ user.username }}</em>
<span>|</span>
<a href="{% url 'users:logout' %}">Logout</a>
</div>
{% else %}
<div class="login_btn fl">
<a href="{% url 'users:login' %}">Login</a>
<span>|</span>
<a href="{% url 'users:register' %}">Register</a>
</div>
{% endif %}
Access the homepage at:
http://127.0.0.1:8000
Page Staticization
Page staticization involves rendering a dynamic template once and saving the result as a static HTML file. This approach is suitable for pages that don't change frequently. The staticization process is triggered when administrators modify the homepage data, with the generation handled asynchronously via Celery to avoid impacting other operations.
Celery Task for Static HTML Generation
from django.core.mail import send_mail
from django.conf import settings
from django.template import loader
from goods.models import GoodsCategory, IndexGoodsBanner, IndexPromotionBanner
from goods.models import IndexCategoryGoodsBanner
@app.task
def generate_static_index_html():
"""Generate static HTML file for homepage"""
# Fetch all product categories
goods_cate = GoodsCategory.objects.all()
# Retrieve banner information
index_banner = IndexGoodsBanner.objects.all().order_by("index")
# Retrieve promotional advertisements
promotion_banners = IndexPromotionBanner.objects.all().order_by("index")
for category in goods_cate:
category_title = IndexCategoryGoodsBanner.objects.filter(
category=category, display_type=0
)[:4]
category.title = category_title
category_image = IndexCategoryGoodsBanner.objects.filter(
category=category, display_type=1
)[:4]
category.img = category_image
cart_num = 0
context = {
"goods_cate": goods_cate,
"index_banners": index_banner,
"promotion_banners": promotion_banners,
"cart_num": cart_num
}
# Load template
template = loader.get_template("static_index.html")
# Render template to generate HTML data
html_data = template.render(context)
# Save static HTML data
file_path = os.path.join(settings.STATICFILES_DIRS[0], "index.html")
with open(file_path, "w") as f:
f.write(html_data)
The template rendering process loads the tempalte, generates HTML data with context, and writes it to index.html. When browsers access the homepage, they receive the static file, reducing server load.
For unauthenticated users, copy index.html to static_index.html in the templates directory and remove authentication-related code:
<div class="login_btn fl">
<a href="{% url 'users:login' %}">Login</a>
<span>|</span>
<a href="{% url 'users:register' %}">Register</a>
</div>
Nginx Configuration
sudo vim /usr/local/nginx/conf/nginx.conf
sudo /usr/local/nginx/sbin/nginx -s reload
Starting Celery Server
cd /path/to/project
celery -A celery_tasks.tasks worker --loglevel=info
Testing Static Generation
from celery_tasks.tasks import generate_static_index_html
generate_static_index_html.delay()
Access via IP address to use the static file:
http://192.168.228.135/
To separate Django dynamic pages from Nginx static files, update the product page URL:
url(r'^index$', views.IndexView.as_view(), name='index')
Triggering Static Generation via Admin
Overide save_model and delete_model methods in the admin to trigger Celery tasks when administrators add or modify models:
from django.contrib import admin
from goods.models import IndexGoodsBanner, IndexCategoryGoodsBanner, IndexPromotionBanner
from goods.models import GoodsCategory
from celery_tasks.tasks import generate_static_index_html
class BaseModel(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
obj.save()
generate_static_index_html.delay()
def delete_model(self, request, obj):
"""Called when admin deletes model data"""
obj.delete()
generate_static_index_html.delay()
class IndexPromotionBannerAdmin(BaseModel):
pass
class GoodsCategoryAdmin(BaseModel):
pass
admin.site.register(IndexPromotionBanner, IndexPromotionBannerAdmin)
admin.site.register(GoodsCategory, GoodsCategoryAdmin)
Caching Implementation
Caching stores backup data in memory. Subsequent requests check memory first—if data exists, it's returned immediately; otherwise, Django computes the data dynamically and caches it for future use.
Django's cache interface:
from django.core.cache import cache
>>> cache.set('my_key', 'hello, world!', 30)
>>> cache.get('my_key')
'hello, world!'
IndexView with Caching
from django.core.cache import cache
class IndexView(View):
def get(self, request):
# Attempt to retrieve data from cache
context = cache.get("index_page_data")
if context is None:
print("Cache miss - querying database")
goods_cate = GoodsCategory.objects.all()
index_banner = IndexGoodsBanner.objects.all().order_by("index")
promotion_banners = IndexPromotionBanner.objects.all().order_by("index")
for category in goods_cate:
category_title = IndexCategoryGoodsBanner.objects.filter(
category=category, display_type=0
)[:4]
category.title = category_title
category_image = IndexCategoryGoodsBanner.objects.filter(
category=category, display_type=1
)[:4]
category.img = category_image
context = {
"goods_cate": goods_cate,
"index_banners": index_banner,
"promotion_banners": promotion_banners,
}
# Store data in cache with 1-hour TTL
cache.set('index_page_data', context, 3600)
# Initialize cart count
cart_num = 0
context.update(cart_num=0)
return render(request, 'index.html', context)
Cache Management and Admin Integration
Delete cache when updating database:
cache.delete("index_page_data")
Set cache expiration:
cache.set("index_page_data", context, 3600)
Admin integration for cache invalidation:
from django.core.cache import cache
class BaseAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
"""Called when admin saves model data"""
obj.save()
generate_static_index_html.delay()
cache.delete("index_page_data")
def delete_model(self, request, obj):
"""Called when admin deletes model data"""
obj.delete()
generate_static_index_html.delay()
cache.delete("index_page_data")
Shopping Cart Data for Authenticated Users
User shopping cart is stored in Redis as a hash structure with key format "cart_user_id" containing product IDs as fields and quantities as values:
from django_redis import get_redis_connection
# For authenticated users, retrieve cart count from Redis
if request.user.is_authenticated():
redis_con = get_redis_connection("default")
cart = redis_con.hgetall('cart_%s' % request.user.id)
# Sum up all product quantities
for count in cart.values():
cart_num += int(count)
context.update(cart_num=cart_num)
Complete IndexView with Cart Integration
class IndexView(View):
def get(self, request):
# Try cache first
context = cache.get("index_page_data")
if context is None:
print("Cache miss - querying database")
goods_cate = GoodsCategory.objects.all()
index_banner = IndexGoodsBanner.objects.all().order_by("index")
promotion_banners = IndexPromotionBanner.objects.all().order_by("index")
for category in goods_cate:
category_title = IndexCategoryGoodsBanner.objects.filter(
category=category, display_type=0
)[:4]
category.title = category_title
category_image = IndexCategoryGoodsBanner.objects.filter(
category=category, display_type=1
)[:4]
category.img = category_image
context = {
"goods_cate": goods_cate,
"index_banners": index_banner,
"promotion_banners": promotion_banners,
}
cache.set('index_page_data', context, 3600)
# Cart count initialization
cart_num = 0
# Retrieve cart from Redis for authenticated users
if request.user.is_authenticated():
redis_con = get_redis_connection("default")
cart = redis_con.hgetall('cart_%s' % request.user.id)
for count in cart.values():
cart_num += int(count)
context.update(cart_num=cart_num)
return render(request, 'index.html', context)
Product Detail Page
The product detail page requires the following backend logic:
- All product category information
- Product SKU details
- Two latest recommended products from same category
- Other specifications of the product
- Order review comments
- Shopping cart count
- User browsing history
DetailView Implementation
class DetailView(View):
def get(self, request, sku_id):
# Fetch all product categories
goods_cate = GoodsCategory.objects.all()
# Fetch product information
try:
sku = GoodsSKU.objects.get(id=sku_id)
except GoodsSKU.DoesNotExist:
return redirect(reverse("goods:index"))
# Get latest 2 recommended products from same category
new_goods = GoodsSKU.objects.filter(
category=sku.category
).order_by("-create_time")[:2]
# Get other specifications
goods_skus = sku.goods.goodssku_set.exclude(id=sku_id)
# Retrieve order review comments
sku_orders = sku.ordergoods_set.all().order_by("-create_time")[:30]
if sku_orders:
for sku_order in sku_orders:
sku_order.create_time = sku_order.create_time.strftime("%Y-%m-%d %H-%M-%S")
sku_order.username = sku_order.order.user.username
else:
sku_orders = []
context = {
"goods_cate": goods_cate,
"sku": sku,
"new_goods": new_goods,
"goods_skus": goods_skus,
"orders": sku_orders
}
# Cart count
cart_num = 0
if request.user.is_authenticated():
user_id = request.user.id
redis_conn = get_redis_connection("default")
# Get cart data from Redis
cart = redis_conn.hgetall("cart_%s" % user_id)
for val in cart.values():
cart_num += int(val)
# Browsing history management
# Remove existing record of this product
redis_conn.lrem("history_%s" % user_id, 0, sku_id)
# Add new browsing record
redis_conn.lpush("history_%s" % user_id, sku_id)
# Keep only latest 5 records
redis_conn.ltrim("history_%s" % user_id, 0, 4)
context.update({"cart_num": cart_num})
return render(request, 'detail.html', context)
Browsing History Logic
The browsing history implementation:
- Removes all existing records of the current product ID using lrem with 0 (which removes all occurrences)
- Adds the new product ID to the front of the list using lpush
- Trims the list to keep only the latest 5 records using ltrim
# Browsing history management
redis_conn.lrem("history_%s" % user_id, 0, sku_id) # Remove existing
redis_conn.lpush("history_%s" % user_id, sku_id) # Add new
redis_conn.ltrim("history_%s" % user_id, 0, 4) # Keep 5 records
Product Detail Page with Caching
class DetailView(View):
def get(self, request, sku_id):
context = cache.get("detail_data_%s" % request.user.id)
if context is None:
goods_cate = GoodsCategory.objects.all()
try:
sku = GoodsSKU.objects.get(id=sku_id)
except GoodsSKU.DoesNotExist:
return redirect(reverse("goods:index"))
new_goods = GoodsSKU.objects.filter(
category=sku.category
).order_by("-create_time")[:2]
goods_skus = sku.goods.goodssku_set.exclude(id=sku_id)
sku_orders = sku.ordergoods_set.all().order_by("-create_time")[:30]
if sku_orders:
for sku_order in sku_orders:
sku_order.create_time = sku_order.create_time.strftime("%Y-%m-%d %H-%M-%S")
sku_order.username = sku_order.order.user.username
else:
sku_orders = []
context = {
"goods_cate": goods_cate,
"sku": sku,
"new_goods": new_goods,
"goods_skus": goods_skus,
"orders": sku_orders
}
# Set cache with 1-hour TTL
cache.set("detail_data_%s" % request.user.id, context, 3600)
# Cart count
cart_num = 0
if request.user.is_authenticated():
user_id = request.user.id
redis_conn = get_redis_connection("default")
cart = redis_conn.hgetall("cart_%s" % user_id)
for val in cart.values():
cart_num += int(val)
# Browsing history
redis_conn.lrem("history_%s" % user_id, 0, sku_id)
redis_conn.lpush("history_%s" % user_id, sku_id)
redis_conn.ltrim("history_%s" % user_id, 0, 4)
context.update({"cart_num": cart_num})
return render(request, 'detail.html', context)
Update all detail page links to use the proper URL pattern:
{% url "goods:detail" sku.id %}
Verify browsing history in the user profile page or by checking Redis directly.