zoukankan      html  css  js  c++  java
  • Django【进阶篇】

    目录

    一、Model

    二、admin

    三、Form组件

    四、Cookie

    五、Session

    六、分页

    七、序列化

    一、Model

    数据库的配置

    1、django默认支持sqlite,mysql, oracle,postgresql数据库。

         <1> sqlite

                django默认使用sqlite的数据库,默认自带sqlite的数据库驱动 , 引擎名称:django.db.backends.sqlite3

         <2> mysql

                引擎名称:django.db.backends.mysql

    2、mysql驱动程序

    •    MySQLdb(mysql python2中用)
    •    mysqlclient
    •    MySQL
    •    PyMySQL(纯python的mysql驱动程序,python3用)

    3、在django的项目中会默认使用sqlite数据库,在settings里有如下设置:

    创建数据库表的步骤:

    1、创建model;

    2、创建生成数据库的py文件:python manage.py makemigrations ;

    3、创建数据库表:python manage.py migrate;

    注意:记得在settings里的INSTALLED_APPS中加入'app01',然后再同步数据库。

    打开pycharm右侧的databases,把创建好的db.sqlite3数据库拖过去,就可以操作数据库表了,这里我们不用操心数据库名的问题;

    如果我们想要使用别的数据库,比如Mysql需要修改如下:

    DATABASES = {
    
        'default': {
    
            'ENGINE': 'django.db.backends.mysql', 
    
            'NAME': 'books',    #你的数据库名称
    
            'USER': 'root',   #你的数据库用户名
    
            'PASSWORD': '', #你的数据库密码
    
            'HOST': '', #你的数据库主机,留空默认为localhost
    
            'PORT': '3306', #你的数据库端口
    
        }
    
    }
    View Code

    注意:

    NAME即数据库的名字,在mysql连接前该数据库必须已经创建,而上面的sqlite数据库下的db.sqlite3则是项目自动创建
    
    USER和PASSWORD分别是数据库的用户名和密码。
    
    设置完后,再启动我们的Django项目前,我们需要激活我们的mysql。
    
    然后,启动项目,会报错:no module named MySQLdb
    
    这是因为django默认你导入的驱动是MySQLdb,可是MySQLdb对于py3有很大问题,所以我们需要的驱动是PyMySQL
    
    所以,我们只需要找到项目名文件下的__init__,在里面写入:
    
    import pymysql
    pymysql.install_as_MySQLdb()
    
    问题解决!
    View Code

    4、ORM(对象关系映射)

    关系对象映射(Object Relational Mapping,简称ORM),用于实现面向对象编程语言里不同类型系统的数据之间的转换,换言之,就是用面向对象的方式去操作数据库的创建表以及增删改查等操作。

    优点: 1、ORM使得我们的通用数据库交互变得简单易行,而且完全不用考虑该死的SQL语句。快速开发,由此而来。

                2、可以避免一些新手程序猿写sql语句带来的性能问题。

     缺点:1、性能有所牺牲,不过现在的各种ORM框架都在尝试各种方法,比如缓存,延迟加载登来减轻这个问题。效果很显著。

                2、对于个别复杂查询,ORM仍然力不从心,为了解决这个问题,ORM一般也支持写raw sql。

                3、通过QuerySet的query属性查询对应操作的sql语句

    author_obj=models.Author.objects.filter(id=2)
    print(author_obj.query)
    

    ORM语法

    1、创建表

    • 基本结构
    from django.db import models
    class Userinfo(models.Model):
        name = models.CharField(max_length=30)
        email = models.EmailField()
        memo = models.TextField()
    • 模型常用的字段类型参数
    <1> CharField
            #字符串字段, 用于较短的字符串.
            #CharField 要求必须有一个参数 maxlength, 用于从数据库层和Django校验层限制该字段所允许的最大字符数.
    
    <2> IntegerField
           #用于保存一个整数.
    
    <3> FloatField
            # 一个浮点数. 必须 提供两个参数:
            #
            # 参数    描述
            # max_digits    总位数(不包括小数点和符号)
            # decimal_places    小数位数
                    # 举例来说, 要保存最大值为 999 (小数点后保存2位),你要这样定义字段:
                    #
                    # models.FloatField(..., max_digits=5, decimal_places=2)
                    # 要保存最大值一百万(小数点后保存10位)的话,你要这样定义:
                    #
                    # models.FloatField(..., max_digits=19, decimal_places=10)
                    # admin 用一个文本框(<input type="text">)表示该字段保存的数据.
    
    <4> AutoField
            # 一个 IntegerField, 添加记录时它会自动增长. 你通常不需要直接使用这个字段; 
            # 自定义一个主键:my_id=models.AutoField(primary_key=True)
            # 如果你不指定主键的话,系统会自动添加一个主键字段到你的 model.
    
    <5> BooleanField
            # A true/false field. admin 用 checkbox 来表示此类字段.
    
    <6> TextField
            # 一个容量很大的文本字段.
            # admin 用一个 <textarea> (文本区域)表示该字段数据.(一个多行编辑框).
    
    <7> EmailField
            # 一个带有检查Email合法性的 CharField,不接受 maxlength 参数.
    
    <8> DateField
            # 一个日期字段. 共有下列额外的可选参数:
            # Argument    描述
            # auto_now    当对象被保存时,自动将该字段的值设置为当前时间.通常用于表示 "last-modified" 时间戳.
            # auto_now_add    当对象首次被创建时,自动将该字段的值设置为当前时间.通常用于表示对象创建时间.
            #(仅仅在admin中有意义...)
    
    <9> DateTimeField
            #  一个日期时间字段. 类似 DateField 支持同样的附加选项.
    
    <10> ImageField
            # 类似 FileField, 不过要校验上传对象是否是一个合法图片.#它有两个可选参数:height_field和width_field,
            # 如果提供这两个参数,则图片将按提供的高度和宽度规格保存.     
    <11> FileField
         # 一个文件上传字段.
         #要求一个必须有的参数: upload_to, 一个用于保存上载文件的本地文件系统路径. 这个路径必须包含 strftime #formatting, 
         #该格式将被上载文件的 date/time 
         #替换(so that uploaded files don't fill up the given directory).
         # admin 用一个<input type="file">部件表示该字段保存的数据(一个文件上传部件) .
    
         #注意:在一个 model 中使用 FileField 或 ImageField 需要以下步骤:
                #(1)在你的 settings 文件中, 定义一个完整路径给 MEDIA_ROOT 以便让 Django在此处保存上传文件. 
                # (出于性能考虑,这些文件并不保存到数据库.) 定义MEDIA_URL 作为该目录的公共 URL. 要确保该目录对 
                #  WEB服务器用户帐号是可写的.
                #(2) 在你的 model 中添加 FileField 或 ImageField, 并确保定义了 upload_to 选项,以告诉 Django
                # 使用 MEDIA_ROOT 的哪个子目录保存上传文件.你的数据库中要保存的只是文件的路径(相对于 MEDIA_ROOT). 
                # 出于习惯你一定很想使用 Django 提供的 get_<#fieldname>_url 函数.举例来说,如果你的 ImageField 
                # 叫作 mug_shot, 你就可以在模板中以 {{ object.#get_mug_shot_url }} 这样的方式得到图像的绝对路径.
    
    <12> URLField
          # 用于保存 URL. 若 verify_exists 参数为 True (默认), 给定的 URL 会预先检查是否存在( 即URL是否被有效装入且
          # 没有返回404响应).
          # admin 用一个 <input type="text"> 文本框表示该字段保存的数据(一个单行编辑框)
    
    <13> NullBooleanField
           # 类似 BooleanField, 不过允许 NULL 作为其中一个选项. 推荐使用这个字段而不要用 BooleanField 加 null=True 选项
           # admin 用一个选择框 <select> (三个可选择的值: "Unknown", "Yes" 和 "No" ) 来表示这种字段数据.
    
    <14> SlugField
           # "Slug" 是一个报纸术语. slug 是某个东西的小小标记(短签), 只包含字母,数字,下划线和连字符.#它们通常用于URLs
           # 若你使用 Django 开发版本,你可以指定 maxlength. 若 maxlength 未指定, Django 会使用默认长度: 50.  #在
           # 以前的 Django 版本,没有任何办法改变50 这个长度.
           # 这暗示了 db_index=True.
           # 它接受一个额外的参数: prepopulate_from, which is a list of fields from which to auto-#populate 
           # the slug, via JavaScript,in the object's admin form: models.SlugField
           # (prepopulate_from=("pre_name", "name"))prepopulate_from 不接受 DateTimeFields.
    
    <13> XMLField
            #一个校验值是否为合法XML的 TextField,必须提供参数: schema_path, 它是一个用来校验文本的 RelaxNG schema #的文件系统路径.
    
    <14> FilePathField
            # 可选项目为某个特定目录下的文件名. 支持三个特殊的参数, 其中第一个是必须提供的.
            # 参数    描述
            # path    必需参数. 一个目录的绝对文件系统路径. FilePathField 据此得到可选项目. 
            # Example: "/home/images".
            # match    可选参数. 一个正则表达式, 作为一个字符串, FilePathField 将使用它过滤文件名.  
            # 注意这个正则表达式只会应用到 base filename 而不是
            # 路径全名. Example: "foo.*.txt^", 将匹配文件 foo23.txt 却不匹配 bar.txt 或 foo23.gif.
            # recursive可选参数.要么 True 要么 False. 默认值是 False. 是否包括 path 下面的全部子目录.
            # 这三个参数可以同时使用.
            # match 仅应用于 base filename, 而不是路径全名. 那么,这个例子:
            # FilePathField(path="/home/images", match="foo.*", recursive=True)
            # ...会匹配 /home/images/foo.gif 而不匹配 /home/images/foo/bar.gif
    
    <15> IPAddressField
            # 一个字符串形式的 IP 地址, (i.e. "24.124.1.30").
    <16># CommaSeparatedIntegerField
            # 用于存放逗号分隔的整数值. 类似 CharField, 必须要有maxlength参数.
    View Code
    • Field重要参数
        <1> null : 数据库中字段是否可以为空
    
        <2> blank: django的 Admin 中添加数据时是否可允许空值
    
        <3> default:设定缺省值
    
        <4> editable:如果为假,admin模式下将不能改写。缺省为真
    
        <5> primary_key:设置主键,如果没有设置django创建表时会自动加上:
            id = meta.AutoField('ID', primary_key=True)
            primary_key=True implies blank=False, null=False and unique=True. Only one
            primary key is allowed on an object.
    
        <6> unique:数据唯一
    
        <7> verbose_name  Admin中字段的显示名称
    
        <8> validator_list:有效性检查。非有效产生 django.core.validators.ValidationError 错误
    
        <9> db_column,db_index 如果为真将为此字段创建索引
    
        <10>choices:一个用来选择值的2维元组。第一个值是实际存储的值,第二个用来方便进行选择。
                    如SEX_CHOICES= (( ‘F’,'Female’),(‘M’,'Male’),)
                    gender = models.CharField(max_length=2,choices = SEX_CHOICES)

    2、操作表

    a、基本操作

    ---------------------增(create  ,  save) ---------------------
    
    from app01.models import *
    
        #create方式一:   Author.objects.create(name='Alvin')
    
        #create方式二:   Author.objects.create(**{"name":"alex"})
    
        #save方式一:     author=Author(name="alvin")
                        author.save()
    
        #save方式二:     author=Author()
                        author.name="alvin"
                        author.save()
    
    ---------------------删(delete) -------------------------
    
    Book.objects.filter(id=1).delete()
    
    ---------------------改(update和save)------------------
    
    # 方法一,get只能得到一个对象
    book = Book.objects.get(author='charlie')
    book.price = 200
    book.save()
    # 方法二,推荐使用
    Book.objects.filter(name='python').update(price=100)
    注意:
    
    <1> 第二种方式修改不能用get的原因是:update是QuerySet对象的方法,
    get返回的是一个model对象,它没有update方法,而filter返回的是一个QuerySet对象
    (filter里面的条件可能有多个条件符合,比如name='alvin',可能有两个name='alvin'的行数据)。
    
    <2>在“插入和更新数据”小节中,我们有提到模型的save()方法,这个方法会
    更新一行里的所有列。 而某些情况下,我们只需要更新行里的某几列。
    
    ---------------------查(filter,value等) ----------------------
    
    #  <1>filter(**kwargs):      它包含了与所给筛选条件相匹配的对象
    
    #  <2>all():                 查询所有结果
    
    #  <3>get(**kwargs):         
    '''返回与所给筛选条件相匹配的对象,返回结果有且只有一个,如果符合筛选条件的
    #对象超过一个或者没有都会抛出错误。'''
    
    #下面的方法都是对查询的结果再进行处理:比如 objects.filter.values()--------
    
    #  <4>values(*field):        
    '''返回一个ValueQuerySet——一个特殊的QuerySet,
    运行后得到的并不是一系列 model的实例化对象,而是一个可迭代的字典序列'''
                                         
    #  <5>exclude(**kwargs):     它包含了与所给筛选条件不匹配的对象
    
    #  <6>order_by(*field):      对查询结果排序
    
    #  <7>reverse():             对查询结果反向排序
    
    #  <8>distinct():            从返回结果中剔除重复纪录,all()后面跟去重没用,因为ID没有重复,只有在values()后面跟去重才有用,对某一个字段进行去重;
    
    #  <9>values_list(*field):   它与values()非常相似,它返回的是一个元组序列,values返回的是一个字典序列
    
    #  <10>count():              返回数据库中匹配查询(QuerySet)的对象数量。
    
    # <11>first():               返回第一条记录
    
    # <12>last():                返回最后一条记录
    
    #  <13>exists():             如果QuerySet包含数据,就返回True,否则返回False。

    实例:

    • models文件内容

    from django.db import models
    
    class Book(models.Model):
        name = models.CharField(max_length=20)
        price = models.IntegerField()
        pub_data = models.DateField()
        author = models.CharField(max_length=20,null=False)
        def __str__(self):
            return self.name
    
    class Author(models.Model):
        name = models.CharField(max_length=20)
        age = models.IntegerField()
    • view文件内容

    #view.py
    def addbook(request):
        # 添加表记录方法一
        book = Book(name='python',price=99,pub_data='2018-2-8',author='charlie')
        book.save()
        # 方法二
        Book.objects.create(name='php',price=88,pub_data='2018-10-8',author='oldboy')
        # Book.objects.create(**dic)添加字典内容
        return HttpResponse('添加成功')
    
    def update(request):
        # 方法一,get用于只得到一个对象,如果对象重复就报错
        # 这种方法会把所有的字段都重新赋值,效率低
        book = Book.objects.get(id=1)
        book.price = 998
        book.save()
        # 方法二,推荐使用,update是Queryset方法,只更新查询到的记录
        Book.objects.filter(name='python').update(price=150)
        return HttpResponse('修改成功')
    
    def select(request):
        book_list = Book.objects.all()
        book_list = Book.objects.all()[:3]#前三个
        book_list = Book.objects.all()[::2]#每两个取一个
        book_list = Book.objects.all()[::-1]#倒着取
        # value只取指定的字段,得到一个查询集,内容是一个字典
        ret = Book.objects.filter(name='charlie').values('name','price')
        # 得到一个查询集,内容是一个列表,列表元素是元组
        ret = Book.objects.filter(name='charlie').values_list('name','price')
        return render(request,'index.html',locals())
    • 提示:对于每次创建一个对象,想显示对应的raw sql,需要在settings加上日志记录部分:
    LOGGING = {
        'version': 1,
        'disable_existing_loggers': False,
        'handlers': {
            'console':{
                'level':'DEBUG',
                'class':'logging.StreamHandler',
            },
        },
        'loggers': {
            'django.db.backends': {
                'handlers': ['console'],
                'propagate': True,
                'level':'DEBUG',
            },
        }
    }

    b、利用双下划线方法进行模糊匹配

    # 获取个数
    models.Tb1.objects.filter(name='seven').count()
    
    # 大于,小于
    #
    models.Tb1.objects.filter(id__gt=1)              # 获取id大于1的值
    models.Tb1.objects.filter(id__gte=1)              # 获取id大于等于1的值
    models.Tb1.objects.filter(id__lt=10)             # 获取id小于10的值
    models.Tb1.objects.filter(id__lte=10)             # 获取id小于10的值
    models.Tb1.objects.filter(id__lt=10, id__gt=1)   # 获取id大于1 且 小于10的值
    
    # in
    #
    models.Tb1.objects.filter(id__in=[11, 22, 33])   # 获取id等于11、22、33的数据
    models.Tb1.objects.exclude(id__in=[11, 22, 33])  # not in
    
    # isnull
    Entry.objects.filter(pub_date__isnull=True)        #某字段是否可以为空
    
    # contains
    #
    models.Tb1.objects.filter(name__contains="ven") #内容里包含某字符串
    models.Tb1.objects.filter(name__icontains="ven") # icontains大小写不敏感
    models.Tb1.objects.exclude(name__icontains="ven") #内容里没有某字段(不区分大小写)
    
    # range
    #
    models.Tb1.objects.filter(id__range=[1, 2])   # 范围bettwen and
    
    # 其他类似
    #
    startswith,istartswith(不区分大小写), endswith, iendswith,
    
    # order by
    #
    models.Tb1.objects.filter(name='seven').order_by('id')    # asc
    models.Tb1.objects.filter(name='seven').order_by('-id')   # desc,反向
    
    # group by
    #
    from django.db.models import Count, Min, Max, Sum
    models.Tb1.objects.filter(c1=1).values('id').annotate(c=Count('num'))
    SELECT "app01_tb1"."id", COUNT("app01_tb1"."num") AS "c" FROM "app01_tb1" WHERE "app01_tb1"."c1" = 1 GROUP BY "app01_tb1"."id"
    
    # limit 、offset
    #
    models.Tb1.objects.all()[10:20]
    
    # regex正则匹配,iregex 不区分大小写
    #
    Entry.objects.get(title__regex=r'^(An?|The) +')
    Entry.objects.get(title__iregex=r'^(an?|the) +')
    
    # date
    #
    Entry.objects.filter(pub_date__date=datetime.date(2005, 1, 1))
    Entry.objects.filter(pub_date__date__gt=datetime.date(2005, 1, 1))
    
    # year
    #
    Entry.objects.filter(pub_date__year=2005)
    Entry.objects.filter(pub_date__year__gte=2005)
    
    # month
    #
    Entry.objects.filter(pub_date__month=12)
    Entry.objects.filter(pub_date__month__gte=6)
    
    # day
    #
    Entry.objects.filter(pub_date__day=3)
    Entry.objects.filter(pub_date__day__gte=3)
    
    # week_day
    #
    Entry.objects.filter(pub_date__week_day=2)
    Entry.objects.filter(pub_date__week_day__gte=2)
    
    # hour
    #
    Event.objects.filter(timestamp__hour=23)
    Event.objects.filter(time__hour=5)
    Event.objects.filter(timestamp__hour__gte=12)
    
    # minute
    #
    Event.objects.filter(timestamp__minute=29)
    Event.objects.filter(time__minute=46)
    Event.objects.filter(timestamp__minute__gte=29)
    
    # second
    #
    Event.objects.filter(timestamp__second=31)
    Event.objects.filter(time__second=2)
    Event.objects.filter(timestamp__second__gte=31)

    c、连表操作(了不起的双下划线)

    • 外键参数设置
    #Django升级到2版本之后models.ForeignKey()需要填写on_delect参数
    on_delete=models.CASCADE,     # 删除关联数据,与之关联也删除
    on_delete=models.DO_NOTHING,  # 删除关联数据,什么也不做,最好不要;
    on_delete=models.PROTECT,     # 删除关联数据,引发错误ProtectedError
    on_delete=models.SET_DEFAULT #设置默认值,前提是ForeignKey必须设置的默认值;
    on_delete=models.SET(...)   
    '''设置给定值,或者如果传入了callable,则调用它的结果。
    在大多数情况下,为了避免在导入models.py时执行查询,必须传递callable'''
    on_delete=models.SET_NULL,    # 删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空,一对一同理)
    # 例如:models.ForeignKey('关联表', on_delete=models.SET_NULL, blank=True, null=True)
    • 一对多操作
    • models.py
    #models.py
    from django.db import models
    
    class Book(models.Model):
        '''书籍'''
        name = models.CharField(max_length=20)
        price = models.IntegerField()
        pub_data = models.DateField()
        publish = models.ForeignKey('Publish',on_delete=models.CASCADE)
    
        def __str__(self):
            return self.name
    
    class Author(models.Model):
        '''作者'''
        name = models.CharField(max_length=20)
    
    class Publish(models.Model):
        '''出版社'''
        #一对多的外键要加在多的哪一张表里,一本书只能有一个出版社
        name = models.CharField(max_length=30)
        city = models.CharField(max_length=30)
    • view.py
    from django.shortcuts import render,HttpResponse
    from app.models import *
    
    #-----------------------增-----------------------
    
        # 方式一,给publish_id赋值,django默认给你的外键字段添加_id
        Book.objects.create(name='linux运维',price=88,pub_data='2016-10-8',publish_id=1)
        # 方式二,给publish字段直接赋值,需要先得到一个对象
        pulish_obj = Publish.objects.filter(name='西湖出版社')[0]
        Book.objects.create(name='Java', price=188, pub_data='2016-1-23', publish=pulish_obj)
    
    #-----------------------删-----------------------
    
        Book.objects.filter(publish__name='西湖出版社').delete()
    
    #-----------------------改-----------------------
    
        Book.objects.filter(publish__name='人民出版社').update(price=300)
    
    #-----------------------查-----------------------
    
        #第一种方式:通过对象(不推荐使用)
        # 正向
        book_obj = Book.objects.get(name='python')
        publish_obj = book_obj.publish
        publish_name = publish_obj.name
        #反向
        pub_obj = Publish.objects.filter(name='西湖出版社')[0]
        #下面的到一个查询集,内容是字典,取第一个字典
        pub_name = pub_obj.book_set.all().values('name','price')[0]
    
        #第二种方式:通过filter(__),推荐使用
        #正向查询,指定出版社的书籍信息;从有外键的表查询为正向;
        book_name = Book.objects.filter(publish__name='西湖出版社').values('name','price')[0]
        # 反向查询,指定书籍的出版社信息,通过  类名__字段名
        pub_name = Publish.objects.filter(book__name='python').values('name','city')[0]
        #通过value(__)
        book_name = Book.objects.filter(name='Java').values('publish__name','publish__city')[0]
        # 查询所有在北京的出版社出版的书籍,得到一个列表
        book_list = Publish.objects.filter(city='北京').values('book__name')
        book_list2 = Book.objects.filter(publish__city='北京').values('name')
    • 多对多操作、聚合查询aggregate( )、分组查询annotate()
    from django.db import models
    
    class Book(models.Model):
        '''书籍'''
        name = models.CharField(max_length=20)
        price = models.IntegerField()
        pub_data = models.DateField()
        # 一对多,一本书只能有一个出版社
        publish = models.ForeignKey('Publish',on_delete=models.CASCADE)
        # 多对多,一本书可以有多个作者,系统会自动创建第三张表:app_book_authors
        #但是不能直接对这张表进行操作,因为没有它的类
        authors = models.ManyToManyField('Author')
        def __str__(self):
            return self.name
    
    class Publish(models.Model):
        '''出版社'''
        #一对多的外键要加在多的哪一张表里,一本书只能有一个出版社
        name = models.CharField(max_length=30)
        city = models.CharField(max_length=30)
        def __str__(self):
            return self.name
    
    class Author(models.Model):
        '''作者'''
        name = models.CharField(max_length=20)
        age = models.IntegerField(default=20)
        def __str__(self):
            return self.name
    models
    from django.shortcuts import render,HttpResponse
    from app.models import *
    from django.db.models import Avg,Min,Max,Count,Sum
    
    #------------------增--------------------
    
        #方式一
        book_obj = Book.objects.get(id=4)
        book_obj.authors.add(1)
        book_obj.authors.add(*[2,3,])
        #方式二
        book_obj = Book.objects.get(id=4)
        author_objs = Author.objects.all()
        book_obj.authors.add(*author_objs)
    
    #------------------删--------------------
    
        # 先得到一个书籍对象
        book_obj = Book.objects.get(id=4)
        #删除
        book_obj.authors.remove(3)
        book_obj.authors.remove(*[1,2,])
        #清空
        book_obj.authors.clear()
    
    #------------------改-------------------- 
      
        #重置,以设置的为准,这里列表不加*,已经有的不动,新内容里没有的就删除
        book_obj.authors.set([1,2,3])
    
    #------------------查-------------------- 
    
        #正向,找到id=2的书籍的所有作者
        book_obj = Book.objects.get(id=2)
        book_obj.authors.all()
        #方式二
        Book.objects.filter(id=2).values('authors__name')
        # 反向,找到作者的所有书籍
        author_obj = Author.objects.get(id=1)
        author_obj.book_set.all()
        #所有书籍名称和对应的作者名
        book_list = Book.objects.all().values('name','authors__name')
        #列举作者charlie的所有书籍
        Book.objects.filter(authors__name="charlie")
        
    #-----------------聚合函数aggregate()------------------
    
        #求所有书籍的平均价格
        ret = Book.objects.all().aggregate(Avg('price'))#{'price__avg': 122.0}
        # 也可以自定义名称,结果:{'avg_price': 122.0}
        ret = Book.objects.all().aggregate(avg_price=Avg('price'))
        #作者Charlie出的所有书,Count参数可以是book表里任意个字段,只是查有几条记录,查谁都一样
        ret = Book.objects.filter(authors__name='charlie').aggregate(Count('name'))
    
    #-----------------分组函数annotate---------------------
    
        #按作者名分组,求每个作者所有书籍的价格总和
        ret = Book.objects.values('authors__name').annotate(Sum('price'))
        '''
        <QuerySet [{'authors__name': 'charlie', 'price__sum': 366}, 
        {'authors__name': 'alex', 'price__sum': 88}, 
        {'authors__name': 'james', 'price__sum': 188}]>
        '''
        #求每个出版社的最低价格的书
        ret = Publish.objects.values('name').annotate(Min('book__price'))
    • 对于多对多的操作,我们也可以手动创建第三张表,但是这样查询起来更加麻烦,所以不推荐;
    #去掉authors = models.ManyToManyField('Author')
    #创建新类
    class Book_Author(models.Model):
        #第三张表
        book = models.ForeignKey('Book',on_delete=models.CASCADE)
        author = models.ForeignKey('Author',on_delete=models.CASCADE)
        def __str__(self):
            return self.name
    
    #添加
    Book_Author.objects.create(book_id=3,author_id=1)
    # 利用对象查询
    book_obj = Book.objects.get(id=2)
    author_name = book_obj.book_author_set.all()[0].author
    print(author_name)
    #双下划线查询
    book_list1 = Author.objects.filter(name='charlie').values('book_author__book')
    print(book_list1)
    book_list2 = Book.objects.filter(book_author__author__name='charlie').values('name')
    print(book_list2)
    • F查询和Q查询
    from django.db.models import F,Q
    
    #F 使用查询条件的值,专门取对象中某列值的操作
    #给所有的书价格加10
    Book.objects.all().update(price=F('price') + 10)
    
    # Q 进行条件或的查询,| 或,满足任意条件
    ret = Book.objects.filter(Q(price=110)|Q(name='GO'))
    #~ 非,不满足条件
    ret = Book.objects.filter(~Q(name='GO'))
    #书籍名称中包含某个字母
    ret = Book.objects.filter(Q(name__contains='J'))
    #Q查询和关键字查询结合使用,Q查询一定要放前面
    ret = Book.objects.filter(Q(price=110),name='GO')
    print(ret)
    #Q(name__startswith='P')  书籍名称以字母P开头

    3、补充整理

    • 跨多张表查询,可以连续使用双下划线

    • ForeignKey()和ManyToManyField()都可以通过related_name参数修改没有外键的那个表里隐藏的字段
        class Book(models.Model):
            '''书籍'''
            name = models.CharField(max_length=20)
            price = models.IntegerField()
            pub_data = models.DateField()
            authors = models.ForeignKey('Author',on_delete=models.CASCADE)
        class Author(models.Model):
            '''作者'''
            name = models.CharField(max_length=20)
            age = models.IntegerField(default=20)
            country = models.ForeignKey('Country',on_delete=models.CASCADE)
        class Country(models.Model):
            '''国家'''
            name = models.CharField(max_length=20)
        #所有中国籍作者出的所有书籍
        Book.objects.filter(authors__country__name='China')
        #正向查找:Book.objects.filter(authors__name='charlie')
        #反向查找:
            obj = Authors.objects.filter(name='charlie').first()
            obj.book_set.all()
        #没有外键的表里其实隐藏了一个字段:类名_set,也可以修改这个字段名
        class Book(models.Model):
            authors = models.ForeignKey('Author',on_delete=models.CASCADE,related_name='book')
        obj = Authors.objects.filter(name='charlie').first()
        book_list = obj.book.all()#作者对应的所有书籍对象查询集[obj(name,price,..),obj(name,price,..),]

    4、惰性机制

    所谓惰性机制:Publisher.objects.all()或者.filter()等都只是返回了一个QuerySet(查询结果集对象),它并不会马上执行sql,而是当调用QuerySet的时候才执行。

    QuerySet特点:<1>  可迭代的;<2>  可切片

    QuerySet的高效使用:

    <1>Django的queryset是惰性的
       Django的queryset对应于数据库的若干记录(row),通过可选的查询来过滤。例如,下面的代码会得
       到数据库中名字为‘Dave’的所有的人:person_set = Person.objects.filter(first_name="Dave")
       上面的代码并没有运行任何的数据库查询。你可以使用person_set,给它加上一些过滤条件,或者将它传给某个函数,
       这些操作都不会发送给数据库。这是对的,因为数据库查询是显著影响web应用性能的因素之一。
    
    <2>要真正从数据库获得数据,你可以遍历queryset或者使用if queryset,总之你用到数据时就会执行sql.
       为了验证这些,需要在settings里加入 LOGGING(验证方式)
            obj=models.Book.objects.filter(id=3)
            for i in obj:
                print(i)
    
            if obj:
               print("ok")
    
    <3>queryset是具有cache的
       当你遍历queryset时,所有匹配的记录会从数据库获取,然后转换成Django的model。这被称为执行
      (evaluation).这些model会保存在queryset内置的cache中,这样如果你再次遍历这个queryset,
       你不需要重复运行通用的查询。但是如果你修改了数据库,就需要再查询一次,否则你查到的还是上次的
      缓存数据。 obj=models.Book.objects.filter(id=3) for i in obj: print(i) models.Book.objects.filter(id=3).update(title="GO") obj_new=models.Book.objects.filter(id=3) for i in obj: print(i) #LOGGING只会打印一次 <4>简单的使用if语句进行判断也会完全执行整个queryset并且把数据放入cache,虽然你并不需要这些 数据!为了避免这个,可以用exists()方法来检查是否有数据: obj = Book.objects.filter(id=4) # exists()的检查可以避免数据放入queryset的cache。 if obj.exists(): print("hello world!") <5>当queryset非常巨大时,cache会成为问题 处理成千上万的记录时,将它们一次装入内存是很浪费的。更糟糕的是,巨大的queryset可能会锁住系统 进程,让你的程序濒临崩溃。要避免在遍历数据的同时产生queryset cache,可以使用iterator()方法 来获取数据,处理完数据就将其丢弃。 objs = Book.objects.all().iterator() # iterator()可以一次只从数据库获取少量数据,这样可以节省内存 for obj in objs: print(obj.name) #BUT,再次遍历没有打印,因为迭代器已经在上一次遍历(next)到最后一次了,没得遍历了 for obj in objs: print(obj.name) 当然,使用iterator()方法来防止生成cache,意味着遍历同一个queryset时会重复执行查询。所以使 用iterator()的时候要当心,确保你的代码在操作一个大的queryset时没有重复执行查询。 总结: queryset的cache是用于减少程序对数据库的查询,在通常的使用下会保证只有在需要的时候才会查询数据库。 使用exists()和iterator()方法可以优化程序对内存的使用。不过,由于它们并不会生成queryset cache,可能 会造成额外的数据库查询。总之一句话,如果查询数据量巨大时,使用迭代器;如果数据量很小,而且又需要重复查询
    时,使用查询集。

    二、admin

    admin是django强大功能之一,它能从数据库中读取数据,呈现在页面中,进行管理。默认情况下,它的功能已经非常强大,如果你不需要复杂的功能,它已经够用,但是有时候,一些特殊的功能还需要定制,比如搜索功能,下面这一系列文章就逐步深入介绍如何定制适合自己的admin应用。

    如果你觉得英文界面不好用,可以在setting.py 文件中修改以下选项:

    LANGUAGE_CODE = 'en-us'  #LANGUAGE_CODE = 'zh-hans'
    

    使用django admin 则需要以下步骤:

    • 创建后台管理员
    • 配置url
    • 注册和配置django admin后台管理页面

    1、创建后台管理员

    python manage.py createsuperuser
    

    2、配置后台管理url

    url(r'^admin/', admin.site.urls)
    

    3、注册和配置django admin 后台管理页面

    a、注册:在admin中执行如下配置

    from django.contrib import admin
       
    from app01 import  models
       
    admin.site.register(models.UserType)
    admin.site.register(models.UserInfo)
    admin.site.register(models.UserGroup)
    admin.site.register(models.Asset)
    

    b、设置数据表字段的显示名称

    #方式一
    class UserType(models.Model):
        name = models.CharField(max_length=50)
       #meta表示数据库中显示的信息
        class Meta:
            db_table = 'UserType'            #数据库表名
            verbose_name = '用户类型'     #数据库字段名
            verbose_name_plural = '用户类型'
    
    #方式二
    #修改models
    class Book(models.Model):
        '''书籍'''
        name = models.CharField(max_length=20,verbose_name='名称')
        price = models.IntegerField('价格')

    c、自定义页面展示

    from django.contrib import admin
    from app.models import *
    
    #自定制admin类
    class BookAdmin(admin.ModelAdmin):
        #不可以显示多对多的关联字段,注意这些都是元组,注意末尾的逗号
        list_display = ('id','name','price','pub_date',)
        #可编辑
        # list_editable = ('name','price','pub_date',)
        # 设置字段可垂直搜索
        filter_horizontal = ('authors',)
        #设置每页显示的条目
        # list_per_page = 2
        #根据字段搜索,关联字段加__,三个字段中重复的部分都会被搜索到
        search_fields = ('id','name','publish__name',)
        #根据字段过滤
        list_filter = ('pub_date','publish',)
        #根据字段排序,可以多个字段,依次排序,‘-id’表示降序排
        ordering = ('id',)
        #添加书籍时隐藏字段,列表里必须是元组
        fieldsets = [
            #默认显示name,fieldes是固定的,后面跟列表
            (None,{'fields':['name',]}),
            #将下列字段以折叠的方式显示
            ('other information',{'fields':['price','pub_date','publish'],'classes':['collapse',]}),
        ]
    
    #将模型注册到admin
    admin.site.register(Book,BookAdmin)

    4、注册medel类到admin的两种方式:

    • 使用register的方法
    admin.site.register(Book,MyAdmin)
    
    • 使用register的装饰器
    @admin.register(Book)
    

    5、装饰器使用方法

    from django.contrib import admin
    from app01.models import *
    # Register your models here.
    
    # @admin.register(Book)#----->单给某个表加一个定制
    class MyAdmin(admin.ModelAdmin):
        list_display = ("title","price","publisher")
        search_fields = ("title","publisher")
        list_filter = ("publisher",)
        ordering = ("price",)
        fieldsets =[
            (None,               {'fields': ['title']}),
            ('price information', {'fields': ['price',"publisher"], 'classes': ['collapse']}),
        ]
    
    admin.site.register(Book,MyAdmin)
    admin.site.register(Publish)
    admin.site.register(Author)

    三、Form组件

    1、django中的Form一般有一下几种功能:

    • 生成HTML标签

    • 验证用户输入:ajax和form表单提交

    • HTML Form提交保留上次提交数据

    • 初始化页面显示内容

    2、用form表单提交的基本流程

    a、在views.py中创建一个类:F(forms.Form),如果正规操作,应该是在APP中新建一个forms.py文件,再导入;

    b、类中创建字段(包含正则表达式和HTML插件)

    c、用户以GET方式请求:

    • 把obj=F()发送到前端,这里不用传入任何参数

    •  {{ obj.user }}...前端自动生成input标签

    d、用户以POST方式请求:

    • obj = Form1(request.POST),对象中包含用户输入信息和错误信息

    • 先验证用户输入是否正确,如果正确就跳转页面,如果错误就发送obj

    • {{ obj.errors.user.0 }} 前端显示错误信息

    补充:

    #mark_safe,不用加safe就可以直接渲染HTML标签
    from django.utils.safestring import mark_safe
    txt = mark_safe("<input type='text'/")
    
    # novalidate忽略浏览器的验证
    <form action="/add_user/" method="POST" novalidate>
    
    # 上传文件记得加上enctype
    <form action="/test/" method="POST" enctype="multipart/form-data" novalidate>
    
    # form表单自动生成input标签,编辑时将数据库查询内容作为input标签默认值
    data = UserInfo.objects.filter(id=nid).first()
    obj = F1({'username':data.username,'email':data.email})

    实例:

    • views.py中创建Form类和函数
    from django.shortcuts import render,redirect,HttpResponse
    from django import forms
    from django.forms import fields
    
    class Form1(forms.Form):
        user = fields.CharField(
            min_length=6,
            max_length=18,
            required=True,
            error_messages={
                'required':'用户名不能为空',
                'min_length':'用户名太短',
                'max_length':'用户名太长',
            }
        )
        pwd = fields.CharField(
            min_length=10,
            required=True,
            error_messages = {
                'required': '密码不能为空',
                'min_length': '密码太短',
            }
        )
        age = fields.IntegerField(
            required=True,
            error_messages={
                'required': '年龄不能为空',
                'invalid': '必须为数字',
            }
        )
        email = fields.EmailField(
            min_length=8,
            required=True,
            error_messages={
                'required': '邮箱不能为空',
                'invalid': '格式错误'
            }
        )
    
    def f1(request):
        if request.method == 'GET':
            #自动生成input标签
            obj = Form1()
            return render(request,'f1.html',{'obj': obj})
        if request.method == 'POST':
            obj = Form1(request.POST)
            #验证是否成功
            if obj.is_valid():
                #如果成功,打印用户提交的数据,跳转
                print("验证成功",obj.cleaned_data)
                return redirect('http://www.baidu.com')
            else:
                print("验证失败", obj.errors)
                return render(request, 'f1.html', {'obj': obj})
    • f1.html
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        {% load staticfiles %}
    </head>
    <body>
        <form action="/f1.html" method="POST" id="fm">
            <p>用户名{{ obj.user }}{{ obj.errors.user.0 }}</p>
            <p>密码{{ obj.pwd }}{{ obj.errors.pwd.0 }}</p>
            <p>年龄{{ obj.age }}{{ obj.errors.age.0 }}</p>
            <p>邮箱{{ obj.email }}{{ obj.errors.email.0 }}</p>
            <input type="submit" value="提交"/>
            <input type="button" value="ajax提交" id="ajaxSubmit"/>
        </form>
    </body>
    </html>

    3、创建Form类时,主要涉及到 【字段】 和 【插件】,字段用于对用户请求数据的验证,插件用于自动生成HTML;

    • 常用的Django内置字段
    Field
        required=True,               是否允许为空
        widget=None,                 HTML插件
        label=None,                  用于生成Label标签或显示内容
        initial=None,                初始值
        help_text='',                帮助信息(在标签旁边显示)
        error_messages=None,         错误信息 {'required': '不能为空', 'invalid': '格式错误'}
        show_hidden_initial=False,   是否在当前插件后面再加一个隐藏的且具有默认值的插件(可用于检验两次输入是否一直)
        validators=[],               自定义验证规则
        localize=False,              是否支持本地化
        disabled=False,              是否可以编辑
        label_suffix=None            Label内容后缀
    
    #字符串
    CharField(Field)
        max_length=None,             最大长度
        min_length=None,             最小长度
        strip=True                   是否移除用户输入空白
    
    #整型
    IntegerField(Field)
        max_value=None,              最大值
        min_value=None,              最小值
    
    #浮点型,十进制小数
    DecimalField(IntegerField)
        max_value=None,              最大长度
        min_value=None,              最小长度
        max_digits=None,             总长度
        decimal_places=None,       小数位长度
    
    #下拉框
    ChoiceField(Field)
        choices=(),                选项,如:choices = ((0,'上海'),(1,'北京'),)
        required=True,             是否必填
        widget=None,               插件,默认select插件
        label=None,                Label内容
        initial=None,              初始值
        help_text='',              帮助提示
    
    TypedChoiceField(ChoiceField)
        coerce = lambda val: val   对选中的值进行一次转换
        empty_value= ''            空值的默认值
    
    #多选框
    MultipleChoiceField(ChoiceField)
    
    TypedMultipleChoiceField(MultipleChoiceField)
        coerce = lambda val: val   对选中的每一个值进行一次转换
        empty_value= ''            空值的默认值
    
    #时间
    DateField(BaseTemporalField)    格式:2015-09-01
    TimeField(BaseTemporalField)    格式:11:12
    DateTimeField(BaseTemporalField)格式:2015-09-01 11:12
    
    #邮箱
    EmailField(CharField)
    
    #自定制正则表达式
    RegexField(CharField)
        regex,                            自定制正则表达式
        max_length=None,            最大长度
        min_length=None,            最小长度
        error_message=None,         忽略,错误信息使用 error_messages={'invalid': '...'}
    
    #上传文件
    FileField(Field)
        allow_empty_file=False     是否允许空文件
    
    ImageField(FileField)      
        ...
        注:需要PIL模块,pip3 install Pillow
        以上两个字典使用时,需要注意两点:
            - form表单中 enctype="multipart/form-data"
            - view函数中 obj = MyForm(request.POST, request.FILES)
    
    #IP
    GenericIPAddressField
        protocol='both',           both,ipv4,ipv6支持的IP格式
        unpack_ipv4=False          解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1, PS:protocol必须为both才能启用
    
    #数据库操作相关字段
    ModelChoiceField(ChoiceField)
        ...                        django.forms.models.ModelChoiceField
        queryset,                  # 查询数据库中的数据
        empty_label="---------",   # 默认空显示内容
        to_field_name=None,        # HTML中value的值对应的字段
        limit_choices_to=None      # ModelForm中对queryset二次筛选
         
    ModelMultipleChoiceField(ModelChoiceField)
        ...                        django.forms.models.ModelMultipleChoiceField
    • 实例:
    #views.py
    
    from django.shortcuts import render
    from django import forms
    from django.forms import fields,widgets
    
    class TestForm(forms.Form):
        user = fields.CharField(
            required=True,
            min_length=3,
            max_length=12,
            error_messages={'required':'不能为空',},
            #widgets定制HTML插件
            # widget=widgets.Select,
            label = '用户名',
            label_suffix='->',
        )
        age = fields.IntegerField(max_value=100,min_value=18)
        email = fields.EmailField()
        #FileField和ImageField功能一样
        img = fields.FileField()
        #生成下拉框,initial默认选中,或者obj = TestForm({'city':2})也可以默认选择
        city = fields.ChoiceField(
            choices=[(1,'北京'),(2,'上海'),],
            initial=2,
        )
        #多选框
        hobby = fields.MultipleChoiceField(
            choices=[(1,'足球'),(2,'篮球'),],
            initial=[1,2],
        )
        country = fields.TypedChoiceField(
            choices=[(1, '中国'), (2, '美国'), ],
            initial=2,
            #将传入的参数做一个数据类型转换 'country': 2
            coerce=lambda x:int(x),
            empty_value='null',#空值的默认值
        )
    
    def test(request):
        if request.method == 'GET':
            obj = TestForm()
            return render(request, 'test.html',locals())
        else:
            #上传的文件在request.FILES中
            obj = TestForm(request.POST,request.FILES)
            obj.is_valid()
            print(obj.cleaned_data)
            return render(request,'test.html',locals())
    
    #test.html
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        {#上传文件记得加上enctype#}
        <form action="/test/" method="POST" enctype="multipart/form-data" novalidate>
            {% csrf_token %}
            <p>{{ obj.user.label }}{{ obj.user }}</p>
            <p>{{ obj.age.label }}{{ obj.age }}</p>
            <p>{{ obj.email.label }}{{ obj.email }}</p>
            <p>{{ obj.img.label }}{{ obj.img }}</p>
            <p>{{ obj.city.label }}{{ obj.city }}</p>
            <p>{{ obj.hobby.label }}{{ obj.hobby }}</p>
            <p>{{ obj.country.label }}{{ obj.country }}</p>
            <input type="submit" value="提交"/>
        </form>
    {#    可以全部自动生成一个标签,但是页面排版无法控制,不建议使用#}
    {#    {{ obj.as_p }}#}
    </body>
    </html>

    4、Django内置插件

    • 调用方法
    #widgets定制HTML插件,每一个字段都有自己的默认插件,还可以定制属性
    widget=widgets.TextInput(attrs={'class':'c1'}),
    • Django内置插件
    TextInput(Input)
    NumberInput(TextInput)
    EmailInput(TextInput)
    URLInput(TextInput)
    PasswordInput(TextInput)
    HiddenInput(TextInput)
    Textarea(Widget)
    DateInput(DateTimeBaseInput)
    DateTimeInput(DateTimeBaseInput)
    TimeInput(DateTimeBaseInput)
    CheckboxInput
    Select
    NullBooleanSelect
    SelectMultiple
    RadioSelect
    CheckboxSelectMultiple
    FileInput
    ClearableFileInput
    MultipleHiddenInput
    SplitDateTimeWidget
    SplitHiddenDateTimeWidget
    SelectDateWidget
    

    5、常用的选择插件

    # 单radio,值为字符串
    user = fields.CharField(
        initial=2,
        widget=widgets.RadioSelect(choices=((1,'上海'),(2,'北京'),))
    )
    
    # 单radio,值为字符串
    user = fields.ChoiceField(
        choices=((1, '上海'), (2, '北京'),),
        initial=2,
        widget=widgets.RadioSelect
    )
    
    # 单select,值为字符串
    user = fields.CharField(
        initial=2,
        widget=widgets.Select(choices=((1,'上海'),(2,'北京'),))
    )
    
    # 单select,值为字符串
    user = fields.ChoiceField(
        choices=((1, '上海'), (2, '北京'),),
        initial=2,
        widget=widgets.Select
    )
    
    # 多选select,值为列表
    user = fields.MultipleChoiceField(
        choices=((1,'上海'),(2,'北京'),),
        initial=[1,],
        widget=widgets.SelectMultiple
    )
    
    # 单checkbox
    user = fields.CharField(
        widget=widgets.CheckboxInput()
    )
    
    # 多选checkbox,值为列表
    user = fields.MultipleChoiceField(
        initial=[2, ],
        choices=((1, '上海'), (2, '北京'),),
        widget=widgets.CheckboxSelectMultiple
    )

    6、在使用选择标签时,需要注意choices的选项是需要从数据库中获取,但是由于是静态字段 ***获取的值无法实时更新***,那么需要自定义__init__构造方法,方法给widget.choices重新从数据库中取值,这样每次页面一刷新就实例化一次form对象,就会执行一次init函数,也就是会从数据库中取一次值。

    方式一:

    from django.shortcuts import render
    from django import forms
    from django.forms import fields,widgets
    from app.models import *#models.py中的类
    class UserInfo(models.Model):
        username = models.CharField(max_length=32)
        email = models.EmailField(max_length=32)
        def __str__(self):
            return self.username
    
    #创建自定义form
    class LoveForm(forms.Form):
        price = fields.IntegerField()
        user_id = fields.IntegerField(widget=widgets.Select)
        #静态字段 获取的值无法实时更新,需要自定义构造方法
        def __init__(self,*args,**kwargs):
            # super必须在上面,它拷贝了所有的静态字段,下面才能去内部取字段
            super(LoveForm,self).__init__(*args,**kwargs)
            self.fields['user_id'].widget.choices = UserInfo.objects.values_list('id','username')
    
    def love(request):
        obj = LoveForm()
        return render(request,'love.html',locals())

    方式二:

    #使用django提供的ModelChoiceField和ModelMultipleChoiceField字段来实现
    
    from django.forms.models import ModelChoiceField
    
    class LoveForm(forms.Form):
        user_id2 = ModelChoiceField(
            #这里无法显示数据库表中的ID以外的字段,需要models中的类加上个__str__方法,返回某个字段
            #虽然一样可以实时更新,但是与数据库的格式关系太大,不能灵活显示每个字段,不建议使用
            queryset=UserInfo.objects.all()
        )

    7、使用ajax提交

    • 无法自动跳转页面,就是在views函数中设置redirect,ajax也不会听从,需要在前端ajax的回调函数自己使用js代码跳转,window.location.href = 'http://www.baidu.com'

    • 错误信息需要自己显示到页面,obj.errors 类型<class 'django.forms.utils.ErrorDict'>继承dict,所以用json.dumps()不会报错。

    class AjaxForm(forms.Form):
        price = fields.IntegerField()
        user_id = fields.IntegerField(
            widget=widgets.Select(choices=[(1, '中国'), (2, '美国'), ],)
        )
    
    def ajax(request):
        if request.method == 'GET':
            #自动生成input标签,并没有开始做验证
            obj = AjaxForm()
            return render(request,'ajax.html',{'obj': obj})
        if request.method == 'POST':
            import json
            response = {'status':True,'msg':None}
            #并没有开始做验证
            obj = AjaxForm(request.POST)
            #is_valid做的验证
            if obj.is_valid():
                print("验证成功",obj.cleaned_data)
                #ajax无法自动跳转,需要在前端手动设置
                return HttpResponse(json.dumps(response))
            else:
                #<class 'django.forms.utils.ErrorDict'>继承dict
                print("验证失败", obj.errors,type(obj.errors))
                response['status'] = False
                response['msg'] = obj.errors
                return HttpResponse(json.dumps(response))
    • ajax.html
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        {% load staticfiles %}
    </head>
    <body>
        <form action="/ajax/" method="POST" novalidate id="fm">
            {% csrf_token %}
            {{ obj.as_p }}
            <input type="button" value="ajax提交" id="ajaxSubmit"/>
        </form>
        <script src="{% static 'js/jquery-1.12.4.js' %}"></script>
        <script>
            $(function () {
                $('#ajaxSubmit').click(function () {
                    $.ajax({
                        url:'/ajax/',
                        type:'POST',
                        data:$('#fm').serialize(),
                        dataType:'JSON',
                        success:function (arg) {
                            if(arg.status){
                                window.location.href = 'http://www.baidu.com';
                            }
                            console.log(arg);
                        }
                    })
                })
            })
        </script>
    </body>
    </html>

    8、在根据源码流程自定义方法

    • clean_字段名,只能验证该字段自己的内容;
    from django.core.exceptions import ValidationError
    
    class AjaxForm(forms.Form):
        username = fields.CharField()
        user_id = fields.IntegerField(
            widget=widgets.Select(choices=[(1, '中国'), (2, '美国'), ],)
        )
        #自定义方法clean_字段名,
        #必须返回值self.cleaned_data['username']
        #如果出错:raise ValidationError('用户名已存在')
        def clean_username(self):
            v = self.cleaned_data['username']
            if UserInfo.objects.filter(username=v).count():
                #如果用户信息中有一个字段错了,整体就错误,显示具体错误的详细信息
                raise ValidationError('用户名已存在')
            return v
        def clean_user_id(self):
            return self.cleaned_data['user_id']
    • 自定义self.clean()方法,对整体错误验证,错误信息放在__all__中,前端调用方法arg.msg.__all__
        def clean(self):
            '''
            obj.errors错误信息
            {
                __all__:[],
                username:[],
                user_id:[],
            }
            '''
            # Django的整体错误信息放在__all__中
            v1 = self.cleaned_data.get('username')
            v2 = self.cleaned_data.get('user_id')
            if v1=='charlie' and v2==1:
                raise ValidationError('整体错误信息')
            return self.cleaned_data
    • __all__源码分析
    from django.core.exceptions import NON_FIELD_ERRORS
    
    #这里添加错误的时候k就是None
    self.add_error(None, e)
    #但是None又被替换成__all__,所以前端需要通过.all来取
    NON_FIELD_ERRORS = '__all__'
    
    # js代码
    console.log(arg.msg.__all__);
    
    #HTML模板
    #模板语言里不支持双下划线格式,所以用obj.non_field_errors.0 

    9、is_valid()验证表单时,一共给我们留了三个钩子

    # 自定义方法名必须是clean_字段名
        def _clean_fields(self):
            for name, field in self.fields.items():
                # value_from_datadict() gets the data from the data dictionaries.
                # Each widget type knows how to retrieve its own data, because some
                # widgets split data over several HTML fields.
                if field.disabled:
                    value = self.get_initial_for_field(field, name)
                else:
                    value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
                try:
                    if isinstance(field, FileField):
                        initial = self.get_initial_for_field(field, name)
                        value = field.clean(value, initial)
                    else:
                        value = field.clean(value)
                    self.cleaned_data[name] = value
                    if hasattr(self, 'clean_%s' % name):
                        value = getattr(self, 'clean_%s' % name)()
                        self.cleaned_data[name] = value
                except ValidationError as e:
                    self.add_error(name, e)
    # 同时验证多个字段或者是联合字段,返回整体错误信息,放在__all__中
        def _clean_form(self):
            try:
                cleaned_data = self.clean()
            except ValidationError as e:
                self.add_error(None, e)
            else:
                if cleaned_data is not None:
                    self.cleaned_data = cleaned_data
    # 没有捕捉异常的代码,所以这个方法不能出错误
        def _post_clean(self):
            """
            An internal hook for performing additional cleaning after form cleaning
            is complete. Used for model validation in model forms.
            """
            pass

    10、扩展:ModelForm

    在使用Model和Form时,都需要对字段进行定义并指定类型,通过ModelForm则可以省去From中字段的定义

    from django import forms
    from crm import models
    
    class CustomerForm(forms.ModelForm):
        class Meta:
            model = models.CustomerInfo
            # fields = ['name','consultant','status']
            # 所有字段
            fields = '__all__'
               
            widgets = {
                'email' : forms.PasswordInput(attrs={'class':"alex"}),
            }
    

      实例:

    # forms.py
    
    class EnrollmentForm(forms.ModelForm):
        """审核学员报名信息"""
        def __new__(cls, *args, **kwargs):
            # cls.base_fields获取数据库表里的所有字段名
            for field_name in cls.base_fields:
                field_obj = cls.base_fields.get(field_name)
                # 定制生成的标签的class属性
                field_obj.widget.attrs.update({'class': 'form-control'})
                # 只读标签不可编辑
                if field_name in cls.Meta.readonly_fields:
                    field_obj.widget.attrs.update({'disabled': 'true'})
            return forms.ModelForm.__new__(cls)
    
        class Meta:
            model = models.StudentEnrollment
            fields = '__all__'
            exclude = ['contract_approved_date']
            readonly_fields = ['contract_agreed',]
    
        def clean(self):
            """负责验证disabled字段的值是否被改动了"""
            if self.errors:
                # 全局的错误信息,通过customer_form.errors调用
                raise forms.ValidationError(("Please fix errors before re-submit."))
            if self.instance.id is not None:
                # 说明这是一个被修改过的表单,需要验证disabled字段
                for field in self.Meta.readonly_fields:
                    old_field_val = getattr(self.instance,field)  # 数据库里的数据
                    form_val = self.cleaned_data.get(field)   # 表单里提交的数据
                    if old_field_val != form_val:
                        # 给单个字段添加错误信息
                        self.add_error(field,"Readonly Field:field should be '{value}',not'{new_value}'".
                                       format(**{'value':old_field_val,'new_value':form_val}))
    • views.py
    from django.shortcuts import render,HttpResponse,redirect
    from django.contrib.auth.decorators import login_required  # 必须登陆才能看到页面
    from crm import forms
    
    @login_required
    def contract_audit(request,enrollment_id):
        '''学员报名审核页'''
        enrollment_obj = models.StudentEnrollment.objects.filter(id=enrollment_id).first()
        if request.method == 'POST':
            enrollment_form = forms.EnrollmentForm(instance=enrollment_obj,data=request.POST)
            if enrollment_form.is_valid():
                enrollment_form.save()
                # 报名成功后,把学员加入数据库,stu_obj = (<Student: 铁锤>, True)
                stu_obj = models.Student.objects.get_or_create(customer=enrollment_obj.customer)[0] # 加入学生表
                stu_obj.class_grades.add(enrollment_obj.class_grade_id)  # 加入班级表
                stu_obj.customer.status = 1   # 修改状态
                stu_obj.save()
                # 修改报名表,是否审核通过,和审核通过时间
                enrollment_obj.contract_approved = True
                enrollment_obj.contract_approved_date = datetime.now()
                enrollment_obj.save()
                # 给用户发邮件
                return redirect('/kingadmin/crm/customerinfo/%s/change/'%enrollment_obj.customer.id)
        else:
            # 创建form表单,发到前端
            customer_form = forms.CustomerForm(instance=enrollment_obj.customer)
            enrollment_form = forms.EnrollmentForm(instance=enrollment_obj)
        return render(request,'crm/contract_audit.html',locals())

    四、Cookie

    cookie的工作原理是:由服务器产生内容,浏览器收到请求后保存在本地;当浏览器再次访问时,浏览器会自动带上cookie,这样服务器就能通过cookie的内容来判断这个是“谁”了。

    1、获取Cookie:

    request.COOKIES['key']
    request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)
        参数:
            default: 默认值
               salt: 加密盐
            max_age: 后台控制过期时间
    

    2、设置Cookie:

    rep = HttpResponse(...) 或 rep = render(request, ...)
     
    rep.set_cookie(key,value,...)
    rep.set_signed_cookie(key,value,salt='加密盐',...)
        参数:
            key,              键
            value='',         值
            max_age=None,     超时时间
            expires=None,     超时时间(IE requires expires, so set it if hasn't been already.)
            path='/',         Cookie生效的路径,/ 表示根路径,特殊的:跟路径的cookie可以被任何url的页面访问
            domain=None,      Cookie生效的域名
            secure=False,     https传输
            httponly=False    只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)
    
    • 由于cookie保存在客户端的电脑上,所以,JavaScript和jquery也可以操作cookie。
    <script src='/static/js/jquery.cookie.js'></script>
    $.cookie("list_pager_num", 30,{ path: '/' });

    3、应用

    from django.shortcuts import render,redirect
    import datetime
    
    def login(request):
        if request.method == 'POST':
            name = request.POST.get('user')
            pwd = request.POST.get('pwd')
            if name == 'charlie' and pwd == '123':
                # redirect render 都有返回值,是一个字典,里面有键值对'sessionid':'5225rffg5hh5'
                ret = redirect('/index/')
                #给cookie添加一个键值对,下次再来发送请求的时候会带着它来,
                #如果和这个一样,就不用重新登陆了,前提是在同一个客户端登陆
                #设置cookie有效时间5秒
                ret.set_cookie('username',name,max_age=5)
                #设置有效期三天
                ret.set_cookie('username',name,expires=datetime.datetime.utcnow()
                                                       +datetime.timedelta(days=3))
                return ret
        return render(request,'login.html')
    
    def index(request):
        if request.COOKIES.get('username',None):
            name = request.COOKIES.get('username',None)
            return render(request,'index.html',locals())
        else:
            return redirect('/login/')

    五、Session

    cookie虽然在一定程度上解决了“保持状态”的需求,但是由于cookie本身最大支持4096字节,以及cookie本身保存在客户端,可能被拦截或窃取,因此就需要有一种新的东西,它能支持更多的字节,并且他保存在服务器,有较高的安全性。这就是session(session默认在服务器端保存15天)。

    Django中默认支持Session,其内部提供了5种类型的Session供开发者使用:

    • 数据库(默认)
    • 缓存
    • 文件
    • 缓存+数据库
    • 加密cookie

    1、数据库Session

    Django默认支持Session,并且默认是将Session数据存储在数据库中,即:django_session 表中。
     
    a. 配置 settings.py
     
        SESSION_ENGINE = 'django.contrib.sessions.backends.db'   # 引擎(默认)
         
        SESSION_COOKIE_NAME = "sessionid"                       # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认)
        SESSION_COOKIE_PATH = "/"                               # Session的cookie保存的路径(默认)
        SESSION_COOKIE_DOMAIN = None                             # Session的cookie保存的域名(默认)
        SESSION_COOKIE_SECURE = False                            # 是否Https传输cookie(默认)
        SESSION_COOKIE_HTTPONLY = True                           # 是否Session的cookie只支持http传输(默认)
        SESSION_COOKIE_AGE = 1209600                             # Session的cookie失效日期(2周)(默认)
        SESSION_EXPIRE_AT_BROWSER_CLOSE = False                  # 是否关闭浏览器使得Session过期(默认)
        SESSION_SAVE_EVERY_REQUEST = False                       # 是否每次请求都保存Session,默认修改之后才保存(默认)
     
     
     
    b. 使用
     
        def index(request):
            # 获取、设置、删除Session中数据
            request.session['k1']
            request.session.get('k1',None)
            request.session['k1'] = 123
            request.session.setdefault('k1',123) # 存在则不设置
            del request.session['k1']
     
            # 所有 键、值、键值对
            request.session.keys()
            request.session.values()
            request.session.items()
            request.session.iterkeys()
            request.session.itervalues()
            request.session.iteritems()
     
     
            # 用户session的随机字符串
            request.session.session_key
     
            # 将所有Session失效日期小于当前日期的数据删除
            request.session.clear_expired()
     
            # 检查 用户session的随机字符串 在数据库中是否
            request.session.exists("session_key")
     
            # 删除当前用户的所有Session数据
            request.session.delete("session_key")
     
            request.session.set_expiry(value)
                * 如果value是个整数,session会在些秒数后失效。
                * 如果value是个datatime或timedelta,session就会在这个时间后失效。
                * 如果value是0,用户关闭浏览器session就会失效。
                * 如果value是None,session会依赖全局session失效策略。
    View Code

    2、缓存Session

    a. 配置 settings.py
     
        SESSION_ENGINE = 'django.contrib.sessions.backends.cache'  # 引擎
        SESSION_CACHE_ALIAS = 'default'                            # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置
     
     
        SESSION_COOKIE_NAME = "sessionid"                        # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串
        SESSION_COOKIE_PATH = "/"                                # Session的cookie保存的路径
        SESSION_COOKIE_DOMAIN = None                              # Session的cookie保存的域名
        SESSION_COOKIE_SECURE = False                             # 是否Https传输cookie
        SESSION_COOKIE_HTTPONLY = True                            # 是否Session的cookie只支持http传输
        SESSION_COOKIE_AGE = 1209600                              # Session的cookie失效日期(2周)
        SESSION_EXPIRE_AT_BROWSER_CLOSE = False                   # 是否关闭浏览器使得Session过期
        SESSION_SAVE_EVERY_REQUEST = False                        # 是否每次请求都保存Session,默认修改之后才保存
     
    b. 使用同上
    

    3、文件Session

    a. 配置 settings.py
        SESSION_ENGINE = 'django.contrib.sessions.backends.file'    # 引擎
        SESSION_FILE_PATH = None                                    # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir()                                                            # 如:/var/folders/d3/j9tj0gz93dg06bmwxmhh6_xm0000gn/T
        SESSION_COOKIE_NAME = "sessionid"                          # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串
        SESSION_COOKIE_PATH = "/"                                  # Session的cookie保存的路径
        SESSION_COOKIE_DOMAIN = None                                # Session的cookie保存的域名
        SESSION_COOKIE_SECURE = False                               # 是否Https传输cookie
        SESSION_COOKIE_HTTPONLY = True                              # 是否Session的cookie只支持http传输
        SESSION_COOKIE_AGE = 1209600                                # Session的cookie失效日期(2周)
        SESSION_EXPIRE_AT_BROWSER_CLOSE = False                     # 是否关闭浏览器使得Session过期
        SESSION_SAVE_EVERY_REQUEST = False                          # 是否每次请求都保存Session,默认修改之后才保存
     
    b. 使用同上
    

    4、缓存+数据库Session

    数据库用于做持久化,缓存用于提高效率
    a. 配置 settings.py
        SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'      # 引擎
    b. 使用同上
    

    5、加密cookie Session

    a. 配置 settings.py 
        SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'   # 引擎
    b. 使用同上
    

    更多参考:猛击这里 和 猛击这里

    6、cookie 和Session的结合使用的两种方式

    • 存储在服务端:通过cookie存储一个session_id,然后具体的数据则是保存在session中。如果用户已经登录,则服务器会在cookie中保存一个session_id,下次再次请求的时候,会把该session_id携带上来,服务器根据session_idsession库中获取用户的session数据。就能知道该用户到底是谁,以及之前保存的一些状态信息。这种专业术语叫做server side session

    • session数据加密,然后存储在cookie中。这种专业术语叫做client side sessionflask采用的就是这种方式,但是也可以替换成其他形式。

    7、应用

    数据库session,需要执行python manage.py makemigrations命令,需要Django帮我们创建session表,用来保存session内容,否则会报错;

    cookie是一个字典,里面默认有两个键值对:‘sessionid’,‘csrftoken’,session是一个字典对象,两个都可以用request.session['username']=name方法来添加键值对;

    删除session:del request.session[key]

    from django.shortcuts import render,redirect
    import datetime
    
    def login(request):
        if request.method == 'POST':
            name = request.POST.get('user')
            pwd = request.POST.get('pwd')
            if name == 'charlie' and pwd == '123':
                #cookie + session
                request.session['is_login']=True
                request.session['user']=name
                return redirect('/index/')
        return render(request,'login.html')
    
    def index(request):
        #cookie + session,加上None防止找不到报错
        if request.session.get('is_login',None):
            name = request.session.get('user',None)
            return render(request,'index.html',locals())
        else:
            return redirect('/login/')

    六、分页

    1、代码实现简单的上一页、下一页

    #index.html
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <ul>
            {% for row in user_list %}
                <li>{{ row.name }} - {{ row.age }}</li>
            {% endfor %}
        </ul>
        <a href="/index.html?p={{ prev_page }}">上一页</a>
        <a href="/index.html?p={{ next_page }}">下一页</a>
    </body>
    </html>
    
    #views.py
    
    from django.shortcuts import render
    
    USER_LIST = []
    for i in range(1,1000):
        temp = {'name':'root' + str(i),'age':i}
        USER_LIST.append(temp)
    
    def index(request):
        #简单的分页
        per_page_count = 10
        current_page = int(request.GET.get('p'))
        #p=1  索引0-10
        #p=2  索引10-20
        #http://127.0.0.1:8005/index.html?p=1
        start = (current_page-1)*per_page_count
        end = current_page*per_page_count
        user_list = USER_LIST[start:end]
        if current_page <= 1:
            prev_page = 1
            next_page = current_page + 1
        else:
            prev_page = current_page - 1
            next_page = current_page + 1
        return render(request,'index.html',locals())

    2、Django内置分页+扩展

      -- Django自带的内置分页只能显示上一页和下一页按钮,不能显示中间的页码,所以这里需要扩展一下

    • views.py
    from django.shortcuts import render
    from django.core.paginator import Paginator,EmptyPage,PageNotAnInteger
    
    USER_LIST = []
    for i in range(1,230):
        temp = {'name':'root' + str(i),'age':i}
        USER_LIST.append(temp)
    
    #扩展自带分页
    class CustomPaginator(Paginator):
        def __init__(self,current_page,per_pager_num,*args,**kwargs):
            #当前页
            self.current_page = int(current_page)
            #每页最多显示多少页码
            self.per_pager_num = int(per_pager_num)
            super(CustomPaginator,self).__init__(*args,**kwargs)
        def page_num_range(self):
            #如果总页数小于每页最多显示页码数量,就显示1-总页码
            if self.num_pages < self.per_pager_num:
                return range(1,self.num_pages+1)
            #如果总页数有很多
            part = int(self.per_pager_num/2)
            if self.current_page <= part:
                return range(1,self.per_pager_num+1)
            #最后一页只显示最后10个页码即可
            if (self.current_page+part) > self.num_pages:
                return range(self.num_pages-self.per_pager_num+1,self.num_pages+1)
            return range(self.current_page-part,self.current_page+part+1)
    
    def index1(request):
        #django自带分页
        current_page = request.GET.get('p')
        #Paginator对象,每页显示10个页码,10条数据
        paginator = CustomPaginator(current_page,11,USER_LIST,10)
        try:
            #Page对象
            posts = paginator.page(current_page)
        except PageNotAnInteger:
            #如果输入不是数字,就显示第一页
            posts = paginator.page(1)
        except EmptyPage:
            #如果输入为空或数字过大,就显示最后一页
            posts = paginator.page(paginator.num_pages)
        return render(request,'index1.html',locals())
    • index1.html,在templates下新建include/pager.html,把所有关于分页的代码单独放到一个文件中,以后可以随时调用
    #注意:这里使用include引用了文件
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <ul>
            {% for row in posts %}
                <li>{{ row.name }} - {{ row.age }}</li>
            {% endfor %}
        </ul>
        {% include 'include/pager.html' %}
    </body>
    </html>
    • 引用pager.html文件
    <a href="/index1.html?p=1">首页</a>
    {% if posts.has_previous %}
        <a href="/index1.html?p={{ posts.previous_page_number }}">上一页</a>
    {% endif %}
    {% for i in paginator.page_num_range %}
        {% if i == posts.number %}
            <a style="font-size: 20px;color: blue;" href="/index1.html?p={{ i }}">{{ i }}</a>
        {% else %}
            <a href="/index1.html?p={{ i }}">{{ i }}</a>
        {% endif %}
    {% endfor %}
    {% if posts.has_next %}
        <a href="/index1.html?p={{ posts.next_page_number }}">下一页</a>
    {% endif %}
    <span>
        [{{ posts.number }}/{{ paginator.num_pages }}]
    </span>
    <a href="/index1.html?p={{ paginator.num_pages }}">尾页</a>

    2、自定义分页

      —   分页功能在每个网站都是必要的,对于分页来说,其实就是根据用户的输入计算出应该在数据库表中的起始位置,并且需要在页面上显示分页的页面。如:[上一页][1][2][3][4][5][下一页]

    • 1、设定每页显示数据条数

    • 2、用户输入页码(第一页、第二页...)

    • 3、设定显示多少页号

    • 4、获取当前数据总条数

    • 5、根据设定显示多少页号和数据总条数计算出,总页数

    • 6、根据设定的每页显示条数和当前页码,计算出需要取数据表的起始位置

    • 7、在数据表中根据起始位置取值,页面上输出数据

    • 8、输出分页html,如:[上一页][1][2][3][4][5][下一页]

    实例:在APP中创建pager.py

    #在APP中创建pager.py
    
    class Pagination(object):
        def __init__(self,total_count,current_page,per_page_item_num=20,max_page_num=7):
            #数据总条目
            self.total_count = total_count
            #当前页
            try:
                v = int(current_page)
                if v <= 0:
                    v = 1
                self.current_page = v
            except Exception as e:
                self.current_page = 1
            #每页显示条目
            self.per_page_item_num = per_page_item_num
            #每页最多显示页码
            self.max_page_num = max_page_num
    
        def start(self):
            return (self.current_page-1)*self.per_page_item_num
    
        def end(self):
            return self.current_page*self.per_page_item_num
    
        @property
        def num_pages(self):
            # 总页数
            a,b = divmod(self.total_count,self.per_page_item_num)
            if b == 0:
                return a
            return a+1
    
        def page_num_range(self):
            #如果总页数小于每页最多显示页码数量,就显示1-总页码
            if self.num_pages < self.max_page_num:
                return range(1,self.num_pages+1)
            #如果总页数有很多
            part = int(self.max_page_num/2)
            if self.current_page <= part:
                return range(1,self.max_page_num+1)
            #最后一页只显示最后10个页码即可
            if (self.current_page+part) > self.num_pages:
                return range(self.num_pages-self.max_page_num+1,self.num_pages+1)
            return range(self.current_page-part,self.current_page+part+1)
    
        def page_str(self):
            page_list = []
            first_page = '<li><a href="/index2.html?p=1">首页</a></li>'
            page_list.append(first_page)
            if self.current_page == 1:
                prev_page = '<li><a href="#">上一页</a>'
            else:
                prev_page = '<li><a href="/index2.html?p=%s">上一页</a></li>'%(self.current_page-1)
            page_list.append(prev_page)
            for i in self.page_num_range():
                if i == self.current_page:
                    temp = '<li class="active"><a href="/index2.html?p=%s">%s</a></li>' % (i, i)
                else:
                    temp = '<li><a href="/index2.html?p=%s">%s</a></li>'%(i,i)
                page_list.append(temp)
            if self.current_page == self.num_pages:
                next_page = '<li><a href="#">下一页</a></li>'
            else:
                next_page = '<li><a href="/index2.html?p=%s">下一页</a></li>' % (self.current_page + 1)
            page_list.append(next_page)
            last_page = '<li><a href="/index2.html?p=%s">尾页</a></li>'%self.num_pages
            page_list.append(last_page)
            return ''.join(page_list)
    • 创建views函数
    from django.shortcuts import render
    from django.core.paginator import Paginator,EmptyPage,PageNotAnInteger
    
    USER_LIST = []
    for i in range(1,600):
        temp = {'name':'root' + str(i),'age':i}
        USER_LIST.append(temp)
    
    def index2(request):
        from app.pager import Pagination
        current_page = request.GET.get('p')
        page_obj = Pagination(600,current_page)
        data = USER_LIST[page_obj.start():page_obj.end()]
        return render(request,'index2.html',locals())
    • 创建index2.html,这里需要引入bootstrap的一个组件
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css"/>
    </head>
    <body>
        <ul>
            {% for row in data %}
                <li>{{ row.name }} - {{ row.age }}</li>
            {% endfor %}
        </ul>
        <ul class="pagination">
            {{ page_obj.page_str|safe }}
        </ul>
    </body>
    </html>

    总结,分页时需要做三件事:

    • 创建处理分页数据的类

    • 根据分页数据获取数据

    • 输出分页HTML,即:[上一页][1][2][3][4][5][下一页]

    七、序列化

      序列化就是把对象转换成可以保存在本地文件中的数据类型,关于Django中的序列化主要应用在将数据库中检索的数据返回给客户端用户,特别的Ajax请求一般返回的为Json格式。

    1、Python中dumps就是序列化,loads就是反序列化;

    2、JavaScript中

        对象转换成字符串 -- str = JSON.stringify({'k':'v'})
        字符串转换成对象 -- dict = JSON.parse(str)

    3、三种情况

    QuerySet内部是:

    • 对象(用all,filter获取数据):serializers.serializer('json',QuerySet)

    • 字典(用values获取):list(QuerySet)

    • 元组(用values_list获取):list(QuerySet)

    from django.core import serializers
    import json def get_data(request): response = {'status': True, 'data': None} try: user_list = UserInfo.objects.all() # serializers只能序列化queryset对象,转换为字符串 response['data'] = serializers.serialize('json', user_list) #如果queryset内部是字典或元组,values和values_list获取,道理一样 user_list = UserInfo.objects.values('id','username') #只需要把外部的queryset变成列表形式,就可以直接json序列化了 response['data'] = list(user_list) except Exception as e: response['status'] = False ret = json.dumps(response) return HttpResponse(ret)

     4、由于json.dumps时无法处理datetime日期,所以可以通过自定义处理器来做扩展,如:

    import json
    from datetime import date
    from datetime import datetime
    
    class JsonCustomEncoder(json.JSONEncoder):
        def default(self, field):
            if isinstance(field, datetime):
                return field.strftime('%Y-%m-%d %H:%M:%S')
            elif isinstance(field, date):
                return field.strftime('%Y-%m-%d')
            elif isinstance(field, Response):
                return field.__dict__    # 对象以字典的方式输出
            else:
                return json.JSONEncoder.default(self, field)
    
    class Response:
        def __init__(self):
            self.status = True
            self.data = 'charlie'
    
    data = {
        'k1': datetime.now(),
        'k2': Response(),
    }
    ds = json.dumps(data, cls=JsonCustomEncoder)
    print(ds)
    # {"k1": "2019-03-08 16:18:28", "k2": {"status": true, "data": "charlie"}}
  • 相关阅读:
    GRUB引导界面背景图片制作完整教程
    git遇到问题 Flandre
    NOIP2021 比赛记录 Flandre
    一个不错的回车提交按钮
    防止SQL注入
    NLog 不能些日志
    ajax 加载partial view ,并且 附加validate验证
    linq 常用查询
    Android开发工具问题之ADTversion
    C# 实现一个简单的FTP服务器
  • 原文地址:https://www.cnblogs.com/charliedaifu/p/10182232.html
Copyright © 2011-2022 走看看