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:\Users\Circle\AppData\Local\Programs\Python\Python37-32\Lib\site-packages\django\contrib\admin

第三步:将需要更改文件拷贝到自己项目下的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的增加,选择文件,然后找一张图片保存

 

查看数据库是否添加成功

数据保存的位置:static\media\school\

 

 

自定义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-

 

 

 

 

 

 

posted @ 2019-06-28 23:23  Hannibal_2018  阅读(299)  评论(0编辑  收藏  举报