Django
text::python
official::Django
Django前后端分离:text::python
一、准备工作
1 简单流程
pip install django
cd path django-admin startproject <project_name>
cd <project_name> python manage.py startapp <app_name> django-admin startapp <app_name>
INSTALLED_APPS = [ ... , <app_name>.apps.<app_name>Config ]
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'Main.apps.MainConfig' ]
from Main import views urlpatterns = [ path('index/', views.index) ]
from django.shortcuts import render,HttpResponse def index(request): return HttpResponse("Hello world")
python manage.py runserver [port]
|
2 默认项目文件
2.1 manage.py
项目管理、启动项目、创建APP、数据管理。
代码不需要修改。但需要经常使用。
2.2 /init.py
>
2.3 asgi.py
异步式接受网络请求。
代码不需要修改。
2.4 wsgi.py
同步式接受网络请求。
代码不需要修改。
2.5 urls.py
路由,URL和views函数的对应关系。
经常修改的文件。
如果导入时爆红,可以按如下方式设置:文件——设置——项目:Web——项目结构——添加内容根——项目的根文件夹(app和默认项目文件的根)
from django.contrib import admin from django.urls import path
from Main import views
urlpatterns = [ path('', views.first) path('index/', views.index) path('apple.html', views.index, name='ap') <a href="{% url 'ap' %}"> path('depart/<int:nid>/edit/', view.depart_edit) ]
|
2.6 setting.py
项目配置。
经常修改的文件。
INSTALLED_APPS = [ ... , 'Main.apps.MainConfig' ]
import os TEMPLATES = [ 'DIRS': [os.path.join(BASE_DIR,'templates')], ]
STATIC_URL = 'static/'
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'dbname', 'USER': 'root', 'PASSWORD': 'root', 'HOST': '127.0.0.1', 'PORT': 3306, } }
LANGUAGE_CODE = "zh-hans"
|
3 APP
3.1 app定义
项目中对功能进行分块管理。
例如:
- app,用户管理。【表结构、函数、HTML、CSS】
- app,订单管理。【表结构、函数、HTML、CSS】
- app,后台管理。【表结构、函数、HTML、CSS】
python manage.py startapp <app_name> django-admin startapp <app_name>
|
3.2 Migrations/init.py
当数据库内容进行修改时进行记录。
固定,不用修改。
3.3 admin.py
django默认提供了admin后台管理。
固定,不用修改。
3.4 apps.py
app的启动类
固定,不用修改。
from django.apps import AppConfig
class MainpageConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'mainPage'
|
3.5 models.py
对数据库进行操作。
经常修改。
执行命令前需要注册app。
Django4对MySQL数据库版本要求是要大于8。
下面试简单案例,详细内容在ORM框架中。
from django.db import models
class UserInfo(models.Model): name = models.CharField(max_length = 32) password = models.CharField(max_length = 64) age = models.IntegerField()
|
3.6 tests.py
单元测试。
固定,不用修改。
3.7 views.py
视图函数。
经常修改。
定义的函数要带有request参数:
- request是一个对象,封装了用户发送过来的所有数据。
templates文件夹:
- 若要在函数中返回模板文件,则在对应view的app下添加templates文件夹,然后在文件夹中写入html文件。
- 框架在寻找templates时,会根据app的注册顺序,逐一的去寻找他们的templates目录。
- 如果在setting的TEMPLATES中配置了DIRS,则会最先找DIRS中的路径,再按app的注册顺序找。
static文件夹:
- 用来存放css、js、img、plugins等静态文件,然后在html中导入是路径改成
/static/css/...
。(注意最前面有一个斜杠)
- 导入方式可以写成上述的绝对路径,但是不建议。建议在html头部加上
{% load static %}
,然后按{% static 'css/...' %}
方式导入。
from django.shortcuts import render,HttpResponse
def index(request): return HttpResponse("Hello world")
def user_list(request): return render(request, "user_list.html")
def depart_edit(request, nid): return render(request, 'depart_edit.html')
|
<link rel="stylesheet" type="text/css" href="/static/css/main.css">
{% load static %} <link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}">
|
4 系统类
from django.utils.safestring import mark_safe
page_str_list = [] for i in range(1, 21): ele = "<li><a href = '?page = {}'>{}</a></li>".format(i, i) page_str_list.append(ele) page_string = mark_safe("".join(page_str_list))
|
二、数据处理
1 向浏览器发送参数
写在views的函数中。
想在html中显示参数值时,可以在html中写上{{para}}
,然后在python函数中传入字典即可。
<div> n1的值是{{n1}} </div> <div> n2的值是{{n2}} </div>
<div> n2的第1个值是{{n2.0}} </div> <div> n2的第2个值是{{n2.1}} </div> <div> n2的第3个值是{{n2.2}} </div>
<div> n3的第name个值是{{n3.name}} </div> <div> n3的第height个值是{{n3.height}} </div> <div> n3的第weight个值是{{n3.weight}} </div>
|
def index(request): n1 = "Hello World" n2 = ["admin","user","people"] n3 = {"name":"Tsumugi","height":152,"weight":38} params = {"n1": n1, "n2": n2, "n3": n3} return render(request, "index.html", params)
|
2 判断
一个块内可以写多行语句。
{% if n3.name == "Tsumugi" %} <span>Tsumugi</span> <span>Tsumugi</span> {% elif n3.height == 160 %} <span>Height is 160</span> {% else %} <span>other</span> {% endif %}
|
3 循环
Django不支持enumerate函数。
{% for val in n2 %} <span>n2的第{{ forloop.counter }}个值是{{ val }}<br /></span> {% endfor %}
{% for k in n3.keys %} <span>keys is {{k}}</span> {% endfor %}
{% for v in n3.values %} <span>values is {{v}}</span> {% endfor %}
{% for k,v in n3.items %} <li>n3的{{k}}是{{v}}</li> {% endfor %}
|
4 获取用户参数
对views函数中的request进行操作。
request是一个对象,封装了用户发送过来的所有数据。
重定向模式:类似DNS的迭代查询,返回网址让浏览器去查询,而不是服务器自己去查询。(可以节约性能)
403 Forbidden:Django自带CSRF漏洞防护,若不带token会报403。所以只需要再form后面加上{% csrf_token %}
带上CSRF的token即可解决该问题。
<form method="post" action="after_login.html"> {% csrf_token %} <input type="text" name="user" placeholder="username"/> <input type="password" name="pwd" placeholder="password"/> <input type="submit" value="submit" /> </form>
|
def test(request): return HttpRespoense("Content") return render(request, "index.html") return redirect("https://www.baidu.com")
request.method
QueryDict = request.GET QueryDict = request.POST
username = request.POST.get('username', '')
dic = request.query_params
dic = request.data
|
from django.shortcuts import redirect from django.http import QueryDict
def my_view(request): params = {'name': 'John', 'age': 25} query_string = QueryDict('', mutable=True) query_string.update(params) return redirect('/new-url/?{}'.format(query_string.urlencode()))
from django.shortcuts import redirect from django.http import QueryDict
def my_view(request): params = {'name': 'John', 'age': 25} query_string = QueryDict('', mutable=True) query_string.update(params)
redirect_url = '/new-url/' redirect_request = request.POST.copy() redirect_request.update(query_string)
return redirect(redirect_url, request=redirect_request)
|
5 pymysql
create database db DEFAULT CHARSET utf8 COLLATE utf8_general_ci;
|
import pymysql
conn = pymysql.connect ( host="127.0.0.1",port = 3306,user = 'root',passwd = "root123", charset = 'utf8 ', db = 'unicom')
cursor = conn.cursor (cursor = pymysql.cursors.DictCursor)
cursor.execute( "insert into admin (username,password,mobile) values ( 'admin' , '123456','11011101110')")
conn.commit()
cursor.close() conn.close()
|
6 ORM框架
因为通过pymysql操作数据库还是太繁琐了,于是Django直接集成了一个ORM框架。底层可以是任意的数据库,类似于PHP的PDO。
代码都是写在models.py中。
使用前要:pip install mysqlclient
ORM可以做的事:
- 创建、修改、删除数据库中的表。(不用写SQL)
- 操作表中的数据。(不用写SQL)
ORM不可以做的事:
设置数据库
models.CharField(max_length = 32)
models.IntegerField()
models.DecimalField(max_digits = 10, decimal_places = 2)
models.DateTimeField() models.DateField()
models.BigAutoField(primary_key = True) models.AutoField(primary_key = True)
models.IntegerField(verbose_name = '用户名')
models.IntegerField(default = 2)
models.IntegerField(null = True, blank = True)
models.IntegerField(primary_key = True)
user = models.ForeignKey(to = "userTable", to_field = "id",on_delete = models.CASCADE) user = models.ForeignKey(to = "userTable", to_field = "id", null = True, blank = True, on_delete = models.SET_NULL)
queryset = models.UserInfo.objects.all() for obj in queryset: obj.depart_id obj.deaprt.title
gender_choices = ((1, "男"),(2, "女")) gender = models.SmallIntegerField(verbose_name = "性别", choices = gender_choices)
queryset = models.UserInfo.objects.all() for obj in queryset: obj.gender obj.get_gender_display()
|
数据操作
from django.db import models
class UserInfo(models.Model): name = models.CharField(max_length = 32) user = models.ForeignKey(to = "userTable", to_filed = "id",on_delete = models.CASCADE) """ 上述语句会通过Django自动翻译成下列sql 表名是app名字 + 下划线 + 类名 id是自动加上的且是自增和主键(设置了主键则不会有id) create table appName_userinfo ( id bigint auto_increment primary key, name varchar(32), password varchar(64), age int ) """
class UserInfo(models.Model): name = models.CharField(max_length = 32) password = models.CharField(max_length = 64)
class UserInfo(models.Model): name = models.CharField(max_length = 32) password = models.CharField(max_length = 64) size = models.IntegerField(default = 2)
UserInfo.objects.create(name = "tsumugi", password = "123456", size = "36")
UserInfo.objects.filter(id = 3).delete() UserInfo.objects.all().delete()
UserInfo.objects.filter(id = 2).update(password = "999") UserInfo.objects.all().update(password = "999")
data_list = UserInfo.objects.filter(id = 1) data_list = UserInfo.objects.all() row_list = UserInfo.objects.filter(id = 1).first()
for obj in data_list: print(obj.id, obj.name, obj.password)
queryset = models.UserInfo.objects.all().order_by("name") queryset = models.UserInfo.objects.all().order_by("-name")
exists = models.UserInfo.objects.filter(mobile=mobile).exists()
queryset = models.UserInfo.objects.exclude(id=id).filter(mobile=mobile)
|
筛选数据
UserInfo.objects.filter(mobile="11111111111",id=1)
data_dict = {"mobile":"11111111111", "id":1} UserInfo.objects.filter(**data_dict)
UserInfo.objects.filter(id=12) UserInfo.objects.filter(id__gt=12) UserInfo.objects.filter(id__gte=12) UserInfo.objects.filter(id__lt=12) UserInfo.objects.filter(id__lte=12)
UserInfo.objects.filter(mobile__startswith="1999") UserInfo.objects.filter(mobile__endswith="999") UserInfo.objects.filter(mobile__contains="995")
|
数据库命令
python manage.py makemigrations
python manage.py migrate
python manage.py makemigrations --empty <app_name>
|
7 模版数据处理
模版内使用函数不要带括号。
模版内不能使用带参数的函数。
<tr> <th>{{ obj.id }}</th> <td>{{ obj.name }}</td> <td>{{ obj.account }}</td> <td>{{ obj.create_time|date: "Y-m-d H:i:s" }}</td> <td>{{ obj.get_gender_display }}</td> <td>{{ obj.deaprt.title H</td> </tr>
|
8 Redis
official::Django 缓存框架
在Django中可以通过以下方式使用Redis数据库:
- 安装redis包:
pip install redis
和pip install django-redis
- 配置Django的settings.py文件,添加Redis作为缓存后端:
CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", "CONNECTION_POOL_KWARGS": {"max_connections": 100} } } }
SESSION_ENGING = 'django.contrib.sessions.backend.cache'
SESSION_CACHE_ALIAS = 'default'
SESSION_COOKIE_AGE = 60 * 24
|
from django_redis import get_redis_connection
conn = get_redis_connection('database_name')
conn.set('key', 'value')
value = conn.get('key')
conn.delete('key')
key_exists = conn.exists('key')
conn.expire('key', 10)
keys_list = conn.keys('*')
|
9 MongoDB
在Django中可以通过以下方式使用Redis数据库:
- 安装redis包:
pip install mongoengine
- 配置Django的settings.py文件,添加Mongo数据库
DATABASES = { 'default': { ... }, 'mongodb': { 'NAME': 'db', 'HOST': '127.0.0.1', 'PORT': 27017, } }
|
import mongoengine from management.settings import DATABASES
mongoengine.connect(DATABASES['mongodb']['NAME'])
class Trends(mongoengine.Document): tid = mongoengine.StringField()
|
from pymongo import MongoClient
client = MongoClient('mongodb://localhost:27017/')
db = client['database_name']
collection = db['collection_name']
document = {"name": "John", "age": 30} result = collection.insert_one(document) print(result.inserted_id)
query = {"name": "John"} results = collection.find(query) for result in results: print(result)
query = {"name": "John"} new_values = {"$set": {"age": 31}} collection.update_one(query, new_values)
query = {"name": "John"} collection.delete_one(query)
|
三、文件设计
1 模板继承
在父html中写上{% block ... %}{% endblock %}
,然后在子页面中先{% extends 'father.html' %}
引入父页面,再重写{% block ... %}{% endblock %}
完成子页面的独立设计。
{% load static %}
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Title</title> <link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}"> {% block css %} {% endblock %} </head> <body> <div class="container"> {% block content %} {% endblock %} </div> <script src="{% static 'js/main.js' %}"></script> {% block js %} {% endblock %} </body> </html>
{% extends 'father.html' %}
{% block css %} <link rel="stylesheet" type="text/css" href="{% static 'css/other.css' %}"> {% endblock %}
{% block content %} <h1>Hello World</h1> {% endblock%}
{% block js %} <script src="{% static 'js/other.js' %}"></script> {% endblock %}
|
2 获得数据库固定数据
urlpatterns = [ path('userAdd.html', view.user_add) ]
class UserInfo(models.Model): name = models.CharField(max_length = 32) gender_choices = ((1, "男"),(2, "女")) gender = models.SmallIntegerField(verbose_name = "性别", choices = gender_choices) depart = models.ForeignKey(to = "Department", to_filed = "id",on_delete = models.CASCADE) class Department(models.Model):
def user_add(request): context = { "gender_choices": models.UserInfo.gender_choices, "depart_list": models.Department.objects.all() } return render(request, 'user_add.html', context)
<div> <label>性别</label> <select class="form-control"> {% for item in gender_choices %} <option value="{{ item.0 }}">{{ item.1 }}</option> {% endfor%} </select> </div> <div class="form-group"> <label>部门</label> <select class="form-control"> {% for item in depart_list %} <option value="{{ item.id }}">{{ item.title }}</option> {% endfor %} </select> </div>
|
3 修改文本
页面没变,通过GET和POST请求的方式不同完成编辑。
urlpatterns = [ path('depart/<int:nid>/list/', view.depart_list), path('depart/<int:nid>/edit/', view.depart_edit) ]
def depart_list(request): queryset = models.Department.objects.all() return render(request, 'depart_edit.html', {"queryset": queryset}) def depart_edit(request, nid): if request.method == "GET": row_object = models.Department.objects.filter(id = nid).first() return render(request, 'depart_edit.html', {"row_object": row_object}) title = request.POST.get("title") models.Department.objects.filter(id = nid).update(title = title) return redirect('/depart/list')
<tbody> {% for obj in queryset %} <!-- GET请求,进入对应编辑界面 --> <a href="/depart/{{ obj.id }}/edit/">编辑</a> {% endfor%} </tbody> <div> <form method="post"> {% csrf_token %} <input name="title" value="{{ row_object.title }}" /> <button type="submit">提交</button> </form> </div>
|
4 设计时的问题
出现的问题:
- 用户提交数据没有校验。
- 出现错误时,页面上应该有错误提示。
- 页面上,每一个字段都需要我们重新写一遍。
- 关联的数据,手动去获取并展示循环展示在页面。
Form组件可以解决前3个问题。ModelForm可以解决全部。
1 简单案例
from django import forms
class MyForm(forms.Form): user = forms.CharField(widget=forms.TextInput) pwd = forms.CharField(widget=forms.PasswordInput) email = forms.CharField(widget=forms.EmailInput)
def index(request): if request.method == "GET": forms = MyForm() return render(request, 'index.html', {"forms": forms})
<form method="post"> {% for field in forms %} {{ field }} {% endfor %} </form>
|
1 简单案例
from django import forms
class MyForm (ModelForm) : xx = forms.CharField(widget=forms.TextInput) class Meta: model = UserInfo fields =["username","password","email","xx"] def debug(): print(self.instance.pk)
def index(request) : if request.method == "GET": forms = MyForm() return render(request, 'index.html', {"forms": forms})
<form method="post"> {% for field in forms %} {{ field.label }} : {{ field }} {% endfor %} </form>
|
2 模版输出
<form method="post"> {% for field in forms %} {{ field.label }} : {{ field }} {% endfor %} </form>
|
class WorkInfo(models.Model): """ 作品表 """ title = models.CharField(verbose_name='作品分区', max_length=32) def __str__(self): return self.title
class UserHodelForm(forms.ModelForm): class Meta: model = models.UserInfo fields = ["name","password","age"] widgets = { "name": forms.TextInput(attrs={"class": "form-control"}), "password": forms.PasswordInput(attrs={"class": "form-control"}), "age": forms.TextInput(attrs={"class": "form-control"}), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for name,field in self.fields.items(): field.widget.attrs = {"class": "form-control"}
|
4 数据操作
class UserHodelForm(forms.ModelForm): name = forms.CharField(label="用户名", min_length = 3) password = forms.CharField(label="密码", validators = [RegexValidator(r'^1[3-9]\d{9}$', '密码格式错误')]) class Meta: model = models.UserInfo fields = ["name","password","age"] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for name,field in self.fields.items(): field.widget.attrs = {"class": "form-control"}
def user_model_form_add(request): """ 添加数据 """ form = UserModelForm(data = request.POST) if form.is_valid(): form.save() return redirect('index.html') else: return render(request, 'index.html', {"form": form})
def user_edit(request, nid): """编辑用户""" row_object = models.UserInfo.objects.filter(id = nid).first() if request.method == "GET": form = UserModelForm(instance = row_object)
return render(request,'index.html',{'form': form}) form = UserModelForm(data = request.POST, instance = row_object) if form.is_valid(): form.save() return redirect('index.html') else: return render(request, 'index.html', {"form": form})
<form method="post"> {% for field in form %} <label> {{ field.label }} </label> {{ field }} <!-- 输出错误信息,可能非常多,是一个列表,只用第一个即可 --> <span style="color: red"> {{ field.errors.0 }} </style> {% endfor %} </form>
|
class Meta: model = models.UserInfo fields = "__all__" exclude = ['level']
|
6 数据校验
class PrettyModelForm(forms.ModelForm):
mobile = forms.CharField( label = "手机号", validators = [RegexValidator(r'^1[3-9]\d9$', '手机号格式错误")],
# 方式2:钩子函数(若字段存在,类会自动生成clean_字段的函数,在接收到数据后会自动调用该函数) def clean_mobile(self): txt_mobile = self.cleaned_data["mobile"] if len(txt_mobile) !=11: # 验证不通过 raise ValidationError("格式错误") # 验证通过,用户输入的值返回 return txt_mobild
|
六、网络
1 持久化
request.session['info'] = {'id': admin_object.id, 'name': admin_object.username}
|
2 跨域问题
同源策略是浏览器的一项安全策略,浏览器只允许js代码请求和当前所在服务器域名,端口,协议相同的数据接口上的数据,这就是同源策略。
也就是说,当协议、域名、端口任意一个不相同时,都会产生跨域问题,所以又应该如何解决跨域问题呢。
安装:
pip install djangorestframework
pip install django-cors-headers
INSTALLED_APPS = [ ... 'rest_framework', 'corsheaders', ]
MIDDLEWARE = [ ... 'corsheaders.middleware.CorsMiddleware', ... ]
CORS_ALLOW_CREDENTIALS = True CORS_ORIGIN_ALLOW_ALL = True CORS_ALLOW_HEADERS = ['*']
CSRF_TRUSTED_ORIGINS = ['127.0.0.1:5173']
|
七、好用组件
在对应app下新建一个utils
文件夹,然后文件夹下放组件
1 分页组件
""" 白定义的分页组件,以后如果想要使用这个分页组件,你需要做如下几件事: 在视图函数中: def pretty_list(request): # 1.根据自己的情况去筛选自己的数据 queryset = models.PrettuNum.objects.all() # 2.实例化分页对象 page_object = Pagination(request, queryset) context = { "queryset": page_object.page_queryset, # 分完页的数据 "page_string": page_object.html() # 生成页码 } return render(request,'pretty_list.html' , context)
在HTML页面中: {% for obj in queryset %} {{obj.xx}} {% endfor %}
<ul class="pagination"> {{ page_string }} </ul> """
from django.utils.safestring import mark_safe
class Pagination(object): def __init__(self, request, queryset, page_size=10, page_param="page", plus=5): """ :param request: 请求的对象 :param queryset: 符合条件的数据(根据这个数据给他进行分页处理) :param page_param: 每页显示多少条数据 :param page_param: 在URL中传递的获取分页的参数,例如: /list/?page=12 :param plus: 显示当前页的前或后几页(页码) """ page = request.GET.get(page_param, "1") if page.isdecimal(): page = int(page) else: page = 1 self.page = page self.page_size = page_size
self.start = (page - 1) * page_size self.end = page * page_size
self.page_queryset = queryset[self.start: self.end]
total_count = queryset.count() total_page_count, div = divmod(total_count, page_size) if div: total_page_count += 1 self.total_page_count = total_page_count self.plus = plus
def html(self): if self.total_page_count <= 2 * self.plus + 1: start_page = 1 end_page = self.total_page_count
else: if self.page <= self.plus: start_page = 1 end_page = 2 * self.plus + 1 else: if (self.page + self.plus) > self.total_page_count: start_page = self.total_page_count - 2 * self.plus end_page = self.total_page_count else: start_page = self.page - self.plus end_page = self.page + self.plus page_str_list = [] page_str_list.append('<li><a href="?page={}">首页</a></li> '.format(1)) if self.page > 1: prev = '<li><a href="?page={}">上一页</a></li>'.format(self.page - 1) else: prev = '<li><a href="?page={}">上一页</a></li>'.format(1) page_str_list.append(prev)
for i in range(start_page, end_page + 1): if i == self.page: ele = '<li class="active"><a href="?page=P{}">{}</a></li>'.format(i, i) else: ele = '<li><a href=" ?page={}">{}</a></li> '.format(i, i) page_str_list.append(ele) if self.page < self.total_page_count: prev = '<li><a href="?page={}">下一页</a></li>'.format(self.page + 1) else: prev = '<li><a href="?page={}">下一页</a></li> '.format(self.total_page_count) page_str_list.append(prev)
page_str_list.append('<li><a href="?page={}">尾页</a></li>'.format(self.total_page_count)) search_string = """ """ page_str_list.append(search_string) page_string = mark_safe("".join(page_str_list)) return page_string
|
2 邮箱验证码
流程:
注册163.com邮箱
在设置中开启IMAP/SMTP和POP3/SMTP服务(授权密码要记好,只会显示一次)
- 配置setting.py
- 发送
official::发送邮件
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' EMAIL_HOST = 'smtp.163.com' EMAIL_PORT = 25
EMAIL_HOST_USER = 'xxx@163.com'
EMAIL_HOST_PASSWORD = ''
EMAIL_FROM = 'name <xxx163.com>'
|
def send_captcha_email(email, captcha): subject = '验证码' message = f'您的验证码是: {captcha}'
send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [email])
|