zoukankan      html  css  js  c++  java
  • Django项目搭建

    Django项目搭建

    本项目是一个典型的电商项目,采用Python语言编写,Django框架搭建。

    github中创建远程仓库

    github上创建仓库meiduo,填写描述,选择.gitignore文件的语言为python,软件许可协议为MIT

    修改.gitignore的内容为:.idea/*.pyc*.log

    新建分支dev

    在本地克隆项目

    git clone https://github.com/junsircoding/meiduo.git # 克隆项目
    cd meiduo # 进入项目目录
    git branch # 查看当前分支
    git branch dev origin/dev # 克隆远程仓库中的dev分支
    git checkout dev # 切换到dev分支
    

    在虚拟环境中创建项目

    workon django_py3_1.11 # 进入虚拟环境
    django-admin startproject shop # 创建项目,项目名称为shop
    cd shop # 进入项目目录
    pwd # 查看当前地址并拷贝,在pycharm中打开
    

    在Pycharm中搭建项目

    重设settings文件路径

    开发环境和上线环境用不同的配置文件比较容易部署和维护,故而最好重新设置settings的路径。

    django自动创建的项目中,根目录下有一个同名目录,在此做一个约定:根目录的shop一级shop,根目录下的同名目录为二级shop

    新建名为settingspython package于二级shop中,将原二级shop中的settings.py更名为dev.py,并将其移动到新建的settings目录中。

    修改一级shop下的manage.py中的环境变量:

    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "shop.settings") # 原
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "shop.settings.dev") # 现
    

    配置jinja2模板

    dev.py(位于二级shop)中配置jinja2的模板:

    TEMPLATES = [
        {
            'BACKEND': 'django.template.backends.jinja2.Jinja2',  # jinja2模板引擎
            'DIRS': [os.path.join(BASE_DIR, 'templates')],
            'APP_DIRS': True,
            'OPTIONS': {
                'context_processors': [
                    'django.template.context_processors.debug',
                    'django.template.context_processors.request',
                    'django.contrib.auth.context_processors.auth',
                    'django.contrib.messages.context_processors.messages',
                ],
                # 补充Jinja2模板引擎环境
                'environment': 'meiduo_mall.utils.jinja2_env.jinja2_environment', 
            },
        },
    ]
    

    新建名为utilspython package二级shop中。

    新建名为jinja2_env.pypython fileutils目录中,并在其中写如下代码:

    from django.contrib.staticfiles.storage import staticfiles_storage
    from django.urls import reverse
    from jinja2 import Environment
    
    
    def jinja2_environment(**options):
        env = Environment(**options)
        env.globals.update({
            'static': staticfiles_storage.url,
            'url': reverse,
        })
        return env
    
    
    """
    确保可以使用Django模板引擎中的{% url('') %} {% static('') %}这类的语句 
    """
    

    配置mysql数据库

    新建mysql数据库

    mysql -uroot -p111
    
    create database shop charset=utf8; # 创建名为shop的数据库,指定字符编码为utf8
    create user shoproot identified by '111'; # 创建名为shoproot的mysql新用户,授权其访问shop数据库
    grant all on shop.* to 'shop'@'%'; # %表示匹配用户名为junsir的所有ip地址
    flush privileges; # 刷新刚才所做的设置
    

    配置mysql数据库于dev.py

    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql', # 数据库引擎
            'HOST': '127.0.0.1', # 数据库主机
            'PORT': 3306, # 数据库端口
            'USER': 'shoproot', # 数据库用户名
            'PASSWORD': '111', # 数据库用户密码
            'NAME': 'shop' # 数据库名字
        },
    }
    

    修改__init__.py(二级shop目录),配置pymysql连接

    from pymysql import install_as_MySQLdb
    install_as_MySQLdb()
    
    

    配置Redis中的缓存Session

    dev.py(二级shop/settings)中添加如下内容:

    CACHES = {
        "default": { # 默认
            "BACKEND": "django_redis.cache.RedisCache",
            "LOCATION": "redis://127.0.0.1:6379/0",
            "OPTIONS": {
                "CLIENT_CLASS": "django_redis.client.DefaultClient",
            }
        },
        "session": { # session
            "BACKEND": "django_redis.cache.RedisCache",
            "LOCATION": "redis://127.0.0.1:6379/1",
            "OPTIONS": {
                "CLIENT_CLASS": "django_redis.client.DefaultClient",
            }
        },
    }
    SESSION_ENGINE = "django.contrib.sessions.backends.cache"
    SESSION_CACHE_ALIAS = "session"
    
    

    配置工程日志

    新建名为logspython package一级shop

    新建名为shop.logfile于目录logs

    dev.py(二级shop/settings)中添加日志的配置信息:

    LOGGING = {
        'version': 1,
        'disable_existing_loggers': False,  # 是否禁用已经存在的日志器
        'formatters': {  # 日志信息显示的格式
            'verbose': {
                'format': '%(levelname)s %(asctime)s %(module)s %(lineno)d %(message)s'
            },
            'simple': {
                'format': '%(levelname)s %(module)s %(lineno)d %(message)s'
            },
        },
        'filters': {  # 对日志进行过滤
            'require_debug_true': {  # django在debug模式下才输出日志
                '()': 'django.utils.log.RequireDebugTrue',
            },
        },
        'handlers': {  # 日志处理方法
            'console': {  # 向终端中输出日志
                'level': 'INFO',
                'filters': ['require_debug_true'],
                'class': 'logging.StreamHandler',
                'formatter': 'simple'
            },
            'file': {  # 向文件中输出日志
                'level': 'INFO',
                'class': 'logging.handlers.RotatingFileHandler',
                'filename': os.path.join(os.path.dirname(BASE_DIR), 'logs/shop.log'),  # 日志文件的位置
                'maxBytes': 300 * 1024 * 1024,
                'backupCount': 10,
                'formatter': 'verbose'
            },
        },
        'loggers': {  # 日志器
            'django': {  # 定义了一个名为django的日志器
                'handlers': ['console', 'file'],  # 可以同时向终端与文件中输出日志
                'propagate': True,  # 是否继续传递日志信息
                'level': 'INFO',  # 日志器接收的最低日志级别
            },
        }
    }
    
    

    配置静态页面

    将静态页面文件拷贝到二级shop目录下

    dev.py中配置静态文件路径

    STATIC_URL = '/static/'
    
    # 配置静态文件加载路径
    STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
    
    

    配置模板文件

    新建名为templatespython package二级shop中,将其标记为Template Folder

    项目环境搭建完毕,开启服务器,在浏览器中查看效果

    在浏览器中输入地址:127.0.0.1:8000/static/index.html

    编写用户模块代码

    新建名为appspython package二级shop

    进入apps目录,用Django命令创建子应用:

    cd apps
    python ../../manage.py startapp users
    
    

    dev.py中注册app,在二级shop/apps/users/apps.py中,右击app名称sConfig,选择Copy Reference,拷贝引用,在dev.py中粘贴

    ‘shop.apps.users.apps.UsersConfig’,
    
    

    dev.py中追加导包路径

    import sys
    sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))
    
    

    改写注册内容:

    ‘users.apps.UsersConfig’,
    
    

    新建名为urls.pypython file二级shop/apps/users中,在其中填写如下内容:

    from django.conf.urls import url
    from . import views
    
    urlpatterns = [
        url(r'^register/$', views.RegisterView.as_view()),  # 匹配注册功能的路由
    ]
    
    

    将此子路由添加至总路由(二级shop/urls.py):

    from django.conf.urls import url, include
    from django.contrib import admin
    
    urlpatterns = [
        url(r'^admin/', admin.site.urls),  # 匹配admin站点的路由
        url(r'^', include('users.urls')),  # 匹配用户模块的路由
    ]
    
    

    编写视图函数RegisterView二级shop/apps/users/views.py中:

    from django.shortcuts import render
    from django.views import View
    
    
    class RegisterView(View):
        def get(self, request):
            return render(request, 'register.html')
    
    
    

    编写用户模型类

    Django自带了用户模型类,如要添加别的字段,只需继承Django自带的模型类,再添加自己的特有字段即可

    二级shop/apps/users/models.py中添加如下内容:

    from django.db import models
    from django.contrib.auth.models import AbstractUser
    
    # Create your models here.
    
    
    class User(AbstractUser):
        """自定义用户模型类"""
        mobile = models.CharField(max_length=11, unique=True, verbose_name='手机号')
    
        class Meta:
            db_table = 'tb_users'
            verbose_name = '用户'
            verbose_name_plural = verbose_name
    
        def __str__(self):
            return self.username
    
    

    dev.py中指定用户模型类:

    AUTH_USER_MODEL = 'users.User' # 应用名.模型类名
    
    

    迁移用户模型类

    创建迁移文件

    python manage.py makemigrations
    
    

    执行迁移文件

    python manage.py migrate
    
    

    编码

    注册功能

    填写完注册页面表单后,后台要处理POST请求

    二级shop/apps/users/views.py/RegisterView中添加如下代码:

    def post(self, request):
        # 1.接收
        user_name = request.POST.get('user_name')
        pwd = request.POST.get('pwd')
        cpwd = request.POST.get('cpwd')
        phone = request.POST.get('phone')
        allow = request.POST.get('allow')
        # 2.验证
        # 2.1验证非空
        if not all([user_name, pwd, cpwd, phone, allow, sms_code_request]):
            return http.HttpResponseBadRequest('参数不完整') # from django import http
        # 2.2用户名拼写是否符合规范,是否已存在该用户名
        if not re.match(r'^[a-zA-Z0-9_-]{5,20}$', user_name):
            return http.HttpResponseBadRequest('请输入5-20个字符') # import re
        if User.object.filter(username=user_name).count() > 0: # from .models import User
            return http.HttpResponseBadRequest('用户名已存在')
        # 2.3密码拼写是否符合规范,两次密码输入是否一致
        if not re.match(r'[0-9A-Za-z]{8,20}$', pwd):
            return http.HttpResponseBadRequest('请输入8-20的密码')
        if pwd != cpwd:
            return http.HttpResponseBadRequest('两次密码输入不一致')
        # 2.4手机号拼写是否符合规范,手机号是否已经被使用
        if not re.match(r'^1[345789]d{9}', phone):
            return http.HttpResponseBadRequest('手机号格式不正确')
        if User.objects.filter(mobile=phone).count() > 0:
            return http.HttpResponseBadRequest('手机号已存在')
        # 3.创建用户对象并保存在列表中
        user = User.objects.create_user(username=user_name, password=pwd, mobile=phone)
        # 4.状态保持
        login(request, user) # from django.contrib.auth import login
        # 5.响应,返回首页
        return redirect('/') # from django.shortcuts import redirect
    
    

    用Ajax异步校验的方式验证用户名是否存在

    在子路由中添加ajax的路由:

    url(r'^usernames/(?P<username>[a-zA-Z0-9_-]{5,20})/count/$', views.UsernameCountView.as_view()),
    
    

    二级shop/apps/users/views.py中添加视图类:

    class UsernameCountView(View):
        def get(self, request, username):
            # 接收
            # 验证
            # 处理
            count = User.objects.filter(username=username).count()
            # 响应
            return http.JsonResponse({
                'code':RETCODE.OK, # from shop.utils.response_code import RETCODE
                'errmsg':'OK',
                'count':count
            })
    
    

    用Ajax异步校验的方式验证手机号是否存在

    在子路由中添加ajax的路由:

    url('^mobiles/(?P<mobile>1[3-9]d{9})/count/$', views.MobileCountView.as_view()),
    
    

    二级shop/apps/users/views.py中添加视图类:

    class MobileCountView(View):
        def get(self, request, mobile):
            # 接收
            # 验证
            # 处理,查询统计
            count = User.objects.filter(mobile=mobile).count()
            # 响应
            return http.JsonResponse({
                'code':RETCODE.OK,
                'errmsg':'OK',
                'count':count
            })
    
    

    新建名为response_codepython fileutils目录中,内容如下:

    class RETCODE:
        OK                  = "0"
        IMAGECODEERR        = "4001"
        THROTTLINGERR       = "4002"
        NECESSARYPARAMERR   = "4003"
        USERERR             = "4004"
        PWDERR              = "4005"
        CPWDERR             = "4006"
        MOBILEERR           = "4007"
        SMSCODERR           = "4008"
        ALLOWERR            = "4009"
        SESSIONERR          = "4101"
        DBERR               = "5000"
        EMAILERR            = "5001"
        TELERR              = "5002"
        NODATAERR           = "5003"
        NEWPWDERR           = "5004"
        OPENIDERR           = "5005"
        PARAMERR            = "5006"
        STOCKERR            = "5007"
    
    

    图片验证码

    安装pillow

    pip install Pillow
    
    

    新建名为libspython package目录于二级shop中,将第三方图片验证码工具captcha拷贝至这里。

    新建名为verificationsappapps中:

    python ../../manage.py startapp verifications
    
    

    注册appdev.py中:

    'verifications.apps.VerificationsConfig',
    
    

    新建路由表urls,在子路由中添加路由:

    urlpatterns = [
        url(r'^image_codes/(?P<uuid>[w-]+)/$', views.ImagecodeView.as_view()), # from . import views
    ] # from django.conf.urls import url
    
    

    在总路由中包含子路由:

    urlpatterns = [
        url(r'^', include('verifications.urls')),
    ]
    
    

    编写视图函数:

    class ImagecodeView(View):
        def get(self, request, uuid):
            # 接收
            # 验证
            # 处理
            # 1.生成图形验证码数据:字符code、图片image
            text, code, image = captcha.generate_captcha()
            # from shop.libs.captcha.captcha import captcha
            # 2.保存字符,用于后续验证
            # 2.1连接redis,参数为caches中的键
            redis_cli = get_redis_connection('verify_code') # from django_redis import get_redis_connection
            # 2.2向redis中写数据
            redis_cli.setex(uuid, constants.IMAGE_CODE_EXPIRES, code)
            # from . import constants
            # 响应,输出图片数据
            return http.HttpResponse(image, content_type='image/png')
    
    

    新建名为constantspy文件于verifications目录下,编写其内容如下:

    # 验证码过期时间,当前为5分钟
    IMAGE_CODE_EXPIRES = 60 * 5 # 设置过期时间
    # 短信验证码过期时间,当前为5分钟
    SMS_CODE_EXPIRES = 60 * 5
    # 过期时间of短信是否重复发送的标记,当前为60s
    SMS_CODE_FLAG_EXPIRES = 60
    
    

    验证码字符在redis缓存中存储,在dev.py中新建缓存字段verify_code

    "verify_code":{
        "BACKEND":"django_redis.cache.RedisCache",
        "LOCATION":"redis://127.0.0.1:6379/2", # 用第二个数据库
        "OPTIONS":{
            "CLIENT_CLASS":"django_redis.client.DefaultClient",
        }
    },
    
    

    注意:chrome有个大坑,它会缓存之前请求过的地址。验证码的请求地址是和host.js关联的。当更改了host.js时,重新访问发现地址并没有更改,这是chrome缓存的缘故,记得定期清理缓存。

    短信验证码

    拷贝工具代码yuntongxun(短信)子shop/libs目录中

    verifications/url中添加路由:

    from django.conf.urls import url
    from . import views
    
    urlpatterns = [
        url(r'^image_codes/(?P<uuid>[w-]+)/$', views.ImagecodeViews.as_view()),
        url('^sms_codes/(?P<mobile>1[3-9]d{9})/$', views.SmscodeView.as_view()),
    ]
    
    

    verifications/views.py中添加名为SmscodeView的视图函数:

    class SmscodeView(View):
        def get(self, request):
            # 接收
            image_code_request = request.GET.get('image_code') # 用户填写的图形验证码文本
            uuid = request.GET.get('image_code_id') # 图形验证码的唯一编号
            # 验证,图形验证码是否正确
            redis_cli = get_redis_connection('verify_code')
            image_code_redis = redis_cli.get(uuid)
            if not image_code_redis:
                return http.JsonResponse({
                    'code':RETCODE.PARAMERR,
                    'errmsg':'图形验证码已过期'
                })
            # 强制验证码只能使用一次
            redis_cli.delete(uuid)
            # 判断用户输入的值是否正确
            # 注意:所有从redis总读取的数据都是bytes,需要转码
            if image_code_redis.decode() != image_code_request.upper():
                return http.JsonResponse({
                    'code':RETCODE.PARAMERR,
                    'errmsg':'图形验证码错误'
                })
            # 处理
            # 1.生成六位随机数
            sms_code = '%6d' % random.randint(0, 999999)
            # 2.保存到redis
            redis_cli.setex('sms_'+mobile, 300, sms_code)
            # 发短信,from shop.lib.yuntongxun.sms import CCP
            # ccp = CCP()
            # ret = ccp.send_template_sms(mobile, [sms_code, contansts.SMS_CODE_EXPIRES / 60], 1)
            print(sms_code)
            # 响应
            return http.JsonResponse({
                'code':RETCODE.OK,
                'errmsg':'OK'
            })
    
    

    在注册视图中验证短信验证码,改写二级shop/apps/users/views.py

    def post(self, request):
        # 1.接收
        user_name = request.POST.get('user_name')
        pwd = request.POST.get('pwd')
        cpwd = request.POST.get('cpwd')
        phone = request.POST.get('phone')
        allow = request.POST.get('allow')
        sms_code_request = request.POST.get('msg_code') # 新添加
        # 2.验证
        # 2.1验证非空
        if not all([user_name, pwd, cpwd, phone, allow, sms_code_request]):
            return http.HttpResponseBadRequest('参数不完整') # from django import http
        # 2.2用户名拼写是否符合规范,是否已存在该用户名
        if not re.match(r'^[a-zA-Z0-9_-]{5,20}$', user_name):
            return http.HttpResponseBadRequest('请输入5-20个字符') # import re
        if User.objects.filter(username=user_name).count() > 0: # from .models import User
            return http.HttpResponseBadRequest('用户名已存在')
        # 2.3密码拼写是否符合规范,两次密码输入是否一致
        if not re.match(r'[0-9A-Za-z]{8,20}$', pwd):
            return http.HttpResponseBadRequest('请输入8-20的密码')
        if pwd != cpwd:
            return http.HttpResponseBadRequest('两次密码输入不一致')
        # 2.4手机号拼写是否符合规范,手机号是否已经被使用
        if not re.match(r'^1[345789]d{9}', phone):
            return http.HttpResponseBadRequest('手机号格式不正确')
        if User.objects.filter(mobile=phone).count() > 0:
            return http.HttpResponseBadRequest('手机号已存在')
        # 2.5短信验证码,from django_redis import get_redis_connection
        # 连接redis
        redis_cli = get_redis_connection('verify_code')
        # 读取短信验证码
        sms_code_redis = redis_cli.get('sms_' + phone)
        # 判断是否过期
        if not sms_code_redis:
            return http.HttpResponseBadRequest('短信验证已过期')
        # 强制立即过期
        redis_cli.delete('sms_' + phone)
        # 判断短信验证码是否正确
        if sms_code_redis != sms_code_request:
            return http.HttpResponseBadRequest('短信验证码错误')
        
        # 3.创建用户对象并保存在列表中
        user = User.objects.create_user(username=user_name, password=pwd, mobile=phone)
        # 4.状态保持
        login(request, user) # from django.contrib.auth import login
        # 5.响应,返回首页
        return redirect('/') # from django.shortcuts import redirect
    
    

    避免频繁发送短信,添加如下代码于verificatons/views.py中:

    # 判断用户输入的值是否正确
    # 注意:所有从redis总读取的数据都是bytes,需要转码
    if image_code_redis.decode() != image_code_request.upper():
        return http.JsonRequest({
            'code':RETCODE.PARAMERR,
            'errmsg':'图形验证码错误'
        })
    # 是否已经向此手机号发送过短信,新加代码
    if redis_cli.get('sms_flag_' + mobile):
        return http.JsonResponse({
            'code':RETCODE.PARAMERR,
            'errmsg':'已经向次手机号发过短信,请查看手机'
        })
    # 处理
    # 1.生成六位随机数
    sms_code = '%6d' % random.randint(0, 999999)
    # 2.保存到redis
    redis_cli.setex('sms_'+mobile, constants.SMS_CODE_EXPIRES, sms_code)
    # 存储60s发送的标记,新加代码
    redis_cli.setex('sms_flag_' + mobile, constants.SMS_CODE_FLAG_EXPIRES, 1)
    # 发短信,from shop.lib.yuntongxun.sms import CCP
    # ccp = CCP()
    # ret = ccp.send_template_sms(mobile, [sms_code, contansts.SMS_CODE_EXPIRES / 60], 1)
    print(sms_code)
    
    

    使用pipeline优化与redis交互,只与redis服务器交互一次,执行多条命令

    # 1.生成六位随机数
    # sms_code = '%6d' % random.randint(0, 999999)
    # 2.保存到redis
    # redis_cli.setex('sms_'+mobile, constants.SMS_CODE_EXPIRES, sms_code)
    # 存储60s发送的标记,新加代码
    # redis_cli.setex('sms_flag_' + mobile, constants.SMS_CODE_FLAG_EXPIRES, 1)
    # 改写成下面这样
    redis_pl = redis_cli.pipeline()
    redis_pl.setex('sms_'+mobile, constants.SMS_CODE_EXPIRES, sms_code)
    redis_pl.setex('sms_flag_' + mobile, constants.SMS_CODE_FLAG_EXPIRES, 1)
    redis_pl.execute()
    
    

    使用celery框架实现异步

    新建名为celery_taskspython package一级shop

    新建名为mainpython filecelery_tasks中,内容如下:

    from celery import Celery
    import os
    
    os.environ["DJANGO_SETTINGS_MODULE"] = "shop.settings.dev"
    # 创建主对象
    app = Celery('shop')
    # 读取配置:指定消息队列,当前使用redis
    app.config_from_object('celery_tasks.config')
    # 自动识别
    app.autodiscover_tasks([
        'celery_tasks.sms',
    ])
    
    

    新建名为configpython filecelery_tasks中,内容如下:

    broker_url = 'redis://127.0.0.1:6379/15'
    
    

    新建名为smspython packagecelery_tasks

    新建名为taskspython filesms中,内容如下:

    from shop.libs.yuntongxun.sms import CCP
    from celery_tasks.main import app
    
    @app.task(bind=True, name='send_sms', retry_backoff = 3)
    def send_sms(self, to, datas, tempid):
        try:
            #ccp = CCP()
            #ret = ccp.send_template_sms(to, datas, 1)
            print(datas[0])
        except Exception as e:
            self.retry(exc = e, max_retries = 3)
    
    

    shell中运行如下命令,开启celery服务:

    celery -A celery_tasks.main worker -l info
    
    

    调用任务,于二级shop/apps/users/views.py中:

    from celery_tasks.sms.tasks import send_sms
    # 发短信,from shop.lib.yuntongxun.sms import CCP
    # ccp = CCP()
    # ret = ccp.send_template_sms(mobile, [sms_code, contansts.SMS_CODE_EXPIRES / 60], 1)
    # print(sms_code)
    send_sms.delay(mobile, [sms_code, contansts.SMS_CODE_EXPIRES / 60], 1)
    
    

    登录功能

    二级shop/apps/users/urls.py中添加登录的路由:

    url('^login/$', views.LoginView.as_view()),
    
    

    二级shop/apps/users/views.py中添加视图类:

    class LoginView(View):
        def get(self, request):
            return render(request, 'login.html') # 之后拷贝模板
        def post(self, request):
            # 接收
            username = request.POST.get('username')
            pwd = request.POST.get('pwd')
            # 验证
            if not all([user_name, pwd]):
                return http.HttpResponseBadRequest('参数不完整') # from django import http
            # 2.2用户名拼写是否符合规范,是否已存在该用户名
            if not re.match(r'^[a-zA-Z0-9_-]{5,20}$', user_name):
                return http.HttpResponseBadRequest('请输入5-20个字符') # import re
            if not re.match(r'[0-9A-Za-z]{8,20}$', pwd):
                return http.HttpResponseBadRequest('请输入8-20的密码')
            # 处理
            user = authenticate(username = username, pwd = pwd) # from django.contrib.auth import authenticate
            if user is None:
                # 用户名或密码错误
                return render(request, 'login.html', {
                    'loginerror':'用户名或密码错误'
                })	
            else:
                # 登录成功,状态保持
                login(request, user)
                return redirect('/')
            # 响应
    
    

    多账号登录

    新建名为shop_backendspython fileutils中,内容如下:

    from django.contrib.auth.backends import ModelBackend
    class ShopModelBackend(ModelBackend):
        def authenticate(self, request, username=None, password=None, **kwargs):
        	# username接收的数据,可能是手机号,也可能是用户名
            try:
                if re.match('^1[3-9]d{9}$', username):
                    # 手机号
                    user = User.objects.get(mobile=username)
                else:
                    # 用户名
                    user = User.objects.get(username=username)
            except:
               	return None
            else:
                # 验证密码
                if user.check_password(password):
                    return user
                else:
                    return None
    
    

    dev.py中添加自定义认证类型:

    AUTHENTICATION_BACKENDS = ['shop.utils.auth_backends.ShopModelBackend']
    
    

    首页用户名显示

    新建应用,cd shop/shop/appspython ../../manage.py startapp contents

    注册应用,新建urls.py

    from django.conf.urls import url
    from . import views
    
    urlpatterns = [
        url('^$', views.IndexView.as_view()),
    ]
    
    

    dev.py中注册应用:

    'contents.apps.ContentsConfig',
    
    

    在总路由中添加子路由:

    url('^', include('content.urls')),
    
    

    添加视图类:

    from django.shortcuts import render
    from django.views import View
    class IndexView(View):
        	def get(self, request):
                return render(request, 'index.html')
    
    

    更改users/views.py.LoginView

    else:
        # 登录成功,状态保持
        login(request, user)
        # 向cookie中输出用户名,用于在前端提示登录状态
        response = redirect('/')
        response.set_cookie('username', user.username, max_age=60*60*24*14)
        return response
    
    

    更改users/views.py.RegisterView

    # 响应
    response = redirect('/')
        response.set_cookie('username', user.username, max_age=60*60*24*14)
        return response		
    
    

    退出

    users/views.py中添加新的视图类:

    class LogoutView(View):
        def get(self, request):
            # 删除session
            logout(request)
            # 删除cookie
            response = redirect('/')
            response.delete_cookie('usename')
            return response
    
    

    users.urls.py中添加路由:

    url('^logout/$', views.LogoutView.as_view()),
    
    

    用户个人信息

    添加路由在users/urls.py中:

    url('^info/$', views.InfoView.as_view()),
    
    

    添加视图类在users/views.py中:

    class InfoView(View):
        def get(self, request):
            # 判断是否登录
            if request.user.is_authenticated:
                # 已登录
                return render(request, 'user_center_info.html')
            else:
                # 未登录
                return redirect('/login/')
    
    

    拷贝页面至templates中。

    判断是否登录

    dev.py中指定登录页视图:

    LOGIN_URL = '/login/'
    
    

    改写views.py中的代码:

    from django.contrib.auth.mixins import LoginRequiredMixin
    class InfoView(LoginRequiredMixin, View):
        def get(self, request):
            # 判断是否登录
            # if request.user.is_authenticated:
                # 已登录
                # return render(request, 'user_center_info.html')
            # else:
                # 未登录
                # return redirect('/login/')
            return render(request, 'user_center_info.html')
    
    

    完善登录视图代码:

    class LoginView(View):
        def get(self, request):
            return render(request, 'login.html') # 之后拷贝模板
        def post(self, request):
            # 接收
            username = request.POST.get('username')
            pwd = request.POST.get('pwd')
            # 接收重定向参数,新加代码
            next_url = request.GET.get('next', '/')
            # 验证
            if not all([user_name, pwd]):
                return http.HttpResponseBadRequest('参数不完整') # from django import http
            # 2.2用户名拼写是否符合规范,是否已存在该用户名
            if not re.match(r'^[a-zA-Z0-9_-]{5,20}$', user_name):
                return http.HttpResponseBadRequest('请输入5-20个字符') # import re
            if not re.match(r'[0-9A-Za-z]{8,20}$', pwd):
                return http.HttpResponseBadRequest('请输入8-20的密码')
            # 处理
            user = authenticate(username = username, pwd = pwd) # from django.contrib.auth import authenticate
            if user is None:
                # 用户名或密码错误
                return render(request, 'login.html', {
                    'loginerror':'用户名或密码错误'
                })	
            else:
            # 登录成功,状态保持
            login(request, user)
            # 向cookie中输出用户名,用于在前端提示登录状态
            response = redirect(next_url)
            response.set_cookie('username', user.username, max_age=60*60*24*14)
            return response
    
    

    QQ授权登录

    在虚拟环境中安装QQLoginTool

    pip install QQLoginTool
    
    

    新建名为oauth的应用,python ../../manage.py startapp oauth

    新建urls.py,内容如下:

    from django.conf.urls import url
    from . import views
    urlpatterns = [
        url('^qq/login/$', views.QQurlView.as_view()),
        url('^oauth_callback$', views.QQopenidView.as_view()),
    ]
    
    

    在总路由中添加子路由:

    url('^', include('oauth.urls')),
    
    

    dev.py中注册app

    'oauth.apps.OauthConfig',
    
    

    views.py中创建类视图:

    from django.shortcuts import render
    from django.views import View
    from django import http
    from QQLoginTool.QQtool import  OAuthQQ
    from django.conf import settings
    
    class QQurlView(View):
        def get(self, request):
            # 生成QQ授权地址url
            next_url = request.GET.get('next', '/')
            # 创建工具对象,提供appid、appkey、回调地址
            oauthqq_tool = OAuthQQ(
                settings.QQ_CLIENT_ID,
                settings.QQ_CLIENT_SECRET,
                settings.QQ_CLIENT_URI,
                next_url
            )
            login_url = oauthqq_tool.get_qq_url()
            return http.JsonResponse({
                'code':RETCODE.OK,
                'errmsg':'OK',
                'login_url':login_url
            })
    class QQopenidView(View):
        def get(self, request):
            # 获取授权用户的唯一标识openid
            # 接收code
            code = request.GET.get('code')
            next_url = request.GET.get('state', '/')
            # 创建工具对象,提供appid、appkey、回调地址
            oauthqq_tool = OAuthQQ(
                settings.QQ_CLIENT_ID,
                settings.QQ_CLIENT_SECRET,
                settings.QQ_CLIENT_URI,
                next_url
            )
            try:
                # 根据code获取token
                token = oauthqq_tool.get_access_token(code)
                # 根据token获取openid
                openid = oauthqq_tool.get_openid(token)
            except:
                openid = '0'
            return http.HttpResponse(openid)
    
    

    dev.py中添加QQ授权信息:

    QQ_CLIENT_ID = '101518219'
    QQ_CLIENT_SECRET = '418d84ebdc7241efb79536886ae95224'
    QQ_REDIRECT_URI = 'http://www.meiduo.site:8000/oauth_callback'
    
    

    /etc/hosts中添加127.0.0.1 www.meiduo.site

    dev.py中添加:ALLOWED_HOSTS = ['www.meiduo.site',]

    host.js中的var host = 'http://www.meiduo.site:8000'的注释打开,其余均注释

    QQ账号信息与本网站绑定

    新建视图类于oauth/model.py中:

    from users.models import User
    from shop.utils.models import BaseModel
    
    class OAuthQQUser(models.Model):
        user = models.ForeignKey(User)
        openid = models.CharField(max_length = 50)
    
        class Meta:
            db_table = 'tb_oauth_qq'
    
    

    新建名为models.pypython file文件于utils目录中,内容为:

    from django.db import models
    class BaseModel(models.Model):
        create_time = models.DateField(auto_now_add = True)
        update_time = models.DateField(auto_now = True)
        class Meta:
            # 当前模型类为抽象类,用于继承,不需要创建表
            abstract = True
        
    
    

    执行迁移:

    python manage.py makemigrations
    python manage.py migrate
    
    

    改写oauth/views.py文件:

    from django.shortcuts import render
    from django.views import View
    from django import http
    from QQLoginTool.QQtool import  OAuthQQ
    from django.conf import settings
    from .models import OAuthQQUser
    
    class QQurlView(View):
        def get(self, request):
            # 生成QQ授权地址url
            next_url = request.GET.get('next', '/')
            # 创建工具对象,提供appid、appkey、回调地址
            oauthqq_tool = OAuthQQ(
                settings.QQ_CLIENT_ID,
                settings.QQ_CLIENT_SECRET,
                settings.QQ_CLIENT_URI,
                next_url
            )
            login_url = oauthqq_tool.get_qq_url()
            return http.JsonResponse({
                'code':RETCODE.OK,
                'errmsg':'OK',
                'login_url':login_url
            })
    class QQopenidView(View):
        def get(self, request):
            # 获取授权用户的唯一标识openid
            # 接收code
            code = request.GET.get('code')
            next_url = request.GET.get('state', '/')
            # 创建工具对象,提供appid、appkey、回调地址
            oauthqq_tool = OAuthQQ(
                settings.QQ_CLIENT_ID,
                settings.QQ_CLIENT_SECRET,
                settings.QQ_CLIENT_URI,
                next_url
            )
            try:
                # 根据code获取token
                token = oauthqq_tool.get_access_token(code)
                # 根据token获取openid
                openid = oauthqq_tool.get_openid(token)
                # 绑定
                try:
                    # 1.查询是否已经绑定过
                    qquser = OAuthQQUser.objects.get(openid=openid)
                except:
                    # 2.如果未绑定过,则提示绑定页面
                    return render(request, 'oauth_callback.html') # 将oauth_callback.html放到templates目录中
                    # 3.如果已绑定过,则状态保持
            except:
                openid = '0'
            return http.HttpResponse(openid)
    
    

    views.py中添加post方法:

    class QQopenidView(View):
        def get(self, request):
            # 获取授权用户的唯一标识openid
            # 接收code
            code = request.GET.get('code')
            next_url = request.GET.get('state', '/')
            # 创建工具对象,提供appid、appkey、回调地址
            oauthqq_tool = OAuthQQ(
                settings.QQ_CLIENT_ID,
                settings.QQ_CLIENT_SECRET,
                settings.QQ_CLIENT_URI,
                next_url
            )
            try:
                # 根据code获取token
                token = oauthqq_tool.get_access_token(code)
                # 根据token获取openid
                openid = oauthqq_tool.get_openid(token)
                # 绑定
                try:
                    # 1.查询是否已经绑定过
                    qquser = OAuthQQUser.objects.get(openid=openid)
                except:
                    # 2.如果未绑定过,则提示绑定页面
                    context = {'token':openid}
                    return render(request, 'oauth_callback.html', context) # 将oauth_callback.html放到templates目录中
                    # 3.如果已绑定过,则状态保持
            except:
                openid = '0'
            return http.HttpResponse(openid)
        def post(self, request):
            # openid与账号绑定
    
    

    安装加密的包:itsdangerous

    pip install itsdangerous
    
    

    新建名为shop_signature.pypython file文件于utils.py中,内容如下:

    from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
    from django.conf import settings
    
    def dumps(json, expires):
        '''
        :param json:字典
        :param expires:加密数据的过期时间
        :return:字符串
        '''
        # 将字典加密,返回字符串
        # 1.创建工具对象
    	serializer = Serializer(settings.SECRET_KEY, expires)
        # 2.加密
        serializer.dumps(json)
        # 3.转字符串,返回
        return s1.decode()
    
    def loadds(s1, expires):
        '''
        :param s1:字符串
        :param expires:加密数据的过期时间
        :return:字典
        '''
        # 将字符串解密,返回字典
        # 1.创建工具对象
    	serializer = Serializer(settings.SECRET_KEY, expires)
        # 2.解密
        try:
            json = serializer.loads(s1)
        except:
            return None
        # 3.返回字典
        return json
        
    
    

    更改views.py

    class QQopenidView(View):
        def get(self, request):
            # 获取授权用户的唯一标识openid
            # 接收code
            code = request.GET.get('code')
            next_url = request.GET.get('state', '/')
            # 创建工具对象,提供appid、appkey、回调地址
            oauthqq_tool = OAuthQQ(
                settings.QQ_CLIENT_ID,
                settings.QQ_CLIENT_SECRET,
                settings.QQ_CLIENT_URI,
                next_url
            )
            try:
                # 根据code获取token
                token = oauthqq_tool.get_access_token(code)
                # 根据token获取openid
                openid = oauthqq_tool.get_openid(token)
                # 绑定
                try:
                    # 1.查询是否已经绑定过
                    qquser = OAuthQQUser.objects.get(openid=openid)
                except:
                    # 2.如果未绑定过,则提示绑定页面
                    token = shop_signature.dumps({'openid':openid}, contants.OPENID_EXPIRES) # from shop.utils import shop_signature, from . import contants
                    context = {'token':openid}
                    return render(request, 'oauth_callback.html', context) # 将oauth_callback.html放到templates目录中
                    # 3.如果已绑定过,则状态保持
            except:
                openid = '0'
            return http.HttpResponse(openid)
        def post(self, request):
            # openid与账号绑定
    
    

    新建名为contantspython file文件于oauth目录中,内容如下:

    # openid的过期时间
    OPENID_EXPIRES = 60 * 10
    
    

    写post方法:

    def post(self, request):
        # openid与账号绑定
        # 接收
        mobile = request.POST.get('mobile')
        pwd = request.POST.get('pwd')
        sms_code_request = request.POST.get('sms_code')
        access_token = request.POST.get('access_token')
        next_url = request.GET.get('state')
        # 验证:与注册逻辑相同,参考注册代码
        json = shop_signature.loadds(access_token, contants.OPENID_EXPIRES)
        if json is None:
            return http.HttpResponseBadRequest('授权信息无效,请重新授权')
        openid = json.get('openid')
        # 处理
        # 根据手机号查询注册对象
        try:
            user = User.objects.get(mobile = mobile)
        except:
            # 手机号不存在,则注册新用户
            user = User.objects.create_user(username=mobile, password=pwd, mobile=mobile)
        else:
            # 手机号存在,则验证密码
            if not user.check_password(pwd):
                return http.HttpResponseBadRequest('密码错误')
        # 绑定
        OAuthQQUSer.objects.create(user=user, openid=openid) # from .models import OAuthQQUser
        # 状态保持
        login(request, user) # from django.contrib.auth import login
        # 响应
        response = redirect(next_url) # from django.shortcuts import redirect
        response.set_cookie('username', user.username, max_age = 60 * 60 * 24 * 14)
        return response
    
    

    非初次授权的情况。补充get方法:

    # 3.如果已绑定过,则状态保持
    login(request, qquser.user)
    response = redirect(next_url)
    response.set_cookie('username', qquser.user.username, max_age = 60 * 60 * 24 * 14)
    return response
    
    

    显示用户的个人信息

    改写users/views.py/InfoView

    def InfoView(LoginRequiredMixin, View):
        def get(self, request):
            # 获取当前登录的用户
            user = request.user
            context = {
                'username':user.username,
                'mobile':user.mobile,
                'email':user.email,
                'email_active':user.email_active
            }
            
    
    

    models.py/User中添加邮箱激活属性:

    class User(AbstractUser):
        mobile = model.CharField(max_length=11)
        # 邮箱是否被激活
        email_active = models.BooleanField(default=False)
    
    

    迁移:

    python manage.py makemigratons
    python manage.py migrate
    
    

    邮箱

    users/urls.py中添加路由:

    url('^emails$', views.EmailView.as_view()),
    
    

    users.views.py中添加视类图:

    class EmailView(LoginRequiredMixin, View):
        def put(self, request):
            # 接收,以put方式发送的请求
            dict1 = json.loads(request.body.decode()) # import json
            email = dict1.get('email')
            # 验证
            if not all([email]):
                return http.JsonResponse({
                    'code':RETCODE.PARAMERR,
                    'errmsg':'没有邮箱参数'
                })
            if not re.match('^[a-z0-9][w.-]*@[a-z0-9-]+(.[a-z]{2,5}){1,2}$', email):
                return http.JsonResponse({
                    'code':RETCODE.PARAMERR,
                    'errmsg':'邮箱格式错误'
                })
            # 处理
            # 修改属性
            user = request.user
            user.email = email
            user.save()
            # 发邮件
            # 响应
            return http.JsonResponse({
                'code':RETCODE.OK,
                'errmsg':'OK'
            })
        
    
    

    发邮件

    dev.py中添加邮件服务器配置:

    EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
    EMAIL_HOST = 'smtp.163.com'
    EMAIL_PORT = 25
    EMAIL_HOST_USER = 'hmmeiduo@163.com'
    EMAIL_HOST_PASSWORD = 'hmmeiduo123'
    EMAIL_FROM = '美多商城<hmmeiduo@163.com>'
    EMAIL_VERIFY_URL = 'http://www.meiduo.site:8000/emails/verification/'
    
    

    新建名为mailpython packagecelery_tasks目录中,在其中新加文件tasks.py

    在其中定义方法:

    from django.core.mail import send_mail
    from django.conf import settings
    from celery_tasks.main import app
    @app.task(name='send_user_email', bind=True)
    def send_user_email(to_mail, verify_url):
        html_message = '您的邮箱为:%s,激活链接为%s' % (to_mail, verify_url)
        try:
            send_mail('美多商城-邮箱激活','', settings.EMAIL_FROM, [to_mail], html_message=html_message)
        except Exception as e:
            self.retry(exc=e, max_retries=2)
    
    

    celery_tasks/sms/main.py中添加任务:

    app.autodiscover_tasks([
        'celery_tasks.sms',
        'celery_tasks.mail',
    ])
    
    

    启动celery:celery -A celery tasks.main worker -l info

    views.py中调用任务

    from celery_tasks.mail.tasks import send_user_email
    ...
    # 发邮件,from django.conf import settings, from shop.utils import shop_signature,from . import contants
    token = shop_signature.dumps({'user_id':user_id}, contants.EMAIL_EXPIRES)
    verify_url = settings.EMAIL_VERIFY_URL + '?token=%s' % token
    send_user_email.delay(email, verify_url)
    
    

    users,目录总新建contants.py,内容为:

    # 邮箱激活的有效时间
    EMAIL_EXPIRES = 60 * 60 * 2
    
    

    激活邮箱

    新建视图类:

    class EmailVerifyView(View):
        def get(self, request):
            # 接收
            token = request.GET.get('token')
    
            # 验证
            dict1 = meiduo_signature.loadds(token, contants.EMAIL_EXPIRES)
            if dict1 is None:
                return http.HttpResponseBadRequest('激活信息无效,请重新发邮件')
            user_id = dict1.get('user_id')
    
            # 处理
            try:
                user = User.objects.get(pk=user_id)
            except:
                return http.HttpResponseBadRequest('激活信息无效')
            else:
                user.email_active = True
                user.save()
    
            # 响应
            return redirect('/info/')
    
    

    添加路由:

    url('^emails/verifyication$', views.EmailVerifyView.as_view()),
    
    

    收货地址

    users/urls.py中添加路由:

    url('^addresses$', views.AddressesView.as_view()),
    
    

    users/views.py中定义视图类:

    class AddressesView(LoginRequiredMixin, View):
        def get(self, request):
            return render(request, 'user_center_site.html')
    
    

    新建应用

    cd shop/shop/apps/
    python ../../manage.py startapp areas
    
    

    新建路由urls.py

    from django.conf.urls import url
    from . import views
    
    urlpatterns = [
        
    ]
    
    

    在总路由中添加子路由

    url('^', include('areas.urls')),
    
    

    注册app

    'areas.apps.AreasConfig',
    
    
  • 相关阅读:
    百度大脑IOCR财会票据识别技术接入小程序,快速实现财会票据识别
    接入百度大脑一站式内容审核平台,可快速提升审核效率
    利用百度AI快速开发出一款“问答机器人”并接入小程序
    接入百度大脑表格文字识别技术,快速降低信息电子化录入成本
    百度大脑UNIT3.0智能对话技术全面解析
    利用百度大脑,快速搭建一个红酒识别的小程序
    百度大脑UNIT3.0详解之嵌入式对话理解技术
    人群流量监控,安全管理升级
    python globals函数
    python reduce/map/filter函数区别
  • 原文地址:https://www.cnblogs.com/lulujunsir/p/meiduo.html
Copyright © 2011-2022 走看看