验证码插件
https://www.jianshu.com/p/c9ede8f80001
安装
pip3 install django-simple-captcha
使用
配置
settings.py
中注册这个应用
INSTALLED_APPS = [
'captcha',
]
生成数据表
makemigrations
migrate
路由
urls.py
# django1.x
url(r'^captcha/', include('captcha.urls')),
# django2.x
path(r'^captcha/', include('captcha.urls')),
Form
from django import forms
from captcha.fields import CaptchaField
class CaptchaTestForm(forms.Form):
myfield = AnyOtherField() # 代指其他任意的字段
captcha = CaptchaField(error_messages={
"invalid": "无效的验证码"
})
上面的类名可以自定义,并且向里面添加新的字段
…or, as a ModelForm
:
from django import forms
from captcha.fields import CaptchaField
class CaptchaTestModelForm(forms.ModelForm):
captcha = CaptchaField()
class Meta:
model = MyModel
视图
views.py
def some_view(request):
if request.POST:
form = CaptchaTestForm(request.POST)
# check the input
if form.is_valid():
human = True
else:
form = CaptchaTestForm()
return render_to_response('template.html',locals())
模板
渲染后的 html
用户注册功能
model
假如有下面的 model
from django.db import models
from django.contrib.auth.models import AbstractUser
class UserProfile(AbstractUser):
pass
# 这里是继承了 Django 的 model
视图函数
配合验证码中的视图继续编写用户注册逻辑
from django.shortcuts import render
from django.contrib.auth.hashers import make_password
from users.models import UserProfile
def some_view(request):
if request.POST:
registerform = CaptchaTestForm(request.POST)
# check the input
if form.is_valid():
user_name = register_form.cleaned_data.get(
'email')
password = register_form.cleaned_data.get(
'password')
# 给用户的密码加密
password = make_password(password)
UserProfile.objects.create(
**{"username": user_name,
"email": user_name,
"password": password})
return render(request, 'login.html')
else:
form = CaptchaTestForm()
return render(request, 'register.html',locals())
登录验证
==前提条件是使用 Django
自带的 User
model, 或者自定义的 model,但是这个 model 需要继承 AbstractUser
==
1. 基于 FBV
# 验证用户
from django.contrib.auth import authenticate
# 验证成功后,把用户放进 session 中
from django.contrib.auth import login
# 用户退出则删除 session
from django.contrib.auth import logout
模板语言可以在前端判断用户是否通过了验证,返回 True/False
{% if request.user.is_authenticated %}
Examp
users.views.py
登录
def user_login(request):
if request.method == "POST":
user_name = request.POST.get('username')
password = request.POST.get('password')
user_obj = authenticate(username=user_name,
password=password)
if user_obj:
login(request, user_obj)
return render(request, 'index.html')
else:
return render(request, 'login.html',
{"msg": "用户名或密码错误"})
elif request.method == "GET":
return render(request, 'login.html')
退出登录
users.views.py
def user_logout(request):
logout(request)
return redirect('/')
添加自定义验证功能
users.views.py
如下示例是验证用户名或者邮箱
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q
from users.models import UserProfile
class CustomBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
try:
# 通过用户名或邮箱来获取用户对象
user_obj = UserProfile.objects.get(
Q(username=username) | Q(email=username)
)
# 验证用户的密码
if user.check_password(password):
return user_obj
except Exception as e:
return None
视图函数原来的代码不变
在 settings.py
文件中配置如下内容
AUTHENTICATION_BACKENDS = (
'users.views.CustomBackend', # 注意后面的逗号
)
2. 基于 CBV
登录
==首先说明一点的是,上面的自定义的验证类,在这里也是可以使用的。==
所以我们这里只关注视图的编写
方式一:
views.py
from django.contrib.auth.views import LoginView
class MyLoginView(LoginView):
template_name = 'login.html' # 指定返回的模板文件
urls.py
from users import views
url(r'^login/$', views.LoginView.as_view(), name='login'),
# django2.x
path(r'^login/$', views.LoginView.as_view(), name='login'),
登录成功与否的跳转到指定的 url
settings.py
from django.core.urlresolvers import reverse_lazy
LOGIN_REDIRECT_URL = reverse_lazy('index')
# index 是一个 url 的名字
# 用户登录 GET 请求的 URL或验证失败还返回登录页面
LOGIN_URL = reverse_lazy('login')
方式二:
urls.py
from django.conf.urls import url
from django.contrib.auth.views import LoginView
urlpatterns = [
url(r'^login/$',
LoginView.as_view(template_name = 'login.html'),
name='login'),
]
Django 的 LoginView
默认有 from
对象,所以可以在前端直接使用
登录成功与否的跳转到指定的 url
settings.py
from django.core.urlresolvers import reverse_lazy
LOGIN_REDIRECT_URL = reverse_lazy('index')
# index 是一个 url 的名字
LOGIN_URL = reverse_lazy('login')
退出
views.py
from django.shortcuts import redirect
from django.views import View
class MyLogout(View):
def get(self, request):
logout(request)
return redirect('/')
urls.py
from users import views
from django.conf.urls import url
urlpatterns = [
url('^logout/$', views.MyLogout.as_view(), name='logout'),
]
自定义登录状态验证类(CBV)
定义
utils/mixin_utils.py
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
class LoginRequiredMixin(object):
@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args,
**kwargs)
在 CBV
中使用
views.py
from utils.mixin_utils import LoginRequiredMixin
class LessonsView(LoginRequiredMixin, View):
"""课程章节"""
def get(self, request, course_id):
return render(request, 'course-video.html')
Django 发送邮件
配置
下面一126邮箱为例
settings.py
EMAIL_HOST = "smtp.126.com"
EMAIL_PORT = 25
EMAIL_HOST_USER = '[email protected]'
EMAIL_HOST_PASSWORD = 'yourpassword' # 这个是授权密码,不是登录密码
EMAIL_USE_TLS = False
EMAIL_FROM = EMAIL_HOST_USER
发送邮件
from django.core.mail import send_mail
from MyProject import settings
send_mail(
subject="邮件标题",
message="邮件内容",
from_email=settings.EMAIL_FROM,
recipient_list=['[email protected]','[email protected]'] # 收件箱地址列表
)
发送激活链接
场景
- 可以用于审批事情多链接
- 用于用户注册后的激活链接
逻辑
下面我用用户注册的功能来表述一下。
一般用户注册成功后,会发给用户的邮箱中一条激活用户的邮件,这个邮件中含有一个激活链接。其实就是服务器生成一条 url
,这条 url
中有服务器生成的随机字符串,用于识别每条 url
的唯一性。
逻辑如下:
用户表里会有一个字段,这个字段用于记录注册成功后用户是否激活。
在用户注册时候,服务器向用户提交的邮箱中发送用于激活的链接,同时会生成一串随机字符串,这个字符串会加入到激活链接中,同时会存放到一个专门用于存放这个字符串的表。
当用户点击这个链接时,服务器从 url
中获取到这个字符串,再从数据库中获取这个字符串,假如能获取到,就修改 用户表中的激活字段,激活用户。
具体实现代码
-
创建一个生成随机字符串的函数
def generate_random_code(length=8): """ 生成给定长度的随机字符串 :return: """ import random s = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' return ''.join(random.sample(s, length))
创建一个发送链接的函数
from django.core.mail import send_mail
def send_to_register_email(email, to_emails,
send_type='register'):
"""
用于给指定的邮箱地址发送链接
:param email: 发件人邮箱地址
:param to_emails: 收件箱地址,必须是列表或者元祖
:param send_type: 类型 注册或者找回密码
:return:
"""
if send_type == 'register':
email_title = "鲨鱼在线学习平台激活链接"
email_body = "请点击此链接地址激活账号:"
url = "http://www.sharkyun.com/active/{}/"
email_body = email_body + url.format(
generate_random_code(16)) # 使用了上面的函数
elif send_type == 'r':
email_title = "鲨鱼在线学习平台找回密码"
email_body = "请点击此链接地址:"
url = "http://www.sharkyun.com/reback/{}/"
email_body = email_body + url.format(
generate_random_code(16))
else:
email_title, email_body = '', ''
send_status = send_mail(
subject=email_title,
message=email_body,
from_email=email,
recipient_list=to_emails
)
if send_status == 1:
print('发送成功')
Django 处理上传下载静态文件
假如用户上传一个图片文件作为自己的头像或者作为一个 logo
, Django
该如何存放在本地。
之后用户访问页面时,Django
又该如何从服务器中取出该图片响应给前端呢?
Model
设计
models.py
class MyStatic(models.Model):
name = models.CharField('姓名', max_length=64)
image = models.ImageField(
'头像',
upload_to='avatar/%Y/%m/%d/',
max_length=100)
配置
首先在 settings.py
中配置
- 上传文件的根路径(针对上传)
- 以后所有的图片都以此路径为自己的根路径,自己只需要写上相对路径即可。
- 还需要配置路由相关信息(针对
GET
请求时的下载,主要是用于页面的展示)
首先在下项目的根目录下创建 media
文件夹
增加配置
settings.py
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
django.template.context_processors.media
如果启用这个处理器,每个MEDIA_URL
将包含一个RequestContext
变量,表示MEDIA_URL 设置的值。
settings.py
TEMPLATES = [
{'OPTIONS': {
'context_processors': [
'django.template.context_processors.media',
]
}}
]
上传
在项目的 urls.py
中
urls.py
from django.conf.urls import url
from learning_static import views as static_views
from LearningDjango1.settings import MEDIA_ROOT
from django.views.static import serve
urlpatterns = [
# 处理上传
url(r'^update/$', static_views.update),
# 处理页面下载展示预览
url(r'^media/(?P.*)', serve, {"document_root": MEDIA_ROOT})
]
views.py
from learning_static.models import MyStatic
def update(request):
if request.method == "POST":
name = request.POST.get('name')
file_name = request.FILES.get('file')
print(name, type(file_name))
obj = MyStatic()
obj.name = name
obj.image = file_name
obj.save()
return HttpResponse("上传成功")
else:
return render(request, 'media.html')
media.html
访问(下载)
urls.py
from django.conf.urls import url
from learning_static import views as static_views
from LearningDjango1.settings import MEDIA_ROOT
from django.views.static import serve
urlpatterns = [
url(r"^show_image/$", static_views.show_image),
# 处理已上传文件的访问路由
url(r'^media/(?P.*)', serve, {"document_root": MEDIA_ROOT})
]
views.py
def show_image(request):
file_name = request.GET.get('name')
file_obj = MyStatic.objects.filter(name=file_name).first()
return render(request,
'show_media.html',
{"image": file_obj})
show_media.html
分页
这里采用的是第三方的模块
地址: https://github.com/jamespacileo/django-pure-pagination
安装
pip3 install django-pure-pagination
注册应用
settings.py
INSTALLED_APPS = (
...
'pure_pagination',
)
配置
setting.py
PAGINATION_SETTINGS = {
'PAGE_RANGE_DISPLAYED': 10,
'MARGIN_PAGES_DISPLAYED': 2,
'SHOW_FIRST_PAGE_WHEN_INVALID': True,
}
PAGE_RANGE_DISPLAYED 每次当前页的两边显示多少个页码
FBV
视图
views.py
def index(request):
try:
page = request.GET.get('page', 1)
except PageNotAnInteger:
page = 1
objects = MyModel.object.all()
# Provide Paginator with the request object for complete querystring generation
p = Paginator(objects, 5, request=request)
# 这里的 5 是每页要显示的几条数据
people = p.page(page)
return render(request, 'index.html',
{"people": people})
基于FBV
的前端显示
index.html
{% load i18n %}
{# 循环数据开始 #}
{% for object in people.object_list %}
{{ object }}
{% endfor %}
{# 循环数据结束 #}
{# 上一页开始 #}
{% if page_obj.has_previous %}
‹‹ {% trans "previous" %}
{% else %}
‹‹ {% trans "previous" %}
{% endif %}
{# 上一页结束 #}
{# 页码显示开始 #}
{% for page in page_obj.pages %}
{% if page %}
{% ifequal page page_obj.number %}
{{ page }}
{% else %}
{{ page }}
{% endifequal %}
{% else %}
...
{% endif %}
{% endfor %}
{# 页码显示结束 #}
{# 下一页开始 #}
{% if page_obj.has_next %}
{% trans "next" %} ››
{% else %}
{% trans "next" %} ››
{% endif %}
{# 下一页结束 #}
CBV
视图
Generic Class-Based Views
views.py
from django.views.generic import ListView
from pure_pagination.mixins import PaginationMixin
from my_app.models import MyModel
class MyModelListView(PaginationMixin, ListView):
# 告诉 ListView 使用分页,每页显示 10 条数据
# 待验证
paginate_by = 10
# 将其替换为您的模型或使用 queryset 对象
object = MyModel
基于CBV
的前端显示
这个和基于FBV
的一样
ModelForm
没有外键
models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
class UserProfile(AbstractUser):
nick_name = models.CharField('昵称', max_length=25, default='')
birday = models.DateField('生日', null=True, blank=True)
mobile = models.CharField('手机', max_length=11)
userforms.py
import re
from django import forms
from users.models import UserProfile
class UserForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ['nick_name', 'birday', 'mobile']
# 针对指定字段添加自定义验证
def clean_mobile(self):
"""
验证手机号是否合法
:return: 合法的数据或者错误信息
"""
mobile = self.cleaned_data['mobile']
PRGEX_MOBILE = r'^1[358]\d{9}|^147\d{8}|^176\d{8}$'
regex = re.compile(PRGEX_MOBILE)
if regex.match(mobile):
return mobile
else:
raise forms.ValidationError('无效的手机号',
code='mobile_invalid')
添加数据功能
views.py
from django.views import View
class UserView(View):
def get(self, request):
user_form = UserForm()
return render(request,'users.html',
{'userForm': 'user_form'})
def post(self, request):
user_form = UserForm(request.POST)
if user_form.is_valid():
user_form.save()
编辑数据功能
views.py
from django.views import View
class EditUserView(View):
def get(self, request):
user_id = request.GET.get("user_id")
user_obj = UserProfile.objects.filter(id=int(user_id))
if user_obj:
user_form = UserForm(instance=user_obj)
return render(request, 'edit_user.html',
{"userForm": user_form,
"user_id": user_id})
def post(self, request):
user_id = request.GET.get("user_id")
user_obj = UserProfile.objects.filter(id=int(user_id))
if user_obj:
user_form = UserForm(data=request.POST, instance=user_obj)
user_form.save()
return HttpResponse("更新成功")
有外键
可以在自定义验证的方法里返回外键对应的一个对象
配置全局404、500页面
要想让 Django
正常显示出我们配置好的 404等页面, 需要配置 settings.py
中的 DEBUG = False
。
DEBUG = False
但是,一旦这样设置后,Django 将不再代管我们的 css 等静态文件,所以原来settings.py
中的 STATICFILES_DIRS
将失效。
我们需要自己配置处理这个静态文件的路由。
settings.py
STATIC_ROOT = os.path.join(BASE_DIR, "static")
在项目的urls.py
文件中配置路由
urls.py
from django.views.static import serve
from SKOnline.settings import STATIC_ROOT
urlpatterns = [
url(r'^static/(?P.*)',
serve, {"document_root": STATIC_ROOT}),
]
Django 中数据互导
把SQLite数据导入到MySQL中
之前我们默认使用的是SQLite数据库,我们开发完成之后,里面有许多数据。如果我们想转换成Mysql数据库,那我们先得把旧数据从SQLite导出,然后再导入到新的Mysql数据库里去。
1、SQLite导出数据
导出之前,我们先确保settins.py数据库配置选项那里,还是使用的是SQLite配置,如果已经修改了,请先修改回来:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
然后在CMD命令行里输入:
$ python manage.py dumpdata > data.json
这样就将数据导出到Django项目根目录下的data.json文件。
2、MySQL导入数据
同样,先将Django的数据库配置改为MySQL的:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': '你的数据库名',
'USER': '你的MySQL用户名',
'PASSWORD': '你的密码',
'HOST': 'localhost',
'PORT': '3306',
}
}
然后在CMD命令行里输入:
$ python manage.py loaddata data.json
注意:
确保Mysql用户表里为空的,如果之前有迁移过数据到Mysql,有可能会出错。注意出错的时候所报的错误信息。如果提示有重复主键,那需要先删掉数据。这些数据是在给MySQL数据库应用迁移文件的时候产生的,一般是content_type相关的表。
进入到MySQL,执行如下的SQL语句:
mysql> use 你的数据库名;
mysql> delete from auth_permission;
mysql> delete from django_content_type;
删除数据之后,再执行一次导入命令即可。基本上,导入不了数据都是MySQL存在数据导致的。
加载时区表
另外,有可能所安装的MySQL没有加载时区表。这个可能会导致filter对日期的查询有问题。Django官方文档也指出这个问题。MySQL官网也有对应处理方法:加载时区表。
Linux/Mac解决方法都很简单。windows系统要先下载一个sql文件:timezone_2018e_posix_sql.zip。
下载完成之后,解压得到一个sql文件,再执行cmd命令导入该文件即可:
$ mysql -u root -p mysql < timezone_posix.sql
MySQL数据到PostgreSQL
操作很简单:
$ ./manage.py dumpdata >> backup.json
到PostgreSQL对应的配置中:
$ ./manage.py loaddata backup.json
添加日志功能
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'standard': {
'format': '%(asctime)s %(levelname)-8s %(message)s'
},
'detail': {
'format': '%(asctime)s %(levelname)-8s %(pathname)s[line:%(lineno)d] %(message)s'
},
},
'handlers': {
'console': {
'level': 'INFO',
'class': 'logging.StreamHandler',
'formatter': 'standard',
},
'file': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'filename': '/var/log/django.log',
'maxBytes': 1024 * 1024 * 5, # 5 MB
'backupCount': 100,
'formatter': 'detail',
},
'app1_file': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'filename': '/var/log/app1.log',
'maxBytes': 1024 * 1024 * 5, # 5 MB
'backupCount': 100,
'formatter': 'detail',
},
'app2_file': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'filename': '/var/log/app2.log',
'maxBytes': 1024 * 1024 * 5, # 5 MB
'backupCount': 100,
'formatter': 'detail',
},
},
'loggers': {
'django': {
'handlers': ['console', 'file'],
'level': 'INFO',
'propagate': True,
},
# 自定义模块日志
'users': {
'handlers': ['console', 'file'],
'level': 'DEBUG',
'propagate': True,
},
'common': {
'handlers': ['console', 'file'],
'level': 'DEBUG',
'propagate': True,
},
'myapp': {
'handlers': ['console', 'file'],
'level': 'DEBUG',
'propagate': True,
},
'app1': {
'handlers': ['console', 'app1_file'],
'level': 'INFO',
'propagate': True,
},
'pushdata': {
'handlers': ['console', 'app2_file'],
'level': 'INFO',
'propagate': True,
},
},
}
此配置分成三个部分:
- formatters: 指定输出的格式,被handler使用。
- handlers: 指定输出到控制台还是文件中,以及输出的方式。被logger引用。
- loggers: 指定django中的每个模块使用哪个handlers。以及日志输出的级别。
注意:日志的输出级别是由loggers中的每个模块中level选项定义。如果没有配置,那么默认为warning级别。
然后在每个模块的views.py中,通过下面代码使用
import logging
# 得到 django 的日志对象,一般用于全局的日志记录
logger = logging.getLogger('django')
# 得到 app1 的日志对象,仅用于对 app1 进行日志记录
app1_logger = logging.getLogger('app1')
具体使用
logger.debug("hello django")
logger.info("hello django")
logger.error("hello django")
app1_logger.debug("hello django")
app1_logger.info("hello django")
app1_logger.error("hello django")
在 Django 外部使用 其配置信息
就是可以在不运行 Django 的情况下,在其他的文件中使用 Django 的配置信息。
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "YuLong.settings")
import django
django.setup()