Python 23 Django基础
零、基本配置操作
1、创建Django程序
命令:django-admin startproject name
2、程序目录
3、配置文件
(1)、数据库
1
2
3
4
5
6
7
8
9
10
|
DATABASES = { 'default' : { 'ENGINE' : 'django.db.backends.mysql' , 'NAME' : 'dbname' , 'USER' : 'root' , 'PASSWORD' : 'xxx' , 'HOST' : '', 'PORT' : '', } } |
1
2
3
4
5
6
|
# 由于Django内部连接MySQL时使用的是MySQLdb模块,而python3中还无此模块,所以需要使用pymysql来代替 # 如下设置放置的与project同名的配置的 __init__.py文件中 import pymysql pymysql.install_as_MySQLdb() |
(2)、模版
1
2
3
|
TEMPLATE_DIRS = ( os.path.join(BASE_DIR, 'templates' ), ) |
(3)、静态文件
1
2
3
|
STATICFILES_DIRS = ( os.path.join(BASE_DIR, 'static' ), ) |
一、url路由系统
1、正则表达式
2、分组和命名分组
re_path(r'xxxx/(\d+)$', xxxx) re_path(r'xxxx/(?P<name>(\d+))$', xxx)
3、include分发
re_path(r'xxxx/', include('crm.urls'))
4、url别名和namespace
re_path(r'xxxx/', name=xx) re_path(r'xxxx/', include('crm.urls', 'crm', namespace='crm'))
5、反向解析
# 在视图中 reverse('xx') reverse('xx', args=(xx, )) reverse('xx', kwargs(xx=xx, )) reverse('crm:xx') # 如果设置了namespace,则需要在前面加crm: # 在模板中 {% url 'xx' %} {% url 'xx' 5 %} {% url 'xx' id=5 %}
二、视图
1、FBV和CBV
2、CBV流程
3、request
request.method request.path_info # 不包含参数、域名和参数 request.files request.body request.is_ajax()
4、response
from django.https import JsonResponse from django.shortcuts import HttpResponse, render, redirect HttpResponse("xxx") render(request, 'xx.html', locals()) redirect('xxx') JsonResponse({xx:xx})
三、模板
模板就是html+Django逻辑语句的组合
1、变量 {{ }}
2、标签(标签是逻辑层面,不能改变数值)
{% for %} #表示遍历循环
{% if %} #表示判断
{% url "name" %} #表示路由地址
{% load %} #加载相关文件
{% csrf_token %} #用于防护跨站请求伪造攻击
{% with %} #重新命名变量名,{% with a as b %} {{ b }} {% endwith %}
{% extends %} #继承模板
{% block %} #重写父类模板的代码
3、过滤器(过滤器可以对数据进行加工)
{{ xx|default: 1 }} 给一个默认值
{{ xx|date: 'Y-m-h' }} 显示时间
{{ xx|safe }} 不再防止xss攻击,不进行转义,也可以将数字类型转化成字符串。
4、自定义simple_tag、filter
1、
在app中创建templatetags文件夹,并创建一个py文件
2、
from django import templates
register = templates.Library()
3、
@register.simple_tag
def xxx(xx):
return xx
4、
在模板中使用{% load xx %}
5、自定义inclusion_tag
inclusion_tag是用来保存一段html代码,可以直接在模板中套用
使用过程和自定义标签类似。首先在app中创建templatetags文件夹,在其中自定义一个my_html.py文件:
from django import template register = template.Library() @register.inclusion_tag('test.html') # 这里传入参数,表示代码所在的html文件 def test(a): return {"xx": xx}
在test.html文件中可以使用传入的xx变量
<div>{{ xx }}</div>
在需要使用的html文件中引用:
{% load my_html %}
{% test request %}
四、ORM
1、映射关系
类--表,对象--数据行,属性--字段
class UserInfo(models.Model): nid = models.AutoField(primary_key=True) username = models.CharField(max_length=32) class Meta: # 数据库中生成的表名称 默认 app名称 + 下划线 + 类名 db_table = "table_name" # 联合索引 index_together = [ ("pub_date", "deadline"), ] # 联合唯一索引 unique_together = (("driver", "restaurant"),) # admin中显示的表名称 verbose_name # verbose_name加s verbose_name_plural
2、字段类型
AutoField(Field) - int自增列,必须填入参数 primary_key=True BigAutoField(AutoField) - bigint自增列,必须填入参数 primary_key=True 注:当model中如果没有自增列,则自动会创建一个列名为id的列 from django.db import models class UserInfo(models.Model): # 自动创建一个列名为id的且为自增的整数列 username = models.CharField(max_length=32) class Group(models.Model): # 自定义自增列 nid = models.AutoField(primary_key=True) name = models.CharField(max_length=32) SmallIntegerField(IntegerField): - 小整数 -32768 ~ 32767 PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) - 正小整数 0 ~ 32767 IntegerField(Field) - 整数列(有符号的) -2147483648 ~ 2147483647 PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) - 正整数 0 ~ 2147483647 BigIntegerField(IntegerField): - 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807 自定义无符号整数字段 class UnsignedIntegerField(models.IntegerField): def db_type(self, connection): return 'integer UNSIGNED' PS: 返回值为字段在数据库中的属性,Django字段默认的值为: 'AutoField': 'integer AUTO_INCREMENT', 'BigAutoField': 'bigint AUTO_INCREMENT', 'BinaryField': 'longblob', 'BooleanField': 'bool', 'CharField': 'varchar(%(max_length)s)', 'CommaSeparatedIntegerField': 'varchar(%(max_length)s)', 'DateField': 'date', 'DateTimeField': 'datetime', 'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)', 'DurationField': 'bigint', 'FileField': 'varchar(%(max_length)s)', 'FilePathField': 'varchar(%(max_length)s)', 'FloatField': 'double precision', 'IntegerField': 'integer', 'BigIntegerField': 'bigint', 'IPAddressField': 'char(15)', 'GenericIPAddressField': 'char(39)', 'NullBooleanField': 'bool', 'OneToOneField': 'integer', 'PositiveIntegerField': 'integer UNSIGNED', 'PositiveSmallIntegerField': 'smallint UNSIGNED', 'SlugField': 'varchar(%(max_length)s)', 'SmallIntegerField': 'smallint', 'TextField': 'longtext', 'TimeField': 'time', 'UUIDField': 'char(32)', BooleanField(Field) - 布尔值类型 NullBooleanField(Field): - 可以为空的布尔值 CharField(Field) - 字符类型 - 必须提供max_length参数, max_length表示字符长度 TextField(Field) - 文本类型 EmailField(CharField): - 字符串类型,Django Admin以及ModelForm中提供验证机制 IPAddressField(Field) - 字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制 GenericIPAddressField(Field) - 字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6 - 参数: protocol,用于指定Ipv4或Ipv6, 'both',"ipv4","ipv6" unpack_ipv4, 如果指定为True,则输入::ffff:192.0.2.1时候,可解析为192.0.2.1,开启刺功能,需要protocol="both" URLField(CharField) - 字符串类型,Django Admin以及ModelForm中提供验证 URL SlugField(CharField) - 字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号) CommaSeparatedIntegerField(CharField) - 字符串类型,格式必须为逗号分割的数字 UUIDField(Field) - 字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证 FilePathField(Field) - 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能 - 参数: path, 文件夹路径 match=None, 正则匹配 recursive=False, 递归下面的文件夹 allow_files=True, 允许文件 allow_folders=False, 允许文件夹 FileField(Field) - 字符串,路径保存在数据库,文件上传到指定目录 - 参数: upload_to = "" 上传文件的保存路径 storage = None 存储组件,默认django.core.files.storage.FileSystemStorage ImageField(FileField) - 字符串,路径保存在数据库,文件上传到指定目录 - 参数: upload_to = "" 上传文件的保存路径 storage = None 存储组件,默认django.core.files.storage.FileSystemStorage width_field=None, 上传图片的高度保存的数据库字段名(字符串) height_field=None 上传图片的宽度保存的数据库字段名(字符串) DateTimeField(DateField) - 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] DateField(DateTimeCheckMixin, Field) - 日期格式 YYYY-MM-DD TimeField(DateTimeCheckMixin, Field) - 时间格式 HH:MM[:ss[.uuuuuu]] DurationField(Field) - 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型 FloatField(Field) - 浮点型 DecimalField(Field) - 10进制小数 - 参数: max_digits,小数总长度 decimal_places,小数位长度 BinaryField(Field) - 二进制类型
3、13条查询语句
all() get() filter() exclude() first() last()
values() value_list()
order_by() reverse()
distinct() count() exist()
4、单表双下划线
__gt # 大于 __lte # 小于等于 __in __range __is_null __contains
5、外键查询
6、多对多操作
#有一张作者表,有一张书的表多对多关联了作者表 #为一本书添加一位不存在的作者 book_obj = Books.objects.filter(id=1).first() book_obj..authors.create(name='xxx') #为一本书添加几位存在的作者 book_obj.authors.add(author_obj1,author_obj2) #也可以直接添加id book_obj.authors.add(1) #或者用set book_obj.authors.set([1,2,3]) #为一位作者添加一本不存在的书 author_obj = Authors.objects.filter(id=1).first() author_obj.books_set.create(name="xx") #为一位作者添加几本不存在的书 author_obj.books_set.add(book_obj1,book_obj2) #也可以直接添加id author_obj.books_set.add(2) #去除一本书关联的某个作者 book_obj.authors.remove(author_obj) #清空一本书关联的所有作者 book_obj.authors.clear()
# 第一种:使用ManyToMany字段 # 第二种:自己创建第三张表并通过外键连接,但是无法使用多对多的方法 # 第三种:自己创建第三张表,并通过ManyToMany字段连接 class Book(models.Model): title = models.CharField(max_length=32, verbose_name="书名") class Author(models.Model): name = models.CharField(max_length=32, verbose_name="作者姓名") books = models.ManyToManyField(to="Book", through="Author2Book", through_fields=("author", "book")) # through_fields接受一个2元组('field1','field2'): # 其中field1是定义ManyToManyField的模型外键的名(author),field2是关联目标模型(book)的外键名。 class Author2Book(models.Model): author = models.ForeignKey(to="Author") book = models.ForeignKey(to="Book") class Meta: unique_together = ("author", "book")
7、高级
(1)聚合、分组
##聚合 # 语法: aggregate(别名=聚合函数('字段')) # 规则: 1.可以同时对多个字段进行聚合处理:aggregate(别名1=聚合函数1('字段1'), ..., 别名n=聚合函数n('字段n')) 3.是QuerySet对象方法 2.方法返回值返回值为dict类型 # 案例:所有书中最贵的书的价格 Book.objects.all().aggregate(high_price=Max('price')) ##分组 # 语法: values('分组字段').annotate(别名=聚合函数('字段')).filter(聚合字段别名条件).values('取分组字段', '取聚合字段别名') # 规则: 1.values(...).annotate(...)为分组组合,values控制分组字段,annotate控制聚合字段 2.values可按多个字段分组values('分组字段1', ..., '分组字段n'),??如果省略代表按操作表的主键分组 3.可以同时对多个字段进行聚合处理annotate(别名1=聚合函数1('字段1'), ..., 别名n=聚合函数n('字段n')) 4.分组后的的filter代表having判断,只对聚合字段进行条件判断,可以省略(对非聚合字段或分组字段进行条件判断代表where判断) 5.取字段值values(...)省略默认取所有分组字段与聚合字段,也可以自主取个别分组字段及聚合字段(取字段的values中出现了非分组或非聚合字段,该字段自动成为分组字段) # 案例:每个出版社出版的最贵的书的价格高于50元的出版社名与最高价格 Book.objects.all().values('publish__name').annotate(high_price=Max('price')).filter(high_price__gt=50).values('publish__name', 'high_price') 分组与聚合
(2)F和Q
# F # # from django.db.models import F # models.Tb1.objects.update(num=F('num')+1) # Q # # 方式一: # Q(nid__gt=10) # Q(nid=8) | Q(nid__gt=10) # Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root') # 方式二: # con = Q() # q1 = Q() # q1.connector = 'OR' # q1.children.append(('id', 1)) # q1.children.append(('id', 10)) # q1.children.append(('id', 9)) # q2 = Q() # q2.connector = 'OR' # q2.children.append(('c1', 1)) # q2.children.append(('c1', 10)) # q2.children.append(('c1', 9)) # con.add(q1, 'AND') # con.add(q2, 'AND') # # models.Tb1.objects.filter(con)
(3)事务
from django.db import transaction with transaction.atomic(): obj = models.Book.objects.filter(xx).select_for_update() xxxxx
8、ORM性能相关
(1)select_related主动连表
# 用户表外键关联角色表 all_users = models.User.objects.all() # 这样取出来的数据不包含角色表中的信息 for user in all_users: print(user.role.name) # 这样每次循环都会进行一次连表查询,去角色表中找id=user.role_id的值,效率低
all_users = models.User.objects.all().select_related("role") # 传入需要主动连表的外键字段 # 这样就会一次查询将外键的字段也获取,保存到对象中,可以直接通过点的方式取值 # .values(xxx)虽然也可以一次查询取外键字段,但是保存的是字典,只能通过字典操作,且需要一个一个字段的输入
(2)prefetch_related不主动连表,但是进行多次子查询
all_users = models.User.objects.all().prefetch_related("role") # 会进行两次查询,第一次查询出所有的用户对象,第二次查询所有id包含在这些用户对象的role_id中的角色,两次查询的速度比连表查询快
(3)only和defer
only的作用和values基本一致,但是values得到的是字典,only得到的是对象,里面保存了想要得到的字段
defer就是反作用,不取哪些字段
9、Content-Type表
https://blog.csdn.net/aaronthon/article/details/81714496
用来解决一张表和多张表建立外键关系的需求。
from django.db import models from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation class Food(models.Model): name = models.CharField(max_length=32) coupons = GenericRelation(to="Coupon") class Fruit(models.Model): name = models.CharField(max_length=32) coupons = GenericRelation(to="Coupon") class Coupon(models.Model): title = models.CharField(max_length=32) content_type = models.ForeignKey("ContentType") # 关联ContentType表 object_id = models.IntegerField() # 定位对应表的id # 这个属性不创建字段,只是用来方便查询,只需要将对应的对象传给content_obj,就能自动找到content_type中的对象并定位到id content_obj = GenericForeignKey("content_type", "object_id")
food_obj = Food.objects.filter(id=1).first() Coupon.objects.create(title="酱香饼买一送小威",content_object=food_obj) food_obj = Food.objects.filter(id=1).first() print(food_obj.coupons.all())
五、cookie和session
1、cookie
保存在浏览器上的键值对,因为http请求没有状态,每一次请求都是独立的,所以需要服务器给客户端发送一个键值对,保存在客户端浏览器上,这样以后每次请求都能验证。
# 设置cookie response.set_cookie(key, value) # 获取cookie request.COOKIES[keys] # 删除cookie response.delete_cookie(key)
2、session
由于cookie是保存在浏览器中,数据不安全,且长度有限制,所以session将键值对保存在服务器中,保证无数据的安全。
session依赖于cookie,他把随机字符串作为一个大字典的键,把数据都存储在这个大字典中,字典存储在服务端,而把这个随机字符串作为cookie的值传给客户端,所以客户端只拿到这个随机值,通过这个值去服务端取数据。
注意:session默认保存在数据库中,需要创建数据库信息,才能把键值对存储在数据库中,不然无法使用,记得makemigrations
request.session.set(key, value) request.session.get(key) del request.session["key"] request.session.session_key #获取用户的随机字符串 request.session.delete(session_key) #删除当前用户的所有session数据 request.session.flush() #删除session和cookie
六、中间件
是在全局范围内修改请求和响应对象
from django.utils.deprecation import MiddlewareMixin class MyMiddleware(MiddlewareMixin): def process_request(self, request): # 在路由之前执行, # 返回None继续,返回HttpResponse则不继续 def process_view(self, request, viewfunc, view_args, view_kwargs): # 在路由匹配之后,视图函数之前执行 # 放回None继续,返回HttpResponse则直接跳到process_response def process_response(self, request, response): # response可能是视图函数返回的,也可能是其他的中间件返回的 def process_exception(self, request, exception): # 返回None则交给下一个,都是None则交给Django处理异常 # 返回Response对象,则后面不再执行此方法 def process_template_response(self, request, response): # 视图中必须返回render
Django的请求声明周期:
WSGI接收请求并封装成request对象 ---- -执行中间件中的process_request方法 ----- 路由匹配 ----- process_view方法 ----- 视图函数(数据库,模板) ----- 中间件中的process_response方法 ----- 还给WSGI
七、ajax
ajax是js的技术,与服务器进行交互,特点:异步,局部刷新
function AjaxSubmit(){ var host = '1.1.1.1'; var port = '1111'; $.ajax({ url:"/app01/ajax_submit/", type:'POST', data:{host:host,port:port}, success: function (arg) { } }); }
function AjaxSubmit_set(){ var data_list = [ {'name':'chenchao','age':18}, {'name':'lisi','age':19}, {'name':'wangwu','age':13} ]; $.ajax({ url:"/app01/ajax_submit_set/", type:'POST', tradition:true, 原生模式 data:{data:JSON.stringify(data_list)}, success: function (arg) { } }); }
//ajax上传注册信息并获取错误信息 $("#register-btn").click(function(){ //ajax上传文件,data部分必须用对象 var formData = new FormData(); formData.append("username",$("#id_username").val()); formData.append("password",$("#id_password").val()); formData.append("re_pwd",$("#id_re_pwd").val()); formData.append("email",$("#id_email").val()); formData.append("avatar",$("#id_avatar")[0].files[0]); formData.append("csrfmiddlewaretoken",$("[name='csrfmiddlewaretoken']").val()); $.ajax({ url:"/register/", type:"post", //ajax上传文件需要加两个参数 processData:false, contentType:false, data:formData, success:function(data){ if(data.status){ //如果有错误,则返回错误信息 $.each(data.msg,function(k,v){ //k是键,v是值,但是v是列表,错误信息可能有多个 $("#id_"+k).next("span").text(v[0]).parent().parent().addClass("has-error"); }) }else{ //如果没有错误,则跳转到指定页面 location.href = "/login/" } } }) }) //输入框取得焦点时,清楚错误信息 $("form input").focus(function(){ $(this).next("span").text("").parent().parent().removeClass("has-error"); });
八、form组件
1、Form
import re from django import forms from django.core.exceptions import ValidationError def mobile_validate(value): mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$') if not mobile_re.match(value): raise ValidationError('手机号码格式错误') class PublishForm(forms.Form): user_type_choice = ( (0, u'普通用户'), (1, u'高级用户'), ) user_type = forms.IntegerField(widget=forms.widgets.Select(choices=user_type_choice, attrs={'class': "form-control"})) title = forms.CharField(max_length=20, min_length=5, error_messages={'required': u'标题不能为空', 'min_length': u'标题最少为5个字符', 'max_length': u'标题最多为20个字符'}, widget=forms.TextInput(attrs={'class': "form-control", 'placeholder': u'标题5-20个字符'})) memo = forms.CharField(required=False, max_length=256, widget=forms.widgets.Textarea(attrs={'class': "form-control no-radius", 'placeholder': u'详细描述', 'rows': 3})) phone = forms.CharField(validators=[mobile_validate, ], error_messages={'required': u'手机不能为空'}, widget=forms.TextInput(attrs={'class': "form-control", 'placeholder': u'手机号码'})) email = forms.EmailField(required=False, error_messages={'required': u'邮箱不能为空','invalid': u'邮箱格式错误'}, widget=forms.TextInput(attrs={'class': "form-control", 'placeholder': u'邮箱'}))
def publish(request): ret = {'status': False, 'data': '', 'error': '', 'summary': ''} if request.method == 'POST': request_form = PublishForm(request.POST) if request_form.is_valid(): request_dict = request_form.clean() print request_dict ret['status'] = True else: error_msg = request_form.errors.as_json() ret['error'] = json.loads(error_msg) return HttpResponse(json.dumps(ret))
from django import forms class LoginForm(forms.Form): username = forms.CharField(label='username', max_length=100) password = forms.CharField(label='password', max_length=100) #钩子函数为对应字段添加判断条件 def clean_username(self): if len(self.cleaned_data.get("username"))>5: print(self.cleaned_data.get("password")) return self.cleaned_data.get("username") def clean_password(self): pass #全局钩子函数 def clean(self): if self.cleaned_data["password"] == self.cleaned_data["repeat_password"]: return self.cleaned_data
2、ModelForm
class BaseForm(ModelForm): def __init__(self, *args, **kwargs): super(BaseForm, self).__init__(*args,**kwargs) for field in self.fields.values(): field.widget.attrs.update({"class": "form-control"}) class RegForm(BaseForm): re_password = forms.CharField( label='确认密码', widget=forms.widgets.PasswordInput(), error_messages={ "required": "请确认密码" } ) class Meta: model = models.User fields = ["username", "password", "re_password", "name", "phone", "department"] widgets = { "password": forms.widgets.PasswordInput } labels = { "password": "密码" } error_messages = { "password": {"required": "请输入密码"}, "username": {"required": "请输入昵称"}, "name": {"required": "请输入姓名"}, } def clean_username(self): username = self.cleaned_data["username"] if models.User.objects.filter(username=username): self.add_error("username", "该昵称已被注册") raise ValidationError("昵称已被注册") else: return self.cleaned_data["username"] def clean(self): pwd = self.cleaned_data.get("password") re_pwd = self.cleaned_data.get("re_password") if pwd == re_pwd: return self.cleaned_data self.add_error("re_password", "两次密码不一致") raise ValidationError("两次密码不一致")
3、formset
注意:使用formset和modelformset需要在模板中写上:{{ xxformset.management_form }},在使用modelformset时,需要在模板中把所有字段都写上,可以隐藏。
from django.forms import formset_factory # 可以帮助我们创建多个表单,并且同时更新 FormSet = formset_factory(forms.StudyRecordForm, extract=0) form_set = FormSet(initial=[{"xx": xx}, {"xx": xx}]) # 传入initial代表数据库中没有,但是想让页面中默认显示的内容 if request.method == "POST": form_set = FormSet(request.POST) if form_set.is_valid(): form_set.save()
4、modelformset
from django.forms import modelformset_factory # 可以帮助我们创建多个表单,并且同时更新 FormSet = modelformset_factory(models.StudyRecord, forms.StudyRecordForm, extract=0) obj_list = models.StudyRecord.object.all() form_set = FormSet(queryset=obj_list) # 传入queryset代表数据库中有的数据 if request.method == "POST": form_set = FormSet(request.POST) if form_set.is_valid(): form_set.save()
九、auth组件
1、继承AbstractUser
from django.contrib.auth.models import AbstractUser class UserInfo(AbstractUser): """ 用户信息表 """ nid = models.AutoField(primary_key=True) phone = models.CharField(max_length=11, null=True, unique=True) def __str__(self): return self.username
2、在settings中配置
# 引用Django自带的User表,继承使用时需要设置 AUTH_USER_MODEL = "app名.UserInfo"
3、使用(必须创建了auth表才能使用)
from django.contrib import auth from django.contrib.auth.models import User from django.shortcuts import render,redirect # 创建用户 User.objects.create_superuser(.......) #创建超级用户 Use.objects.create_user(....) #创建普通用户
# 这样创建的数据,密码是密文形式,直接create密码是明文
# 验证登陆 def index(request): if request.user.is_authenticated: username = request.user.username return render(request,"index.html",locals()) return redirect("/login") def login(request): if request.method=="POST": username=request.POST.get("username") password=request.POST.get("password") user = auth.authenticate(username=username,password=password) auth.login(request,user) if user: return redirect("/index") else: return render(request,"login.html") return render(request,"login.html",locals()) def logout(request): auth.logout(request) return redirect("/login")
十、配置media
MEDIA_URL = "media/" MEDIA_ROOT = os.path.join(BASE_DIR, "media")
from django.views.static import serve # media路径配置 url(r'media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT})