• Django-C005-说说MVT之外的事情


    此文章完成度【100%】留着以后忘记的回顾。多写多练多思考,我会努力写出有意思的demo,如果知识点有错误、误导,欢迎大家在评论处写下你的感想或者纠错。 

    【Django version】: 2.1

    【pymysql version】:0.9.3

    【python version】: 3.7

    【Pillow version】:6.0.0

    常用


     到此为止,关于Django框架的三大块MVT已经告一段落,让我们扩充一些Django其他的功能,为了更好的完成开发,而努力吧

    主要知识点如下:

    1. 静态文件处理

    2. 中间件

    3. 上传图片

    4. admin站点

    5. 分页

    6. 示例:省市区选择、jquery、ajax

    接下来才是每天都最重要的环节,重复重复不断重复的创建项目:  

    创建项目test5

    django-admin startproject test5

    进入到项目目录test5,创建应用school

    cd test5
    python manage.py startapp school

    在test5下的settings中的INSTALLED_APPS中注册应用

    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'school',
    ]

    在test5下的settings中的DATABASES中指定数据库引擎,并配置

    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',
            'NAME': 'school',
            'USER': 'root',
            'PASSWORD': 'toor',
            'HOST': 'localhost',
            'PORT': 3306,
        }
    }

    在test5下的settings中的TEMPLATS中添加模板路径

    TEMPLATES = [
        {
            'BACKEND': 'django.template.backends.django.DjangoTemplates',
            'DIRS': [os.path.join(BASE_DIR, 'templates')],
            'APP_DIRS': True,
            'OPTIONS': {
                'context_processors': [
                    'django.template.context_processors.debug',
                    'django.template.context_processors.request',
                    'django.contrib.auth.context_processors.auth',
                    'django.contrib.messages.context_processors.messages',
                ],
            },
        },
    ]

    创建模板目录,并将所有school的html添加到school这个文件夹中

    在test5下的urls.py,添加url配置指向到school应用下的urls.py中

    from django.contrib import admin
    from django.urls import path, include
    
    app_url_patterns = ('school.urls', 'school')
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('', include(app_url_patterns, namespace='school')),
    ]

    在school应用下创建urls.py 并且添加index测试路径

    from django.urls import re_path, include
    from . import views
    urlpatterns=[
        re_path(r'^$', views.index, name='index'),
    ]

    在school应用下的views.py中创建index视图

    from django.shortcuts import render
    
    def index(request):
        return render(request, 'school/index.html')

    在模板templates/school目录下创建index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>常用技术练习</title>
    </head>
    <body>
        <h1>常用技术练习</h1>
    </body>
    </html>

    在应用school下的模型models.py中定义AreaInfo

    from django.db import models
    
    class AreaInfo(models.Model):
        """地区模型类"""
        name = models.CharField(max_length=50)
        area_parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True)

     静态文件


    项目中的CSS、图片、js都是静态文件。一般会将静态文件放到一个单独的目录中,以方便管理。在html页面中调用,也需要指定静态文件的路径,django中提供了一个解析的方式配置静态文件路径。静态文件可以放在项目根目录下,也可以放在应用的目录下, 由于有些静态文件在项目中是通用,所以推荐放在项目的根目录下、方便使用。 

    创建静态文件存放目录 ,这里选择的是在项目的根目下创建,并且分别常见css、js、img目录

    在应用下的urls.py中创建img_static的url规则

    from django.urls import re_path, include
    from . import views
    
    
    urlpatterns=[
        re_path(r'^$', views.index, name='index'),
        re_path(r'^img_static$', views.img_static),
    ]

    在视图views.py中定义img_static视图函数

    from django.shortcuts import render
    
    ...
    
    def img_static(request):
        return render(request, 'school/img_static.html')

    在templates/school下创建img_static.html

    {% load static %}
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Document</title>
    </head>
    <body>
        <img src="{% static 'img/cat.jpg'%}">
        <img src="static/img/cat.jpg">
    </body>
    </html>

    当然这些还是不够的,最重要的一步就是,在test5/settings文件中配置路径

    STATIC_URL = '/static/'
    
    STATICFILES_DIRS = [
        os.path.join(BASE_DIR, 'static'),
    ]

    保存图片到static/img/目录下

    访问127.0.0.1:8000/img_static,两个img标签的图片都显示在浏览器中,所以这两种写路径的方式都可以。

    配置静态文件

    Django提供了一种配置,可以在html页面中隐藏真是路径

    <1> 在test5/settings.py中修改STATIC_URL项

    # STATIC_URL = '/static/'
    STATIC_URL = '/abc/'

    再次访问127.0.0.1:8000/img_static,可以看出动态获取的还是可以显示,但是静态写入的地址就不能生效了

     

    为了安全考虑可以通过配置项隐藏真实图片路径

    注意:这种方案可以隐藏真实的静态文件路径,但是结合Nginx布署时,会将所有的静态文件都交给Nginx处理,而不用转到Django部分,所以这项配置就无效了。

    如果你想禁用一些非法操作的用户,那么你需要怎么呢?

    当然就是获取用户的IP将它废掉,那么如何获取IP呢,我就不再罗嗦

    # 获取浏览器端的IP地址
    # 使用request对象的META属性
    request.META['REMOTE_ADDR']

    既然要封其IP,断其连接,那就是我们应用下的所有网页都不允许访问,我们这就可以写成一个函数装饰器

    在视图中views.py 定义装饰器函数

    from django.shortcuts import render
    from django.http import HttpResponse
    
    # 禁用的IP列表
    EXCLUDE_IPS = ['192.168.3.5']  # 这个ip是我自己的,
    
    def blocked_ips(view_func):
        """阻止访问IP的装饰器函数"""
        def wrapper(request, *view_args, **view_kwargs):
            user_ip = request.META['REMOTE_ADDR']  # 获取用户的ip
            if user_ip in EXCLUDE_IPS:
                            # 直接返回一个response页面给他
                return HttpResponse('<h1>禁用访问111</h1>')
            return view_func(request, *view_args, **view_kwargs)
        return wrapper 
    
    # 将所有禁止访问的页面都添加一个函数装饰器
    @blocked_ips  # 阻止IP访问的函数装饰器
    def index(request):
        user_ip = request.META['REMOTE_ADDR']
        return render(request, 'school/index.html')

     访问127.0.0.1:8000 因为禁止的是我自己ip,所以成功得到了禁止访问111

    那么你会说这依然还是很麻烦。那么就让我们学习一下中间件,看看他当中是否有能解决,我们这个问题的能力

    中间件


    Django中的中间件是一个轻量级、底层的插件系统,可以介入Django的请求和响应处理过程,修改Django的输入或输出。中间件的设计为开发者提供了一种无侵入式的开发方式,增强了Django框架的健壮性,其他的MVC框架也有这个功能,名称为IoC。

     

    >>> : 接下来是插播,理解不深也不用着急,下面会有更全面的解释 

    那么接下来说人话,就好比刚才用函数装饰器来阻断一个ip的访问,我们会在他访问每一个页面的时候返回一个response给他。所以他看到的是我们准备给他的一个针对他的页面。如果启用中间件的话,就比那个简单得多了,首先应用好比一个大楼,在发来请求的时候,在访问视图之前,在通过url匹配之后,我们准备函数阻断他的行为,这样就不需要在每一个函数上给装了一个防盗门(装饰器),就好比直接在大楼外安插了一个保安对每一个违规请求者, 在直接拒绝在大门外,之后的扩展的新视图函数也不需要依次添加装饰器。

    <1> 将之前的装饰器注释掉,或者删除掉,因为你了解了这个方法,就不会在管那些装饰器啥啥的。

    <2> 在school应用下创建一个中间件名字叫middleware.py的文件,并在里面创建一个BlankedIPMiddleware这个中间件类 

      < 2-1 > 简单的讲解一下,先从我们导入的这个类说起 【from django.utils.deprecation import MiddlewareMixin】,为啥是它,因为NB,为啥NB,Django提供了 django.utils.deprecation.MiddlewareMixin 来简化中间件类的创建,这些中间件类同时兼容 MIDDLEWARE 和 旧的MIDDLEWARE_CLASSES。所以我们这里就继承自MiddlewareMixin

    在school目录下创建middleware.py文件

    from django.http import HttpResponse
    from django.utils.deprecation import MiddlewareMixin
    
    
    class BlockedIPMiddleware(MiddlewareMixin):
      """阻断ip访问的中间件类"""
        EXCLUDE_IPS = ['192.168.3.5','127.0.0.1']  # 定义一个类属性,来记录所有阻止访问的IP
      
       # 处理视图前,url匹配成功后,调用。
        # 返回None就会继续接下来的操作,什么都没有发生一样或直接返回HttpResponse对象阻断那些不友好者
        def process_view(self, request, view_func, *view_args, **view_kwargs):
            user_ip = request.META['REMOTE_ADDR']  # 获取当前的浏览器提交的IP
            if user_ip in BlockedIPMiddleware.EXCLUDE_IPS:  
                return HttpResponse('<h1>通过中间件禁止访问</h1>')  

    <3>在test5/settings中注册中间件

    MIDDLEWARE = [
        'django.middleware.security.SecurityMiddleware',
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.common.CommonMiddleware',
        'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
        'school.middleware.BlockedIPMiddleware',  # 注册中间件
    ]

    <4>访问12.0.0.1:8000

     

     

    每个中间件组件都负责执行一些特定的功能,例如,Django中包含的一个中间件组件,AuthenticationMiddleware它将用户与使用的请求会话关联起来。接下来我将讲解一下中间件的工作原理,如何注册中间件,你如何创建自己的中间件,Django有一些内置的中间件,您可以立即使用它们,他们已经设置在settings中的MIDDLEWARE中,并且后续需要注册的中间件也在这里:

    MIDDLEWARE = [
        'django.middleware.security.SecurityMiddleware',
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.common.CommonMiddleware',
        'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
    ]

    中间件将获取一个get_response 然后返回一个中间件,它接受请求并返回响应,就像视图一样。例如:

    def simple_middleware(get_response):
        # 只初始化一次
    
        def middleware(request):
            # 这里的代码会在每一个请求之前执行
            # 调用视图(和之后的中间件)
    
            response = get_response(request)
    
            # 这里的代码会在请求/响应之后执行
            # 视图已经被调用
    
            return response
    
        return middleware

    或者他也可以写成一个类

    class SimpleMiddleware:
        def __init__(self, get_response):
            self.get_response = get_response
            # 只初始化一次
    
        def __call__(self, request):
            # 这里的代码会在每一个请求之前执行
            # 调用视图(和之后的中间件)
    
            response = self.get_response(request)
    
            # 这里的代码会在请求/响应之后执行
            # 视图已经被调用
    
            return response

    初始化:必须只有get_response参数,你也可以使用中间件初始化一些全局状态。请注意这两点:

    • Django的get_response只是来初始化你的中间件,不能在这里定义其他参数
    • 当Web应用运行时,__init__() 只被调用一次
      def __init__(self, get_response):
          self.get_response = get_response

    在请求阶段,在调用视图之前,Django按中间件中定义的顺序自上向下定义中间件。您可以把它看作是一个洋葱:每个中间件类都是一个“层”,它们包裹在视图之外,视图位于洋葱的核心。如果请求通过洋葱的所有层(每个层都调用get_response将请求传递到下一层),一直传递到核心视图,那么在返回的过程中,响应将通过每一层(在有内向外)。

    除了前面描述的基本请求/响应中间件模式之外,您还可以向基于类的中间件添加其他三种特殊方法:

    <1> 处理视图前:在每一个请求上,视图函数调用之前,返回None找到指定的视图或HttpResponse对象直接返回:

    def process_view(self, request, view_func, *view_args, **view_kwargs):
        pass

    它会返回None或者HttpResponse对象,如果返回None,Django将继续处理这个请求,执行其他中间件中的process_view(),然后执行匹配的视图。如果它返回HttpResponse对象,Django就不会调用视图,它将把响应中间件应用到HttpResponse并返回结果。

    注意:请不要在中间件中访问request.POST

    <2> 异常处理:当视图抛出异常时调用,在每一个请求上调用,返回一个HttpResponse对象返回给浏览器,否则,将启动缺省异常处理:

    process_exception(self, request,exception)

    <3>模版响应:在视图完成后执行,如果响应实例具有render()方法,表明它是一个模板响应或等效的响应。

    process_template_response(request, response)

    中间件常见的用途:缓存、会话认证、日志记录、异常

    中间件执行流程

    演示

    在school下的middleware.py中创建中间件类:

    class MyMiddleware:
        
        def __init__(self, get_response):
            print('-----init-----')
            self.get_response = get_response
    
        def __call__(self, request):
            print("中间件开始")
            response = self.get_response(request)
            print("中间件结束")
            return response
    
        def process_view(self, request, view_func, *view_args, **view_kwargs):
            print("请求实际函数前执行")

    在settings.py中,向MIDDLEWARE中注册中间件:

    MIDDLEWARE = [
        'django.middleware.security.SecurityMiddleware',
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.common.CommonMiddleware',
        'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
        # 'school.middleware.BlockedIPMiddleware',
        'school.middleware.MyMiddleware',   
    ]

    修改views.py中index视图:

    from django.shortcuts import render
    
    def index(request):
        print("----index 视图执行-----")
        return render(request, 'school/index.html')

    运行服务器

    访问127.0.0.1:8000,然后再访问一次

     

    __init__()只会在web服务运行时加载一次

    注意:如果多个注册的中间件类中都有process_exception的方法,则先注册的后执行。

     

    Admin站点


    内容发布的部分由网站的管理员负责增、删、改、查数据的,开发这些重复的功能是一件单调乏味、缺乏创造性的工作,为此,Django能够根据自定义的模块类自动地生成管理模块。

    在第一部分对管理站点坐了简单的介绍,现在我们将零碎的知识扩充一下, 在Django项目中默认启用admin管理站点。

    第一步:准备工作:创建管理员的用户名和密码。

    python manage.py createsuperuser

    第二步:使用在应用的admin.py中注册模型类

    from django.contrib import admin
    from school.models import AreaInfo
    
    admin.site.register(AreaInfo)  # 将区域模型类注册到Admin站点

    第三步:访问127.0.0.1:8000/admin

    列表页显示效果

     更改管理页展示效果

    ModelAdmin类可以控制模型admin界面中的展示方式,主要包括在列表页的展示方式、添加修改页面的展示方式

    第一步:在应用下的admin.py中,注册魔心那个累前定义管理类AreaAdmin

    from django.contrib import admin
    from school.models import AreaInfo
    
    
    class AreaAdmin(admin.ModelAdmin):
        pass
    
    
    admin.site.register(AreaInfo)

    管理类有两种使用方式:注册参数、装饰器

    注册参数:打开应用下的admin.py文件,注册模型类

    admin.site.register(AreaInfo, AreaAdmin)

    装饰器:打开应用下的admin.py文件,在管理类上注册模型类

    from django.contrib import admin
    from school.models import AreaInfo
    
    @admin.register(AreaInfo)
    class AreaAdmin(admin.ModelAdmin):
      pass

    更改列表页展示效果

    列表页选项

    页大小

    默认每页显示100条数据,list_per_page属性

    list_per_page = 100

    打开应用下的admin.py文件,修改AreaAdmin类

    from django.contrib import admin
    from school.models import AreaInfo
    
    @admin.register(AreaInfo)
    class AreaAdmin(admin.ModelAdmin):
        list_per_page = 20
        

    访问127.0.0.1:8000/admin/school/areainfo/

    “操作选项”的位置

    顶部显示的属性,设置为True在顶不显示,设置为False不在顶部显示,默认为True

    actions_on_top = True

    效果区分

    底部显示的属性,设置为True在底部显示,默认为False

    actions_on_bottom = True  # 设置选项在底部,默认值是False

    列表中的列

    属性

    list_display = ['id', 'name', 'area_parent_id']  # 借用AreaInfo模型类的信息展示

    点击列头可以进行排序的转换

    将方法作为列

    列可以是模型里的字段,也可以是模型里的方法,如果列表项显示的是方法,该方法必须要有返回值

    修改一:在应用下的models.py文件中,修改AreaInfo类:

    from django.db import models
    
    class AreaInfo(models.Model):
        """地区模型类"""
        name = models.CharField(max_length=50)
        area_parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True)
    
        def title(self):
            return str(self.name) + ":" +str(self.id) 

    修改二:在应用下的admin.py文件中,将方法名添加到列表中

    from django.contrib import admin
    from school.models import AreaInfo
    
    @admin.register(AreaInfo)
    class AreaAdmin(admin.ModelAdmin):
        list_per_page = 20  # 每一页展示多少数据
        actions_on_top = True  # 设置选项在顶部,默认值是True
        actions_on_bottom = True  # 设置选项在底部,默认值是False
        list_display = ['id', 'name', 'area_parent_id', 'title']  # 添加了一个方法名(title)

    访问127.0.0.1:8000/admin/school/areainfo/?o=1

    方法列默认是不能排序的。如果需要排序,需要为方法指定排序依据

    在应用下的models.py文件中修改AreaInfo类

    title.admin_order_field = 'id'   # 设置title这个方法的排序依据为id

    列头上的1和2是排序的优先级

    列表题:列标题默认为属性或方法的名称,可以通过属性设置,需要先将模型字段封装成方法,在对方法使用这个属性,模型字段不能直接使用这个属性:short_description

    在应用下的models.py文件,修改AreaInfo类

    from django.db import models
    
    class AreaInfo(models.Model):
        """地区模型类"""
        name = models.CharField(max_length=50)
        area_parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True)
    
        def title(self):
            return str(self.name) + ":" +str(self.id)
    
        title.admin_order_field = 'id' 
        title.short_description = '区域名称:id'  # 更改列标题

    访问127.0.0.1:8000/admin/school/areainfo/?o=1

    关联对象

    优化访问关联对象显示它的name属性

    在应用下models.py文件中修改AreaInfo类:

    from django.db import models
    
    class AreaInfo(models.Model):
        """地区模型类"""
            ...
    
        def parent(self):
            if self.area_parent is None:
                return ""
            return self.area_parent.name
    
        parent.short_description = "父级地区"    

    在应用下的admin.py中修改list_display

    from django.contrib import admin
    from school.models import AreaInfo
    
    @admin.register(AreaInfo)
    class AreaAdmin(admin.ModelAdmin):
        list_per_page = 20  # 每一页展示多少数据
        actions_on_top = True  # 设置选项在顶部,默认值是True
        actions_on_bottom = True  # 设置选项在底部,默认值是False
        # list_display = ['id', 'name', 'area_parent_id', 'title']
        list_display = ['id', 'name', 'parent', 'title']  # 修改为parent

    访问127.0.0.1:8000/admin/school/areainfo/?o=1

    右侧栏过滤器

    属性 list_filter

    只能接受字段,会将对应字段的值列出来,用于快速郭列。一般用于重复值的字段。

    在应用下的admin.py文件中修改AreaAdmin类

    from django.contrib import admin
    from school.models import AreaInfo
    
    @admin.register(AreaInfo)
    class AreaAdmin(admin.ModelAdmin):
        ...
        list_filter = ['name']

    访问127.0.0.1:8000/admin/school/areainfo/?o=1

     搜索框

    属性 search_fields

    用于对指定字段的值进行搜索,支持模糊查询。类型为列表,表示这些字段上进行搜索。

    在应用下的admin.py修改AreaAdmin类

    from django.contrib import admin
    from school.models import AreaInfo
    
    @admin.register(AreaInfo)
    class AreaAdmin(admin.ModelAdmin):
             ...
        search_fields = ['id','name']    

    访问127.0.0.1:8000/admin/school/areainfo/?o=1

    中文标题

    在应用下的models.py文件中修改AreaInfo模型类,为字段指定verbose_name参数。第一个参数

    from django.db import models
    
    
    class AreaInfo(models.Model):
        """地区模型类"""
        name = models.CharField('地区名称', max_length=50)
            ...

    访问127.0.0.1:8000/admin/school/areainfo/?o=1

     编辑页选项

    显示字段顺序

    属性fields,类型是个列表

    在应用下的admin.py文件,修改AreaAdmin类

    from django.contrib import admin
    from school.models import AreaInfo
    
    @admin.register(AreaInfo)
    class AreaAdmin(admin.ModelAdmin):
        ...
        fields = ['area_parent', 'name']

    随便点击一行id,可以转到修改页面

     Area parent点开下拉列表你会发现输出的都是AreaInfo object(id),这样会非常不好识别,选择父级非常的麻烦,这样我就需要想一个解决办法,把它格式化成字符串。

    str方法考虑一下,在应用下打开models.py,修改AreaInfo类,添加str方法

    from django.db import models
    
    class AreaInfo(models.Model):
        """地区模型类"""
            ...
        def __str__(self):
            return self.name

    分组显示

    属性fieldset

    fieldset=(
        ('组1标题',{'fields':('字段1','字段2')}),
        ('组2标题',{'fields':('字段3','字段4')}),
    )

    在应用下的admin.py文件修改AreaAdmin类:

    from django.contrib import admin
    from school.models import AreaInfo
    
    @admin.register(AreaInfo)
    class AreaAdmin(admin.ModelAdmin):
           ...
        # fields = ['area_parent', 'name']
        fieldset = (
            ('基本',{'fields': ['name']}),
            ('高级',{'fields': ['area_parent']}),
        )
            

    随便点击一个id

    注意: fields与fieldsets两者需要选择一个使用

    关联对象

    在一对多的关系中,可以在一这边的编辑页面中编辑多这边的对象,嵌入多这边的对象的方式包括表格、块两种。类型是InlineModelAdmin:表示在模型的编辑页面嵌入关联模型的编辑,子类TabularInline:以表格的形式嵌入。子类StackedInline:以块的形式嵌入。

    StackedInline:在应用下的admin.py文件中创建AreaStackedInline类,并且修改AreaAdmin类

    from django.contrib import admin
    from school.models import AreaInfo
    
    class AreaStackedInline(admin.StackedInline):
        model = AreaInfo  # 关联子对象
        extra = 2  # 额外编辑2个子对象
    
    
    @admin.register(AreaInfo)
    class AreaAdmin(admin.ModelAdmin):
        list_per_page = 20  # 每一页展示多少数据
        actions_on_top = True  # 设置选项在顶部,默认值是True
        actions_on_bottom = True  # 设置选项在底部,默认值是False
        # list_display = ['id', 'name', 'area_parent_id', 'title']
        list_display = ['id', 'name', 'parent', 'title']
        list_filter = ['name']
        search_fields = ['id','name']
        # fields = ['area_parent', 'name']
        fieldsets = (
            ('基本',{'fields': ['name']}),
            ('高级',{'fields': ['area_parent']}),
        )
    
        inlines = [AreaStackedInline]

    刷新浏览器,下面将显示出所有关联:北京市:110100 的区域,并且还有可以额外增加两条地区的地方

    下面我们感受一下表格的形式嵌入式什么感觉,注释你刚才添加块的代码。

    TabularInline:在应用下修改admin.py文件,并创建AreaTabularInline类:

    from django.contrib import admin
    from school.models import AreaInfo
    
    
    # class AreaStackedInline(admin.StackedInline):
    #   model = AreaInfo  # 关联子对象
    #   extra = 2  # 额外编辑2个子对象
    
    
    class AreaTabularInline(admin.TabularInline):
        model = AreaInfo  # 关联子对象
        extra = 2  # 额外编辑2个子对象
    
    
    @admin.register(AreaInfo)
    class AreaAdmin(admin.ModelAdmin):
        list_per_page = 20  # 每一页展示多少数据
        actions_on_top = True  # 设置选项在顶部,默认值是True
        actions_on_bottom = True  # 设置选项在底部,默认值是False
        # list_display = ['id', 'name', 'area_parent_id', 'title']
        list_display = ['id', 'name', 'parent', 'title']
        list_filter = ['name']
        search_fields = ['id','name']
        # fields = ['area_parent', 'name']
        fieldsets = (
            ('基本',{'fields': ['name']}),
            ('高级',{'fields': ['area_parent']}),
        )
    
        # inlines = [AreaStackedInline]
        inlines = [AreaTabularInline]

    刷新页面,点击id进入修改模式,多观察一些数据

     

     

    重写模板


     

    第一步:在templates目录下创建admin目录

    第二步:找到Django的项目下的admin文件夹,大家的路径应该都不一样。大概流程是你安装的Python路径下的Lib中找到一个叫site-packages的文件夹下面会有你安装的Django在找到contrib下就会有admin

    C:UsersCircleAppDataLocalProgramsPythonPython37-32Libsite-packagesdjangocontribadmin

    第三步:将需要更改文件拷贝到自己项目下的templates里的admin,此处以base_site.html为例:

    第四步:编辑base_site.html文件:

    {% extends "admin/base.html" %}
    
    {% block title %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %}
    
    {% block branding %}
    <h1 id="site-name"><a href="{% url 'admin:index' %}"><h1>自定义的管理页模板</h1>{{ site_header|default:_('Django administration') }}</a></h1>
    {% endblock %}
    
    {% block nav-global %}{% endblock %}

    刷新网页

    多熟悉一下你想更改的模板的源码。你就可以创建一个属于你的模板,想更改什么就去寻找什么

    上传图片


     

    在Python中进行图片操作,需要安装PIL,可以使用pip安装

    在Django中上传图片包括两种方式:

    • 管理页面admin中上传

    • 自定义form表单中上传图片

    上传图片后,将图片存储在服务器上,然后将图片的路径存储在表中。

    创建包含图片的模型类

      将模板类的属性定义成models.ImageField类型。

    在应用下的models.py文件中定义PicTest模型类

    from django.db import models
    
    class PicTest(models.Model):
        pic = models.ImageField(upload_to='school')

    生成迁移文件,执行迁移

    python manage.py makemigrations
    python manage.py migrate

    打开项目下的settings.py文件设置图片保存到static目录下

    MEDIA_ROOT=os.path.join(BASE_DIR,"static/media")

    在static目录下创建media目录,在创建应用名的目录

    在管理页面admin中上传图片


    在应用下的admin.py文件,注册PicTest

    admin.site.register(PicTest)

    刷新页面,点击PicTest的增加,选择文件,然后找一张图片保存

     

    查看数据库是否添加成功

    数据保存的位置:staticmediaschool

    自定义form表单中上传图片


    在应用下的views.py文件创建视图pic_upload

    from django.shortcuts import render
    ...
    def pic_upload(request):
        return render(request, 'school/pic_upload.html')

    在应用下的urls.py文件中配置url

    re_path(r'^pic_upload$', views.pic_upload),

    在templates下的school目录中创建pic_upload.html

    在模板中定义上传表单要求:

    • form的属性enctype="multipart/form-data"
    • form的method为post
    • input类型为file
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>上传图片</title>
    </head>
    <body>
        <form method="POST" action="/pic_handle" enctype="multipart/form-data">
            {%csrf_token%}
            <input type="file" name="pic"><br>
            <input type="submit" value="上传">
        </form>
    </body>
    </html>

    在应用views.py文件中创建视图pic_handle,用于接收表单保存图片,request对象的FILES属性用于接收请求的文件、图片

    from django.http import HttpResponse
    from django.conf import settings
    
    def pic_handle(request):
        p = request.FILES.get('pic')  
        p_name = '%s/school/%s' % (settings.MEDIA_ROOT, p.name)
        with open(p_name, 'wb') as pic:
            for c in p.chunks():
                pic.write(c)
        return HttpResponse('OK')

    在应用下的urls.py 中配置url

    re_path(r'^pic_handle$', views.pic_handle),

    访问127.0.0.1:8000/pic_upload

    会获得pic_handle返回的ok,查看static/media/school下是否的到文件

     修改视图pic_handle,添加写入数据库的功能

    from django.http import HttpResponse
    from django.conf import settings
    from school.models import PicTest 
    
    def pic_handle(request):
        p = request.FILES.get('pic')
        p_name = '%s/school/%s' % (settings.MEDIA_ROOT, p.name)
        with open(p_name, 'wb') as pic:
            for c in p.chunks():
                pic.write(c)
        pic_db = PicTest()  # 创建PicTest模型类对象
        pic_db.pic = 'school/' + p.name 
        pic_db.save()
        return HttpResponse('OK')

    查看数据库中是否写入

    显示图片


    在应用的views.py文件中创建pic_show视图 

    from django.shortcuts import render
    from school.models import PicTest 
    
    def pic_show(request):
        pt = PicTest.objects.all()
        context = {'pt': pt}
        return render(request, 'school/pic_show.html', context)

    在应用下的urls.py配置url

    re_path(r'^pic_show$', views.pic_show),

    在templates在school中创建pic_show.html文件

    {% load static %}
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>显示图片</title>
    </head>
    <body>
        {%for pic in pt%}
        <img src="{%static '' %}media/{{pic.pic}}">
        {%endfor%}
    </body>
    </html>

    访问127.0.0.1:8000/pic_show

    分页


    Django提供了数据分页的类,这些类被定义在django/core/paginator.py中,类Paginator用于对列进行一页n条数据的分页运算,类Page用于表示第m页的数据

    Paginator类实例对象

    • 方法_init_(列表,int):返回分页对象,第一个参数为列表数据,第二个参数为每页数据的条数。

    • 属性count:返回对象总数。

    • 属性num_pages:返回页面总数。

    • 属性page_range:返回页码列表,从1开始,例如[1, 2, 3, 4]。

    • 方法page(m):返回Page类实例对象,表示第m页的数据,下标以1开始。

    Page类实例对象

    • 调用Paginator对象的page()方法返回Page对象,不需要手动构造。

    • 属性object_list:返回当前页对象的列表。

    • 属性number:返回当前是第几页,从1开始。

    • 属性paginator:当前页对应的Paginator对象。

    • 方法has_next():如果有下一页返回True。

    • 方法next_page_number(): 返回下一页的页码

    • 方法has_previous():如果有上一页返回True。

    • 方法previous_page_number():返回前一页的页码

    • 方法len():返回当前页面对象的个数。

    演示:

    在应用下的views.py文件中创建视图page_test

    from django.shortcuts import render
    from school.models import AreaInfo
    from django.core.paginator import Paginator
    def page_test(request, pIndex): # 查询所有的省 area_list = AreaInfo.objects.filter(area_parent__isnull = True) # 将地区信息按一页10条显示 paginator = Paginator(area_list, 10) # 默认获取第一页 if pIndex == '': pIndex = '1' # 将字符串转换成int类型 pIndex = int(pIndex) # 获取第pIndex页的数据 page = paginator.page(pIndex) return render(request, 'school/page_test.html', {'page': page})

    在应用下的urls.py文件中配置url

    re_path(r'^page(?P<pIndex>d*)$', views.page_test),

    在templates文件夹下的school中创建page_test.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>分页展示</title>
    </head>
    <body>
        显示当前页的地区信息:<br>
        <ul>
            {%for area in page%}
            <li>{{ area.id }} -- {{ area.name }}</li>
            {%endfor%}
        </ul>
        <hr>
        {%if page.has_previous%}
            <a href='/page{{ page.previous_page_number }}'>上一页</a>&nbsp;&nbsp;
        {%else%}
            上一页
        {%endif%}
        {%for pindex in page.paginator.page_range%}
            {%if page.number == pindex%}
                {{pindex}}&nbsp;&nbsp;
            {%else%}
                <a href='/page{{ pindex }}'>{{pindex}}</a>&nbsp;&nbsp;
            {%endif%}
        {%endfor%}
        {%if page.has_next%}
            <a href='/page{{ page.next_page_number }}'>下一页</a>&nbsp;&nbsp;
        {%else%}
            下一页
        {%endif%}
    </html>

    访问127.0.0.1:8000/page

    既然省地区信息的已经可以在页面显示,那么我们做一个关于地区的三级联动案例,这个案例需要在Django中使用到jquery的ajax进行数据交互,jquery框架提供了三种方法用于异步交互,$.ajax、$.get、$.post,由于CSRF约束,我们这里也只是查询信息,所以使用$.get演示:先观看一下最终实现效果:

    第一步:将jquery文件拷贝到static/js目录下

    第二步:在应用下的views.py文件中,添加areas视图

    def areas(request):
    return render(request, 'school/areas.html')

    第三步:在应用下的urls.py中配置url

    re_path(r'^areas$', views.areas),

    第四步:在templates中的school下创建areas.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>地区三级联动</title>
        {%load static%}
        <script type="text/javascript" src='{% static "js/jquery-1.12.4.min.js"%}'></script>
        <script type="text/javascript">
            $(function(){
                // ajax post请求方式的简写 第一个参数是访问的url,第二个参数是传入的数据, 第三个参数回调函数
                // $.post('/prov', {'val':'value'}, function(data){
                // })
                //ajax get请求的简写,第一个参数是请求的url,第二个参数是回调函数
                $.get('/prov', function(data){
                    // 回调函数
                    // 获取省列表
                    pro_data = data.area_data
                    pro = $('#pro')
                    // 遍历省的列表
                    for (var i = 0; i < pro_data.length; i++) {
                        //将id和name分别获取
                        id = pro_data[i][0]
                        name = pro_data[i][1]
                        // 格式化option标签
                        option_str = '<option value=' + id + '>' + name + '</option>'
                        // 将每一个标签添加到页面
                        pro.append(option_str)
                    }
    
                })
    
                //绑定pro下拉列表框的change事件,获得省下面的市的信息
                $('#pro').change(function(){
                    //发起一个ajax请求,/city 获取省下面的市
                    //获取点击省的id
                    pro_id = $(this).val()
    
                    $.get('/prov'+pro_id, function(data){
                        //回调函数
                        city_data = data.area_data
                        // append默认是追加模式,如果不清空,每次求情都会在最后面追加
                        city = $('#city')
                        city.empty().append("<option>----请选择市区----</option>")
                        dis = $('#dis')
                        dis.empty().append("<option>----请选择市区----</option>")
                        // 遍历的第二种方式
                        $.each(city_data, function(index, item){
                            id = item[0]
                            name = item[1]
                            // console.log(id)  //测试
                            // console.log(name)  //测试
                            option_city = '<option value=' + id + '>' + name + '</option>'
                            city.append(option_city)
                        })
    
                    })
                })
    
                            //绑定pro下拉列表框的change事件,获得省下面的市的信息
                $('#city').change(function(){
                    //发起一个ajax请求,/city 获取省下面的市
                    //获取点击省的id
                    city_id = $(this).val()
    
                    $.get('/prov'+city_id, function(data){
                        //回调函数
                        dis_data = data.area_data
                        dis = $('#dis')
                        dis.empty().append("<option>----请选择市区----</option>")
                        $.each(dis_data, function(index, item){
                            id = item[0]
                            name = item[1]
                            // console.log(id)
                            // console.log(name)
                            option_city = '<option value=' + id + '>' + name + '</option>'
                            dis.append(option_city)
                        })
    
                    })
                })
                
            })
        </script>
    
    </head>
    <body>
        <h1 style='text-align: center'>省市区列表</h1>
        <div>
            <select id="pro">
                <option >----请选择省----</option>
            </select>
            <select id='city'>
                <option>----请选择市区----</option>
            </select>
            <select id='dis'>
                <option>----请选择区县----</option>
            </select>
        </div>
    </body>
    </html>

    第五步:在应用下views.py添加prov视图

    from django.http import JsonResponse
    def prov(request, parent_id):
        # 如果为空说明请求的是省的下拉列表
        if parent_id == '':
            areas = AreaInfo.objects.filter(area_parent__isnull = True)
        else:
            # 否则就是请求parent_id所对应的下拉列表
            areas = AreaInfo.objects.filter(area_parent = parent_id)    
            
        # 用于添加json数据
        area_list = []
        # 遍历获得的区域列表,添加到json数据中
        for area in areas:
            area_list.append((area.id,area.name))
        # 返回json数据给页面
        return JsonResponse({'area_data':area_list})

    第六步:在应用下的urls.py文件中配置url

    re_path(r'^prov(?P<parent_id>d*)$', views.prov),

    第七步:访问127.0.0.1:8000/areas

    -The End-

     

  • 相关阅读:
    移动设备横竖屏判断 CSS 、JS
    Jquery监听value的变化
    设置了line-block的div会出现间隙
    移动端点击可点击元素时,出现蓝色默认背景色
    网页顶部进度条-NProcess.js
    ios UITableView
    ios UIScrollView
    ios Xcode 快捷方式
    ios常用方法、基础语法总结
    Mac eclipse Tomcat安装
  • 原文地址:https://www.cnblogs.com/Hannibal-2018/p/11105251.html
Copyright © 2020-2023  润新知