-
django项目流程 -
需求分析 1. 用户模块
1) 注册页
1. 完成用户信息注册
2. 注册时校验用户名是否已被注册
3. 给用户的注册邮箱发送邮件,用户点击邮件中的激活链接完成账户的激活
2) 登录页
1. 实现登录功能
3) 用户中心
1. 用户中心信息页: 显示登陆用户信息,包括用户名、电话和地址,
同时页面下方显示出用户最近浏览的商品信息
2. 用户中心地址页: 显示登陆用户的默认收件地址
页面下方表单可以新增用户的收货地址
3. 用户中心订单页: 显示用户的订单信息
4) 其他
1. 如果用户已登录,页面顶部显示登录用户的信息
2. 商品模块
1) 首页
1. 动态指定首页幻灯片商品信息
2. 动态指定首页活动信息
3. 动态获取商品的种类信息并显示
4. 动态指定首页显示的每个种类的商品(包括图片商品和文字商品)
5. 点击某个商品,跳转到该商品的详情页
2) 商品详情页
1. 显示出该商品的详情信息
2. 页面左下方显示出该种类商品的2个新品信息
3) 商品列表页
1. 显示出某一个种类商品的列表数据,分页显示并支持按默认、价格、人气进行排序
2. 页面左下方显示出该种类商品的2个新品信息
4) 其他
1. 通过页面搜索框搜索商品信息
3. 购物车模块
1. 列表页和详情页将商品添加到购物车
2. 用户登录后,首页,详情页,列表页显示用户购物车中的商品数目
3. 购物车页面: 对用户购物车中商品的操作
如果选择某件商品,增加或减少购物车中商品的数目
4. 订单模块
1. 提交订单页面: 显示用户准备购买的商品信息
2. 点击提交订单完成订单的创建
3. 用户中心订单页: 显示用户的订单信息
4. 点击支付,完成订单的支付
3. 架构设计 4. 数据库表结构设计 5. 模块代码实现
**用户模块**
1. 用户模块代码实现
1. 用户模块有5个页面,注册页,登录页,用户中心信息/订单/地址页
2. 注册页功能 -- 注册,发送激活邮件
3. 登录页功能 -- 登录
4. 用户中心信息页 -- 显示个人信息,浏览记录 -- 浏览记录需要和商品模块配合,这部分写不了
5. 用户中心地址页 -- 显示,添加收货地址
6. 用户中心订单页 -- 显示订单信息 -- 需要和订单模块配合使用,暂时写不了
2. models.py 知识点
1. 用户表,可以使用django自带的用户表基类,是组成django认证用户的一部分
from django.contrib.auth.models import AbstractUser
2. 继承了AbstractModel类,需要对表名以及后台管理界面的字段显示作出修改
class Meta:
db_table = 'df_user' # db_table 重新命名表名
verbose_name = '用户' # 重新定义models在后台管理页面中的名字
verbose_name_plural = verbose_name # 重新定义models在后台管理页面中的名字的复数
3. 所有的表都有创建时间,修改时间,是否删除标识,所以要封装成模型抽象基类。
通过以下代码可以说明该类是模型抽象基类:
class Meta:
abstract = True
4. 要多次获取默认的收货地址可以自定义管理器类,继承Models.manager类,此处使用的是自定义管理器功能1;
1、什么是管理器
管理器是Django的模型进行数据库操作的接口,Django应用的每个模型类都拥有至少一个管理器。Django支持自定义管理器类,继承自models.Manager
objects是Django帮我自动生成的管理器对象,通过这个管理器可以实现对数据的查询
objects是models.Manger类的一个对象。自定义管理器之后Django不再帮我们生成默认的objects管理器
2、自定义管理器步骤
1、自定义一个管理器类,这个类继承models.Manger类。
2、再在具体的模型类里定义一个自定义管理器类的对象
3、自定义管理器类有两个功能(两个应用场景):
1. 封装方法:用户操作模型类对应的数据表(增删改查)
class BookInfoManager(models.Manager):
...
def create_book(self, title, pub_date):
book = self.model()
book.btitle = title
book.bpub_date = pub_date
book.bread=0
book.bcommet=0
book.isDelete = False
book.save()
return book
class BookInfo(models.Model):
...
books = BookInfoManager()
2. 改变查询集结果
class BookInfoManager(models.Manager):
def all(self):
return super().all().filter(isDelete=False)
class BookInfo(models.Model):
...
objects = BookInfoManager()
from django.db import models
from django.contrib.auth.models import AbstractUser
from db.base_model import BaseModel
class User(AbstractUser, BaseModel):
'''用户模型类'''
class Meta:
db_table = 'df_user'
verbose_name = '用户'
verbose_name_plural = verbose_name
class AdressManager(models.Manager):
'''地址模型管理类,封装方法'''
def get_default_address(self, user):
try:
addr = self.get(user=user, is_default=True)
print(addr.address)
print('---------------------------------')
except:
addr = None
return addr
class Address(BaseModel):
'''地址模型类'''
user = models.ForeignKey('User', verbose_name= '所属账户')
receiver = models.CharField(max_length=20, verbose_name='收件人')
address = models.CharField(max_length=256, verbose_name='收件地址')
zip_code = models.CharField(max_length=6, null=True, verbose_name='邮编')
phone_number = models.CharField(max_length=11, verbose_name='联系电话')
is_default = models.BooleanField(default=False, verbose_name='是否默认')
objects = AdressManager()
class Meta:
db_table = 'df_address'
verbose_name = '地址'
verbose_name_plural = verbose_name
from django.db import models
class BaseModel(models.Model):
'''模型抽象基类'''
create_time = models.DateTimeField(auto_now_add=True, verbose_name= '创建时间')
update_time = models.DateTimeField(auto_now=True, verbose_name= '更新时间')
is_delete = models.BooleanField(default=False, verbose_name= '删除标记')
class Meta:
abstract = True
3. views.py 知识点
1. 类视图
通用类视图基类:
django.views.generic.View ( 与django.views.generic.base.View 是同一个)
urls.py中配置路由使用类视图的as_view()方法
由dispatch()方法具体将请求request分发至对应请求方式的处理方法中(get、post等)
get方法是显示,
post方法是验证信息并修改数据库,处理信息步骤如下
1. 接收信息,request.POST.get
2. 校验数据
1. 校验数据完整性:if not all([username, password, cpassword, email]):
2. 其余的校验视数据特性设计
3. 业务处理,修改数据库
4. 返回应答
2. 修改数据表
1. 增
b = BookInfo()
b.btitle="射雕英雄传"
from datetime import date
b.bpub_date=date(1991,1,31)
b.save()
补充: 每增加一条记录需要创建一个实例对象
# 使用 create()
from datetime import date
BookInfo.objects.create(btitle= '射雕英雄传', bpub_data= data(1991,1,31))
Address.objects.create(
user= user,
receiver= receiver,
address= address,
zip_code= zip_code,
phone_number= phone_number,
is_default= is_default,
)
# 继承django.contrib.suth.models.AbstractUser类,
# 可以使用create_user(username,email=None,password=None)方法
# 该方法创建保存一个is_active=True的User对象并返回。
# username不能够为空,否则抛出ValueError异常。
# email和password都是可选的。email的domain部分会被自动转变为小写。password如果没有提供,则User对象的set_unusable_password()方法将会被调用。
# 3. 进行业务处理,注册信息
user = User.objects.create_user(username=username, password=password, email=email)
# 用户未激活,user对象的is_active是1
user.is_active = 0
user.save()
2. 查
b1 = BookInfo.objects.get(id=1) # 返回对象
b1.btitle # 就可以使用 对象.属性 进行查询值
BookInfo.objects.all() # 返回QuerySet对象,返回所有信息
b1.heroinfo_set.all() # 关联操作,查找id为1的图书中所有英雄人物的信息
3. 删
b1 = BookInfo.objects.get(id=1)
b1.delete()
4. 改
b1 = BookInfo.objects.get(id=1)
b1.bpub_date=date(2017,1,1)
b1.save()
3. 发送电子邮件 -- 使用celery 异步发送
# 发送激活邮件,包含激活链接: http://127.0.0.1:8000/user/active/3(user_id)
# 激活链接中需要包含用户的身份信息, 并且要把身份信息(user_id)进行加密
1. 数据加密 -- itsdangerous模块
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
serializer = Serializer(secret_key, 过期时间)
# info需要加密的信息,可以是字典也可以是元组
token = serializer.dumps(info) # 加密,byte类型
token = token.decode() # 转换为str类型
info = serializer.loads(token) # 解密
# 加密用户的身份信息,生成激活token,使用的secret_key是django settings中自带的秘钥
serializer = Serializer(settings.SECRET_KEY, 7200)
info = {'confirm': user.id} # 加密信息
token = serializer.dumps(info) # 生成加密文字,是byte类型
token = token.decode()
2. 发邮件 -- django内置模块django.core.mail.send_mail
语法:send_mail(subject, message, sender, receiver, html_message=html_message)
from django.core.mail import send_mail
celery任务发布,在views.py中调用任务函数,任务函数.delay(*args, **kwargs),即为发布任务
需要在settings.py里面配置
163邮箱显示的授权码: *********
# 邮箱配置
# django内置模块
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
# smpt服务地址与端口
EMAIL_HOST = 'smtp.163.com'
EMAIL_PORT = 25
# 发送邮件的邮箱
EMAIL_HOST_USER = '**********@163.com'
# 在邮箱中设置的客户端授权密码
EMAIL_HOST_PASSWORD = '**************'
# 收件人看到的发件人
EMAIL_FROM = '天天生鲜<**********@163.com>'
邮箱需要和celery搭配使用
创建celery_tasks模块,在模块捏有tasks.py文件
在tasks.py文件中定义的任务函数
# 定义任务函数
def send_register_active_email(to_addr, username, token):
'''发送激活邮件'''
# 组织邮件信息
subject = '天天生鲜欢迎您' # 邮件标题
message = '' # 邮件正文,HTML内容会进行转义,就是你写啥,网页显示啥
sender = settings.EMAIL_FROM # 发件人
receiver = [to_addr] # 收件人
html_message = '''<h1>%s,欢迎成为天天生鲜注册会员</h1>
<br /><br/>
请点击下面的链接激活您的账户<br/>
<a href = 'http://127.0.0.1:8000/user/active/%s'>http://127.0.0.1:8000/user/active/%s</a>'''%(username, token, token) # 需要显示html内容
send_mail(subject, message, sender, receiver, html_message=html_message)
在views.py中使用时,from celery_tasks.tasks import send_register_active_email
需要 send_register_active_email.delay()
3. celery模块
celery: 三部分组成,任务发出者,经纪人(broker),任务处理者(worker)
broker这部分需要借助RabbitMQ,redis,此处使用redis
任务发出者是 send_register_active_email.delay(email, username, token)
broker是:app = Celery('celery_tasks.tasks', broker='redis://127.0.0.1:6379/2')
worker:celery -A <路径> worker -l info -P eventlet
创建Celery实例对象,书写任务函数,将任务函数状态改为发布(用Celery实例对象的task函数包装任务函数)
from celery import Celery
app = Celery('celery_tasks.tasks', broker='redis://127.0.0.1:6379/2') # 第一个参数是Celery实例对象的名称,第二个是broker
发布任务函数
@app.task
# 任务函数
def send_register_active_email(to_addr, username, token):
pass
启动celery worker,worker所在电脑也要有项目代码
# 在任务处理者一端加这几句,因为celery启动也需要django环境
import os
import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dailyfresh.settings")
django.setup()
celery -A <路径> worker -l info -P eventlet
4. 用户登录相关函数
1. user = authenticate(username= username, password= password) # 验证用户名,密码与数据表中是否一致
2. login(request, user) # 将用户id保存在session中
3. logout(request) # 清除session信息
4. user.is_authenticated # 登录,返回True,未登录,返回False
5. user = request.user
6. login_required装饰器,已登录可以正常跳转,未登录,返回127.0.0.1:8000/user/login?next=/user
所以重新登陆后要跳转到之前想登录的页面,地址即是next后面的值
5. 添加/删除session
response.set_cookie('username', username, max_age=7*24*3600)
response.delete_cookie('username')
from django.shortcuts import render,redirect
from django.core.urlresolvers import reverse
from django.views.generic import View
from django.http import HttpResponse
from django.conf import settings
from django.contrib.auth import authenticate, login, logout
from user.models import User, Address
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
from itsdangerous import SignatureExpired
from celery_tasks.tasks import send_register_active_email
import re
class RegisterView(View):
'''注册页面显示及注册信息处理'''
def get(self, request):
'''显示注册页面'''
return render(request, 'user/register.html')
def post(self,request):
'''处理注册信息'''
username = request.POST.get('user_name')
password = request.POST.get('pwd')
cpassword = request.POST.get('cpwd')
email = request.POST.get('email')
allow = request.POST.get('allow')
if not all([username, password, cpassword, email]):
return render(request, 'user/register.html', {'errmsg': '数据不完整'} )
if cpassword != password:
return render(request, 'user/register.html', {'errmsg': '两次密码不一致'})
if not re.match(r'^[a-z0-9][\w.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
return render(request, 'register.html', {'errmsg': '邮箱格式不正确'})
if allow != 'on':
return render(request, 'register.html', {'errmsg': '请同意协议'})
try:
user = User.objects.get(username= username)
except User.DoesNotExist:
user = None
if user:
return render(request, 'user/register.html', {'errmsg': '用户名重复'})
user = User.objects.create_user(username=username, password=password, email=email)
user.is_active = 0
user.save()
serializer = Serializer(settings.SECRET_KEY, 7200)
info = {'confirm': user.id}
token = serializer.dumps(info)
token = token.decode()
send_register_active_email.delay(email, username, token)
return redirect(reverse('user:login'))
class ActiveView(View):
'''用户激活'''
def get(self, request, token):
serializer = Serializer(settings.SECRET_KEY, 7200)
try:
info = serializer.loads(token)
user_id = info['confirm']
user = User.objects.get(id= user_id)
user.is_active = 1
user.save()
return redirect(reverse('user:login'))
except SignatureExpired as e:
return HttpResponse('激活链接已过期')
class LoginView(View):
'''用户登录'''
def get(self, request):
'''显示登录页面'''
if 'username' in request.COOKIES:
username = request.COOKIES.get('username')
checked = 'checked'
else:
checked = ''
username = ''
context = {'username': username, 'checked': checked}
return render(request, 'user/login.html', context)
def post(self, request):
'''处理登录信息'''
username = request.POST.get('username')
password = request.POST.get('pwd')
print(username)
print(password)
if not all([username, password]):
return render(request, 'user/login.html', {'errmsg': '数据不完整'})
user = authenticate(username= username, password= password)
print('*'*50)
print(user)
print('*'*50)
if user is not None:
if user.is_active:
login(request, user)
next_url = request.GET.get('next', reverse('goods:index'))
response = redirect(next_url)
remember = request.POST.get('remember')
if remember == 'on':
response.set_cookie('username', username, max_age=7*24*3600)
else:
response.delete_cookie('username')
return response
else:
return render(request, 'user/login.html', {'errmsg': '账户未激活'})
else:
return render(request, 'user/login.html', {'errmsg': '用户名或密码错误'})
class LogoutView(View):
'''用户退出'''
def get(self, request):
logout(request)
return redirect(reverse('goods:index'))
from django.core.mail import send_mail
from django.conf import settings
from django.template import loader
from celery import Celery
import time
import os
import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dailyfresh.settings")
django.setup()
from goods.models import GoodsType, IndexGoodsBanner, IndexPromotionBanner, IndexTypeGoodsBanner, GoodsSKU
app = Celery('celery_tasks.tasks', broker='redis://127.0.0.1:6379/2')
@app.task
def send_register_active_email(to_addr, username, token):
'''发送激活邮件'''
subject = '天天生鲜欢迎您'
message = ''
sender = settings.EMAIL_FROM
receiver = [to_addr]
html_message = '<h1>%s, 欢迎您成为天天生鲜注册会员</h1>请点击下面链接激活您的账户<br/><a href="http://127.0.0.1:8000/user/active/%s">http://127.0.0.1:8000/user/active/%s</a>' % (username, token, token)
send_mail(subject, message, sender, receiver, html_message=html_message)
time.sleep(5)
6. 用户中心需要登录才能跳转,使用login_required装饰器
1. 登录装饰器 -- @login_required == login-required(函数)
功能:装饰view函数,只有登陆之后,(view函数才能工作)才能访问,未登录访问,返回settings.login_url
使用:login_required(View.as_view()),封装函数
说明:url配置中 UserInfoView.as_view(),执行LoginRequiredMinxin的as_view()方法;
返回 login_required(view),其中view使用的是super(LoginRequiredMinxin, cls).as_view(**initkwargs);
在多重继承中UserInfoView在LoginRequiredMinxin类的下一个继承是View类,所以执行的是View中的as_view方法
from django.contrib.auth.decorators import login_required
class LoginRequiredMinxin(object):
@classmethod
def as_view(cls, **initkwargs):
# 调用父类的as_view()
view = super(LoginRequiredMinxin, cls).as_view(**initkwargs)
return login_required(view)
sttings.py里面
# 配置登录url地址
LOGIN_URL='/user/login' # /accounts/login
7. redis充当缓存,浏览历史记录的实现
from django.shortcuts import render,redirect
from django.core.urlresolvers import reverse
from django.views.generic import View
from django.http import HttpResponse
from django.contrib.auth import authenticate, login, logout
from django_redis import get_redis_connection
from user.models import User, Address
from utils.mixin import LoginRequiredMinxin
import re
class UserInfoView(LoginRequiredMinxin, View):
'''用户中心--信息页'''
def get(self, request):
'''显示'''
user = request.user
addr = Address.objects.get_default_address(user)
con = get_redis_connection('default')
history_key = 'history_%d'%user.id
sku_ids = con.lrange(history_key, 0, 4)
goods_li = []
for sku_id in sku_ids:
good = GoodsSKU.objects.get(id=sku_id)
goods_li.append(good)
context = {
'page': 'user',
'addr': addr,
'goods_li': goods_li
}
return render(request, 'user/user_center_info.html', context)
class UserOrderView(LoginRequiredMinxin, View):
'''用户中心--订单页'''
pass
class UserAddressView(LoginRequiredMinxin, View):
'''用户中心--地址页'''
def get(self, request):
'''显示'''
user = request.user
addrs = Address.objects.filter(user=user)
ADDR = ''
print('addr是%s'%addrs)
HtmlStrList = []
for addr in addrs:
if not addr.is_default:
HtmlStr = "<dd>%s (%s 收) %s</dd>" % (addr.address, addr.receiver, addr.phone_number)
HtmlStrList.append(HtmlStr)
else:
ADDR = addr
context = {
'page': 'address',
'addr': ADDR,
'HtmlStrList': HtmlStrList,
}
return render(request, 'user/user_center_site.html', context)
def post(self, request):
'''添加地址'''
receiver = request.POST.get('receiver')
address = request.POST.get('address')
phone_number = request.POST.get('phone_number')
zip_code = request.POST.get('zip_code')
if not all([receiver, address, phone_number]):
return render(request, 'user/user_center_site.html', {'errmsg': '数据不完整'})
if not re.match(r'^1[3|4|5|7|8]\d{9}$', phone_number):
return render(request, 'user/user_center_site.html', {'errmsg': '手机号码不正确'})
user = request.user
print('user的值是:%s'%user)
addr = Address.objects.get_default_address(user)
print(addr)
if addr:
is_default = False
else:
is_default = True
Address.objects.create(
user= user,
receiver= receiver,
address= address,
zip_code= zip_code,
phone_number= phone_number,
is_default= is_default,
)
return redirect(reverse('user:address'))
from django.contrib.auth.decorators import login_required
class LoginRequiredMinxin(object):
@classmethod
def as_view(cls, **initkwargs):
view = super(LoginRequiredMinxin, cls).as_view(**initkwargs)
return login_required(view)
|