用戶注冊并向163郵箱發(fā)送郵件(兩種方式:同步和異步,ubuntu和windows上的不同)
************** 三.用戶注冊功能的實(shí)現(xiàn)* ******************
在總項(xiàng)目下新建static/css,images,js,在總項(xiàng)目下新建templates
注冊頁面出現(xiàn)樣式
1將注冊頁面的html文件放到templates
2.為注冊頁面寫一個(gè)View
from django.shortcuts import render
def register(request):
return render(request,"register.html")
3.為這個(gè)View配置一個(gè)一級路由
在總項(xiàng)目的users下:url(r'^user/', include("user.urls",namespace="user")), #用戶模塊
4.寫二級路由:
from django.conf.urls import url
from user import views
url(r'^register$', views.register,name="register"), #注冊
5.讓程序運(yùn)行起來,通過瀏覽器進(jìn)行訪問127.0.0.1:8000/user/register進(jìn)行訪問
6.修改靜態(tài)資源的路徑
在register.html中的<head>標(biāo)簽上面一行寫
{% load staticfiles %} {#修改靜態(tài)資源的路徑#}
然后將register.html中的css,images,js文件以這種方式來修改href="{% static 'css/reset.css' %}"
=====================================================================
1.將register.html中的表單form進(jìn)行如下修改
<form method="post" action="/user/register_handle">
{% csrf_token %}
2.為此表單的action屬性提供views,因?yàn)橐?yàn)證郵箱的合法性用到了正則,所有要導(dǎo)入import re
def register_handle(request):
進(jìn)行注冊處理
接收數(shù)據(jù)
username = request.POST.get("user_name")
password = request.POST.get("pwd")
email = request.POST.get("email")
allow = request.POST.get("allow") #用戶有沒有接受協(xié)議
進(jìn)行數(shù)據(jù)校驗(yàn)
if not all([username,password,email]):
return render(request,"register.html",{"errmsg":"數(shù)據(jù)不完整"})
if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
return render(request, "register.html",{"errmsg":"郵箱格式不正確"})
if allow != "on":
return render(request, "register.html", {'errmsg':'請同意協(xié)議'})
try:
user = User.objects.get(username = username)
except User.DoesNotExist:
user = None
if user:
return render(request,"register.html",{"errmsg":"用戶名已經(jīng)存在"})
進(jìn)行業(yè)務(wù)處理,進(jìn)行用戶注冊
返回應(yīng)答
3.寫二級路由: url(r'^register_handle$',views.register_handle,name="register_handle"), #注冊處理
=====================================================================
.進(jìn)行業(yè)務(wù)處理,進(jìn)行用戶注冊
在views中
from user.models import User
以下兩種方法任選其一,最好選用(2)
(1)
在def register_handle(request):
加入進(jìn)行業(yè)務(wù)處理,進(jìn)行用戶注冊部分,如下寫
user = User()
user.username = username
user.password = password
user.email = email
user.save()
(2)使用django自帶的認(rèn)證系統(tǒng)create_user()輔助函數(shù)
在def register_handle(request):
加入進(jìn)行業(yè)務(wù)處理,進(jìn)行用戶注冊,如下寫
user = User.objects.create_user(username, email, password)
=======================================================
返回應(yīng)答:實(shí)現(xiàn)注冊成功以后,跳轉(zhuǎn)到首頁
1.為首頁配置View,首頁屬于商品模塊,所以在goods應(yīng)用的views里面寫代碼,即在goods\views
中要定義一個(gè)index函數(shù)
from django.shortcuts import render
def index(request):
return render(request,"index.html")
2.配置goods下的二級路由
from django.conf.urls import url
from goods import views
urlpatterns = [
url(r'^$', views.index,name="index"), #首頁
]
3.在user/views中寫重定向,和反轉(zhuǎn)
from django.shortcuts import redirect
from django.core.urlresolvers import reverse
進(jìn)行重定向的時(shí)候,想使用反向解析的形式
return redirect(reverse("goods:index"))
反向解析的過程
在總項(xiàng)目的一級路由urls中url(r'^', include("goods.urls",namespace="goods")), #商品模塊
在商品的urls中url(r'^$', views.index,name="index"), #首頁
運(yùn)行程序后,打開127.0.0.1:8000/user/register,此時(shí)可以實(shí)現(xiàn)注冊成功,自動跳轉(zhuǎn)到index.html頁面中去,注冊的數(shù)據(jù)會出現(xiàn)在數(shù)據(jù)庫里,在mysql數(shù)據(jù)庫中能進(jìn)行如下查詢,select * from df_user \G,
is_active為1表示該用戶已經(jīng)激活了,那如果我不想讓其進(jìn)行激活,在user/view中
進(jìn)行業(yè)務(wù)處理,進(jìn)行用戶注冊的地方
user = User.objects.create_user(username, email, password)
user.is_active = 0
user.save()
再點(diǎn)擊注冊,is_active為0
=======================================================
測試一下數(shù)據(jù)不合法的情況
在register.html下的</form>下面一行這樣寫
{{ errmsg }}
再次進(jìn)行注冊測試,注冊不合法的原因會出現(xiàn)在注冊下面
在user/views中要寫驗(yàn)證用戶名是否重復(fù)的代碼,可以使用get()方法,它只能返回滿足條件的一條記錄,且只能有一條的記錄,如果查詢不到它會報(bào)一個(gè)異常,所以我們需要try:
try:
user = User.objects.get(username = username)
except User.DoesNotExist:
user = None
if user:
return render(request,"register.html",{"errmsg":"用戶名已經(jīng)存在"})
user = User.objects.create_user(username, email, password)
==============================================================
==============================================================
上面的方法需要兩個(gè)url地址才能完成注冊,下面這種方法是將顯示注冊頁面和注冊處理使用同一個(gè)url地址
----------------(注冊使用的是get請求,注冊處理使用的是post請求)
1.將register.html中的form進(jìn)行修改
<form method="post" action="/user/register">
2.其次,在user/views中進(jìn)行if----else的請求判斷,if requests.method =="GET"則返回return render(request,"register.html")否則else:進(jìn)行注冊處理,和數(shù)據(jù)接收校驗(yàn)
def register(request):
if request.method == "GET":
return render(request,"register.html")
else:
# def register_handle(request):
#這里是進(jìn)行注冊處理
# 1.接收數(shù)據(jù)
username = request.POST.get("user_name")
password = request.POST.get("pwd")
email = request.POST.get("email")
allow = request.POST.get("allow")
# 2.進(jìn)行數(shù)據(jù)校驗(yàn)
if not all([username,password,email]):
return render(request,"register.html",{"errmsg":"數(shù)據(jù)不完整"})
if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
return render(request, "register.html",{"errmsg":"郵箱格式不正確"})
if allow != "on":
return render(request, "register.html", {'errmsg':'請同意協(xié)議'})
try:
user = User.objects.get(username = username)
except User.DoesNotExist:
user = None
if user:
return render(request,"register.html",{"errmsg":"用戶名已經(jīng)存在"})
# 3.業(yè)務(wù)處理,進(jìn)行用戶注冊
# user = User()
# user.username = username
# user.password = password
# user.email = email
# user.save()
user = User.objects.create_user(username, email, password)
# 不想讓其進(jìn)行激活
# user.is_active = 0
# user.save()
# 4.返回應(yīng)答
return redirect(reverse("goods:index"))
==================================================================
類視圖的使用:使用一個(gè)特定的函數(shù)提供服務(wù),并且具有一個(gè)特定的模板,django使用叫做‘URLconfs’的配置來為URL匹配視圖。 一個(gè)URLconf負(fù)責(zé)使用正則表達(dá)式將URL模式匹配到視圖。
from django.views.generic import View
from django.core.urlresolvers import reverse
from django.shortcuts import render, redirect
一.定義一個(gè)視圖和函數(shù)
class RegisterView(View):
def get(self,request):
if request.method =="GET":
return render(request,"register.html")
def post(self,request):
1.接收數(shù)據(jù)
username = request.POST.get("user_name")
password = request.POST.get("pwd")
email = request.POST.get("email")
allow = request.POST.get("allow")
print(allow)
2.進(jìn)行數(shù)據(jù)校驗(yàn)
3.業(yè)務(wù)處理,進(jìn)行用戶注冊
4.返回應(yīng)答
二.修改一個(gè)新的視圖的路由配置:
因?yàn)橐呀?jīng)定義好新的視圖RegisterView了,在user/urls里面將注冊的路由register和register_handle的路由注釋掉,寫入新的路由,先導(dǎo)入from user.views import RegisterView
再url(r'^register$',RegisterView.as_view(),name="register"), #注冊與注冊處理
=======================================================================
到該步驟即可實(shí)現(xiàn)注冊,判斷注冊的是否合法,并在注冊成功后實(shí)現(xiàn)跳轉(zhuǎn)到首頁,
此時(shí)注冊頁面的網(wǎng)址是http://127.0.0.1:8000/user/register
跳轉(zhuǎn)到首頁的網(wǎng)址是http://127.0.0.1:8000/
*********** 四.激活注冊的用戶并實(shí)現(xiàn)同步登陸 *************
因?yàn)樵趗ser/view中
進(jìn)行業(yè)務(wù)處理,進(jìn)行用戶注冊的地方
user = User.objects.create_user(username, email, password)
user.is_active = 0
user.save()
點(diǎn)擊注冊,is_active為0
那么用戶屬于尚未激活,激活用戶的辦法:打算使用將登陸網(wǎng)址發(fā)送郵件給注冊了的用戶,用戶在郵箱點(diǎn)擊進(jìn)入登陸頁面,該用戶才能被激活,登陸成功后,才進(jìn)入首頁,該方法具有一定的安全性。
在發(fā)送激活郵件的時(shí)候,包含的激活鏈接:http://127.0.0.1:8000/user/active/3 其中3為注冊的用戶在mysql數(shù)據(jù)庫中保存的用戶id,由于在激活的鏈接中需要包含用戶的身份信息,所以要先進(jìn)行身份信息加密后,再進(jìn)行鏈接的發(fā)送。
加密用到的模塊(http://itsdangerous.readthedocs.io/en/latest/)
首先安裝pip install itsdangerous
然后在user/views里面from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
TimedJSONWebSignatureSerializer這個(gè)方法就是用來進(jìn)行加密和解密的,使用Serializer()創(chuàng)建對象,需要填寫兩個(gè)參數(shù)(1)secretkey是加密的密鑰,(2)過期時(shí)間是3600,單位是秒,也就是一個(gè)小時(shí)
django項(xiàng)目自帶一個(gè)secret_key,位于總項(xiàng)目的setting下面,可以使用這個(gè)key作為加密的密鑰,也可以自己去設(shè)置,SECRET_KEY = '9yfj@mf=&xw#6&2wxz&dq=dgxo=37i=(riiv!ujiv%2tsi!#%!'
1.在user/views里面
from django.conf import settings
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
2.在進(jìn)行業(yè)務(wù)處理,進(jìn)行用戶注冊的地方進(jìn)行加密生成新的token
user = User.objects.create_user(username, email, password)
user.is_active = 0
user.save()
加密用戶的身份信息,生成激活的token,
serializer=Serializer(settings.SECRET_KEY,3600) #密鑰,過期時(shí)間一個(gè)小時(shí)
info ={"confirm":user.id} #定義一個(gè)要加密的字典對象
token = serializer.dumps(info) # dumps()方法是進(jìn)行加密的,返回值就是加密后的內(nèi)容(想要解密的話serializer.loads(token))
token =token.decode("utf8") #進(jìn)行解碼:默認(rèn)使用的是utf8,“utf8”可以省略
.
.
.
.
# 返回應(yīng)答,跳轉(zhuǎn)到首頁
return redirect(reverse("goods:index"))
====================================================================
發(fā)送郵件的過程
1.為激活的鏈接地址配置View:
先在user/view里面導(dǎo)入
from django.http import HttpResponse
from itsdangerous import SignatureExpired
class ActiveView(View):
def get(self,request,token):
# 進(jìn)行用戶激活
# 解密,獲取激活的用戶信息
serializer = Serializer(settings.SECRET_KEY,3600)
try:
info=serializer.loads(token)
user_id = info["confirm"]
user = User.objects.get(id=user_id)
user.is_active = 1
user.save()
return redirect(reverse("user:login"))
except SignatureExpired as e:
return HttpResponse("激活鏈接已經(jīng)過期")
2.配置新的路由,用于用戶激活,user/views里面
url(r'^active/(?P<token>.*)$',ActiveView.as_view(),name="active"), #用戶激活
此時(shí)在該文件里面導(dǎo)入了兩個(gè)視圖,from user.views import RegisterView,ActiveView
==============================================================
1.在用戶激活后需要進(jìn)行登陸,所以還要配置一個(gè)關(guān)于登陸的路由
先在user/views里面寫一個(gè)關(guān)于登陸的路由
class LoginView(View):
def get(self,request):
return render(request,"login.html")
2.在用戶二級路由下,
from user.views import LoginView
url(r'^login$',LoginView.as_view(),name="login"), #登陸
注意:實(shí)際在做一個(gè)項(xiàng)目的時(shí)候,如果激活鏈接已經(jīng)過期,應(yīng)該再返回一個(gè)頁面,告訴你激活鏈接已經(jīng)過期了,再單擊一個(gè)什么按鈕的,再發(fā)送一個(gè)。
=====================================================================
django中內(nèi)置了郵件發(fā)送功能,被定義在django.core.mail模塊中。
需要使用SMTP服務(wù)器
163郵件為例:開啟POP3/SMTP/IMAP客戶端授權(quán)密碼
用戶名為:chushang1220525352,密碼: ,授權(quán)碼為:dailyfresh123456
1.打開settings.py文件進(jìn)行配置:
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST ="smtp.163.com"
EMAIL_PORT = 25
EMAIL_HOST_USER = "chushang1220525352@163.com"
EMAIL_HOST_PASSWORD = "dailyfresh123456"
EMAIL_FROM = "葉良臣chushang1220525352@163.com"
======================================================================
同步的方式:
1.在user/views里面導(dǎo)入發(fā)郵件的包
from django.core.mail import send_mail
在token =token.decode("utf8")下面一行對齊的代碼
# 發(fā)郵件
subject = "天天都有好吃的,天天生鮮歡迎你的到來"
message = ""
html_message = "<h1>%s你好,歡迎%s小朋友來注冊會員</h1>請點(diǎn)擊下面的鏈接激活你的賬戶<br><a %(username,username,token,token)
sender = settings.EMAIL_FROM
receiver = [email]
#time.sleep(10)
send_mail(subject,message,sender,receiver,html_message=html_message)
##由于html_message不是 send_mail自帶的參數(shù),故而使用默認(rèn)值參數(shù)的方法進(jìn)行傳遞,html_message這個(gè)參數(shù)是用戶自己命名的,可以更換
return redirect(reverse("goods:index"))
=============================================================
在windows虛擬環(huán)境中python manage.py runserver
運(yùn)行項(xiàng)目后打開http://127.0.0.1:8000/user/register
進(jìn)行按條件注冊,注冊成功后,django會發(fā)送一封郵件到163郵箱中去,在郵箱中點(diǎn)擊這封郵件的里的鏈接,進(jìn)入到http://127.0.0.1:8000/user/login界面,將之前注冊的那個(gè)賬戶和密碼進(jìn)行登陸,登陸成功后自動跳轉(zhuǎn)到首頁。
該同步的方法存在的問題:send_mail()是堵塞的情況下進(jìn)行發(fā)送的,如果還沒有發(fā)送成功的時(shí)候,就會一直進(jìn)行堵塞的。這樣就會造成用戶長時(shí)間的等待,用戶體驗(yàn)不好。
********* 四.celery異步發(fā)送郵件的問題 *******************
send_mail()方法是將郵件發(fā)送到smtp服務(wù)器,然后,smtp服務(wù)器將郵件發(fā)送到目的郵箱,時(shí)間是不固定
這種情況類似于在send_mail()后面加了個(gè)time.sleep()的方法,用戶體驗(yàn)是非常不好------------------解決辦法---------使用celery
celery是一個(gè)功能完備即插即用的任務(wù)隊(duì)列,任務(wù)隊(duì)列是一種跨線程、跨機(jī)器工作的一種機(jī)制。
celery特點(diǎn)是:簡單靈活高效
celery通過消息進(jìn)行通信,通常使用一個(gè)叫Broker(中間人)來協(xié)client(任務(wù)的發(fā)出者)和worker(任務(wù)的處理者)。clients發(fā)出消息到隊(duì)列中,broker將隊(duì)列中的信息派發(fā)給worker來處理。
作為中間人有種方案可選擇:RabbitMQ、Redis。在這個(gè)項(xiàng)目中使用redis作為中間人。
celery的安裝在windows的虛擬環(huán)境項(xiàng)目里面pip install celery
使用redis作為中間人,要先查看redis是否已經(jīng)啟動,在ubuntu里面,查看redis的進(jìn)程 ps -aux | grep redis
并ifconfig來查看ubuntu此時(shí)的地址inet :192.168.XXX.XXX
給任務(wù)發(fā)出者安裝redis,在windows的虛擬環(huán)境里面,pip install redis
======================================================================
在總的項(xiàng)目下面創(chuàng)建一個(gè)celery_tasks/tasks.py的文件,在這個(gè)文件里面
1.創(chuàng)建celery()對象:
from celery import Celery
app = Celery("celery_tasks.tasks",broker="redis://192.168.XXX.XXX:6379/8") #第一個(gè)參數(shù)是所在包的名字,第二個(gè) 是指定中間人,8代表所使用的數(shù)據(jù)庫的編號。
app = Celery("celery_tasks.tasks",broker="redis://127.0.0.1:6379/7") #用于在windows上的redis進(jìn)行鏈接
2.發(fā)送郵件的代碼
在celery_tasks/tasks.py的文件里面加上
from django.core.mail import send_mail
from django.conf import settings
import time
@app.task
def send_register_active_email(to_email,username,token):
subject = "天天都有好吃的,天天生鮮歡迎你的到來"
message = ""
html_message = "<h1>%s你好,歡迎%s小朋友來注冊會員</h1>請點(diǎn)擊下面的鏈接激活你的賬戶<br><a % (
username, username, token, token)
sender = settings.EMAIL_FROM
receiver = [to_email]
#time.sleep(10)
send_mail(subject, message, sender, receiver, html_message=html_message)
time.sleep(10)
3.在user/views里面
from celery_tasks.tasks import send_register_active_email
因?yàn)榇藭r(shí)在tasks里面已經(jīng)有發(fā)送郵件的代碼了,我們可以將user/views里面的發(fā)送郵件的代碼注釋掉
在加密用戶身份信息,生成激活token,token =token.decode("utf8")后面只留下
send_register_active_email.delay(email, username, token)
# 返回應(yīng)答,跳轉(zhuǎn)到首頁
return redirect(reverse("goods:index"))
=============================================================
在linux里面直接執(zhí)行celery會報(bào)錯(cuò):
原因:是因?yàn)槲覀兿葐觲orker的,后啟動項(xiàng)目的,那worker中需要用到settings文件中的配置,所以報(bào)錯(cuò)了。那啟動項(xiàng)目的時(shí)候?yàn)槭裁床粓?bào)錯(cuò)呢,原因是因?yàn)?,Django已經(jīng)給我們做了初始化的工作,總項(xiàng)目wsgi代碼在下面:
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dailyfresh1807.settings") #初始化的工作
application = get_wsgi_application()
解決辦法:將wsgi代碼初始化工作的代碼復(fù)制在celery_tasks/tasks.py的文件里面
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dailyfresh1807.settings")
==================================================================
任務(wù)的處理者-----------------方法一:開啟ubuntu
1.在windows的虛擬環(huán)境的項(xiàng)目里面pip freeze > requirements.txt
該創(chuàng)建好的文件位于項(xiàng)目下面
2.使用ftp將該總的項(xiàng)目傳到ubuntu的桌面,打開linux的虛擬環(huán)境,在項(xiàng)目下pip install -r requirements.txt
3.執(zhí)行celery,celery -A celery_tasks.tasks worker -l info
方法二:使用windows的redis
1.確認(rèn)windows虛擬環(huán)境中的redis已經(jīng)開啟,celery_tasks/tasks.py的文件里面
app = Celery("celery_tasks.tasks",broker="redis://127.0.0.1:6379/7")
2.其次還要在windows的虛擬環(huán)境中安裝eventlet,pip install eventlet
3.在cmd的項(xiàng)目下celery -A celery_tasks.tasks worker -l info -P eventlet
===============================================================
有時(shí)會出現(xiàn)數(shù)據(jù)庫的密碼錯(cuò)誤問題,原因是有的用戶的windows數(shù)據(jù)庫和linux的數(shù)據(jù)庫密碼不一致
解決方法有兩種:
① 讓項(xiàng)目使用linux系統(tǒng)上的mysql數(shù)據(jù)庫,并修改配置文件中數(shù)據(jù)庫的配置,而且還在Linux系統(tǒng)中創(chuàng)建項(xiàng)目所需要的數(shù)據(jù)庫,重新生成遷移文件,并通過遷移文件生成表。
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': "dailyfresh1807",
'USER': "root",
'PASSWORD': "root",
'HOST': "192.168.xxx.xxx",
# 'HOST': "127.0.0.1",
'PORT': "3306",
}
}
② 讓任務(wù)的處理者與任務(wù)的發(fā)出者在同一臺電腦上,也就是都是windows電腦,并修改redis的鏈接代碼。
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': "dailyfresh1807",
'USER': "root",
'PASSWORD': "root",
# 'HOST': "192.168.xxx.xxx",
'HOST': "127.0.0.1",
'PORT': "3306",
}
}
=============================================================
對數(shù)據(jù)庫的表里的字段進(jìn)行修改的話,要重新生成遷移文件,并執(zhí)行遷移文件,生成新的表
查看是否已經(jīng)被修改了,例如select * from df_user \G
==============================================================
如果任務(wù)的發(fā)出者,中間人還有任務(wù)的處理者,不在同一臺電腦上的話,這三者之間必須要能進(jìn)行通信的,也就是說需要在同一個(gè)網(wǎng)段,而且處理者所在的電腦必須要能上網(wǎng),否則它也給163的郵箱發(fā)不出去。