Python全栈开发之Django基础
No.1 MVC&MTV
MVC
M全拼为Model,主要封装对数据库层的访问,对数据库中的数据进行增、删、改、查操作
创新互联专注于阿拉山口企业网站建设,自适应网站建设,商城网站建设。阿拉山口网站建设公司,为阿拉山口等地区提供建站服务。全流程定制网站建设,专业设计,全程项目跟踪,创新互联专业和态度为您提供的服务
V全拼为View,用于封装结果,生成页面展示的html内容
C全拼为Controller,用于接收请求,处理业务逻辑,与Model和View交互,返回结果
MTV
M全拼为Model,与MVC中的M功能相同,负责和数据库交互,进行数据处理
V全拼为View,与MVC中的C功能相同,接收请求,进行业务处理,返回应答
T全拼为Template,与MVC中的V功能相同,负责封装构造要返回的html
No.2 安装与配置
虚拟环境
为什么要使用虚拟环境?
如果在一台机器上,想开发不同的项目,这些项目依赖的同一个包的版本不同,其他项目就无法正常运行了,所有我们要用到虚拟环境,虚拟环境就是对真实Python环境的复制,通过建立多个虚拟环境,在不同的虚拟环境中开发项目就实现了项目之间的间隔
创建虚拟环境
pip3 install virtualenv # 安装虚拟环境
pip3 install virtualenvwrapper-win # 安装虚拟环境扩展包
mkvirtualenv 虚拟环境名称 # 创建虚拟环境
deactivate # 退出虚拟环境
workon # 直接输入workon查看已创建的虚拟环境,后面接虚拟环境名称进入该虚拟环境
rmvirtualenv 虚拟环境名称 # 删除虚拟环境
pip list # 查看该虚拟环境中安装的包
pip install # 虚拟环境包管理
pip install django==1.11.11 # 安装django1.11.11
Django基本使用
创建项目
创建第一个项目
django-admin startproject mysite;
项目默认目录
manage.py # 项目管理文件,通过它管理项目
与项目同名的目录,此处为mysite
_init_.py # 一个空文件,作用是这个目录test可以被当作包使用
settings.py # 项目的整体配置文件
urls.py # 项目的URL配置文件
wsgi.py # 项目与WSGI兼容的Web服务器入口
创建应用
python manage.py startapp app01;
应用目录结构
__init__.py # 一个空文件,表示当前目录可以当作一个python包使用
tests.py # 开发测试用例,在实际开发中会有专门的测试人员
models.py # 数据库操作相关
views.py # 接收浏览器请求,进行处理,返回页面相关
admin.py # 站点管理
migrations:
安装应用
# mysite/setting.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app01',
]
开发服务器
python manage.py runserver ip:端口
设计模型
定义模型类
# app01/models.py
from django.db import models
class BookInfo(models.Model):
"""图书表"""
btitle = models.CharField(max_length=20)
bpub_date = models.DateField()
class HeroInfo(models.Model):
"""英雄表"""
hname = models.CharField(max_length=20)
hgender = models.BooleanField()
hcomment = models.CharField(max_length=100)
hbook = models.ForeignKey(BookInfo)
迁移
python manage makemigration # 生成迁移文件
python manage migrate # 执行迁移
数据操作
# 进入项目并引入模块
python manage.py shell
from datetime import date
from booktest.models import BookInfo,HeroInfo
# 增删改查
BookInfo.objects.create(title="射雕英雄传",bpub_date=date(2018,10,4))
BookInfo.objects.filter(id=1).delete()
BookInfo.objects.filter(id=1).update(title='神雕侠侣')
BookInfo.objects.filter.all()
# 对象关联操作
HeroInfo.objects.create(hname='a1',hgender=False,hcomment='he is a boy',hbook=BookInfo.objects.get(id=1))
# 获得关联集合
BookInfo.objects.get(id=1).heroinfo_set.all()
站点管理
管理页面本地化
# mysite/setting.py
LANGUAGE_CODE = 'zh-hans' #使用中国语言
TIME_ZONE = 'Asia/Shanghai' #使用中国上海时间
创建管理员
python manage.py createsuperuser
注册模型类
# app01/admin.py
from django.contrib import admin
from app01.models import BookInfo,HeroInfo
admin.site.register(BookInfo)
admin.site.register(HeroInfo)
自定义管理界面
# app01/admin.py,list_display表示要显示的字段
from django.contrib import admin
from booktest.models import BookInfo,HeroInfo
class BookInfoAdmin(admin.ModelAdmin):
list_display = ['id', 'btitle', 'bpub_date']
class HeroInfoAdmin(admin.ModelAdmin):
list_display = ['id', 'hname','hgender','hcomment']
admin.site.register(BookInfo,BookInfoAdmin)
admin.site.register(HeroInfo,HeroInfoAdmin)
视图
定义视图
# app01/views.py
from django.http import HttpResponse
def index(request):
return HttpResponse("index")
配置URLconf
请求者在浏览器中输入url,请求到网站后,获取url信息,然后在URL.conf逐条匹配,如果匹配成功返回相应的视图函数,如果所有URLconf都没有匹配成功,返回404错误
# app01/views.py
from django.conf.urls import include, url
from django.contrib import admin
urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
url(r'^', include('app01.urls')),
]
# mysite/urls.py
from django.conf.urls import url
from app01 import views
urlpatterns = [
url(r'^$', views.index),
]
模板
创建模板
# mysite/setting.py
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',
],
},
},
]
定义模板
# templtes/app01/index.html
图书列表
{{title}}
{%for i in list%}
{{i}}
{%endfor%}
视图调用模板
# app01/views.py
from django.shortcuts import render
def index(request):
context={'title':'图书列表','list':range(10)}
return render(request,'app01/index.html',context)
No.3 模型
负责和数据库交互,进行数据处理
ORM
什么是orm?
对象关系映射,是随着面向对象思想发展而产生的,是一种程序技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换,面向对象是从软件工程基本原则(如耦合、聚合、封装)的基础上发展起来的,而关系数据库则是从数学理论发展而来的,两套理论存在显著的区别,为了解决这个不匹配的现象,对象关系映射技术应运而生
使用MySQL
# mysite/setting.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'python', #数据库名字,
'USER': 'root', #数据库登录用户名
'PASSWORD': '123456', #数据库登录密码
'HOST': 'localhost', #数据库所在主机
'PORT': '3306', #数据库端口
}
}
# mysite/__init__.py
import pymysql
pymysql.install_as_MySQLdb()
定义模型类
字段类型
- AutoField:自动增长的IntegerField,通常不用指定,不指定时Django会自动创建属性名为id的自动增长属性
- BooleanField: 布尔字段,值为True或False
- NullBooleanField: 支持Null,True,False
- CharField(max_length): 字符串,表示最大字符个数
- TextField: 大文本字段,字符超过4000使用
- IntgerField: 整数字段
- DecimalField(max_digits,decimal_places): 十进制浮点数,max_digits表示总位数,decimal_places表示小数位数
- FloatField: 浮点数
- DateField(auto_now,auto_now_add): auto_now表示每次保存对象时,自动设置该字段为当前时间,用于最后一次修改的时间戳,默认为False,auto_now_add表示当对象第一次创建时自动设置当前时间,用于创建时的时间戳,默认为False
- TimeField: 时间字段,同上
- DateTimeField: 日期时间字段,同DateField
- FileField: 上传文件字段
- ImageField: 继承自FileField,对上传的内容进行校验,确保是有效的图片
字段约束
- null:如果为True,表示允许为空,默认值是False
- blank:如果为True,则该字段允许为空白,默认值是False,null是数据库范畴的概念,blank是表单验证范畴的
- db_column:字段的名称,如果未指定,则使用属性的名称
- db_index:若值为True, 则在表中会为此字段创建索引,默认值是False
- default:默认值
- primary_key:若为True,则该字段会成为模型的主键,默认值是False,一般作为AutoField的选项使用
- unique:如果为True, 这个字段在表中必须有唯一值,默认值是False
条件查询
查询
exact 表示判等
list = BookInfo.objects.filter(id__exact=1)
list = BookInfo.objects.filter(id=1)
模糊查询
contains 是否包含
list = BookInfo.objects.filter(btitle__contains='传')
list = BookInfo.objects.filter(btitle__icontains='传') # 不区分大小写
startswith 以指定字符开头
list = BookInfo.objects.filter(btitle__startswith='神')
list = BookInfo.objects.filter(btitle__istartswith='神') # 不区分大小写
endswithch 以指定字符结尾
list = BookInfo.objects.filter(btitle__endswitch='侣')
list = BookInfo.objects.filter(btitle__iendswitch='侣') # 不区分大小写
空查询
isnull 是否为空
list = BookInfo.objects.filter(btitle__isnull=False)
范围查询
in 是否包含在范围内
list = BookInfo.objects.filter(id__in=[1, 3, 5])
比较查询
gt: 大于
gte: 大于等于
lt: 小于
lte:小于等于
list = BookInfo.objects.filter(id__gt=3)
不等于查询
exclude() 不等于运算符
list = BookInfo.objects.exclude(id=3)
日期查询
year、month、day、week_day、hour、minute、second
list = BookInfo.objects.filter(bpub_date__year=1980)
F对象
比较一个对象中的两个属性
list = BookInfo.objects.filter(bread__gt=F('bcomment') * 2)
Q对象
多个过滤器逐个调用表示逻辑与关系,同sql语句中where部分的and关键字
list=BookInfo.objects.filter(bread__gt=20).filter(id__lt=3)
如果想实现逻辑或的功能,就要使用到Q对象查询,Q对象可以使用&、|连接,&表示逻辑与,|表示逻辑或,~表示not
list = BookInfo.objects.filter(Q(bread__gt=20) | Q(pk__lt=3))
聚合查询
使用aggregate()过滤器调用聚合函数,聚合函数包括:Avg,Count,Max,Min,Sum
list = BookInfo.objects.count()
查询集
查询集表示从数据库中查询到的对象集合
返回查询集的过滤器
- all():返回所有数据
- filter(): 返回满足条件的数据
- exclude(): 返回不满足条件的数据
- order_by(): 对结果集排序
返回单个值的过滤器
- get(): 返回单个满足条件的对象,如果未找到会抛出DoesNotExist异常,如果返回多条抛出MultipleObjectReturnned异常
- count(): 返回当前查询结果的总条数
- aggregate(): 聚合,返回一个字典
判断一个查询集中是否有数据
两个特性
- 惰性 创建查询集不会访问数据库,直到调用数据时,才会访问数据库
- 缓存 使用同一个结果集,第一次使用会触发查询数据库,然后将结果缓存下载,再次使用直接调用缓存
限制结果集
可以对结果集进行切片操作,等同于数据库中的分页操作,但是不支持负数
list = BookInfo.objects.all()[0:2]
关联
关系字段类型
- ForeignKey:一对多,将字段定义在多的一端中
- ManyToManyField:多对多,将字段定义在任意一端中
- OneToOneField:一对一,将字段定义在任意一端中
- 可以维护递归的关联关系,使用'self'指定,详见"自关联"
一对多
一本图书中可以对应多个英雄,所以图书和英雄是一对多的关系
class BookInfo(models.Model):
btitle = models.CharField(max_length=20)
bpub_date = models.DateField()
bread = models.IntegerField(default=0)
bcomment = models.IntegerField(default=0)
isDelete = models.BooleanField(default=False)
class HeroInfo(models.Model):
hname = models.CharField(max_length=20)
hgender = models.BooleanField(default=True)
isDelete = models.BooleanField(default=False)
hcomment = models.CharField(max_length=200)
hbook = models.ForeignKey('BookInfo')
多对多
一个类别中多条新闻,一条新闻也可以分为不同的类别,所以新闻是多对多关系
class TypeInfo(models.Model):
tname = models.CharField(max_length=20)
class NewsInfo(models.Model):
ntitle = models.CharField(max_length=60)
ncontent = models.TextField()
npub_date = models.DateTimeField(auto_now_add=True)
ntype = models.ManyToManyField('TypeInfo')
通过对象执行关联查询
- 由一对多的访问语法
一端的对象.多端的类名_set
b = BookInfo.objects.get(id=1)
b.HeroInfo_set.all()
- 由多对一的访问语法
多端的模型对象.多端模型类的类关系字段
h = HeroInfo.objects.get(id=1)
h.hbook
- 访问一对应的模型类关联对象的id语法:
多对应的模型类对象.关联类属性_id
h = HeroInfo.objects.get(id=1)
h.book_id
通过模型类执行关联查询
- 由多模型类条件查询一模型类数据:
语法:
关联模型类名小写__属性名__条件运算符=值
list = BookInfo.objects.filter(heroinfo__hcontent__contains='八')
- 由一模型类条件查询多模型类数据:
语法:
一模型类关联属性名__一模型类属性名__条件运算符=值
list = HeroInfo.objects.filter(hbook__btitle='天龙八部')
No.4视图
视图负责接受Web请求HttpRequest,进行逻辑处理,返回Web响应HttpResponse给请求者
URLconf
位置参数
url(r'^delete(\d+)/$',views.show_arg),
关键字参数
url(r'^delete(?P\d+)/$',views.show_arg),
内置错误视图,如果想看到错误视图而不是调试信息的话,需要修改setting文件的DEBUG选项
# mysite/setting.py
DEBUG = False
ALLOWED_HOSTS = ['*', ]
HttpReqeust对象
- path: 一个字符串,表示请求的完整路径,不包含域名和参数
- method: 一个字符串,表示请求方法,常用的有GET、POST
- encoding: 一个字符串,表示提交数据的编码类型
- GET: QueryDict类型对象,类似于字典,包含get请求方式的所有参数
- POST:QueryDict类型对象,类似于字典,包含post请求方式的所有参数
- FILES:一个类似于字典的对象,包含所有的上传文件
- COOKIES:一个标准的Python字典,包含所有的cookie,键和值都为字符串
- session:一个可读写的类似于字典的对象,表示当前的会话,只有当Django 启用会话的支持时才可用
HttpResponse对象
属性
- content:表示返回的内容。
- charset:表示response采用的编码字符集,默认为utf-8。
- status_code:返回的HTTP响应状态码。
- content-type:指定返回数据的的MIME类型,默认为'text/html'。
方法
- init:创建HttpResponse对象后完成返回内容的初始化
set_cookie:设置Cookie信息
- delete_cookie(key):删除指定的key的Cookie,如果key不存在则什么也不发生
- write:向响应体中写数据
Cookie
某些网站为了辨别用户身份、进行session跟踪而储存在用户本地终端上的数据,Cookie是由服务器端生成,发送给User-Agent(一般是浏览器),浏览器会将Cookie的key/value保存到某个目录下的文本文件内,下次请求同一网站时就发送该Cookie给服务器,Cookie名称和值可以由服务器端开发自己定义,这样服务器可以知道该用户是否是合法用户以及是否需要重新登录等,服务器可以利用Cookies包含信息的任意性来筛选并经常性维护这些信息,以判断在HTTP传输中的状态
Cookie特点
- Cookie以键值对的方式存储数据
- Cookie基于域名安全,不同域名下的Cookie是不可以互相访问的
- 当浏览器请求某网站时,会将浏览器存储的跟网站相关的所以Cookiet提交给网站服务器
设置Cookie
def cookie_set(request):
response = HttpResponse("设置Cookie,请查看响应报文头
")
response.set_cookie('h2', '你好')
return response
读取Cookie
def cookie_get(request):
response = HttpResponse("读取Cookie,数据如下:
")
if 'h2' in request.COOKIES:
response.write('' + request.COOKIES['h2'] + '
')
return response
Session
对于敏感、重要的信息,建议要储在服务器端,不能存储在浏览器中,如用户名、余额、等级、验证码等信息
禁用Session中间件
存储方式
存储在数据库中,如下设置可以写,也可以不写,这是默认存储方式
SESSION_ENGINE='django.contrib.sessions.backends.db'
存储在缓存中:存储在本机内存中,如果丢失则不能找回,比数据库的方式读写更快
SESSION_ENGINE='django.contrib.sessions.backends.cache'
混合存储:优先从本机内存中存取,如果没有则从数据库中存取
SESSION_ENGINE='django.contrib.sessions.backends.cached_db'
依赖于Cookie
在使用Session后,会在Cookie中存储一个sessionid的数据,每次请求时浏览器都会将这个数据发给服务器,服务器在接收到sessionid后,会根据这个值找出这个请求者的Session
对象及方法
以键值对的格式写session
request.session['键']=值
根据键读取值
request.session.get('键',默认值)
清除所有session,在存储中删除值部分
request.session.clear()
清除session数据,在存储中删除session的整条数据
request.session.flush()
删除session中的指定键及值,在存储中只删除某个键及对应的值
del request.session['键']
设置会话的超时时间,如果没有指定过期时间则两个星期后过期
request.session.set_expiry(value) 如果value是一个整数,会话将在value秒没有活动后过期,如果value为0,那么用户会话的Cookie将在用户的浏览器关闭时过期,如果value为None,那么会话永不过期
No.5 模板
负责封装构造要返回的html
模板语言
变量
语法:{{变量}}
解析顺序:
- 字典book['title']
- 先属性后方法,将book当作对象,先把title当作属性,如果找不到会认为它是方法
- 如果格式是book 0,则解析为book[0]
标签
for
{% for item in book_list %}
循环逻辑
{{forloop.counter}}表示当前是第几次循环,从1开始
{%empty%}列表为空执行此逻辑
{% end for %}
if
{%if ...%}
逻辑1
{%elif ...%}
逻辑2
{%else%}
逻辑3
{%endif%}
过滤器
语法:变量|过滤器:参数
data|default:'默认值'
更多内建过滤器
自定义过滤器
在应用中创建templatetags目录
在该目录下创建filters.py文件
#导入Library类
from django.template import Library
#创建一个Library类对象
register=Library()
#使用装饰器进行注册
@register.filter
#定义求余函数mod,将value对2求余
def mod(value):
return value%2 == 0
使用load标签引入模块
{%load filters%}
模板继承
父模板
如果发现在多个模板中某些内容相同,那就应该把这段内容定义到父模板中
标签block:用于在父模板中预留区域,留给子模板填充差异性的内容,名字不能相同
{%block 名称%}
预留区域,可以编写默认内容,也可以没有默认内容
{%endblock 名称%}
字模板
标签extends:继承,写在子模板文件的第一行
{% extends "父模板路径"%}
{%block 名称%}
实际填充内容
{{block.super}}用于获取父模板中block的内容
{%endblock 名称%}
CSRF
跨站请求伪造,CSRF指***者盗用了你的身份,以你的名义发送恶意请求
CSRF能够做的事情:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账......
造成的问题:个人隐私泄露以及财产安全
如果想防止CSRF,首先是重要的信息传递都采用POST方式而不是GET方式
防止CSRF
- Django提供了csrf中间件用于防止CSRF***,只需要在mysite/settings.py中启用csrf中间件即可
- 接下来templates/app01/post.html内容,在form表单中使用标签csrf_token
保护原理
加入csrf_token这个标签后,会想客户端浏览器写入一条cookie,还会在表单中加入一个隐藏域,里面存放有一个value值,然后提交数据的时候,会将这两个值提交到服务器进行校验,如果value值cookie值相同,正常执行业务逻辑,否则,返回403错误
验证码
新用户注册,为了防止请求过多,可以加入验证码功能,如果验证码错误,不需要执行后续操作,减轻服务器的压力
手动实现验证码
- 安装Pillow
pip3 install Pillow
- 在app01/views中,创建视图函数
from PIL import Image, ImageDraw, ImageFont
from django.utils.six import BytesIO
...
def verify_code(request):
#引入随机函数模块
import random
#定义变量,用于画面的背景色、宽、高
bgcolor = (random.randrange(20, 100), random.randrange(
20, 100), 255)
width = 100
height = 25
#创建画面对象
im = Image.new('RGB', (width, height), bgcolor)
#创建画笔对象
draw = ImageDraw.Draw(im)
#调用画笔的point()函数绘制噪点
for i in range(0, 100):
xy = (random.randrange(0, width), random.randrange(0, height))
fill = (random.randrange(0, 255), 255, random.randrange(0, 255))
draw.point(xy, fill=fill)
#定义验证码的备选值
str1 = 'ABCD123EFGHIJK456LMNOPQRS789TUVWXYZ0'
#随机选取4个值作为验证码
rand_str = ''
for i in range(0, 4):
rand_str += str1[random.randrange(0, len(str1))]
#构造字体对象,ubuntu的字体路径为“/usr/share/fonts/truetype/freefont”
font = ImageFont.truetype('FreeMono.ttf', 23)
#构造字体颜色
fontcolor = (255, random.randrange(0, 255), random.randrange(0, 255))
#绘制4个字
draw.text((5, 2), rand_str[0], font=font, fill=fontcolor)
draw.text((25, 2), rand_str[1], font=font, fill=fontcolor)
draw.text((50, 2), rand_str[2], font=font, fill=fontcolor)
draw.text((75, 2), rand_str[3], font=font, fill=fontcolor)
#释放画笔
del draw
#存入session,用于做进一步验证
request.session['verifycode'] = rand_str
#内存文件操作
buf = BytesIO()
#将图片保存在内存中,文件类型为png
im.save(buf, 'png')
#将内存中的图片数据返回给客户端,MIME类型为图片png
return HttpResponse(buf.getvalue(), 'image/png')
- 打开app01/urls.py文件,配置url
url(r'^verify_code/$', views.verify_code),
调用验证码
- 在app01/views.py文件中,创建视图verify_show
def verify_show(request):
return render(request,'app01/verify_show.html')
- 打开bapp01/urls.py文件,配置url
url(r'^verify_show/$', views.verify_show),
- 在templates/app01/目录下创建verify_show.html
验证码
验证
- 在app01/views.py文件中,创建视图verify_yz
def verify_yz(request):
yzm=request.POST.get('yzm')
verifycode=request.session['verifycode']
response=HttpResponse('no')
if yzm==verifycode:
response=HttpResponse('ok')
return response
- 打开app01/urls.py文件,配置url
url(r'^verify_yz/$', views.verify_yz),
看不清,换一个
...
看不清,换一个
反向解析
- 在app01/urls.py中为include定义namespace属性
url(r'^',include('app01.urls',namespace='app01')),
- 在app01/urls.py中为url定义name属性,并修改为fan2
url(r'^fan2/$', views.fan2,name='fan2'),
- 在模板中使用url标签做超链接,此处为templates/app01/fan1.html文件
反向解析
普通链接:fan2
反向解析:fan2
- 在app01/urls.py中,将fan2修改为fan_show
url(r'^fan_show/$', views.fan2,name='fan2'),
- 反向解析也可以应用在视图的重定向中
from django.shortcuts import redirect
from django.core.urlresolvers import reverse
return redirect(reverse('app01:fan2'))
总结:在定义url时,需要为include定义namespace属性,为url定义name属性,使用时,在模板中使用url标签,在视图中使用reverse函数,根据正则表达式动态生成地址,减轻后期维护成本
No.6 后台
内容发布的部分由网站的管理员负责查看、添加、修改、删除数据,开发这些重复的功能是一件单调乏味、缺乏创造力的工作,为此,Django能够根据定义的模型类自动地生成管理模块
页选项
页大小,每页显示多少条数据
list_per_page=100
操作选项的位置
actions_on_top=True 顶部显示的属性,True为显示,默认为True,False即为不显示
actions_on_bottom=True 同上,只不过是底部显示的属性
字段排序
admin_order_field=[字段1,字段2]
列标题
short_description='列标题'
侧边栏过滤器
list_filter=[]
搜索框
search_fields=[]
中文标题
在模型类的字段为其指定verbose_name
分组显示
fieldset=(
('组1标题',{'fields':('字段1','字段2')}),
('组2标题',{'fields':('字段3','字段4')}),
)
上传图片
创建包含图片类型字段的模型类
将模型的类型定义成ImageField字段
class Pic(models.Model):
pic = models.ImageField(upload_to='app01/')
迁移
python managee.py makemigrations
python manage.py migrate
设置图片保存位置
MEDIA_ROOT=os.path.join(BASE_DIR,"static/media")
并且在static创建media目录,在meida目录下创建应用名称的目录,此为app01
在管理页面上传图片
在admin中注册该模型类
admin.site.register(Pic)
文章题目:Python全栈开发之Django基础
当前URL:http://hbruida.cn/article/gpiesp.html