一、配置
1 创建项目和应用
- 配置项目setting.py(应用,templates路径,数据库,STATICFILES_DIRS)
- 配置项目urls.py,namespace起名最好和应用名一样,不容易混淆
2 配置应用
- 在每个应用中创建urls.py
- 同时对应该应用的views.py,同步配置视图函数,name和视图函数以及url的名字最好保持一致
3 创建相应的文件夹,将前台文件放入
static
- images
- js
- css
templates
templates下创建每个应用单独的文件夹,将html文件放入
二、拆分模板
- 创建父模板
在templates下创建文件夹common;
该文件夹存储父模板base.html和status.html模板(其他相同代码片模板)
父模板留空给子模板写不同代码片
{% block 模板名 %}
{% endblock 模板名 %}
继承父模板
{% extends 父模板名 %}
包含相同代码片模板
{% include 'common/status.html' %}
- 在html页面中修改,继承父模板
- 记得更改html页面中static元素
加载静态文件
{% load staticfiles %}
静态文件写入方式:
{% static ‘路径/文件名‘ %}
该路径以static文件为相对路径
三、创建模型
1、创建抽象模型基类
- 在项目下新建文件夹db,储存AbstractModel.py 在该文件中创建抽象基类
- 在models.py中
from db.AbstractModel import *
创建模型(继承抽象基类)和模型管理器 - 表迁移
2、模型操作的原则
如果定义的方法是针对表数据操作,需要定义在自定义管理类中
例如:插入数据、查询表中的数据如果定义的方法针对是行数据进行操作,需要定义在模型类中
例如:修改某一行数据的某个字段数据在模型管理类中写方法可以直接用self;
在模型类中写方法,需要self.objects;
两者都可以用 模型类.objects。表级操作用的是模型管理器对象;
行级操作用的是那一行数据对象(例如:self.get(user_id = XX)获取到的对象)。
模型管理类
- self.all()
模型类
- self.objects.all()
- user = User()
user.objects.all()
出错点:
模型类要重新写objects = 管理类(),objects记得写s,
提示错误信息,User没有objects
三、注册功能
页面 user/register
1 更改register.html中表单提交方式和表单提交的视图
method=“post” action=“{% url ‘/user/register_handle‘ %}”
2 创建视图register_handle,处理表单提交事件
views.py中导入 from .functions import *
functions中导入 from utils.wrapper import *
视图中只写大概的逻辑判断(根据信息匹配成功与否判断是否提交),具体的函数另外同级创建一个文件functions.py处理,使views保持整洁,逻辑易读。
(1) 同级创建 functions.py
该文件主要对该视图内需要里获取的参数进行判断,只对users来使用,并把判断结果返回给视图。
(2) 创建utils文件夹
该文件夹下的模块的函数可以对全局使用
a. 创建 wrapper.py
在functions.py中写函数进行判断后,此时需要用到django的原生函数post获取数据,我们在项目下再创建一个文件夹utils工具,在utils下创建wrapper.py,在该文件内对post等原生函数进行封装,方便调用。
举例:
def post(request, param):
<!--去掉取出数据的两边空格-->
return request.POST.get(param, "").strip()
b. 修改html页面
获取页面用户输入数据,需要表单中的input标签的name属性,去html文件中进行name属性的命名修改。
(3) functions.py处理表单提交的数据
- 获取所有用户输入数据
- 建立flag,假设所有数据均为错误
- 对每个数据进行逐一格式判断,更改flag
-
判断用户名是否已存在
需要查询用户表,对数据库进行操作,对数据库进行的所有操作均在models.py中进行,写在uesr管理器中。
models中管理类中写入方法对数据库表进行操
- 注册功能用到了查询和插入数据两项
- 方法的参数为request或者是已经处理过的数据
- 查询该用户是否存在于数据库中(参数为用户名)
若通过校验,获取数据,存入数据库
若没有通过校验:
a. 视图之间通信 from django.contrib import messages
messages也是基于cookie和session的封装
从一个页面(视图)跳转到另一个页面(视图)时,如果需要传递参数,可以用get,在URL后面加上?xx=xx,
但是这里传递错误消息,内容较长,用这种方式不合理,就需要一个可以在视图间进行数据传输的工具。这个模块是进行视图之间通信,数据传输所用,相当于一个队列,可以放入数据,取出数据,一次性使用。
<!--message.INFO INFO 代表信息级别,
还有ERROR WARNING ,
这里仅仅是传递一个错误信息,INFO足够了-->
添加信息
messages.add_message(request, message.INFO, "数据内容")
取出信息
mess = messages.get_messages(request)
这里mess并不是一个队列,但是是 可以迭代的,可通过for循环取出
这里mess并不是一个队列,但是是可以迭代的,可通过for循环取出
值得注意的是,取出的数据是先进先出,取出,并不能确定哪条数据归属于哪个内容下的错误提示,
所以要在wrapper.py中对这两个原生接口进行封装:
- 添加消息时给每个消息加个消息头,组成类似于字典的字符串“key:value”;
- 取出数据时,根据:对消息进行切割,组成字典的形式。
3 处理错误提示消息
user/register/当点击注册时在前端页面显示
- 将错误提示消息放在register视图中取出,记为mess;
- 在index.html中显示错误提示消息的地方添加一样的标签,更改class,并写入{% mess.key %},会自动寻找字典中值而添加。
- 修改样式,和前端的提示信息css样式一致,更改display:block为显示;
- 修改js中焦点触发事件,在触发焦点时,后台错误提示消息的标签隐藏。
4 通过ajax请求,异步判断用户名是否存在
js发送ajax请求后台判断用户名是否存在,返回结果给js进行异步判断,注册页面在用户输入用户名时可实时显示用户名是否存在
四、登录功能
不能进行用户是否存在验证,不安全
1 建立处理用户登录时的视图
在该视图中写入主要逻辑:
如果验证通过,回到xx页面;(先返回首页,后面写中间件处理过后,再判断session中是否有pre_url,如果有,返回pre_url,没有则返回主页)
验证不通过,重回登录页面,并提示错误消息
2 在function.py中判断用户名和密码输入的格式是否正确
- 如果格式不正确,直接回到登录页面
- 如果格式正确,进行用户名和密码验证
- 验证通过:先跳转到首页
- 验证不通过:
通过messages模块,将错误提示消息(用户名或密码错误)放入队列中;
更改views中的登录视图,取出message中的错误提示消息,更改登录页面login.html和js,使错误提示消息在表单提交后显示。
3 设置cookie和session
(1)思路
- 如果用户登录时,选择记住用户名,需要设置用户登录名的cookie,存储在浏览器;
- 当用户登录后,需要在每个页面保持用户的登录状态,需要设置用户登录名的session;
因为用户每次发送请求,request中都带有cookie,cookie中带有sessionid,可以识别用户; - 当未登录用户在商品等页面点击或跳转至登录页面之后,如果登录成功,则直接跳转回上次的商品浏览页面;
- 当用户未登录时,不能进入用户中心,地址和订单页面,可使用装饰器对这三个视图进行包装,若点击这个三个链接,直接重定向到登录页面。
(2)实现
先登录
1. 在warpper.py中对cookie和session进行封装
response.set_cookie[key] = value
request.get_cookie[key]
response.delete_cookie[key] 如果key不存在什么也不发生
<!--设置session过期时间:
0为关闭浏览器时过期,
整数为多少秒后过期,
不设置或value为None时使用默认的,
Django默认为两周-->
<!--request.session.set_expiry(value)-->
request.session[key] = value
request.session.get(key,"") 获取session,key不存在默认为空
request.session[key] 获取session,key不存在会报错
del request.session[key]
request.session.flush()
<!--del request.session[key]只是删除value,key还在,所以一般用request.session.flush()全部删除-->
2. 用户登录:
- 设置session,记录登录状态
- 若勾选记住用户名,需设置cookie
3. 用户登出,删除session
4. 浏览商品后再登录,需要:中间件–记录上次的浏览页面
- 在视图处理url前,记录上次浏览的页面,process_request和process_view均可,这里显然process_request更方便(传入参数只需要request);
- 设置一个列表,存储,不需要保存的URL;
(1)例如,登录、注册的url,处理表单的URL,用户中心、地址、订单的URL,网络图标favicon.ico等。
(2)这里也包含了用户中心、地址、订单页面,如果用户在该页面登出,再登录,也返回首页。
(3)如果需求为再登录时,回到之前的用户中心、地址、订单页面,中间件中这几个url就不加入列表。 - 获取上次登录页面pre_url=request.get_all_path()的包含带有?的参数等全部url。
如果url在列表中,则直接返回首页;
如果不在列表中,则返回上次登录页面,即pre_url。
!!! 中间件修改:
- 由于process_request会记录一些跳转页面,所以这里更改为process_response
- 将拦截条件添加response.status_code==200,最后一定要返回response。
4 用户浏览页面的权限
需求:
若已登录,可以进入用户中心、订单、地址、页面;
若未登录,点击这些链接,回到登录页面。
这里使用装饰器
def permission_verfy(view_func):
def wraper(request,*args,**kwargs):
<!--判断用户是否登录-->
if get_session(request, 'username'):
return view_func(request,*args,**keargs)
else:
return redirect(首页url)
return wrapper
五、用户中心
1 用户中心左侧菜单栏拆分
- 由于左侧菜单一样,需要拆分模板;
- 左侧菜单种当前页面的栏目有特殊样式;
方法一:
模板包含的时候可以通过with传递参数
{% include 'user/left_menu.html' with flag="order" %}
在通用模板left_menu.html中,进行判断
{% if flag="order" %}class="active"{% endif %}
方法二(根据列表自动生成菜单栏):
使用过滤器:
{% with menus=flag|creat_meau %}
(将参数flag传入过滤器函数creat_meau,函数返回结果赋给menus)
在过滤器中设置列表,列表中字典根据键值对存储主要参数,
对于特殊样式,action键可以通过三步运算判断
flag = "order" and "active" or ""
在left_menu.html中for循环自动生成菜单栏,
2 用户个人中心修改地址
form表单提交,套路:
- 前台js验证输入是否合法;
- 配制form表单提交方式和提交的url;
- views后台获取form表单数据,验证数据是否合法;
- 合法数据存入数据库,
- 验证到不合法数据,将验证失败的错误提示通过messages加入队列;
- 前台html显示。
- 添加html后台错误提示的标签;
- 修改后台错误提示的样式,display=block;
- 修改js,焦点获取时blur(),将后台的错误提示标签隐藏;
- 后台用户中心页面view取出messages队列中的错误提示,加入html中。
六、商品
1 创建商品分类模型,商品信息模型
(1)商品分类模型
- 商品类名
(2)商品信息模型
- 商品名
- 商品价格
- 商品单位
- 商品简介
- 商品详情(采用富文本编辑器)
- 商品状态(上架,下架)
- 商品图片
- 商品分类(外键关联商品分类模型)
- 商品浏览量
- 商品销售量
2 创建测试数据
3 商品首页的数据显示
(1)获取最新商品
(2)获取最热门商品
3 商品详细页面
(1)设计为带?参数的url或者正则匹配的url
这里为/goods/detail/?id=xxx
根据商品id动态生成商品详细页面
在商品详情url的视图中,通过utl中传入的参数,get(request,”id”)获取该商品对象,在html中写入数据.
(2)获取两个最新产品
(3)配制这些图片的链接
带参数的?url
{% url "goods:detail" %}?id={{ goods.id }}
或 正则提取url
{% url "goods:detail" goods.id %}
(4)js操作 根据商品价格和数量实时变动总价
js就够了,不需要ajax,让后台来做
(5) 商品浏览记录
a.创建商品浏览记录模型
- 浏览用户(外键关联用户表)
- 浏览商品(外键关联商品表)
b.这里需要用户id
我们把user_id,添加到session或者cookie中,可以直接获取
1. 当用户登录成功,设置cookie,user_id
2. 在商品详细页面的视图中,获取user_id判断用户是否登录,如果用户已登录,获取goods_id,将它们存入数据库;
3. 在存入数据库时,进行判断:
如果用户存在:
获取用户的浏览商品record_goods_list,并按更新时间排序
1 如果该商品存在:
直接save,可以更新时间
2 如果商品不存在:
判断商品数量:
如果商品数量<5:直接存入数据库;
否则:获取更新时间最早的一条数据对象,record_goods_list[0],对其进行更新覆盖。
- 去用户中心,在用户中心视图获取浏览记录,在html中显示。
4 商品列表页
(1)需求:
- 根据分类显示商品
- 根据销量、人气、默认进行商品排序
(2)设计url
可以正则匹配两个参数,param1为商品分类id,param2为商品排序方式
^"/goods/detail/(\d)/(\w+)"/$
视图获取商品
goods = Goods.objects.filter(分类字段_id=param1).order_by(param2)
(3)商品列表分页
分页器 Paginator
页码用?在url中传入参数
from django.core.paginator import Paginator
<!--创建分页器-->
paginator = Paginator(所有商品对象,每页放置商品的个数)
<!--获取当前页码-->
page_now = get(request,"page","1") # 给默认页码为1
<!--获取当前页码内商品对象-->
goods_now = paginator.page(page_now)
---常用属性---
paginator属性:
paginator.page_range 页码范围,可以在html中for循环生成页码
goods_now的属性、方法:
goods_now.number 当前页码
has_next() 是否有下一页
has_previous() 是否有上一页
next_page_number() 返回下一页页码
previous_page_number() 返回上一页页码
七、购物车
1 创建购物车模型
- 用户 (关联用户表用户id)
- 商品 (关联商品表商品id)
- 商品数量
2 在商品详细页面做添加购物车点击事件
(1)异步更新购物车数量
ajax向后台传入数据(添加商品数量,商品id),配制添加购物车url和视图,返回json数据(现有购物车数量),异步更新购物车数量标签。
js如何获取商品id?
在detail页面中添加一个隐藏域,js可以通过标签获取val。
<input type="hidden" value={{ goods.id }} class="xx">
- 进行判断,并存入数据库
1 如果用户未登录,跳转至登录页面
2 如果已登录,
判断该用户是否在购物车表中
(1)存在:获取用户id的所有商品对象
判断该商品是否存在:
如果存在,更新数量
如果不存在,直接存入
(2)不存在该用户
直接存入
(2)全局显示购物车商品数量
a.需求:
- 由于商品首页,列表页,详情页面均由购物车的标签,所以需要全局实时显示购物车数量。
b.实现:
- 由于每次进行页面请求,均会发出request,所以让购物车商品总数成为request的一个属性,这样每次request请求都会携带这个数据。
- 不能在每个视图中都对这个属性进行设置,这样代码重复度太高,也同样秉持着原代码最好不要进行修改的原则,所以写一个装饰器,装饰器内设置这个属性。
- 但是,不是所有的页面均需要该属性,在商品首页、列表页、详情页的视图上添加该装饰器。
3 购物车页面
(1)从数据库获取数据,数据显示
(2)js操作,商品数量、单品总价,选中商品总价格实时变化
- 这里需要后台先更新数据库的购物车表数据,前台才能更新数据,所以这里的ajax请求需要同步,后台更新数据之后,前台html才显示更新;
- ajax默认异步async:true,这里需要具体配置;
- post方式不同于get方式可以被django直接得到,因为django为post加入了csrf保护;
- 解决方法一:在HTML页面中添加一个隐藏域{% csrf_token %},手动获取name和value,发送给后台;
<input type='hidden' name='csrfmiddlewaretoken' value='MErcUv3Df23t84YsP4skrRbIWCAtVwpe' />
- 解决方法二(不可取):在 settings.py 中 MIDDLEWARE_CLASSES 中 注释掉’django.middleware.csrf.CsrfViewMiddleware’
<!--get请求-->
$.ajax({
url:"请求的url",
data:{json数据},
dataType:"json", // 默认为json数据格式
async:false, // 设置同步
type:"get", // 默认为get请求方式
}).done(function(data){
获得从后台返回的json数据
}).fail(function(){
alert("服务器超时,请重试!");
})
<!--post请求 -->
$.ajax({
url:"请求的url",
data:{"csrfmiddlewaretoken":xxxxxxxx},
dataType:"json", // 默认为json数据格式
async:false, // 设置同步
type:"post", // 默认为get请求方式
}).done(function(data){
获得从后台返回的json数据
}).fail(function(){
alert("服务器超时,请重试!");
})
- 将实时更新页面单品总价,商品总数量,购物车总价等内容,写一个通用的函数,方便调用,将点击事件的标签当做参数传给这个函数即可。这些标签可以通过$(this)父级标签往下找。
- 注意:在ajax请求内部,不能直接操作$(this),可以在外部设置一个旗子flag=false,在ajax内部操作flag;在外部通过对flag值的判断来继续执行。
(3) 提交订单
a. 为购物车HTML页面添加form,提交地址为提交订单页面,这时就要换到order应用下操作了。
b. js写入表单提交事件,判断是否选中商品,若选中,则提交表单,未选中,提示用户。
4 提交订单页面
- 获取商品id列表
request.POST.getlist("标签name属性")
- 通过商品id列表获取商品对象列表,HTML渲染