master
parent
27877dc537
commit
b97312dbdf
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AreasConfig(AppConfig):
|
||||
name = 'areas'
|
@ -0,0 +1,28 @@
|
||||
# Generated by Django 2.2.8 on 2023-08-25 09:37
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Area',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=20, verbose_name='名称')),
|
||||
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='subs', to='areas.Area', verbose_name='上级行政区划')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '省市区',
|
||||
'verbose_name_plural': '省市区',
|
||||
'db_table': 'tb_areas',
|
||||
},
|
||||
),
|
||||
]
|
Binary file not shown.
Binary file not shown.
@ -0,0 +1,17 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
# Create your models here.
|
||||
class Area(models.Model):
|
||||
"""省市区"""
|
||||
name = models.CharField(max_length=20, verbose_name='名称')
|
||||
parent = models.ForeignKey('self', on_delete=models.SET_NULL,
|
||||
related_name='subs', null=True, blank=True, verbose_name='上级行政区划')
|
||||
|
||||
class Meta:
|
||||
db_table = 'tb_areas'
|
||||
verbose_name = '省市区'
|
||||
verbose_name_plural = '省市区'
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
@ -0,0 +1,7 @@
|
||||
from django.contrib import admin
|
||||
from django.urls import path
|
||||
from .views import *
|
||||
|
||||
urlpatterns = [
|
||||
path('areas/', AreasView.as_view()), # 省市区数据 ,子路由
|
||||
]
|
@ -0,0 +1,52 @@
|
||||
from django.core.cache import cache
|
||||
from django.http import JsonResponse
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
from django.views import View
|
||||
|
||||
from areas.models import Area
|
||||
from utils.response_code import RETCODE
|
||||
|
||||
|
||||
class AreasView(View):
|
||||
"""省市区数据"""
|
||||
|
||||
def get(self, request):
|
||||
"""提供省市区数据"""
|
||||
area_id = request.GET.get('area_id')
|
||||
if not area_id:
|
||||
province_list = cache.get('province_list') # 读取省份缓存数据
|
||||
if not province_list:
|
||||
try:
|
||||
province_model_list = Area.objects.filter(parent__isnull=True)
|
||||
province_list = [] # 构建省级数据
|
||||
for province_model in province_model_list:
|
||||
province_list.append({'id': province_model.id, 'name': province_model.name})
|
||||
except Exception as e:
|
||||
return JsonResponse({'code': RETCODE.DBERR, 'errmsg': '省份数据错误'})
|
||||
# 存储省份缓存数据
|
||||
cache.set('province_list', province_list, 3600)
|
||||
# 响应省份数据
|
||||
return JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'province_list': province_list})
|
||||
else:
|
||||
# 读取市或区缓存数据
|
||||
sub_data = cache.get('sub_area_' + area_id)
|
||||
if not sub_data:
|
||||
try:
|
||||
parent_model = Area.objects.get(id=area_id) # 查询市或区的父级
|
||||
sub_model_list = parent_model.subs.all()
|
||||
sub_list = [] # 构建市或区数据
|
||||
for sub_model in sub_model_list:
|
||||
sub_list.append({'id': sub_model.id, 'name': sub_model.name})
|
||||
sub_data = {
|
||||
'id': parent_model.id, # 父级pk
|
||||
'name': parent_model.name, # 父级name
|
||||
'subs': sub_list # 父级的子集
|
||||
}
|
||||
except Exception as e:
|
||||
return JsonResponse({'code': RETCODE.DBERR, 'errmsg': '城市或区数据错误'})
|
||||
# 储存市或区缓存数据
|
||||
cache.set('sub_area_' + area_id, sub_data, 3600)
|
||||
# 响应市或区数据
|
||||
return JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'sub_data': sub_data})
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class CartsConfig(AppConfig):
|
||||
name = 'carts'
|
Binary file not shown.
@ -0,0 +1,3 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
@ -0,0 +1,11 @@
|
||||
from django.urls import path
|
||||
from . import views
|
||||
app_name = 'carts'
|
||||
urlpatterns = [
|
||||
# 购物车管理
|
||||
path('carts/', views.CartsView.as_view(), name='info'),
|
||||
# 选择购物车商品
|
||||
path('carts/selection/', views.CartsSelectAllView.as_view()),
|
||||
# 简单购物车
|
||||
path('carts/simple/', views.CartsSimpleView.as_view()),
|
||||
]
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ContentsConfig(AppConfig):
|
||||
name = 'contents'
|
@ -0,0 +1,50 @@
|
||||
# Generated by Django 2.2.8 on 2023-08-18 11:39
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ContentCategory',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
('name', models.CharField(max_length=50, verbose_name='名称')),
|
||||
('key', models.CharField(max_length=50, verbose_name='类别键名')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '广告内容类别',
|
||||
'verbose_name_plural': '广告内容类别',
|
||||
'db_table': 'tb_content_category',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Content',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
('title', models.CharField(max_length=100, verbose_name='标题')),
|
||||
('url', models.CharField(max_length=300, verbose_name='内容链接')),
|
||||
('image', models.ImageField(blank=True, null=True, upload_to='', verbose_name='图片')),
|
||||
('text', models.TextField(blank=True, null=True, verbose_name='内容')),
|
||||
('sequence', models.IntegerField(verbose_name='排序')),
|
||||
('status', models.BooleanField(default=True, verbose_name='是否展示')),
|
||||
('category', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='contents.ContentCategory', verbose_name='类别')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '广告内容',
|
||||
'verbose_name_plural': '广告内容',
|
||||
'db_table': 'tb_content',
|
||||
},
|
||||
),
|
||||
]
|
Binary file not shown.
Binary file not shown.
@ -0,0 +1,36 @@
|
||||
from django.db import models
|
||||
from utils.models import BaseModel
|
||||
|
||||
|
||||
# Create your models here.
|
||||
class ContentCategory(BaseModel):
|
||||
"""广告内容类别"""
|
||||
name = models.CharField(max_length=50, verbose_name='名称')
|
||||
key = models.CharField(max_length=50, verbose_name='类别键名')
|
||||
|
||||
class Meta:
|
||||
db_table = 'tb_content_category'
|
||||
verbose_name = '广告内容类别'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class Content(BaseModel):
|
||||
"""广告内容"""
|
||||
category = models.ForeignKey(ContentCategory, on_delete=models.PROTECT, verbose_name='类别')
|
||||
title = models.CharField(max_length=100, verbose_name='标题')
|
||||
url = models.CharField(max_length=300, verbose_name='内容链接')
|
||||
image = models.ImageField(null=True, blank=True, verbose_name='图片')
|
||||
text = models.TextField(null=True, blank=True, verbose_name='内容')
|
||||
sequence = models.IntegerField(verbose_name='排序')
|
||||
status = models.BooleanField(default=True, verbose_name='是否展示')
|
||||
|
||||
class Meta:
|
||||
db_table = 'tb_content'
|
||||
verbose_name = '广告内容'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def __str__(self):
|
||||
return self.category.name + ': ' + self.title
|
@ -0,0 +1,20 @@
|
||||
from sklearn.feature_extraction.text import TfidfVectorizer
|
||||
from sklearn.metrics.pairwise import linear_kernel
|
||||
|
||||
class ContentBasedRecommender:
|
||||
def __init__(self, products):
|
||||
self.products = products
|
||||
self.build()
|
||||
|
||||
def build(self):
|
||||
tfidf_vectorizer = TfidfVectorizer(stop_words='english')
|
||||
tfidf_matrix = tfidf_vectorizer.fit_transform([product.caption for product in self.products])
|
||||
self.similarity_matrix = linear_kernel(tfidf_matrix, tfidf_matrix)
|
||||
|
||||
def recommend_products(self, product_id, num_recommendations=5):
|
||||
product_index = next(index for (index, product) in enumerate(self.products) if product.id == product_id)
|
||||
similarity_scores = list(enumerate(self.similarity_matrix[product_index]))
|
||||
similarity_scores = sorted(similarity_scores, key=lambda x: x[1], reverse=True)
|
||||
similar_products = similarity_scores[1:num_recommendations + 1]
|
||||
|
||||
return [self.products[product[0]] for product in similar_products]
|
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
@ -0,0 +1,9 @@
|
||||
|
||||
from django.contrib import admin
|
||||
from django.urls import path
|
||||
from .views import *
|
||||
|
||||
app_name = 'contents'
|
||||
urlpatterns = [
|
||||
path('', IndexView.as_view(), name='index'),
|
||||
]
|
@ -0,0 +1,61 @@
|
||||
from django.shortcuts import render
|
||||
from django.views import View
|
||||
|
||||
from contents.recommender import ContentBasedRecommender
|
||||
from contents.utils import get_categories
|
||||
from collections import OrderedDict
|
||||
|
||||
from goods.models import SKU
|
||||
from contents.models import ContentCategory
|
||||
|
||||
|
||||
# class IndexView(View):
|
||||
# def get(self, request):
|
||||
# """提供首页广告页面"""
|
||||
# categories = get_categories()
|
||||
# # 查询首页广告数据
|
||||
# # 查询所有的广告类别
|
||||
# content_categories = ContentCategory.objects.all()
|
||||
# # 使用广告类别查询出该类别对应的所有的广告内容
|
||||
# contents = OrderedDict()
|
||||
# for content_categorie in content_categories:
|
||||
# contents[content_categorie.key] = content_categorie.content_set.filter(status=True).order_by(
|
||||
# 'sequence') # 查询出未下架的广告并排序
|
||||
# # 渲染模板的上下文
|
||||
# context = {
|
||||
# 'categories': categories,
|
||||
# 'contents': contents,
|
||||
# }
|
||||
# return render(request, 'index.html', context)
|
||||
|
||||
|
||||
class IndexView(View):
|
||||
def get(self, request):
|
||||
"""提供首页广告页面"""
|
||||
skus = SKU.objects.filter(is_launched=True)
|
||||
|
||||
recommender = ContentBasedRecommender(skus)
|
||||
user_interests = SKU.objects.all()
|
||||
|
||||
recommended_products = []
|
||||
for interest in user_interests:
|
||||
recommendations = recommender.recommend_products(interest.id)
|
||||
recommended_products.extend(recommendations)
|
||||
|
||||
# 查询商品分类
|
||||
categories = get_categories()
|
||||
# 查询首页广告数据
|
||||
# 查询所有的广告类别
|
||||
content_categories = ContentCategory.objects.all()
|
||||
# 使用广告类别查询出该类别对应的所有的广告内容
|
||||
contents = OrderedDict()
|
||||
for content_categorie in content_categories:
|
||||
contents[content_categorie.key] = content_categorie.content_set.filter(status=True).order_by(
|
||||
'sequence') # 查询出未下架的广告并排序
|
||||
# 构造上下文
|
||||
context = {
|
||||
'categories': categories,
|
||||
'page_skus': recommended_products,
|
||||
'contents': contents,
|
||||
}
|
||||
return render(request, 'index2.html', context)
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class GoodsConfig(AppConfig):
|
||||
name = 'goods'
|
@ -0,0 +1,188 @@
|
||||
# Generated by Django 2.2.8 on 2023-08-18 11:39
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Brand',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
('name', models.CharField(max_length=20, verbose_name='名称')),
|
||||
('logo', models.ImageField(upload_to='', verbose_name='Logo图片')),
|
||||
('first_letter', models.CharField(max_length=1, verbose_name='品牌首字母')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '品牌',
|
||||
'verbose_name_plural': '品牌',
|
||||
'db_table': 'tb_brand',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='GoodsCategory',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
('name', models.CharField(max_length=10, verbose_name='名称')),
|
||||
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='subs', to='goods.GoodsCategory', verbose_name='父类别')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '商品类别',
|
||||
'verbose_name_plural': '商品类别',
|
||||
'db_table': 'tb_goods_category',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='GoodsChannelGroup',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=20, verbose_name='频道组名')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '商品频道组',
|
||||
'verbose_name_plural': '商品频道组',
|
||||
'db_table': 'tb_channel_group',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SKU',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
('name', models.CharField(max_length=50, verbose_name='名称')),
|
||||
('caption', models.CharField(max_length=100, verbose_name='副标题')),
|
||||
('price', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='单价')),
|
||||
('cost_price', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='进价')),
|
||||
('market_price', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='市场价')),
|
||||
('stock', models.IntegerField(default=0, verbose_name='库存')),
|
||||
('sales', models.IntegerField(default=0, verbose_name='销量')),
|
||||
('comments', models.IntegerField(default=0, verbose_name='评价数')),
|
||||
('is_launched', models.BooleanField(default=True, verbose_name='是否上架销售')),
|
||||
('default_image', models.ImageField(blank=True, default='', max_length=200, null=True, upload_to='', verbose_name='默认图片')),
|
||||
('category', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='goods.GoodsCategory', verbose_name='从属类别')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '商品SKU',
|
||||
'verbose_name_plural': '商品SKU',
|
||||
'db_table': 'tb_sku',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SPU',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
('name', models.CharField(max_length=50, verbose_name='名称')),
|
||||
('sales', models.IntegerField(default=0, verbose_name='销量')),
|
||||
('comments', models.IntegerField(default=0, verbose_name='评价数')),
|
||||
('desc_detail', models.TextField(default='', verbose_name='详细介绍')),
|
||||
('desc_pack', models.TextField(default='', verbose_name='包装信息')),
|
||||
('desc_service', models.TextField(default='', verbose_name='售后服务')),
|
||||
('brand', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='goods.Brand', verbose_name='品牌')),
|
||||
('category1', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='cat1_spu', to='goods.GoodsCategory', verbose_name='一级类别')),
|
||||
('category2', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='cat2_spu', to='goods.GoodsCategory', verbose_name='二级类别')),
|
||||
('category3', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='cat3_spu', to='goods.GoodsCategory', verbose_name='三级类别')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '商品SPU',
|
||||
'verbose_name_plural': '商品SPU',
|
||||
'db_table': 'tb_spu',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SPUSpecification',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
('name', models.CharField(max_length=20, verbose_name='规格名称')),
|
||||
('spu', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='specs', to='goods.SPU', verbose_name='商品SPU')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '商品SPU规格',
|
||||
'verbose_name_plural': '商品SPU规格',
|
||||
'db_table': 'tb_spu_specification',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SpecificationOption',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
('value', models.CharField(max_length=20, verbose_name='选项值')),
|
||||
('spec', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='options', to='goods.SPUSpecification', verbose_name='规格')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '规格选项',
|
||||
'verbose_name_plural': '规格选项',
|
||||
'db_table': 'tb_specification_option',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SKUSpecification',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
('option', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='goods.SpecificationOption', verbose_name='规格值')),
|
||||
('sku', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='specs', to='goods.SKU', verbose_name='sku')),
|
||||
('spec', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='goods.SPUSpecification', verbose_name='规格名称')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'SKU规格',
|
||||
'verbose_name_plural': 'SKU规格',
|
||||
'db_table': 'tb_sku_specification',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SKUImage',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
('image', models.ImageField(upload_to='', verbose_name='图片')),
|
||||
('sku', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='goods.SKU', verbose_name='sku')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'SKU图片',
|
||||
'verbose_name_plural': 'SKU图片',
|
||||
'db_table': 'tb_sku_image',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='sku',
|
||||
name='spu',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='goods.SPU', verbose_name='商品'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='GoodsChannel',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
('url', models.CharField(max_length=50, verbose_name='频道页面链接')),
|
||||
('sequence', models.IntegerField(verbose_name='组内顺序')),
|
||||
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='goods.GoodsCategory', verbose_name='顶级商品类别')),
|
||||
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='goods.GoodsChannelGroup', verbose_name='频道组名')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '商品频道',
|
||||
'verbose_name_plural': '商品频道',
|
||||
'db_table': 'tb_goods_channel',
|
||||
},
|
||||
),
|
||||
]
|
Binary file not shown.
Binary file not shown.
@ -0,0 +1,174 @@
|
||||
from django.db import models
|
||||
from utils.models import BaseModel
|
||||
|
||||
|
||||
# Create your models here.
|
||||
class GoodsCategory(BaseModel):
|
||||
"""商品类别"""
|
||||
name = models.CharField(max_length=10, verbose_name='名称')
|
||||
parent = models.ForeignKey('self', related_name='subs', null=True, blank=True, on_delete=models.CASCADE,
|
||||
verbose_name='父类别')
|
||||
|
||||
class Meta:
|
||||
db_table = 'tb_goods_category'
|
||||
verbose_name = '商品类别'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class GoodsChannelGroup(models.Model):
|
||||
"""商品频道组"""
|
||||
name = models.CharField(max_length=20, verbose_name='频道组名')
|
||||
|
||||
class Meta:
|
||||
db_table = 'tb_channel_group'
|
||||
verbose_name = '商品频道组'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class GoodsChannel(BaseModel):
|
||||
"""商品频道"""
|
||||
group = models.ForeignKey(GoodsChannelGroup, verbose_name='频道组名', on_delete=models.CASCADE)
|
||||
category = models.ForeignKey(GoodsCategory, on_delete=models.CASCADE, verbose_name='顶级商品类别')
|
||||
url = models.CharField(max_length=50, verbose_name='频道页面链接')
|
||||
sequence = models.IntegerField(verbose_name='组内顺序')
|
||||
|
||||
class Meta:
|
||||
db_table = 'tb_goods_channel'
|
||||
verbose_name = '商品频道'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def __str__(self):
|
||||
return self.category.name
|
||||
|
||||
|
||||
class Brand(BaseModel):
|
||||
"""品牌"""
|
||||
|
||||
name = models.CharField(max_length=20, verbose_name='名称')
|
||||
logo = models.ImageField(verbose_name='Logo图片')
|
||||
first_letter = models.CharField(max_length=1, verbose_name='品牌首字母')
|
||||
|
||||
class Meta:
|
||||
db_table = 'tb_brand'
|
||||
verbose_name = '品牌'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class SPU(BaseModel):
|
||||
"""商品SPU"""
|
||||
|
||||
name = models.CharField(max_length=50, verbose_name='名称')
|
||||
brand = models.ForeignKey(Brand, on_delete=models.PROTECT, verbose_name='品牌')
|
||||
category1 = models.ForeignKey(GoodsCategory, on_delete=models.PROTECT,
|
||||
related_name='cat1_spu', verbose_name='一级类别')
|
||||
category2 = models.ForeignKey(GoodsCategory, on_delete=models.PROTECT,
|
||||
related_name='cat2_spu', verbose_name='二级类别')
|
||||
category3 = models.ForeignKey(GoodsCategory, on_delete=models.PROTECT,
|
||||
related_name='cat3_spu', verbose_name='三级类别')
|
||||
sales = models.IntegerField(default=0, verbose_name='销量')
|
||||
comments = models.IntegerField(default=0, verbose_name='评价数')
|
||||
desc_detail = models.TextField(default='', verbose_name='详细介绍')
|
||||
desc_pack = models.TextField(default='', verbose_name='包装信息')
|
||||
desc_service = models.TextField(default='', verbose_name='售后服务')
|
||||
|
||||
class Meta:
|
||||
db_table = 'tb_spu'
|
||||
verbose_name = '商品SPU'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class SKU(BaseModel):
|
||||
"""商品SKU"""
|
||||
name = models.CharField(max_length=50, verbose_name='名称')
|
||||
caption = models.CharField(max_length=100, verbose_name='副标题')
|
||||
spu = models.ForeignKey(SPU, on_delete=models.CASCADE, verbose_name='商品')
|
||||
category = models.ForeignKey(GoodsCategory, on_delete=models.PROTECT, verbose_name='从属类别')
|
||||
price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='单价')
|
||||
cost_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='进价')
|
||||
market_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='市场价')
|
||||
stock = models.IntegerField(default=0, verbose_name='库存')
|
||||
sales = models.IntegerField(default=0, verbose_name='销量')
|
||||
comments = models.IntegerField(default=0, verbose_name='评价数')
|
||||
is_launched = models.BooleanField(default=True, verbose_name='是否上架销售')
|
||||
default_image = models.ImageField(max_length=200, default='', null=True, blank=True, verbose_name='默认图片')
|
||||
|
||||
class Meta:
|
||||
db_table = 'tb_sku'
|
||||
verbose_name = '商品SKU'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def __str__(self):
|
||||
return '%s: %s' % (self.id, self.name)
|
||||
|
||||
|
||||
class SKUImage(BaseModel):
|
||||
"""SKU图片"""
|
||||
|
||||
sku = models.ForeignKey(SKU, on_delete=models.CASCADE, verbose_name='sku')
|
||||
image = models.ImageField(verbose_name='图片')
|
||||
|
||||
class Meta:
|
||||
db_table = 'tb_sku_image'
|
||||
verbose_name = 'SKU图片'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def __str__(self):
|
||||
return '%s %s' % (self.sku.name, self.id)
|
||||
|
||||
|
||||
class SPUSpecification(BaseModel):
|
||||
"""商品SPU规格"""
|
||||
|
||||
spu = models.ForeignKey(SPU, on_delete=models.CASCADE, related_name='specs', verbose_name='商品SPU')
|
||||
name = models.CharField(max_length=20, verbose_name='规格名称')
|
||||
|
||||
class Meta:
|
||||
db_table = 'tb_spu_specification'
|
||||
verbose_name = '商品SPU规格'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def __str__(self):
|
||||
return '%s: %s' % (self.spu.name, self.name)
|
||||
|
||||
|
||||
class SpecificationOption(BaseModel):
|
||||
"""规格选项"""
|
||||
spec = models.ForeignKey(SPUSpecification, related_name='options',
|
||||
on_delete=models.CASCADE, verbose_name='规格')
|
||||
value = models.CharField(max_length=20, verbose_name='选项值')
|
||||
|
||||
class Meta:
|
||||
db_table = 'tb_specification_option'
|
||||
verbose_name = '规格选项'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def __str__(self):
|
||||
return '%s - %s' % (self.spec, self.value)
|
||||
|
||||
|
||||
class SKUSpecification(BaseModel):
|
||||
"""SKU具体规格"""
|
||||
|
||||
sku = models.ForeignKey(SKU, related_name='specs', on_delete=models.CASCADE, verbose_name='sku')
|
||||
spec = models.ForeignKey(SPUSpecification, on_delete=models.PROTECT, verbose_name='规格名称')
|
||||
option = models.ForeignKey(SpecificationOption, on_delete=models.PROTECT, verbose_name='规格值')
|
||||
|
||||
class Meta:
|
||||
db_table = 'tb_sku_specification'
|
||||
verbose_name = 'SKU规格'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def __str__(self):
|
||||
return '%s: %s - %s' % (self.sku, self.spec.name, self.option.value)
|
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
@ -0,0 +1,19 @@
|
||||
from django.contrib import admin
|
||||
from django.urls import path
|
||||
from .views import *
|
||||
|
||||
app_name = 'goods'
|
||||
urlpatterns = [
|
||||
# 商品列表页
|
||||
path('list/<int:category_id>/<int:page_num>/', ListView.as_view(), name='list'),
|
||||
# 商品详情
|
||||
path('detail/<int:sku_id>/', DetailView.as_view(), name='detail'),
|
||||
# 热销排行
|
||||
path('hot/<int:category_id>/', HostGoodsView.as_view()),
|
||||
# 商品搜索
|
||||
path('search/', SearchView.as_view()),
|
||||
# 商品评价
|
||||
path('comments/<int:sku_id>/', GoodsCommentView.as_view()),
|
||||
# 商品类别
|
||||
path('categorys/', CategorysView.as_view()),
|
||||
]
|
@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env python
|
||||
"""Django's command-line utility for administrative tasks."""
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'xiaoyu_mall.settings')
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
) from exc
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class OrdersConfig(AppConfig):
|
||||
name = 'orders'
|
@ -0,0 +1,60 @@
|
||||
# Generated by Django 2.2.8 on 2023-08-25 09:37
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('goods', '0001_initial'),
|
||||
('users', '0002_auto_20230825_1737'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='OrderInfo',
|
||||
fields=[
|
||||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
('order_id', models.CharField(max_length=64, primary_key=True, serialize=False, verbose_name='订单号')),
|
||||
('total_count', models.IntegerField(default=1, verbose_name='商品总数')),
|
||||
('total_amount', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='商品总金额')),
|
||||
('freight', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='运费')),
|
||||
('pay_method', models.SmallIntegerField(choices=[(1, '货到付款'), (2, '支付宝')], default=1, verbose_name='支付方式')),
|
||||
('status', models.SmallIntegerField(choices=[(1, '待支付'), (2, '待发货'), (3, '待收货'), (4, '待评价'), (5, '已完成'), (6, '已取消')], default=1, verbose_name='订单状态')),
|
||||
('address', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='users.Address', verbose_name='收货地址')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='下单用户')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '订单基本信息',
|
||||
'verbose_name_plural': '订单基本信息',
|
||||
'db_table': 'tb_order_info',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='OrderGoods',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
('count', models.IntegerField(default=1, verbose_name='数量')),
|
||||
('price', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='单价')),
|
||||
('comment', models.TextField(default='', verbose_name='评价信息')),
|
||||
('score', models.SmallIntegerField(choices=[(0, '0分'), (1, '20分'), (2, '40分'), (3, '60分'), (4, '80分'), (5, '100分')], default=5, verbose_name='满意度评分')),
|
||||
('is_anonymous', models.BooleanField(default=False, verbose_name='是否匿名评价')),
|
||||
('is_commented', models.BooleanField(default=False, verbose_name='是否评价了')),
|
||||
('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='skus', to='orders.OrderInfo', verbose_name='订单')),
|
||||
('sku', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='goods.SKU', verbose_name='订单商品')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '订单商品',
|
||||
'verbose_name_plural': '订单商品',
|
||||
'db_table': 'tb_order_goods',
|
||||
},
|
||||
),
|
||||
]
|
Binary file not shown.
Binary file not shown.
@ -0,0 +1,78 @@
|
||||
from django.db import models
|
||||
# Create your models here.
|
||||
from goods.models import SKU
|
||||
from users.models import User, Address
|
||||
from utils.models import BaseModel
|
||||
|
||||
|
||||
class OrderInfo(BaseModel):
|
||||
"""订单信息"""
|
||||
PAY_METHODS_ENUM = {
|
||||
"CASH": 1,
|
||||
"ALIPAY": 2
|
||||
}
|
||||
PAY_METHOD_CHOICES = (
|
||||
(1, "货到付款"),
|
||||
(2, "支付宝"),
|
||||
)
|
||||
ORDER_STATUS_ENUM = {
|
||||
"UNPAID": 1,
|
||||
"UNSEND": 2,
|
||||
"UNRECEIVED": 3,
|
||||
"UNCOMMENT": 4,
|
||||
"FINISHED": 5
|
||||
}
|
||||
ORDER_STATUS_CHOICES = (
|
||||
(1, "待支付"),
|
||||
(2, "待发货"),
|
||||
(3, "待收货"),
|
||||
(4, "待评价"),
|
||||
(5, "已完成"),
|
||||
(6, "已取消"),
|
||||
)
|
||||
order_id = models.CharField(max_length=64, primary_key=True, verbose_name="订单号")
|
||||
user = models.ForeignKey(User, on_delete=models.PROTECT, verbose_name="下单用户")
|
||||
address = models.ForeignKey(Address, on_delete=models.PROTECT, verbose_name="收货地址")
|
||||
total_count = models.IntegerField(default=1, verbose_name="商品总数")
|
||||
total_amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="商品总金额")
|
||||
freight = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="运费")
|
||||
pay_method = models.SmallIntegerField(choices=PAY_METHOD_CHOICES, default=1, verbose_name="支付方式")
|
||||
status = models.SmallIntegerField(choices=ORDER_STATUS_CHOICES, default=1, verbose_name="订单状态")
|
||||
|
||||
class Meta:
|
||||
db_table = "tb_order_info"
|
||||
verbose_name = '订单基本信息'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def __str__(self):
|
||||
return self.order_id
|
||||
|
||||
|
||||
class OrderGoods(BaseModel):
|
||||
"""订单商品"""
|
||||
|
||||
SCORE_CHOICES = (
|
||||
(0, '0分'),
|
||||
(1, '20分'),
|
||||
(2, '40分'),
|
||||
(3, '60分'),
|
||||
(4, '80分'),
|
||||
(5, '100分'),
|
||||
)
|
||||
order = models.ForeignKey(OrderInfo, related_name='skus',
|
||||
on_delete=models.CASCADE, verbose_name="订单")
|
||||
sku = models.ForeignKey(SKU, on_delete=models.PROTECT, verbose_name="订单商品")
|
||||
count = models.IntegerField(default=1, verbose_name="数量")
|
||||
price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="单价")
|
||||
comment = models.TextField(default="", verbose_name="评价信息")
|
||||
score = models.SmallIntegerField(choices=SCORE_CHOICES, default=5, verbose_name='满意度评分')
|
||||
is_anonymous = models.BooleanField(default=False, verbose_name='是否匿名评价')
|
||||
is_commented = models.BooleanField(default=False, verbose_name='是否评价了')
|
||||
|
||||
class Meta:
|
||||
db_table = "tb_order_goods"
|
||||
verbose_name = '订单商品'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def __str__(self):
|
||||
return self.sku.name
|
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
@ -0,0 +1,13 @@
|
||||
from django.contrib import admin
|
||||
from django.urls import path
|
||||
from .views import *
|
||||
|
||||
app_name = 'orders'
|
||||
urlpatterns = [
|
||||
# 结算订单
|
||||
path('orders/settlement/', OrderSettlementView.as_view(), name='settlement'),
|
||||
# 提交订单
|
||||
path('orders/commit/', OrderCommitView.as_view()),
|
||||
# 提交订单成功
|
||||
path('orders/success/', OrderSuccessView.as_view()),
|
||||
]
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue