1.创建项目
项目架构:
1)前端、后端部分(各五个分类):
1: 用户user 2: 商品goods 3: 购物车cart 4: 订单order 5: 后台admin
2)关系数据库:mysql
3)缓存服务器(session):redis内存型数据库
4)异步任务处理:celery
5)分布式文件存储系统:fastdfs
数据库设计:
(1)用户表:id,用户名,密码,邮箱,激活标识,权限标识
class User(AbstractUser, BaseModel):
'''用户模型类'''
class Meta:
db_table = 'df_user'
verbose_name = '用户'
verbose_name_plural = verbose_name
(2)地址表:id,收件人,收件地址,邮编,联系方式,是否默认,用户id
class Address(BaseModel):
'''地址模型类'''
user = models.ForeignKey('User', verbose_name='所属账户')
receiver = models.CharField(max_length=20, verbose_name='收件人')
addr = models.CharField(max_length=256, verbose_name='收件地址')
zip_code = models.CharField(max_length=6, null=True, verbose_name='邮政编码')
phone = models.CharField(max_length=11, verbose_name='联系电话')
is_default = models.BooleanField(default=False, verbose_name='是否默认')
# 自定义一个模型管理器对象,Django 会确保在模型类中至少有一个默认的Manager
# 若没有添加自己的Manager,Django 将添加一个属性objects,它包含默认的Manager 实例
# 若添加自己的Manager实例的属性,默认值则不会出现
objects = AddressManager()
class Meta:
db_table = 'df_address'
verbose_name = '地址'
verbose_name_plural = verbose_name
class AddressManager(models.Manager):
'''地址模型管理器类'''
# 1.改变原有查询的结果集:all()
# 2.封装方法:用户操作模型类对应的数据表(增删改查)
def get_default_address(self, user):
'''获取用户默认收货地址'''
# self.model:获取self对象所在的模型类
try:
address = self.get(user=user, is_default=True) # models.Manager
except self.model.DoesNotExist:
# 不存在默认收货地址
address = None
return address
(3)商品SKU表:id,名称,简介,价格,单位,库存,销量,图片,状态,种类ID,SPU ID
class GoodsSKU(BaseModel):
'''商品SKU模型类'''
status_choices = (
(0, '下线'),
(1, '上线'),
)
type = models.ForeignKey('GoodsType', verbose_name='商品种类')
goods = models.ForeignKey('Goods', verbose_name='商品SPU')
name = models.CharField(max_length=20, verbose_name='商品名称')
desc = models.CharField(max_length=256, verbose_name='商品简介')
price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='商品价格')
unite = models.CharField(max_length=20, verbose_name='商品单位')
image = models.ImageField(upload_to='goods', verbose_name='商品图片')
stock = models.IntegerField(default=1, verbose_name='商品库存')
sales = models.IntegerField(default=0, verbose_name='商品销量')
status = models.SmallIntegerField(default=1, choices=status_choices, verbose_name='商品状态')
class Meta:
db_table = 'df_goods_sku'
verbose_name = '商品'
verbose_name_plural = verbose_name
(4)商品种类表:id,种类名称,logo,图片
class GoodsType(BaseModel):
'''商品类型模型类'''
name = models.CharField(max_length=20, verbose_name='种类名称')
logo = models.CharField(max_length=20, verbose_name='标识')
image = models.ImageField(upload_to='type', verbose_name='商品类型图片')
class Meta:
db_table = 'df_goods_type'
verbose_name = '商品种类'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
(5)首页轮播商品表:id,sku_id,图片,index(代码略,同下模型类)
(6)首页促销活动表:id,图片,活动url,index
(7)首页SPU表:id,名称,详情
(8)首页分类商品展示表:id,sku_id,种类id,展示标识,index
(9)商品图片表:id,图片,sku_id
(10)订单商品表:id,订单id,sku_id,商品数量,商品价格,评论
(11)订单信息表:订单id,地址id,用户id,支付方式,总数目,总金额,运费,支付状态,创建时间
2.创建项目
项目开发环境:linux
安装好虚拟环境(virtualenvwrapper)以及相关第三方库
输入指令:
django-admin startproject 项目名称
cd 项目目录
进入项目目录当中进行开发
3.创建应用
一个项目中可包含很多应用,本项目应用进行了模块化处理。
输入命令创建四个主要应用:
python manage.py startapp users
python manage.py startapp goods
python manage.py startapp orders
python manage.py startapp cart
在settings中的INSTALLED_APPS中配置四个应用:
(1)users(2)goods(3)orders(4) cart
python manage.py createsuperuser
创建的超级管理员也会在df_user的表中。
3.配置数据库
Django支持多种数据库,这里演示使用 sqlite,具体数据库配置于项目下的settings.py中进行。
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': os.path.join(BASE_DIR, 'db.mysql'),
}
}
5.编写models.py文件
MVT框架的M,表示model,负责与数据库交互。
进行各模型类代码编写(部分代码见项目数据库设计部分)
6.迁移模型至数据库
(1)激活模型:
于settings的INSTALLED_APPS中注册应用(如上图)
(2)生成迁移文件
进行迁移这一过程,会根据定义的模型类来生成对应的sql语句:
终端执行如下指令:
python manage.py makemigrations
如此mysql数据库部分已搭建好了。
7. 编写模板文件
MVT框架的T,表示template,负责呈现内容到浏览器。 模板是Django项目中用来向用户展示网站具体信息的部分,为html文件,不过在其基础上添加了类python的可执行代码,具有python代码的html文件通过渲染后,可生成普通的html文件。
Django项目中模板的路径为项目根目录下的templates中。
8.编写视图文件
视图Django当中用来处理用户请求的部分,对应MVT中的V(view),是框架的核心,负责接收请求、获取数据和返回结果。
视图会根据用户所发出的请求向模型获取所需数据,并将数据发送给模板,并对模板进行渲染操作,将渲染好的html文件返回给浏览器进行展示。(鉴于篇幅,本文展示项目当中购物车cart模块的view文件内容)
from django.shortcuts import render
from django.views.generic import View
from django.http import JsonResponse
from goods.models import GoodsSKU
from django_redis import get_redis_connection
from utils.mixin import LoginRequiredMixin
# Create your views here.
# 添加商品到购物车:
# 1)请求方式,采用ajax post
# 如果涉及到数据的修改(新增,更新,删除), 采用post
# 如果只涉及到数据的获取,采用get
# 2) 传递参数: 商品id(sku_id) 商品数量(count)
# ajax发起的请求都在后台,在浏览器中看不到效果
# /cart/add
class CartAddView(View):
'''购物车记录添加'''
def post(self, request):
'''购物车记录添加'''
user = request.user
if not user.is_authenticated():
# 用户未登录
return JsonResponse({'res':0, 'errmsg':'请先登录'})
# 接收数据
sku_id = request.POST.get('sku_id')
count = request.POST.get('count')
# 数据校验
if not all([sku_id, count]):
return JsonResponse({'res':1, 'errmsg':'数据不完整'})
# 校验添加的商品数量
try:
count = int(count)
except Exception as e:
# 数目出错
return JsonResponse({'res':2, 'errmsg':'商品数目出错'})
# 校验商品是否存在
try:
sku = GoodsSKU.objects.get(id=sku_id)
except GoodsSKU.DoesNotExist:
# 商品不存在
return JsonResponse({'res':3, 'errmsg':'商品不存在'})
# 业务处理:添加购物车记录
conn = get_redis_connection('default')
cart_key = 'cart_%d'%user.id
# 先尝试获取sku_id的值 -> hget cart_key 属性
# 如果sku_id在hash中不存在,hget返回None
cart_count = conn.hget(cart_key, sku_id)
if cart_count:
# 累加购物车中商品的数目
count += int(cart_count)
# 校验商品的库存
if count > sku.stock:
return JsonResponse({'res':4, 'errmsg':'商品库存不足'})
# 设置hash中sku_id对应的值
# hset->如果sku_id已经存在,更新数据, 如果sku_id不存在,添加数据
conn.hset(cart_key, sku_id, count)
# 计算用户购物车商品的条目数
total_count = conn.hlen(cart_key)
# 返回应答
return JsonResponse({'res':5, 'total_count':total_count, 'message':'添加成功'})
# /cart/
class CartInfoView(LoginRequiredMixin, View):
'''购物车页面显示'''
def get(self, request):
'''显示'''
# 获取登录的用户
user = request.user
# 获取用户购物车中商品的信息
conn = get_redis_connection('default')
cart_key = 'cart_%d'%user.id
# {'商品id':商品数量, ...}
cart_dict = conn.hgetall(cart_key)
skus = []
# 保存用户购物车中商品的总数目和总价格
total_count = 0
total_price = 0
# 遍历获取商品的信息
for sku_id, count in cart_dict.items():
# 根据商品的id获取商品的信息
sku = GoodsSKU.objects.get(id=sku_id)
# 计算商品的小计
amount = sku.price*int(count)
# 动态给sku对象增加一个属性amount, 保存商品的小计
sku.amount = amount
# 动态给sku对象增加一个属性count, 保存购物车中对应商品的数量
sku.count = count
# 添加
skus.append(sku)
# 累加计算商品的总数目和总价格
total_count += int(count)
total_price += amount
# 组织上下文
context = {'total_count':total_count,
'total_price':total_price,
'skus':skus}
# 使用模板
return render(request, 'cart.html', context)
# 更新购物车记录
# 采用ajax post请求
# 前端需要传递的参数:商品id(sku_id) 更新的商品数量(count)
# /cart/update
class CartUpdateView(View):
'''购物车记录更新'''
def post(self, request):
'''购物车记录更新'''
user = request.user
if not user.is_authenticated():
# 用户未登录
return JsonResponse({'res': 0, 'errmsg': '请先登录'})
# 接收数据
sku_id = request.POST.get('sku_id')
count = request.POST.get('count')
# 数据校验
if not all([sku_id, count]):
return JsonResponse({'res': 1, 'errmsg': '数据不完整'})
# 校验添加的商品数量
try:
count = int(count)
except Exception as e:
# 数目出错
return JsonResponse({'res': 2, 'errmsg': '商品数目出错'})
# 校验商品是否存在
try:
sku = GoodsSKU.objects.get(id=sku_id)
except GoodsSKU.DoesNotExist:
# 商品不存在
return JsonResponse({'res': 3, 'errmsg': '商品不存在'})
# 业务处理:更新购物车记录
conn = get_redis_connection('default')
cart_key = 'cart_%d'%user.id
# 校验商品的库存
if count > sku.stock:
return JsonResponse({'res':4, 'errmsg':'商品库存不足'})
# 更新
conn.hset(cart_key, sku_id, count)
# 计算用户购物车中商品的总件数 {'1':5, '2':3}
total_count = 0
vals = conn.hvals(cart_key)
for val in vals:
total_count += int(val)
# 返回应答
return JsonResponse({'res':5, 'total_count':total_count, 'message':'更新成功'})
# 删除购物车记录
# 采用ajax post请求
# 前端需要传递的参数:商品的id(sku_id)
# /cart/delete
class CartDeleteView(View):
'''购物车记录删除'''
def post(self, request):
'''购物车记录删除'''
user = request.user
if not user.is_authenticated():
# 用户未登录
return JsonResponse({'res': 0, 'errmsg': '请先登录'})
# 接收参数
sku_id = request.POST.get('sku_id')
# 数据的校验
if not sku_id:
return JsonResponse({'res':1, 'errmsg':'无效的商品id'})
# 校验商品是否存在
try:
sku = GoodsSKU.objects.get(id=sku_id)
except GoodsSKU.DoesNotExist:
# 商品不存在
return JsonResponse({'res':2, 'errmsg':'商品不存在'})
# 业务处理:删除购物车记录
conn = get_redis_connection('default')
cart_key = 'cart_%d'%user.id
# 删除 hdel
conn.hdel(cart_key, sku_id)
# 计算用户购物车中商品的总件数 {'1':5, '2':3}
total_count = 0
vals = conn.hvals(cart_key)
for val in vals:
total_count += int(val)
# 返回应答
return JsonResponse({'res':3, 'total_count':total_count, 'message':'删除成功'})
9.配置url 在完成网站各个功能模块的model、templates、view创建后,接下来需要将这几个部分打通起来,使其组合起来协调工作。此时,需要进行url的配置。
当客户端浏览器一个request请求到服务器的时候,首先要对请求的url信息进行解析,获取当前请求需要的信息是什么,请求会对应一个具体的view视图,视图会将模板和模型组合起来。
10.项目配置
有关项目所需进行的配置:
(1)设置应用所在路径,以便于应用注册可找到相应应用
import os
import sys
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))
(2)uwsgi上线部署
DEBUG = False
# 为了让我们的项目可以被远程访问,在设置文件中指定项目运行的主机IP:
# 在uwsgi服务器中可设置为‘*’
ALLOWED_HOSTS = ['*']
(3)uwsgi应用路径设置
WSGI_APPLICATION = 'dailyfresh.wsgi.application'
(4)url全局配置
ROOT_URLCONF = 'dailyfresh.urls'
(5)url全局配置
ROOT_URLCONF = 'dailyfresh.urls'
(6)用户模型配置
# django认证系统使用的用户模型类(由于自行定制了用户模型类)
AUTH_USER_MODEL='user.User'
(7)url全局配置
# 修改站点语言
# LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'zh-Hans'
# 修改站点时区
# TIME_ZONE = 'UTC'
TIME_ZONE = 'Asia/Shanghai'
(8)静态页面及文件的配置
STATIC_URL = '/static/' # 静态文件的路径设置
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
# 指定收集静态文件的路径
STATIC_ROOT='/var/www/dailyfresh/static'
(9)Tinymce富文本编辑器配置
# 富文本编辑器配置
TINYMCE_DEFAULT_CONFIG = {
'theme': 'advance',
'width': 600,
'height': 400,
}
(10)邮件配置
# 发送邮件配置
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
# smpt服务地址
EMAIL_HOST = 'smtp.163.com'
EMAIL_PORT = 25
# 发送邮件的邮箱
EMAIL_HOST_USER = '[email protected]'
# 在邮箱中设置的客户端授权密码
EMAIL_HOST_PASSWORD = 'smartli123'
# 收件人看到的发件人
EMAIL_FROM = '天天生鲜'
(11)Django缓存配置
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://172.16.179.142:6379/9", # redis对应的服务器ip和端口
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
}
}
(12)配置session存储
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "default"
(13)配置登录url地址
登录地址因项目需要而定制url登录地址,故需于settings文件中自行配置
LOGIN_URL='/user/login' # /accounts/login?next=/user
(14)FDFS配置
# 设置Django的文件存储类
DEFAULT_FILE_STORAGE='utils.fdfs.storage.FDFSStorage'
# 设置fdfs使用的client.conf文件路径
FDFS_CLIENT_CONF='./utils/fdfs/client.conf'
# 设置fdfs存储服务器上nginx的IP和端口号
FDFS_URL='http://172.16.179.131:8888/'
(15)全文检索框架的配置
# 全文检索框架HAYSTACK的配置
HAYSTACK_CONNECTIONS = {
'default': {
# 使用whoosh引擎
# 'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
'ENGINE': 'haystack.backends.whoosh_cn_backend.WhooshEngine',
# 索引文件路径
'PATH': os.path.join(BASE_DIR, 'whoosh_index'),
}
}
# 当添加、修改、删除数据时,自动生成索引
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
# 指定搜索结果每页显示的条数
HAYSTACK_SEARCH_RESULTS_PER_PAGE=1
11.启动服务
# 启用django正常指令
python manage.py runserver ip:port
# 配置uwsgi服务器
启动:uwsgi –-ini 配置文件路径 例如:uwsgi –-ini uwsgi.ini
停止:uwsgi --stop uwsgi.pid路径 例如:uwsgi –-stop uwsgi.pid
12.站点管理
Django自带一套完整可定制的后端站点管理系统,可实现对项目数据库的增删改查操作。
进入方式是在项目网址后面加 /admin
(1)创建超级用户
但在进入站点管理前必须创建一个超级用户,通过超级用户才能登陆到站点管理后台。
# 创建超级用户指令
python manage.py createsuperuser
(2)注册模型
进入网站管理界面主要是为了管理数据库中的内容,但之前的工作中只是定义了模型并完成模型带数据库的迁移操作,这些只是准备操作。
要想在站点中管理数据库对象必须要先在系统管理中进行注册。
注册方法:在所创建的应用目录下的admin.py文件,在该文件中导入对应的模型列,调用admin模块的register模块。
from django.contrib import admin
from django.core.cache import cache
from goods.models import GoodsType,IndexPromotionBanner,IndexGoodsBanner,IndexTypeGoodsBanner
admin.site.register(GoodsType)
admin.site.register(IndexGoodsBanner)
admin.site.register(IndexTypeGoodsBanner)
admin.site.register(IndexPromotionBanner)
对相应的模型类注册后,便可以登录到管理界面进行数据处理了。