前提
已安装python3.X
安装完django
创建项目
#在命令行执行
#HelloWorld 项目名称
django-admin startproject HelloWorld
#进入项目
cd HelloWorld
目录说明
|-- HelloWorld
| |-- init.py #空文件,告诉Python这是一个Python的包
| |-- asgi.py #一个 ASGI 兼容的 Web 服务器的入口,以便运行你的项目。
| |-- settings.py #该 Django 项目的设置/配置。
| |-- urls.py #该 Django 项目的 URL 声明; 一份由 Django 驱动的网站"目录"。
|-- wsgi.py
-- manage.py #一个 WSGI 兼容的 Web 服务器的入口,以便运行你的项目。
运行项目
#确保已进入HelloWorld目录下
#多种运行项目的方式
python manage.py runserver
# 指定端口
python manage.py runserver 8080
# 任意ip接入
python manage.py runserver 0.0.0.0:8080
# 这里和settings.py的配置ALLOWED_HOSTS = []
# 不一样的地方在于,这个是服务器监听的IP,IP不对无法连接到服务;
# ALLOWED_HOSTS是已经连接到服务,但连接的IP不在ALLOWED_HOSTS范围内就无法正常提供服务
运行完项目访问 127.0.0.1:8000,就能看到一个绿色的小火箭!
视图和URL配置
注意import
HelloWorld 目录新建一个 views.py 文件
#目录:HelloWorld/HelloWorld/views.py
from django.http import HttpResponse
def hello(request):
return HttpResponse("Hello world ! ")
绑定 URL 与视图函数
from django.conf.urls import url
from . import views
urlpatterns = [
url(, views.hello),
]
path() 函数
path(route, view, kwargs=None, name=None)
- route: 字符串,表示 URL 规则,与之匹配的 URL 会执行对应的第二个参数 view。(可用正则表达式)
- view: 用于执行与正则表达式匹配的 URL 请求。
- kwargs: 视图使用的字典类型的参数。
- name: 用来反向获取 URL。
route:
- 对应端口号后的路径
- 例如:http://127.0.0.1:8000/hello/,则route = “hello/”
views:
- from . import views导入的
- views.hello 对应的是views中的一个函数
kwargs:
- 少用到,暂不学
模板
为什么需要模板?
在上文views的hello中使用 django.http.HttpResponse() 来输出 "Hello World!",该方式将数据与视图混合在一起,不符合 Django 的 MVC 思想
模板:模板是一个文本,用于分离文档的表现形式和内容。
建立模板
在 HelloWorld 目录底下创建 templates 目录并建立 runoob.html
项目结构
HelloWorld/
|-- HelloWorld
| |-- __init__.py
| |-- __init__.pyc
| |-- settings.py
| |-- settings.pyc
| |-- urls.py
| |-- urls.pyc
| |-- views.py
| |-- views.pyc
| |-- wsgi.py
| `-- wsgi.pyc
|-- manage.py
`-- templates
`-- runoob.html
HelloWorld/templates/runoob.html 文件代码
<h1>{{ hello }}</h1>
修改HelloWorld/settings.py
修改 TEMPLATES 中的 DIRS 为 [os.path.join(BASE_DIR, 'templates')]
- 注意导入:import os
...
import os
...
TEMPLATES = [
{
...
'DIRS': [os.path.join(BASE_DIR, 'templates')]
...
},
]
...
修改 views.py
from django.shortcuts import render
def runoob(request):
context = {}
context['hello'] = 'Hello World!'
return render(request, 'runoob.html', context)
#context可换成{"name":views_name}
修改HelloWorld/HelloWorld/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('runoob/', views.runoob),
]
模板语法
views中的变量与html中变量“绑定”
view:{"HTML变量名" : "views变量名"}
HTML:{{变量名}}
#html页面中
<h1>{{HTML变量名}}</h1>
#在views中
return render(request, 'runoob.html', {"HTML变量名" : "views变量名"})
网页导航和页脚
使用子模版继承父模版,减少代码冗余
父模板
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>base</title>
</head>
<body>
<h1>Hello World!</h1>
<p>父模板测试</p>
{% block mainbody %}
<p>base template</p>
{% endblock %}
</body>
</html>
子模板
{%extends "base.html" %}
- 继承了一个父模板(网页)
父模板的{% block 名称%}{% 名称%}
- 相当于留空位给子模版新增内容
- 标签是可以被继承者们替换掉的部分
{%extends "base.html" %}
{% block mainbody %}
<p>继承了 base.html 文件</p>
{% endblock %}
模型
Django 模型使用自带的 ORM。
- 对象关系映射(Object Relational Mapping,简称 ORM )用于实现面向对象编程语言里不同类型系统的数据之间的转换。
# 安装 mysql 驱动
pip install pymysql
在数据库软件中新建数据库
-- runoob 数据库名
create database runoob default charset=utf8;
修改 HelloWorld/HelloWorld/settings.py 中的DATABASES
DATABASES = {
'default':
{
'ENGINE': 'django.db.backends.mysql', # 数据库引擎
'NAME': 'runoob', # 数据库名称
'HOST': '127.0.0.1', # 数据库地址,本机 ip 地址 127.0.0.1
'PORT': 3306, # 端口
'USER': 'root', # 数据库用户名
'PASSWORD': '123456', # 数据库密码
}
}
告诉 Django 使用 pymysql 模块连接 mysql 数据库
# 在与 settings.py 同级目录下的 __init__.py 中引入模块和进行配置
import pymysql
pymysql.install_as_MySQLdb()
定义模型
Django 规定,如果要使用模型,必须要创建一个 app。
#命令行中输入
# TestModel与manage.py在同级目录下
django-admin.py startapp TestModel
创建表
修改 TestModel/models.py 文件
# models.py
from django.db import models
class Test(models.Model):
name = models.CharField(max_length=20)
类名代表了数据库表名(Test)
类里面的字段代表数据表中的字段(name)
数据类型则由CharField(相当于varchar)、DateField(相当于datetime)
max_length 参数限定长度。
告诉django我们安装的app
在 settings.py 中找到INSTALLED_APPS
- setting.py在HelloWorld目录下
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'TestModel', # 添加此项
)
命令行中运行
python manage.py migrate # 创建表结构
python manage.py makemigrations TestModel # 让 Django 知道我们在我们的模型有一些变更
python manage.py migrate TestModel # 创建表结构
#如果代码有报异常,继续往下翻。
第二行代码执行后
第三行代码执行后
该表结构
Django 会自动添加一个 id 作为主键。
异常
django.db.utils.OperationalError:(1045,"Access denied for user 'root'@'localhost'
- 确保用户名和密码正确后再试一次
- 可以用命令行测试数据库连接
- 开启服务:net start 服务名(例如:mysql5.7)
- mysql -h 主机名(ip) -u 用户名 -P 端口 -p
数据库操作
在 HelloWorld 目录中添加 testdb.py 文件,并修改 urls.py
#urls.py
from django.urls import path
from . import views,testdb
urlpatterns = [
path('runoob/', views.runoob),
path('testdb/', testdb.testdb),
]
添加数据
from django.http import HttpResponse
from TestModel.models import Test
# 数据库操作
def testdb(request):
test1 = Test(name='runoob')
test1.save()
return HttpResponse("<p>数据添加成功!</p>")
启动服务器并访问
python manage.py runserver #启动服务器
#访问 http://127.0.0.1:8000/testdb/
#看到页面输出:数据添加成功
查看数据库
获取数据、删除、修改
#获取
# 通过objects这个模型管理器的all()获得所有数据行,相当于SQL中的SELECT * FROM
list = Test.objects.all()
# filter相当于SQL中的WHERE,可设置条件过滤结果
response2 = Test.objects.filter(id=1)
# 获取单个对象
response3 = Test.objects.get(id=1)
# 限制返回的数据 相当于 SQL 中的 OFFSET 0 LIMIT 2;(分页)
Test.objects.order_by('name')[0:2]
#数据排序
Test.objects.order_by("id")
# 上面的方法可以连锁使用
Test.objects.filter(name="runoob").order_by("id")
#修改
#save() 或 update():
# 修改其中一个id=1的name字段,再save,相当于SQL中的UPDATE
test1 = Test.objects.get(id=1)
test1.name = 'Google'
test1.save()
# 另外一种方式
Test.objects.filter(id=1).update(name='Google')
# 修改所有的列
Test.objects.all().update(name='Google')
#删除
# 删除id=1的数据
test1 = Test.objects.get(id=1)
test1.delete()
# 另外一种方式
Test.objects.filter(id=1).delete()
# 删除所有数据
Test.objects.all().delete()
异常
问题:修改模型后无法更新数据库表结构,删除后再执行命令也不行。参考
解决:
删除app/migrations/目录下 “_pycache_” 文件
删除app下面目录migrations下面除了init.py其他的所有文件
删除对应数据库中django_migrations表中app名称所在的列
(或者delete from django_migrations where app=‘yourappname’;),解决无法生成表
表单
HTML表单是网站交互性的经典方式。
HTTP协议以"请求-回复"的方式工作。
- 客户发送请求时,可以在请求中附加数据。
- 服务器通过解析请求,就可以获得客户传来的数据,并根据URL来提供特定的服务。
GET
创建一个 search.py 文件,用于接收用户的请求
#/HelloWorld/HelloWorld/search.py 文件
from django.http import HttpResponse
from django.shortcuts import render
#表单
def search_form(request):
return render(request,'search_form.html')
#接收请求数据
def search(request):
request.encoding='utf-8'
if('q' in request.GET and request.GET['q']):
message = '您搜索的内容为:' + request.GET['q']
else:
message = '您提交了空表单'
return HttpResponse(message)
模板目录 templates 中添加 search_form.html 表单
#/HelloWorld/templates/search_form.html 文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>form_test</title>
</head>
<body>
<form action="/search/" method="GET">
<input type="text" name="q">
<input type="submit" value="搜索">
</form>
</body>
</html>
urls.py 规则 添加以下代码
使用url能匹配正则表达式(path好像不行)
...
from django.conf.urls import url
urlpatterns = [
...,
url(r'^search-form/$',search.search_form),
url(r'^search/$',search.search)
]
访问 http://127.0.0.1:8000/search-form/,搜索后提交即可看到效果
GET请求 视图显示和请求处理分成两个函数处理。
POST
提交数据时更常用POST方法。
- 一个URL和处理函数,同时显示视图和处理请求。
Request对象
表单form通过HTTP POST方法提交请求,但是表单中可以没有数据。
因此,不能使用语句if request.POST来判断是否使用HTTP POST方法;
应该使用if request.method == "POST"
路由
Django不会匹配域名和协议
举例:
请求:访问http://127.0.0.1:8000/polls/34/
Django项目到urls中顺序进行正则匹配/polls/34/,匹配到polls/,就切掉文本"polls/",剩余文本"34/"继续进行匹配
- path('polls/',include('polls.urls'))
- path('<int:question_id>/', views.detail, name='detail')
匹配到了views.detail
得到参数
question_id=34
由<int:question_id>
匹配生成。
- 使用尖括号“捕获”这部分 URL,且以关键字参数的形式发送给视图函数。
- 上述字符串的
:question_id>
部分定义了将被用于区分匹配模式的变量名int:
则是一个转换器决定了应该以什么变量类型匹配这部分的 URL 路径。
路由简单的来说就是根据用户请求的 URL 链接来判断对应的处理程序,并返回处理结果,
- URL 与 Django 的视图建立映射关系。
Django 路由在 urls.py 配置,urls.py 中的每一条配置对应相应的处理方法。
Django 不同版本 urls.py 配置有点不一样
Django1.1.x 版本
url() 方法:普通路径和正则路径均可使用,需要自己手动添加正则首位限制符号。
from django.conf.urls import url # 用 url 需要引入
urlpatterns = [
url(r'^admin/$', admin.site.urls),
url(r'^index/$', views.index), # 普通路径
url(r'^articles/([0-9]{4})/$', views.articles), # 正则路径
]
Django 2.2.x 之后
- path:用于普通路径,不需要自己手动添加正则首位限制符号,底层已经添加。
- re_path:用于正则路径,需要自己手动添加正则首位限制符号。
from django.urls import re_path # 用re_path 需要引入
urlpatterns = [
path('admin/', admin.site.urls),
path('index/', views.index), # 普通路径
re_path(r'^articles/([0-9]{4})/$', views.articles), # 正则路径
]
总结:Django1.1.x 版本中的 url 和 Django 2.2.x 版本中的 re_path 用法相同。
分组
正则还有分组的概念,但是在Django中把分组分为两种:
- 无名分组
- 有名分组
无名分组
无名分组:普通的正则匹配中加上()
作用:在后端的views上,会得到一个分组的参数
- 访问views.login函数(下方)的参数除了request,还需要添加一个参数(名字随意)
- 要设置几个参数就要多几个分组
以下代码访问:login/2222,那么xxx的值为2222
#urls.py
urlpatterns = [
url(r'^login/([0-9]{4})$',views.login),
]
#views.py
def login(request,xxx):
print(xxx)
#search2.py
def divede_group_year(request,year):
return HttpResponse('您的选择是:%s' % year)
def divede_group_month(request,year,month):
return HttpResponse('选择的月份是:%s-%s' %(year,month))
#urls.py
from django.urls import path,re_path
...
urlpatterns = [
...,
# 记得添加结束标签 $
re_path(r'^divede-group/([0-9]{4})/$',search2.divede_group_year),
re_path(r'^divede-group/([0-9]{4})/([0-9]{2})/$',search2.divede_group_month)
]
有名分组
有名分组: 有名分组其实就是在无名的分组的基础上加上了名字
语法为:(?P<名字> 正则表达式)
#urls.py
...,
urlpatterns = [
...,
url(r'^login/(?P<year>[0-9]{4})$',views.login),
]
#views.py
...,
def login(request,year):
print(year)
举例
#search2.py
from django.http import HttpResponse
def divede_group_name(request,name):
return HttpResponse('有名分组:%s' %(name))
#urls.py
from . import search2
re_path(r'^divede-group/(?P<name>[a-z]{4})/$',search2.divede_group_name)
视图函数的参数name 要和有名参数的名字name一致,否则会报错!
注意:官方规定,有名分组和无名分组不能一起使用!
路由分发(include)
问题:
- 项目里多个app目录共用一个 urls 容易造成混淆
- 后期维护不方便
解决:
- 使用路由分发(include),让每个app目录都单独拥有自己的 urls。
步骤:
- 1、在每个 app 目录里都创建一个 urls.py 文件。
- 2、在项目名称目录下的 urls 文件里,统一将路径分发给各个 app 目录。
举例:
有两个app,都有index.html页面
- 如果在项目的urls中配置,则会造成混淆
- 且如果有太多的url,则维护十分难
所以应该把各种的url分给各自去管理
#路由 未分发
#项目主目录下的urls.py
urlpatterns = [
url(r'^app-one/index/$',views.index),
url(r'^app-two/index/$',views.index)
]
新建两个app
# 语法:python manage.py startapp <app名称>
# 命令行中运行
python manage.py startapp app01
python manage.py startapp app02
app1目录下
1.views文件
from django.http import HttpResponse
def index(request):
return HttpResponse('app01的index页面!')
2.新建urls.py文件
from . import views
from django.conf.urls import url #没有引入会报错:NameError: name 'url' is not defined django
urlpatterns = [
url(r'^index/$',views.index)
]
app2目录下
1.views文件
from django.http import HttpResponse
def index(request):
return HttpResponse('app2:index!')
2.新建urls.py文件
from . import views
from django.conf.urls import url
urlpatterns = [
url(r'^index/$',views.index)
]
项目主目录下的urls.py
urlpatterns = [
url(r'^app-one/',include('app01.urls')),
url(r'^app-two/',include('app02.urls'))
]
访问
注意访问路径:
- app-one/index/
- 对照上面的urls.py文件,就能理解路由转发了!
app01
运行项目:python manage.py runserver
app02
反向解析
目的:解耦
- 路由层的 url 发生变化,就需要去更改对应的视图层和模板层的 url,不便维护
反向解析
- 当路由层 url 发生改变,在视图层和模板层动态反向解析出更改后的 url,免去修改的操作。
- 一般用在模板中的超链接及视图中的重定向
问题举例
#urls.py 中的一条url
...
url(r'^login/$',views.login)
...
#html页面中的提交表单
<form action="/login/" method="P=post">
...
</form>
当更改路由中的url时,需要在html页面也做相应的更改!
解耦目的:只更改路由中的url,html中会自动做出更改,我们无需去改变!
反向解析
方法:给路由器起别名
应用:
1.普通路径:name="路由别名
重定向:
- redirect(reverse("login"))
超链接:
- <form action="{% url 'login' %}" method="post">
2.正则路径:无名分组
re_path(r"^login/([0-9]{2})/$", views.login, name="login")
重定向:
- reverse("路由别名",args=(符合正则匹配的参数,))
- redirect(reverse("login",args=(10,)))
超链接:
- {% url "路由别名" 符合正则匹配的参数 %}
- {% url 'login' 10 %}
3.正则路径:有名分组
re_path(r"^login/(?P<year>[0-9]{4})/$", views.login, name="login")
重定向:
- reverse("路由别名",kwargs={"分组名":符合正则匹配的参数})
- redirect(reverse("login",kwargs={"year":3333}))
超链接:
- {% url "路由别名" 分组名=符合正则匹配的参数 %}
- {% url 'login' year=3333 %}
命名空间
命名空间(英语:Namespace)是表示标识符的可见范围。
一个标识符可在多个命名空间中定义,它在不同命名空间中的含义是互不相干的。
一个新的命名空间中可定义任何标识符,它们不会与任何重复的标识符发生冲突,因为重复的定义都处于其它命名空间中。
存在问题:路由别名 name 没有作用域,Django 在反向解析 URL 时,会在项目全局顺序搜索,当查找到第一个路由别名 name 指定 URL 时,立即返回。当在不同的 app 目录下的urls 中定义相同的路由别名 name 时,可能会导致 URL 反向解析错误。
解决:使用命名空间。
普通路径
方式一:
#主目录下的urls
urlpatterns = [
path('admin/', admin.site.urls),
path('polls/',include('polls.urls'))
]
# polls中的urls.py
from django.urls import path
from . import views
app_name = 'polls' #命名空间
urlpatterns = [
...
# ex: /polls/5/
path('<int:question_id>/', views.detail, name='detail'),
...
]
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
方式二:
定义命名空间(include 里面是一个元组)
include(("app名称:urls","app名称"))
实例
path("app01/", include(("app01.urls","app01")))
path("app01/", include(("app02.urls","app02")))
起别名
path("login/", views.login, name="login")
调用
#重定向
redirect(reverse("app01:login")
#超链接
{% url "app名称:路由别名" %}
视图
每个视图必须要做的只有两件事:
- 返回一个包含被请求页面内容的
HttpResponse
对象- 或者抛出一个异常,比如
Http404
。
Admin 管理工具
介绍
Django 提供了基于 web 的管理工具。
Django 自动管理工具是 django.contrib 的一部分。
在项目的 settings.py 中的 INSTALLED_APPS 可以看到
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
)
django.contrib是一套庞大的功能集,它是Django基本代码的组成部分。
激活管理工具
在生成项目时会在 urls.py 中自动设置好,我们只需去掉注释即可。
配置如下
# urls.py
from django.conf.urls import url
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
]
使用管理工具
创建超级用户
# python manage.py createsuperuser
Username (leave blank to use 'root'): admin
Email address:
Password:admin
Password (again):
Superuser created successfully.
- 创建模型(model)
- 修改admin.py
- 告知Django模型有更改
- 创建表
之前在 TestModel 中已经创建了模型 Test 。修改 TestModel/admin.py:
from django.contrib import admin
from TestModel.models import Test
# Register your models here.
admin.site.register(Test)
复杂模型
TestModel/models.py 中增加一个更复杂的数据模型
from django.db import models
# Create your models here.
class Test(models.Model):
name = models.CharField(max_length=20)
class Contact(models.Model):
name = models.CharField(max_length=20)
age = models.IntegerField(default=0)
email = models.EmailField()
def __unicode__(self):
return self.name
class Tag(models.Model):
concate = models.ForeignKey(Contact,on_delete=models.CASCADE)
name = models.CharField(max_length=50)
def __unicode__(self):
return self.name
TestModel/admin.py中修改
from django.contrib import admin
from TestModel.models import Test,Contact,Tag
# Register your models here.
admin.site.register([Test,Contact,Tag])
现在只是有了Concate和Tag模型,在数据库中还没有对应的表
(Test是文章前面建的)
使用以下命令创建表结构
python manage.py makemigrations TestModel # 让 Django 知道我们在我们的模型有一些变更
python manage.py migrate TestModel # 创建表结构
第一条命令执行完,多了一个文件
第二条命令执行完,创建了对应的表
(有关数据库连接请看“模型”)
自定义admin界面
自定义表单
1.把Age隐藏起来
修改 TestModel/admin.py:
from django.contrib import admin
from TestModel.models import Test,Contact,Tag
# Register your models here.
class ContactAdmin(admin.ModelAdmin): #新增代码
fields = ('name','email') #新增代码
admin.site.register(Contact,ContactAdmin) #新增代码
admin.site.register([Test,Tag]) #删除",Contact"
代码说明
定义了一个 ContactAdmin 类,用以说明管理页面的显示格式。
- fields 属性定义了要显示的字段
- 该类对应的是 Contact 数据模型,我们在注册的时候,需要将它们一起注册
刷新后显示效果如下
2.将输入栏分块,每个栏也可以定义自己的格式。
注意字母拼写不要错误!!!
from django.contrib import admin
from TestModel.models import Test,Contact,Tag
# Register your models here.
class ContactAdmin(admin.ModelAdmin):
fieldsets = (
['Main',{
'fields':('name','email'),
}],
['Advance',{
'classes': ('collapse',), # CSS
'fields': ('age',),
}]
)
admin.site.register(Contact, ContactAdmin)
admin.site.register([Test, Tag])
刷新页面
点击“Show”
说明
上面的栏目分为了 Main 和 Advance 两部分。
classes 说明它所在的部分的 CSS 格式。这里让 Advance 部分隐藏
内联(Inline)显示
上面的 Contact 是 Tag 的外部键,所以有外部参考的关系。
问题:在默认的页面显示中,将两者分离开来,无法体现出两者的从属关系。
解决:可以使用内联显示,让 Tag 附加在 Contact 的编辑页面上显示。
修改TestModel/admin.py
from django.contrib import admin
from TestModel.models import Test,Contact,Tag
# Register your models here.
class TagInline(admin.TabularInline): #新增代码
model = Tag #新增代码
class ContactAdmin(admin.ModelAdmin):
inlines = [TagInline] #Inline #修改代码
fieldsets = (
['Main',{
'fields':('name','email'),
}],
['Advance',{
'classes':('cpllapse',), #CSS
'fields':('age',),
}]
)
admin.site.register(Contact,ContactAdmin)
admin.site.register([Test]) #修改代码
刷新显示
列表页的显示
在 Contact 输入数条记录后,Contact 的列表页看起来如下
目的:在列表中显示更多的栏目
- 在 ContactAdmin 中增加 list_display 属性
from django.contrib import admin
from TestModel.models import Test,Contact,Tag
# Register your models here.
class TagInline(admin.TabularInline):
model = Tag
class ContactAdmin(admin.ModelAdmin):
list_display = ('name','age','email') #新增list_display属性
inlines = [TagInline] #Inline
fieldsets = (
['Main',{
'fields':('name','email'),
}],
['Advance',{
'classes':('cpllapse',), #CSS
'fields':('age',),
}]
)
admin.site.register(Contact,ContactAdmin)
admin.site.register([Test])
刷新显示
app
一个项目中可以创建很多个app
app可以用路由分发解决url冲突
使用命名空间也可以减少模板冲突等等
应用1
qyvxtest:项目名
charlist:新建的app
app可以成为绝对路径的引入,解决的导入模块时的问题
例如:在一个项目中,新建了一个app,在该app的views中
from . import models
#异常
#RuntimeError: Model class chatlist.models.User doesn't declare an explicit app_label and isn't in an application in INSTALLED_APPS
在setting中
INSTALLED_APPS = [
...,
'chatlist',
]
则在该app的view中可以
from . import models
#或者
from chatlist import models