9.Django

Django

模式简介

MVC(Model, View, Controller)

  • MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。MVC被独特的发展起来用于映射传统的输入、处理和输出功能在一个逻辑的图形化用户界面的结构中。 通俗的来讲就是,强制性的使应用程序的输入,处理和输出分开。

  • 核心思想:解耦

  • 优点:减低各个模块之间的耦合性,方便变更,更容易重构代码,最大程度的实现了代码的重用

  • Model: 即数据存取层。用于封装于应用程序的业务逻辑相关的数据,以及对数据的处理。说白了就是模型对象负责在数据库中存取数据

  • View: 即表现层。负责数据的显示和呈现。渲染的html页面给用户,或者返回数据给用户。

  • Controller: 即业务逻辑层。负责从用户端收集用户的输入,进行业务逻辑处理,包括向模型中发送数据,进行CRUD操作。

    这里写图片描述浏览器中MVC的表现形式图解: 这里写图片描述

MVT (Model, View, Template)

  • 严格来说,Django的模式应该是MVT模式,本质上和MVC没什么区别,也是各组件之间为了保持松耦合关系,只是定义上有些许不同。

  • Model: 负责业务与数据库(ORM)的对象

  • View: 负责业务逻辑并适当调用Model和Template

  • Template: 负责把页面渲染展示给用户

    注意: Django中还有一个url分发器,也叫作路由。主要用于将url请求发送给不同的View处理,View在进行相关的业务逻辑处理。

    这里写图片描述

  • B/S—–browser—-server – 表示浏览器/服务器模式

  • C/S—-client—-server – 表示客户机/服务器结构

配置python虚拟环境(cmd操作)

  • 安装—————-pip install virtualenv

    创建并进入d:\env

  • 创建虚拟环境—-virtualenv –no-site-packages -p(xxx) + 环境名(testenv)

    • -p————–指定python版本

    • testenv——创建一个文件夹testenv来装虚拟环境

  • 启动虚拟环境—-d:\env\testenv\Scripts>activate

  • 退出虚拟环境—deactivate

配置django

启动虚拟环境并进入项目文件夹:

  • 安装django—————–pip install django==1.11 (虚拟环境里面安装)

  • 创建一个django项目—–django-admin startproject + 项目名

  • 启动项目———————python manage.py runserver + ip:端口

  • 将英文改成中文———–setting.py里面改成LANGUAGE_CODE = ‘zh-hans’

  • 获取时间———————TIME_ZONE = ‘UTC’(比北京时间少8个小时)

    ​ TIME_ZONE = ‘Asia/Shanghai’(正常的北京时间)

  • 创建app———————-python manage.py startapp + app名字

  • __init__.py——————-初始化配置pymysql连接的地方

  • setting.py——————–配置信息位置,database等

  • urls.py————————-url路由

  • wsgi—————————–网关

了解app

  • __init__.py——— —初始化

  • admin.py————-管理后台注册模型

  • apps.py—————setting.py里面注册app时需要用到,导入apps.py文件中的APPCONFIG下的name属性,一般不用这种方法,而是在项目的setting.py中直接加上app名字。

    INSTALLED_APPS = [
      'django.contrib.admin',
      'django.contrib.auth',
      'django.contrib.contenttypes',
      'django.contrib.sessions',
      'django.contrib.messages',
      'django.contrib.staticfiles',
      'app',
      'stu'
    ]
  • models.py————定义class模型的地方(继承models.Model)

  • tests.py—————-写测试脚本

  • views.py—————写处理业务逻辑的地方

数据库系列操作

在项目__init__.py中引入数据库

import pymysql

pymysql.install_as_MySQLdb()

连接数据库(setting.py)

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'HOST': 'localhost',
        'USER': 'root',
        'PASSWORD': 'xxxxxx',
        'PORT': 3306,
        'NAME': 'hr'              # 数据库名称
    }
}

添加数据表结构(models.py)

from django.db import models

class Student(models.Model):
    name = models.CharField(max_length=20)  # 数据表中name的数据类型
    sex = models.BooleanField()             # 数据表中sex的数据类型

    class Meta:                             # 定义元
        db_table = 'stu'                    # 数据库中表名称

在MySQL相应table中加入数据

def add(request):              # 保存数据
    stu = Student()
    stu.name = '李白'
    stu.sex = 1
    stu.save()
    return HttpResponse('完成')
  • python manage.py makemigrations

  • python manage.py migrate

  • 在浏览器中输入相应的url运行已经定义好的add函数,并观察数据库中相应表格数据的变化

创建超级管理员的账户和密码

  • python manage.py createsuperuser

理解ORM 对象关系映射–翻译机

django自带后台个性化设置

from stu.models import Student

# 第二种注册方式 利用装饰器
@admin.register(Student)
class StudentAdmin(admin.ModelAdmin):

    def set_sex(self):
        if self.sex:
            return '男'
        else:
            return '女'
    # 修改性别字段的描述
    set_sex.short_description = '性别'
    # 展示字段
    list_display = ['id', 'name', set_sex]
    # 过滤
    list_filter = ['name']
    # 搜索
    search_fields = ['name']
    # 分页
    list_per_page = 2

# 1.注册的第一种方式
# admin.site.register(Student, StudentAdmin)

字段设置

模型字段

  • CharField:字符串

  • BooleanField:布尔类型

  • DateField:年月日,日期

    • auto_now_add:第一次创建的时候赋值
    • auto_now:每次修改的时候赋值

      • DateTimeField:年月日时分秒
    • auto_now_add
    • auto_now
  • AutoField:自动增长

  • DecimalField:

    • models.DecialField(max_digits=3, decimal_place=1) 最大99.9

    • max_digits:总位数

    • decimal_places:小数后多少位

  • TextField:存文本信息

  • IntegerField:整数

  • FloatField:浮点

  • FileField:文件上传字段

  • URLField:

  • EmailField:

  • ImageField:上传图片

    • upload_to=“”:指定上传图片的记录
      python
      i_image = models.ImageField(upload_to='upload', null=True)
      # upload_to表示上传文件存入的位置:'/media/upload/'
      # 需要自己创建media文件夹,并在setting.py中配置路径。在下文‘配置上传文件夹并设置为静态可访问文件夹’中有相应介绍
      # 注意:ImageField需要依赖一个包:
      # Pillow:pip install Pillow
      # 注意:在使用此字段时。
      # 如果相应html模板中有<input type="file">标签,其相应的form标签必须加入enctype="multipart/form-data"

模型参数

  • default:默认
  • null:设置是否为空,针对数据库中该字段是否可以为空
  • blank:设置是否为空,针对表单提交该字段是否可以为空
  • primary_key:创建主键
  • unique:唯一

objects对象

通过 {模型.objects} 来实现数据的CRUD操作

  • 获取所有

    • 模型.objects.all()
  • create():创建

    def addStu(request):
    
      if request.method =='GET':        
          return render(request,'addstu.html')
    
      if request.method == 'POST':
          Student.objects.create(
                            stu_birth=request.POST.get('birth'),
                            stu_sex=request.POST.get('sex'),
                            stu_name=request.POST.get('name'),
                            stu_yuwen=request.POST.get('yuwen'),
                            stu_shuxue=request.POST.get('shuxe'),
                            )
          return render(request, 'addstu.html')
  • delete():删除

    Student.objects.filter(id=nn).delete()
  • update():更新

    Student.objects.filter(id=nn).update(stu_name='xx')
  • 过滤条件

    • filter(条件)
    • git(条件)
    • 区别:

    • get:只返回一个满足条件的对象,没有满足条件的则直接报DoesNotExit的异常,查询到多个数据时报MultiObjectReturned异常

    • filter:返回满足条件的所有结果,没有就返回空
    • order_by(‘xxx’):按照xxx从小到大排序

    • last():返回最后一条数据

    • first():返回第一条数据

    • count():求和(返回个数)

    • exists():验证是否存在(返回布尔值)

    • __gt/__gte:大于/大于等于

    • __lt/__lte:小于/小于等于

    • __startswith:以xxx开始

    • __endswith:以xxx结束

    • __contains:包含xxx

    • __in:验证是否在xxx中

    • ~:表示取反

    • F(): # 一般与__gt/__gte/__lt/__lte连用

    • Q(): # 一般与|和&连用

    理解F(),Q()

    from django.db.models import F,Q

参数传递

  • 方法一:利用?
# 传值:
<a href="http://127.0.0.1:8000/user/refer/?name={{ user.u_name }}">查询相应权限</a> 
                    # 把user.u_name传递到http://127.0.0.1:8000/user/refer/
# 取值:
def refer(request):
    name = request.GET.get('name')
  • 方法二:利用url正则表达式
url(r'^s/', include('stu.urls'))   # one   项目下
url(r'^stu/(\d+)/',views.allstu)   # two  app下
<a href="/s/stu/{{ g.id }}/"></a>  # three 模板中
def stu(request,xxx):          # four views中
                        xxx将获取g.id的值,后面直接调用xxx
  • 优化方法二:利用url
url(r'^s/', include('stu.urls', namespace='s')) # one   项目下
url(r'^stu/(\d+)/',views.allstu,name='alls')    # two  app下
<a href="{% url 's:alls' g.id  %}"></a>     # three 模板中
def ooo(request,xxx):               # four views中        
    xxx将获取g.id的值,后面直接调用xxx

传递多个参数

url(r'^s/', include('stu.urls', namespace='s'))           # one   项目下
url(r'^stu/(\d+)/(\d+)/(\d+)/',views.allstu,name='alls')  # two  app下
<a href="{% url 's:alls' 1 2 3  %}"></a>          # three 模板中
def ooo(request,x,y,z):
               x,y,z将分别或取123;x,y,z的位置很关键
  • 优化

    url(r'^s/', include('stu.urls', namespace='s'))                      # one   项目下
    url(r'^stu/(?P<xx>\d+)/(?P<yy>\d+)/(?P<zz>\d+)/',views.allstu,name='alls')       # two  app下
    <a href="{% url 's:alls' 1 2 3  %}"></a>                                 # three 模板中
    def ooo(request,yy,xx,zz):
             xx,yy,zz将分别取123;这里与xx,yy,zz的位置无关(正则表达式分组)

关联表

一对一

  • models.OneToOneField———-主键和外键是一对一的关系,关联表id与主表id一一对应

    
    # 表 xxx
    
    ooo = models.OneToOneField(yyy, on_delete=models.CASCADE, related_name='info')
  • 主表找拓展表的信息: 主表对象.关联表的model_name——–object(xxx下的).ooo

  • 拓展表找主表: 拓展信息对象.关联字段——————————object(yyy下的).info

    • 如果创建外键时没有定义related_name=’info’—————-object(yyy下的).yyy(小写)

一对多

  • models.ForeignKey————-主键和外键是一对多的关系,主表不同id可以对应关联表相同id,关联表相同的id不能对应主表同一个id

    
    # 表 xxx
    
    ooo = models.ForeignKey(yyy, null=True)
  • 主表找拓展表的信息: 主表对象.关联表的model_name.all()——–object(xxx下的).ooo.all()

  • 拓展表找主表: 拓展信息对象.关联字段————————————object(yyy下的).xxx_set.all()

多对多

  • models.ManyToManyField————主键和外键是多对多的关系,主表id、关联表id都可以相互任意搭配

  • 
    # 表xxx
    
    ooo= models.ManyToManyField(yyy)
  • (查表方法同一对多)

  • on_delete方法

    • cascade(默认),主表删除,从表也删除
    • set_null,主表删除,从表关联字段设置位空
    • protect,不让删除
    • set_default,主表删除,从表关联字段设置位默认值

静态资源加载

  • 配置静态目录——项目中创建static文件夹并在setting.py中加上路径
STATIC_URL = '/static/'   #在这之后加上下面语句

STATICFILES_DIRS=[
    os.path.join(BASE_DIR,'static')
]
  • 应用静态目录——-在static文件下创建图片文件夹images,后面模板中直接调用

    <body>
    {#第一种方法#}
    <img src="/static/images/1-1.PNG" alt="图片">
    
    {#第二种方法#} 
    {# {% load static from staticfiles%} #}   
    {% load static %}
    <img src="{% static 'images/1-1.png' %}" alt="图片">
    </body>

templates模板创建

  • 在项目下创建templates文件夹,并且在setting.py中加上路径

    TEMPLATES = [
      {
          ……
          'DIRS': [os.path.join(BASE_DIR, 'templates')],
          # 加上的语句
    
          'APP_DIRS': True,
          ……
      },
    ]

配置上传文件夹并设置为静态可访问文件夹

  • 配置上传文件夹

    在工程下创建上传文件夹media:

                                # sep----1
    
    # 在工程setting.py中
    
    
    
    # 配置上传文件路径
    
    MEDIA_URL = '/media/'
    MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
  • 配置上传

    
    # models.py中
    
    class StudentInfo(models.Model):
      i_addr = models.CharField(max_length=30)
      s = models.OneToOneField(Student)
      i_image = models.ImageField(upload_to='upload', null=True)
      # upload_to='upload'表示在/media/upload/下存文件(upload提前创建好)
      # 这样设置后,图片自动上传到/media/upload/下,数据库中保存图片地址
      class Meta:
          db_table = 'stuinfo'
    
    
    # html的form表单中
    
    头像:<input type="file" name="img">
    
    
    # views.py中
    
    img = request.FILES.get('img')
    StudentInfo.objects.create(i_addr=addr,s_id = stu_id,i_image=img)
  • 结果展示

    这里写图片描述

    这里写图片描述

  • 设置为静态url可访问文件夹

                            # sep----2
    
    # 在工程urls.py中
    
    from django.contrib.staticfiles.urls import static
    from w9d5 import settings
    
    urlpatterns += static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT)
  • 应用

    <img src="/media/{{ info.i_image }}" width="50" alt="头像">

jinja2语法

注释

  • 单行注释
{#  #}
  • 多行注释
{% comment %}
...
{% endcomment %}

for循环:

<body>
{% for user in users %}
    商品:{{ user.u_name }}
    <br>
{% empty %}
    没有商品信息
{% endfor %}

</body>

forloop

  • 循环计数 forloop.counter

    计数从0开始:{{ forloop.counter0 }}
    计数从1开始:{{ forloop.counter }}
    计数从最后开始,到1停:{{ forloop.revcounter }}
    计数从最后开始,到0停:{{ forloop.revcounter0 }}

过滤器(|)——在变量显示前修改

date:y-m-d h:m:s (年-月-日 时:分:秒)
    年月日中:
        y两位年,Y四位年
        m数字月份,M中文或英文月份
        d数字日期,D中文或英文星期几
    时分秒中:
        h十二时制,H二十四时制
        m
        s
lower/upper-------------大小写转换
add:xx------------------加上xx
{% widthratio 基数 分母 分子%}------给基数乘上 分子/分母 eg:{% widthratio stu.stu_yuwen 1 10 %}
divisibleby:xx----------------------判断能否被xx整除,返回布尔值

if/ifequal判断

<body>
    {% for stu in stus %}       
        # 第三个人的姓名变大
        {% ifequal forloop.counter 3 %}
            <h1>姓名:{{ stu.stu_name }}</h1>
        {% else %}
            <h5>姓名:{{ stu.stu_name }}</h5>
        {% endifequal %}

        性别:
        {% if stu.stu_sex %}{% else %}{% endif %}

        生日:{{ stu.stu_birth | date:'Y-m-d'}}
        创建时间:{{ stu.stu_create_time }}

        语文成绩:{{ stu.stu_yuwen | add:10 }}  # 在原来的基础上加10
        数学成绩:{{ stu.stu_shuxue | add:-10}} # 在原来的基础上减10

        <br>
    {% empty %}
        没有学生信息
    {% endfor %}
</body>

对html模板中的相同内容进行封装,后面利用挖坑填坑方法调用

{{block.super}}:

base.html(相同内容)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>
        {% block title %} {% endblock %}
    </title>
</head>
<body>
    {% block contain %} {% endblock %}
</body>
</html>

index.html(被调用的文件)

“`jinja2
{% extends ‘base.html’ %} {# 必须写到第一行 #}

{# extends可以修改原来内容,include不能修改已经定义的内容 #}

{% block title %}
我是标题
{% endblock %}
{% block contain %}
我是内容
{% for stu in stus %}
学生id:{{ stu.id }}
学生名称:{{ stu.stu_name }}
学生班级:{{ stu.g_id }}

{% endfor %}
{% endblock %}
“`

跳转页面

url(r'^s/', include('stu.urls', namespace='s'))                 # one 项目下
url(r'^redirect/(?P<g_id>\d+)/',views.redirectStu, name='red'),         # two app下
<a href="{% url 's:red' g.id  %}"></a>                                  # three templates下

from django.core.urlresolvers import reverse
def redirectStu(request,g_id):                          # four views下   
            # 这里可以写业务处理
    return HttpResponseRedirect(reverse('s:red', kwargs={'g_id':g_id}))
            # 跳转页面的同时传递参数。注意:此处相应url中也必须有参数匹配的正则表达式
    # (or) 
    # return HttpResponseRedirect('/g/grade/')
            # 跳转页面,不传递参数

错误页面处理

分别写出现404和500错误时显示的页面,名字分别为page_not_found,server_error,保存在templates文件夹下。然后在setting.py和项目urls.py文件中做如下修改

setting.py中修改

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True           # 改为 DEBUG = False

ALLOWED_HOSTS = []     # 改为 ALLOWED_HOSTS = ['*']

项目urls.py中增加

from xxx.views import page_not_found, server_error

handler404 = page_not_found   # django自动识别
handler500 = server_error

请求与响应

请求

  • post———-提交数据(隐藏了)
  • get————提交数据在url上,?xx=xxx
  • put————更新全部数据
  • patch———更新局部数据
  • delete——–删除 eg: delete:/s/stu/s_id/

响应(response)

  • cookies,get,body,user,files,post,path,code,pk==id ……
  • Qureydict与dict区别:

注册、登陆与注销:

注册

def regist(request):

    if request.method == 'GET':
        # 进入注册页面
        return render(request, 'day6_regist.html')
    if request.method == 'POST':
        # 提交注册信息
        name = request.POST.get('name')
        password = request.POST.get('password')
        # 对密码加密
        password = make_password(password)

        Users.objects.create(u_name=name,u_password=password)
        # 跳转到登陆页面
        return HttpResponseRedirect('/uauth/login/')

登陆

def login(request):
    if request.method == 'GET':
        # 进入登陆页面
        return render(request, 'day6_login.html')
    if request.method == 'POST':
        # 提交登陆信息
        name = request.POST.get('name')
        password = request.POST.get('password')
        # 验证用户是否存在
        if Users.objects.filter(u_name=name).exists():
            user = Users.objects.get(u_name=name)
            # 验证密码是否正确
            if check_password(password, user.u_password):
                # 验证通过,为用户创建一个唯一的ticket
                s = 'abcdefghijklmnopqrstuvwsyz1234567890'
                ticket = ''
                for i in range(15):
                    ticket += random.choice(s)
                now_time = int(time.time())
                ticket = 'TK' + ticket + str(now_time)
                # response = HttpResponse('登陆成功,可以跳转到详情页面了"/stu/index"')
                # 跳转到下一页面并绑定令牌到cookie中,max_age表示令牌存活时间
                response = HttpResponseRedirect('/stu/index/')
                response.set_cookie('ticket',ticket,max_age=30000)
                # 同时把ticket存在服务端,为下次验证用户提供ticket
                user.u_ticket = ticket
                user.save()
                return response
            else:
                # return HttpResponse('用户密码错误')
                return render(request, 'day6_login.html', {'password':'用户密码错误'})
        else:
            # return HttpResponse('用户不存在')
            return render(request, 'day6_login.html', {'name': '用户不存在'})

注销

def logout(request):
    # 注销登陆
    if request.method == 'GET':
        # 跳转到登陆页面
        re= HttpResponseRedirect('/uauth/login/')
        # 删除cookie中的ticket
        re.delete_cookie('ticket')
        #  只有response才有删除cookie功能
        return re

中间键/面向切面编程(AOP)

这里写图片描述

创建相关文件夹及文件

  • 在项目下创建utils文件夹

    • 创建__init__.py:表示此文件夹是一个包,可以引用

    • 创建UserAuthMiddleware.py文件,并定义一个名为’AuthMiddleware’的类

    这里写图片描述

配置中间件

  • 在setting.py中‘MIDDLEWARE’下增加下面语句:

    • python
      'utils.UserAuthMiddleware.AuthMiddleware',

应用中间件

  • UserAuthMiddleware.py文件类AuthMiddleware中:

    • process_request:在处理url路由之前进行处理逻辑
    • process_response:在响应返回浏览器之前调用
    • process_view:调用视图之前执行
    • process_templates_response:在试图刚好执行完的时候调用
  • from django.utils.deprecation import MiddlewareMixin
    from django.http import HttpResponseRedirect
    from uauth.models import Users
    
    
    class AuthMiddleware(MiddlewareMixin):
    
      def process_request(self, request):
          # 统一验证登陆
          # return None or 不写return 表示通过
    
          # 登陆和注册时不要验证ticket
          if request.path == '/uauth/login/' or '/uauth/logout/':
              return None
    
          ticket = request.COOKIES.get('ticket')
          if not ticket:
              return HttpResponseRedirect('/uauth/login/')
    
          users = Users.objects.filter(u_ticket=ticket)
          if not users:
              return HttpResponseRedirect('/uauth/login/')
          request.user=users[0]

利用django自带登陆、注册、注销功能

  • 利用自带的功能将省略自己实现ticket验证,中间键等功能

注册

from django.contrib.auth.models import User
# django会把用户数据存到django已经创建好的auth_user表中

def djregist(request):

    if request.method == 'GET':
        return render(request, 'day6_regist.html')
    if request.method == 'POST':
        name = request.POST.get('name')
        password = request.POST.get('password')
        User.objects.create_user(username=name, password=password)
        return HttpResponseRedirect('/uauth/dj_login/')

登陆

from django.contrib import auth

def djlogin(request):

    if request.method == 'GET':
        return render(request, 'day6_login.html')

    if request.method == 'POST':
        name = request.POST.get('name')
        password = request.POST.get('password')
        # 验证用户和密码,通过就返回user对象
        user = auth.authenticate(username=name, password=password)
        if user:
            # 验证成功
            auth.login(request, user)
            return HttpResponseRedirect('/stu/index/')
            pass
        else:
            return render(request, 'day6_login.html')

注销

from django.contrib import auth

def djlogout(request):

    if request.method == 'GET':
        auth.logout(request)
        return HttpResponseRedirect('/uauth/dj_login/')

中间件功能

  • 如果需要实现某个功能必须要登陆后才能访问,则把相应url的视图传递给login_required()函数
from django.contrib.auth.decorators import login_required

urlpatterns = [
    url(r'^index/', login_required(views.index)),
]

配置没登陆的跳转地址

# setting.py中

# 没登陆的跳转地址
LOGIN_URL = '/uauth/dj_login/'

分页

分页函数

from django.core.paginator import Paginator
# 默认参数如下(查询相关手册)
class Paginator(object):

    def __init__(self, object_list, per_page, orphans=0,allow_empty_first_page=True):
        self.object_list = object_list
        self._check_object_list_is_ordered()
        self.per_page = int(per_page)
        self.orphans = int(orphans)
        self.allow_empty_first_page = allow_empty_first_page
  • Paginator对象拥有的方法:
    • page(number):返回number页的数据
    • count:返回总数据条数
    • num_pages:返回一总页数
    • page_range:返回一个从1开始num_pages结束的列表
  • page对象:
    • has_next:是否有下一页
    • next_page_number:下一页id
    • has_previous:是否有上一页
    • previous_page_number:上一页id
    • number:当前页id

分页举例

在views.py中定义分页信息

def stuPage(request):

    if request.method == 'GET':
        page_id = int(request.GET.get('page_id', 1))
        stus = Student.objects.all()
        # 每页显示3个stus成员,不建议把数量写死,而是在setting.py中定义
        pagina = Paginator(stus,3)
        # 返回第几页的信息
        page = pagina.page(page_id)
        return render(request, 'index_page.html', {'page':page})

在html模板中提取页面信息

<body>
{% for stu in stus %}
    姓名:{{ stu.s_name }}
    电话:{{ stu.s_tel }}
    地址:{{ stu.studentinfo.i_addr }}
    头像:
    <br>
{% endfor %}

<h4>一共{{ stus.paginator.num_pages }}页/共{{ stus.paginator.count }}条数据</h4>
<h5>
    {% for i in stus.paginator.page_range %}
        <a href="/stu/stupage/?page_id={{ i }}">{{ i }}</a>
    {% endfor %}
</h5>

{% if stus.has_previous %}
    <a href="/stu/stupage/?page_id={{ stus.previous_page_number }}">上一页</a>
{% endif %}

当前第{{ stus.number }}页

{% if stus.has_next %}
    <a href="/stu/stupage/?page_id={{ stus.next_page_number }}">下一页</a>
{% endif %}

</body>

post请求防护

打开setting.py中的:’django.middleware.csrf.CsrfViewMiddleware’,中间件

然后再每个post请求表单中加入:{% csrf_token %}后就可以不被禁止

统计页面点击次数(简单统计)

创建数据表

# 在models.py中

class Visit(models.Model):

    v_url = models.CharField(max_length=255)
    v_time = models.IntegerField()

    class Meta:
        db_table = 'visit_time'

创建中间件

# 在utils中创建中间件VisitTimesMiddleware.py

from django.utils.deprecation import MiddlewareMixin
from uauth.models import Visit


class VisitTimes(MiddlewareMixin):

    def process_request(self, request):

        # 统计访问的url以及次数
        path = request.path

        try:
            visit = Visit.objects.get(v_url=path)
            if visit:
                visit.v_time += 1
                visit.save()
        except Exception as e:
            print(e)
            Visit.objects.create(v_url=path, v_time=1)

设置中间件

# 在setting.py文件MIDDLEWARE中加入下面语句

'utils.VisitTimesMiddleware.VisitTimes',

日志

前后分离:前端vue,后端restframeork

  • 日志组成部分
    • loggers:用来处理传入的信息
    • handlers:用来处理信息的
    • filters:过滤loggers传入handlers的信息,加一些处理控制
    • formatters :格式化,统一传入到日志文件中的信息
  • 错误信息的错误等级:critical>error>warning>info>debug
    • critical:重大bug
    • error:系统里面的错误
    • warning:警告,不影响使用
    • info:正常
    • debug:调试信息

创建log日志存放路径和文件夹

# setting.py  中

# 创建日志的路径
LOG_PATH = os.path.join(BASE_DIR, 'log')

# 不存在地址则创建地址
if not os.path.isdir(LOG_PATH):
    os.mkdir(LOG_PATH)

定义日志

格式:这里写图片描述

# setting.py中

LOGGING = {
    # version只能为1
    'version': 1,
    # Flase表示打开loggers
    'disable_existing_loggers': False,

    'formatters': {

        'default': {
            'format': '%(levelno)s %(pathname)s %(funcName)s %(module)s %(asctime)s %(message)s'
        },

        'simple': {
            'format': '%(levelname)s %(module)s %(created)s %(message)s'
        }
    },


    'handlers': {

        'stu_handlers': {
            'level': 'DEBUG',
            # 日志文件指定为5M,超过则重新备份,然后写入新的日志信息
            'class': 'logging.handlers.RotatingFileHandler',
            'maxBytes': 5 * 1024 * 1024,
            # 文件地址
            'filename': '%s/log.txt' % LOG_PATH,
            'formatter': 'default',
        },

        'uauth_handlers': {
            'level': 'DEBUG',
            # 日志文件指定为5M,超过则重新备份,然后写入新的日志信息
            'class': 'logging.handlers.RotatingFileHandler',
            'maxBytes': 5 * 1024 * 1024,
            # 文件地址
            'filename': '%s/uauth.txt' % LOG_PATH,
            'formatter': 'simple',
        },
    },

    'loggers': {

        'stu': {
            'handlers': ['stu_handlers'],
            'level': 'INFO',
        },

        'uauth': {
            'handlers': ['uauth_handlers'],
            'level': 'INFO',
        },

    },

    'filters': {

    },
}

应用定义的log

# 在views.py中
import logging

logger = logging.getLogger('stu')
# -----------------------
def index(request):
    infos = StudentInfo.objects.all()
    logger.info('url:%s method:%s 获取学生信息成功' % (request.path, request.method))
    return render(request, 'index.html', {'infos': infos})
# 在中间键中:
import logging

logger = logging.getLogger('uauth')

# ----------------------------
except Exception as e:
    print(e)
    logger.error(e)

运行后查看结果

这里写图片描述

这里写图片描述

数据接口api(restful)

  • 什么是rest风格:

    REST是所有Web应用都应该遵守的架构设计指导原则。Representational State Transfer,翻译是”表现层状态转化”

    面向资源是REST最明显的特征,对于同一个资源的一组不同的操作。资源是服务器上一个可命名的抽象概念,资源是以名词为核心来组织的,首先关注的是名词。

    REST要求,必须通过统一的接口来对资源执行各种操作。对于每个资源只能执行一组有限的操作。

    GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT(PATCH)用来更新资源,DELETE用来删除资源。

    1. api定义规范
    http://xxx.com/api/
    1. 资源

    在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。

    举例来说,有一个API提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样。

    https://api.example.com/v1/zoos
    
    https://api.example.com/v1/animals
    
    https://api.example.com/v1/employees
    
    1. http请求方式

    GET(SELECT):从服务器取出资源(一项或多项)

    POST(CREATE):在服务器新建一个资源

    PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)

    PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)

    DELETE(DELETE):从服务器删除资源

    1. filter过滤

    ?page=2&per_page=100:指定第几页,以及每页的记录数。

    ?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。

    ?animal_type_id=1:指定筛选条件

    1. 状态码

    服务端向用户返回请求api的结果,在结果中包含了status codes 状态码的,可以通过状态码去判断请求api的状态是成功还是失败

    200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。

    201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。

    202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)

    204 NO CONTENT - [DELETE]:用户删除数据成功。

    400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。

    401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。

    403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。

    404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。

    406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。

    410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。

    422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。

    500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。

    1. 错误处理

    如果状态码是4xx,就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可。

    {
    error: ‘错误信息’
    }

    1. django中使用restful
    pip install djangorestframework
    
    pip install django-filter  # Filtering support

django中restful简单应用

数据库中的数据及结构如下

这里写图片描述

这里写图片描述

  • 在setting.py的INSTALLED_APPS中加入’rest_framework’,
    • 否则在浏览器中访问restful的url时会报错:TemplateDoesNotExist
  • 在django的app中创建一个serializers.py设置序列化的字段

    
    # serializers.py中
    
    
    from rest_framework import serializers
    from stu.models import Student
    
    
    class StudentSerializer(serializers.ModelSerializer):
    
      class Meta:
          # 关联数据表
          model = Student
          # 确定需要序列化的字段,也就是返回数据中有的字段
          fields = ['id', 's_name', 's_tel']
    
      def to_representation(self, instance):
    
          data = super().to_representation(instance) # instance会依次返回每个Student对象
    
          try:
              # 得到关联表中的字段,必须用try,否则如果存在一个没有定义i_addr的学生时,系统将报错
              data['s_addr'] = instance.studentinfo.i_addr
          except Exception as e:
              data['s_addr'] = ''
    
          return data
  • 在django的app下views.py中创建一个序列化的类StudentEdit(名字自取),定义api对数据可用的请求 和数据内容筛选

    
    # views.py 中
    
    
    from rest_framework import mixins,viewsets
    from stu.serializers import StudentSerializer
    from stu.models import Student
    
    
    class StudentEdit(mixins.ListModelMixin,      # 表示可以在Postman类似的软件中查找所有数据
                    viewsets.GenericViewSet,    
                    mixins.RetrieveModelMixin,  # 表示可以在Postman类似的软件中查找单一数据
                    mixins.UpdateModelMixin,    # 表示可以在Postman类似的软件中更新单一数据
                    mixins.DestroyModelMixin,   # 表示可以在Postman类似的软件中删除单一数据
                    mixins.CreateModelMixin):   # 表示可以在Postman类似的软件中创建单一数据
    
      # 查询所有信息,固定写法
      queryset = Student.objects.all()
      # 序列化,固定写法
      serializer_class = StudentSerializer
    
  • 在app的urls.py文件中创建路由

    
    # 在urls.py中
    
    
    from stu import views
    from rest_framework.routers import SimpleRouter
    
    router = SimpleRouter() # 定义路由
    router.register(r'student', views.StudentEdit) # 注册路径,不要写‘/’ 
    
    urlpatterns = [
      # 其他url
    ]
    
    urlpatterns += router.urls          # 把路由url“r'student'”加入urlpatterns

自定义api

自定义数据结构

  • 在utils文件夹下创建RenderResponse.py文件,里面定义api数据结构

    from rest_framework.renderers import JSONRenderer
    
    
    class CustomJsonRenderer(JSONRenderer):
      def render(self, data, accepted_media_type=None, renderer_context=None):
          """
          结构
          {
              'code':xxx,
              'msg':请求成功,
              'data':{返回数据}
          }
          """
          if renderer_context:
    
              if isinstance(data, dict):
                  msg = data.pop('msg', '请求成功')
                  code = data.pop('code', 0)
              else:
                  msg = '请求成功'
                  code = 0
    
              response = renderer_context['response']
              response.status_code = 200
              res = {
                  'code': code,
                  'msg': msg,
                  'data': data
              }
              return super().render(res, accepted_media_type, renderer_context)
          else:
              return super().render(data, accepted_media_type, renderer_context)
  • 配置结构

    
    # 配置restful api返回结果
    
    
    REST_FRAMEWORK = {
      # 返回自定义结构
      'DEFAULT_RENDERER_CLASSES': (
          'utils.RenderResponse.CustomJsonRenderer',   # 自定义结构这个类的路径
      )
    }

为返回数据设置分页

  • 配置分页
# 在setting.py配置api返回结果时 加上分页 如下:


REST_FRAMEWORK = {
    # 分页
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': '2',                                          # 每页数据条数

    # 返回自定义结构
    'DEFAULT_RENDERER_CLASSES': (
        'utils.RenderResponse.CustomJsonRenderer',             # 自定义结构这个类的路径
    )
}

为api数据添加筛选方法

  • 配置筛选

    
    # 配置restful api返回结果时加上 筛选条件 如下:
    
    
    REST_FRAMEWORK = {
      # 分页
      'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
      'PAGE_SIZE': '2',
    
      # 配置筛选 
      # 这里djangorestframework-3.8.2会报错,建议用djangorestframework-3.4.6
      # 卸载:pip uninstall djangorestframework==3.8.2
      # 安装:pip install djangorestframework==3.4.6
    
      'DEFAULT_FILTER_BACKENDS':('rest_framework.filters.DjangoFilterBackend',
                                 'rest_framework.filters.SearchFilter'
                                  ),
    
      # 返回自定义结构
      'DEFAULT_RENDERER_CLASSES': (
          'utils.RenderResponse.CustomJsonRenderer',
      )
    }
    
  • 在app下创建filters.py文件,里面定义筛选字段及方法:

    from rest_framework import filters
    import django_filters
    
    from stu.models import Student
    
    
    class StuFilter(filters.FilterSet):
    
      name = django_filters.CharFilter('s_name', lookup_expr='icontains')
      tel = django_filters.CharFilter('s_tel')
      status = django_filters.CharFilter('s_status')
      operate_time_min = django_filters.DateTimeFilter('s_operate_time', lookup_expr='gte')
      operate_time_max = django_filters.DateTimeFilter('s_operate_time', lookup_expr='lt')
      yuwen_min = django_filters.NumberFilter('s_yuwem', lookup_expr='gte')
      yuwen_max = django_filters.NumberFilter('s_yuwem', lookup_expr='lt')
    
      class Meta:
          model=Student
          # 高版本django必须写fields = [],里面的参数最好写上,也可以不写
          # fields = ['s_name','s_tel','s_status','s_operate_time','s_yuwem']
          fields = []
    
    
    # 筛选时直接在api的url上命name、tel、status、operate_time_min等变量值,比较方法lookup_expr已经定义
    
  • 在views.py下序列化的类StudentEdit中加入过滤类StuFilter:

    from rest_framework import mixins,viewsets
    from stu.serializers import StudentSerializer
    from stu.models import Student
    from stu.filters import StuFilter
    
    class StudentEdit(mixins.ListModelMixin,      # 表示可以在Postman类似的软件中查找所有数据
                    viewsets.GenericViewSet,
                    mixins.RetrieveModelMixin,  # 表示可以在Postman类似的软件中查找数据
                    mixins.UpdateModelMixin,    # 表示可以在Postman类似的软件中更新数据
                    mixins.DestroyModelMixin,   # 表示可以在Postman类似的软件中删除数据
                    mixins.CreateModelMixin):   # 表示可以在Postman类似的软件中创建数据
    
      # 查询所有信息
      queryset = Student.objects.all()
      # 序列化
      serializer_class = StudentSerializer
      # 过滤
      filter_class = StuFilter
  • 实际应用:

    筛选时间在2018-03-012018-05-01且分数在80-90的学生
    http://127.0.0.1:8000/stu/student/?operate_time_max=2018-05-01&operate_time_min=2018-03-01&yuwen_max=90&yuwen_min=80
    
    筛选语文成绩不及格的学生
    http://127.0.0.1:8000/stu/student/?yuwen_max=60
    
    筛选状态是NEXT_SCH的学生
    http://127.0.0.1:8000/stu/student/?status=NEXT_SCH

给返回的数据排序

  • 在views.py下序列化的类StudentEdit中加入排序函数:

    from rest_framework import mixins,viewsets
    from stu.serializers import StudentSerializer
    from stu.models import Student
    from stu.filters import StuFilter
    
    
    class StudentEdit(mixins.ListModelMixin,      # 表示可以在Postman类似的软件中查找所有数据
                    viewsets.GenericViewSet,
                    mixins.RetrieveModelMixin,  # 表示可以在Postman类似的软件中查找数据
                    mixins.UpdateModelMixin,    # 表示可以在Postman类似的软件中更新数据
                    mixins.DestroyModelMixin,   # 表示可以在Postman类似的软件中删除数据
                    mixins.CreateModelMixin):   # 表示可以在Postman类似的软件中创建数据
    
      # 查询所有信息
      queryset = Student.objects.all()
      # 序列化
      serializer_class = StudentSerializer
      # 过滤
      filter_class = StuFilter
    
      # 对要显示的学生(前提是没被删除)进行排序
      def get_queryset(self):
          query = self.queryset
          return query.filter(s_delete=0).order_by('-id') 
          # 按照id倒序排序,filter(s_delete=0)表示学生还没有被删除,下面讲软删除

数据软删除

  • 在django默认delete请求数据时会直接把数据库中的数据删除(),这样删除数据是及其危险的操作,为了安全,我们把删除的数据的s_delete字段设置为1,没有删除的s_delete字段设置为0;这样我们就需要重写mixins.DestroyModelMixin下destroy方法,具体如下:

    from rest_framework import mixins,viewsets
    from stu.serializers import StudentSerializer
    from stu.models import Student
    from stu.filters import StuFilter
    
    
    class StudentEdit(mixins.ListModelMixin,      # 表示可以在Postman类似的软件中查找所有数据
                    viewsets.GenericViewSet,
                    mixins.RetrieveModelMixin,  # 表示可以在Postman类似的软件中查找数据
                    mixins.UpdateModelMixin,    # 表示可以在Postman类似的软件中更新数据
                    mixins.DestroyModelMixin,   # 表示可以在Postman类似的软件中删除数据
                    mixins.CreateModelMixin):   # 表示可以在Postman类似的软件中创建数据
    
      # 查询所有信息
      queryset = Student.objects.all()
      # 序列化
      serializer_class = StudentSerializer
      # 过滤
      filter_class = StuFilter
    
      # 对要显示的学生(前提是没被删除)进行排序
      def get_queryset(self):
          query = self.queryset
          return query.filter(s_delete=0).order_by('-id') 
          # 按照id倒序排序,filter(s_delete=0)表示学生还没有被删除,下面讲软删除
    
      # 重构delete请求方法,达到软删除目的
      def destroy(self, request, *args, **kwargs):
          instance = self.get_object()
          instance.s_delete = 1
          instance.save()
          return Response({'msg': '删除成功','code':200})

定义返回s_status字段对应的相关数据

  • 这里我们要将s_status字段中的信息返回给前端,但不是”NONE”,”DROP_SCK”……而是对应的”正常”,”退学”……

    这里写图片描述

    这里写图片描述

    
    # 在选择序列化字段时更改返回数据
    
    
    
    # stu/serializers.py中
    
    
    from rest_framework import serializers
    from stu.models import Student
    
    
    class StudentSerializer(serializers.ModelSerializer):
    
      class Meta:
          # 关联数据表
          model = Student
          # 确定需要序列化的字段,也就是返回数据中有的字段
          fields = ['id', 's_name', 's_tel', 's_status', 's_operate_time', 's_yuwem']
    
      def to_representation(self, instance):
    
          data = super().to_representation(instance) # instance会依次返回每个Student对象
    
          try:
              # 得到关联表中的字段,必须用try,否则如果存在一个没有定义i_addr的学生时,系统将报错
              data['s_addr'] = instance.studentinfo.i_addr
          except Exception as e:
              data['s_addr'] = ''
    
          # 把列表STATUS转换成字典后利用键值对取值
          data['s_status'] = dict(Student.STATUS)[data['s_status']]
    
          return data

结果预览(分页+筛选+排序+数据对应)

  • 每页两个数据,筛选语文分数在80-90分,按照id倒序,显示对应状态中文信息

这里写图片描述

自定义api数据错误信息

当在更新/添加数据时,出现输入数据与定义数据类型、长度、是否可以为空等冲突时,django会报英文错误,此处我们可以把英文替换为中文提示,返回给前端

  • 在定义序列化字段的时定义好相应字段错误信息(重写CharField里面的默认错误信息default_error_messages为error_messages后作为参数传入CharField)

    
    # stu/serializers.py
    
    
    
    # 说明:这里序列化的字段只有['id', 's_name', 's_tel', 's_addr']
    
    
    from rest_framework import serializers
    from stu.models import Student
    
    
    class StudentSerializer(serializers.ModelSerializer):
    
      # 定义s_name和s_tel错误返回信息
      s_name = serializers.CharField(error_messages = {
          'invalid': '数据类型错误',
          'blank': '用户名不能为空',
          'max_length': '用户名不能超过10个字符',
          'min_length': '用户名太短'
      }, max_length=10)
      s_tel = serializers.CharField(error_messages={
          'invalid': '数据类型错误',
          'blank': '用户名不能为空',
          'max_length': '用户名太长',
          'min_length': '用户名太短'
      })
    
      class Meta:
          # 关联数据表
          model = Student
          # 确定需要序列化的字段
          fields = ['id', 's_name', 's_tel']
    
      def to_representation(self, instance): # instance会依次返回每个Student对象
    
          data = super().to_representation(instance)
          try:
              # 得到关联表中的字段,必须用try,否则如果存在一个没有定义i_addr的学生时,系统将报错
              data['s_addr'] = instance.studentinfo.i_addr
          except Exception as e:
              data['s_addr'] = ''
    
          return data

利用ajax方法获取/更新/编辑/删除数据:

  • 基础页面

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>展示所有学生信息</title>
      <script type="text/javascript" src="/static/js/jquery.min.js"></script>
    </head>
    <body>
    {% csrf_token %}
    
    {# {% csrf_token %}会自动创建下面input标签 #}
    {# <input type="hidden" name="csrfmiddlewaretoken"\
            value="dRopPSt0IKs5OkaMHn83H3R05dsx2wGHKypCFTnsdvdtIQI85SnUDHMCaweReACZ"> #}
    {# 目的是为了得到 \
      "dRopPSt0IKs5OkaMHn83H3R05dsx2wGHKypCFTnsdvdtIQI85SnUDHMCaweReACZ" 给后端进行提交验证,\
      否则将禁止访问 #}
    
    <div id="div_stu"></div>
    <div id="div_update_stu"></div>
    <input id="showstu" type="button" value="获取学生信息">
    <input id="addstu" type="button" value="添加学生信息" onclick="add_stu()">
    </body>
    </html>
  • 利用get方法获取数据:get

    <script type="text/javascript" src="/static/js/jquery.min.js"></script>
    <script type='text/javascript'>
      $(function () {
          $('#showstu').click(function () {
              $.get('/stu/student/', function (msg) {
                  datas = msg.data;
                  s = '<table><tr><td>ID</td><td>姓名</td><td>地址</td><td>操作</td></tr>';
                  for(var i=0; i<msg.length; i++){
                       s += '<tr><td>' + datas[i].id + '</td>\
                      <td>' + datas[i].s_name + '</td>\
                      <td>' + datas[i].s_tel + '</td><td>\
                      <a href="javascript:;" onclick="update_stu(' + datas[i].id + ')">\
                      编辑</a>|\
                      <a href="javascript:;" onclick="del_stu(' + datas[i].id + ')">删除</a>\
                      </td></tr>'
                  }
                  s += '</table>';
                  $('#div_stu').html(s)
              }, 'json')
          })
      });
    </script>
  • 利用ajax删除数据:delete

    <script type="text/javascript" src="/static/js/jquery.min.js"></script>
    <script type='text/javascript'>
      function del_stu(i) {
          csrf = $('input[name="csrfmiddlewaretoken"]').val(); 
          // 取{% csrf_token %}的val()值,name为固定写法 
          $.ajax({
              url:'/stu/student/' + i + '/',
              type:'delete',
              headers:{'X-CSRFToken':csrf}, 
              //把上面val()放在头部传递给后端,否则会                                                   被'django.middleware.csrf.CsrfViewMiddleware',禁止 
              dataType:'json',
              success:function () {
                  alert('删除成功')
              },
              error:function () {
                  alert('删除失败')
              }
          })
      }
    </script>
  • 利用ajax更新数据:patch

    <script type="text/javascript" src="/static/js/jquery.min.js"></script>
    <script type='text/javascript'>        
      function update_stu(i) {
          s = '姓名<input type="text" id="s_name" name="name">\
            电话:<input type="text" id="s_tel" name="tel">\
            <input type="button" value="提交" onclick="update(' + i + ')">';
          $('#div_update_stu').html(s);
      }
      function update(i) {
          csrf = $('input[name="csrfmiddlewaretoken"]').val();
          s_name = $('#s_name').val();
          s_tel = $('#s_tel').val();
          $.ajax({
              url:'/stu/student/'+ i +'/',
              type:'patch',
              data:{'s_name':s_name, 's_tel':s_tel},
              dataType:'json',
              headers:{'X-CSRFToken':csrf},
              success:function (msg) {
                  alert('更新成功')
              },
              error:function (msg) {
                  alert('修改失败')
              }
          })
      }
    </script>
  • 利用ajax添加数据:post

    <script type="text/javascript" src="/static/js/jquery.min.js"></script>
    <script type='text/javascript'>          
      function add_stu(){
          s = '姓名<input type="text" id="name" name="name">\
                  电话:<input type="text" id="tel" name="tel">\
                  <input type="button" value="提交" onclick="add()">';
          $('#div_update_stu').html(s);
      }
      function add() {
          s_name = $('#name').val();
          s_tel = $('#tel').val();
          csrf = $('input[name="csrfmiddlewaretoken"]').val();
          $.ajax({
              url:'/stu/student/',
              type:'post',
              data:{'s_name':s_name, 's_tel':s_tel},
              dataType:'json',
              headers:{'X-CSRFToken':csrf},
              success:function (msg) {
                  alert('添加成功');
                  console.log(msg)
              },
              error:function (msg) {
                  alert('添加失败')
              }
          })
      }
    </script>

利用ajax和DOM获取数据

  • 数据显示时最好利用DOM操作创建标签,不用字符串形式,这样看起来比较清晰,比如下面方法

    <script type="text/javascript" src="/static/js/jquery.min.js"></script>
    <script type='text/javascript'>      
      $(function () {
          $('#getstu').on('click', function () {
              $.ajax({
                  url: '/stu/student/',
                  type:'get',
                  dataType:'json',
                  success: function (obj) {
                      var myarray = obj.data;
                      var ones = $('<tr>').append($('<td>').text('id')).
                                          append($('<td>').text('姓名')).
                                          append($('<td>').text('电话')).
                                          append($('<td>').text('地址'));
                      $('#stus').append(ones);
                      for (var i = 0; i < myarray.length; i += 1){
                          var stus = $('<tr>');
                          var one = $('<td>').text(myarray[i].id);
                          var two = $('<td>').text(myarray[i].s_name);
                          var three = $('<td>').text(myarray[i].s_tel);
                          var four = $('<td>').text(myarray[i].s_addr);
                          stus.append(one).append(two).append(three).append(four);
                          $('#stus').append(stus);
                      }
                  }
              })
          })
      })
    </script>

项目部署(ubuntu为例)

项目文件配置

  • 修改setting.py文件

    
    # SECURITY WARNING: don't run with debug turned on in production!
    
    DEBUG = False                     # 关闭debug模式,否则会在网页上显示错误信息
    ALLOWED_HOSTS = ['*']             # '*' 表示任何ip/域名都可以访问
    
    
    # 在STATIC_UTL下一行或上一行加上下面语句,为浏览器获取静态文件时定位文件地址
    
    STATIC_ROOT = os.path.join(BASE_DIR, 'static')
  • 修改项目urls.py文件

    from axfweb import settings
    from django.views.static import serve
    from django.contrib.staticfiles.urls import static
    
    
    # 在urlpatterns中加上下面三句话,分别定位静态文件/媒体文件/首页地址
    
    urlpatterns = [
      ………
      url(r'^static/(?P<path>.*)$', serve, {"document_root": settings.STATIC_ROOT}),
      url(r'^media/(?P<path>.*)$', serve, {"document_root": settings.MEDIA_ROOT}),
      url(r'^$', views.home)
    ]

服务器环境配置

  • 更新安装源

    
    # 进入ubuntu系统
    
    
    sudo apt update
    
    # 或者
    
    sudo apt-get update
  • 安装python及使用的库

    apt intall python3
    apt install python3-pip
    pip3 install django==1.11
    pip3 install pymysql
    pip3 install Pillow
  • 安装数据库及配置

    
    # 安装数据库MySQL,mysql默认端口为3306,确保没被占用
    
    apt install mysql-server mysql-client
    
    # 配置MySQL
    
    cd /etc/mysql/mysql.conf.d
    vim mysqld.cnf
    
    # 打开后注释掉bind_address ,为了数据库可以远程访问
    
    
    
    # 重启数据库并进入数据库
    
    use mysql
    
    # 为root用户配置密码,此处我的密码为123456,根据自己需要设置
    
    GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '123456' WITH GRANT OPTION;
    
    # 
    
    flush privileges; 
    
    
    ## 补充 
    
    netstat -lntp 查看进程,可以看到端口号
    
    ## 注意
    
    
    # 必须保证项目代码中连接的数据库信息与服务器mysql信息一致
    
  • 安装nginx服务

    
    # 安装nginx
    
    sudo apt-get install nginx
    
    
    # 查看nginx的状态
    
    
    systemctl status nginx # 查vi看nginx的状态
    systemctl start/stop/enable/disable nginx # 启动/关闭/设置开机启动/禁止开机启动
    #
    service nginx status/stop/restart/start
  • 安装python的uwsgi库

    pip install uwsgi

项目部署

准备数据,上传代码

利用navicat把本地数据传输到远程数据库

这里写图片描述

利用xshell的xftp把本地代码上传到服务器

此处我的代码上传到/home/cfx/app/下

这里写图片描述

方法1(不推荐)

cd /home/cfx/app/axfweb/axfweb      # 此处我的项目文件夹为axfweb,工程目录也为axfweb
python3 manage.py runserver 0.0.0.0:8000  # 0.0.0.0表示任何ip都可以访问
# 项目启动后就可以用服务器域名或ip访问

方法2(推荐)

# 利用nginx+uwsgi部署

# 配置
# 在app目录下创建conf和log文件夹,分别用来存放配置文件和日志
# 在conf文件夹下创建nginx.conf来配置nginx,写入下面代码
server{
        listen   80;                            # 监听80端口,外网80端口访问此服务
        server_name 127.0.0.1 localhost;        # 127.0.0.1可以更换成自己的域名
        access_log /home/cfx/app/log/access.log;# 日志
        error_log /home/cfx/app/log/error.log;  # 日志(目录为自己设置的)

        location / {                            # 所有地址访问地址
                include uwsgi_params;
                uwsgi_pass 127.0.0.1:8890;      # 服务启动在本地8890端口,外网用80端口访问
        }

        location /static/ {                    # 静态文件访问地址
                alias /home/cfx/app/axfweb/static/;  # 静态文件地址
                expires 30d;
        }

        location /favicon.ico {                # 禁用商标加载失败时日志写入,因为本项目没有商标
                log_not_found off;
                access_log off;
        }

        location ~ /\. {
                deny all;
                access_log off;
                log_not_found off;
        }
}


# 在conf文件夹下创建uwsgi.ini来配置uwsgi,写入下面代码
[uwsgi]                             # 必须写

master = true

processes = 4                       # 启动4个进程

pythonpath = /home/cfx/app/axfweb   # 配置项目路径

module = axfweb.wsgi                # 定义wsgi在项目下的位置

socket = 127.0.0.1:8890             # 服务启动在本地端口8890,必须和nginx中配置的一样

logto = /home/cfx/app/log/uwsgi.log # uwsgi日志存放路径

## 启动方法:
cd /home/cfx/app/conf
uwsgi --ini uwsgi.ini #执行后,服务器就启动起来了


## 注意:
# 部署方法2存在权限问题,如果项目文件为root权限才能操作,那么要求nignx要有root权限,否则在执行中nginx需要写文件,执行文件时将受到阻止:最好把项目文件归属于普通用户组

猜你喜欢

转载自blog.csdn.net/gold_time_/article/details/80314269