接上一篇文章,好不容易解决了跳转链接报错的问题,点了click here
后,弹出顶部提醒:A new confirmation email has been sent to you.
…但是,查看邮箱,垃圾邮件里面都看了好几遍,翻烂了都没看见邮件!
之前新用户注册,自动给管理员发送邮件,却收不到邮件,是因为pycharm读不到环境变量:上次收不到邮件的全过程记录
这次是代码重构以后,需要用户验证,又收不到邮件了
-
检查了邮箱开启SMTP服务
-
为重构后的启动文件flasky.py在
Run -> Edit Configurations -> Environment variables
重新配置MAIL_USERNAME等等信息 -
检查了confirmed字段的默认值和类型,期间还弄清楚了pycharm的SQLite界面Boolean类型的操作,横线表示FALSE,√表示TRUE,空的表示还没定义。若需要直接在数据库中更改,在更改处点击右键,-Edit,更改完后,再右键-Submit才算成功
-
检查了代码拼写错误、捋顺逻辑
全部都没问题。邮件发不出去,肯定是邮件那部分的问题。
打印了send_email函数的各个变量,也没问题,那为什么一切准备就绪,就是不发邮件呢?
各种花式描述问题,最终在Stack Overflow上找到了答案 Flask-Mail not sending emails, no error is being reported
应该设置TESTING = False !想起来了,我就是在config.py中默认用的这个
config = {
......
'default': TestingConfig
}
而TestingConfig中这个字段我设置的是True :
class TestingConfig(Config):
TESTING = True # 不发送邮件
SQLALCHEMY_DATABASE_URI = os.environ.get('TEST_DATABASE_URL') or 'sqlite://'
TESTING = True 就是默认不发送邮件,怪不得一直收不到。
改成False,这下肯定会成功了!!!!!
但是,现在又报了验证错误?????
之前用的时候MAIL_USERNAME 和 MAIL_PASSWORD 是有效的呀?难道由于时间太长失效了?
于是从网上找了一篇demo,测试了一下这俩参数的有效性:
from flask import Flask
from flask_mail import Mail, Message
app = Flask(__name__)
mail_config = {
"MAIL_SERVER": "smtp.163.com",
"MAIL_PORT": 465,
"MAIL_USE_SSL": True,
"MAIL_USERNAME": "xxx@163.com", # 发送邮箱
"MAIL_PASSWORD": "这里填你的授权码" # 客户端授权码
}
app.config.update(mail_config)
mail = Mail(app)
@app.route("/send_mail")
def send_mail():
"""
发送邮件
"""
message = Message("标题", sender=app.config["MAIL_USERNAME"], recipients=["xxx@163.com"])
message.body = "内容"
mail.send(message)
print(message.sender,message.recipients)
return "发送成功"
if __name__ == "__main__":
app.run()
测试了两遍,都收到了邮件
说明我这两个环境变量是有效的,那问题在哪儿?
算了,我不沾惹TESTING这个参数了,我把默认config换成DevelopmentConfig算了,去config.py 中设置:
config = {
......
'default': DevelopmentConfig
}
class DevelopmentConfig(Config):
DEBUG = True
# 注意!!!注意!!
# TestingConfig 和 DevelopmentConfig 千万不能用同一个数据库!!!
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'data-dev.sqlite')
这下应该成功了吧!!!
但是,跟上面一模一样地报了验证错误???
为什么???!!!
继续去Stack Overflow上看看
找到了这篇Flask-Mail Cannot read configurations when config.from_object() is used
跟着答案在config.py更新了我的配置参数,重要的是,为了方便,直接把用户名,授权码贴了上去,而不是从环境中读取
# MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
# MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
MAIL_USERNAME = 'xxx@163.com'
MAIL_PASSWORD = '这里填授权码'
然后,竟然成功了!!!!
看着还挺像那么一回事儿哈哈哈哈哈哈哈
讲道理TestingConfig也不应该有问题,我把配置文件又改回 TestingConfig,注意TESTING = False
config = {
......
'default': TestingConfig
}
class TestingConfig(Config):
TESTING = False # 注意这里改成 False才能发送邮件
# 注意!!!注意!!
# TestingConfig 和 DevelopmentConfig 千万不能用同一个数据库!!!
SQLALCHEMY_DATABASE_URI = os.environ.get('TEST_DATABASE_URL') or 'sqlite://'
竟然也成功了!!!!!
说明我一开始改 TESTING = False 理论上就能解决问题了 T^T
但还是报了验证错误,是因为MAIL_USERNAME 和MAIL_PASSWORD 从环境中读取不进来
我还饶了这么一大圈才发现…
行,现在就解决为什么读不进来环境变量。
上次收不到邮件的全过程记录 我应该是弄清楚了pycharm怎么配置环境变量的问题,为运行代码的文件(flasky.py)设置了环境变量,Run -> Edit Configurations -> Environment variables
为什么还不对? 难道只是我以为我弄清楚了?
-
猜想1: 是不是应该在flasky.py 的上一级设置环境变量,上次成功是因为代码没重构,所有东西都在那一个文件里面,所以只需要设置那个文件的环境变量就行了。这次代码重构以后,分散成了很多文件,应该给最高级的文件夹设置。
失败… -
猜想2: 是不是在哪个文件中读取的环境变量,就应该去设置那个文件。本项目由是在config.py 内执行
MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
等等环境变量的读取。
失败…
那是什么原因呢??
为什么读不进来呢??
是读不进来还是读错了
去send_email 函数中打印看看到底啥情况
def send_email(to, subject, template, **kwargs):
app = current_app._get_current_object()
print(app.config['MAIL_USERNAME'])
print(app.config['MAIL_PASSWORD']) # 打印看看情况
msg = Message(app.config['FLASKY_MAIL_SUBJECT_PREFIX'] + ' ' + subject,
sender=app.config['FLASKY_MAIL_SENDER'], recipients=[to])
msg.body = render_template(template + '.txt', **kwargs)
msg.html = render_template(template + '.html', **kwargs)
# thr = Thread(target=send_async_email, args=[app, msg])
# thr.start()
# return thr
mail.send(msg)
等等,前面怎么没对齐? 多了个空格??
原来是环境变量输错了,前面多输入了个空格导致授权码有误。上次我的实验验证没有错,我是真的弄清楚了!开心!
收到邮件!
点击链接跳转,验证成功!
刷新数据库:看到confirmed字段已经打上勾!
末尾附上我的配置文件config.py,感觉在查资料的时候还挺需要这个(大部分都是原封不动搬运本书配套的GitHub上的配置)
import os
basedir = os.path.abspath(os.path.dirname(__file__))
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard to guess string'
MAIL_SERVER = os.environ.get('MAIL_SERVER', 'smtp.163.com')
MAIL_PORT = int(os.environ.get('MAIL_PORT', '465'))
MAIL_USE_SSL = True
MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
FLASKY_MAIL_SUBJECT_PREFIX = '[Flasky]'
FLASKY_MAIL_SENDER = '发送方的邮箱@163.com'
FLASKY_ADMIN = os.environ.get('FLASKY_ADMIN')
SQLALCHEMY_TRACK_MODIFICATIONS = False
TESTING = False
DEBUG = False
@staticmethod
def init_app(app):
pass
class DevelopmentConfig(Config):
DEBUG = True
# 注意!!!注意!!
# TestingConfig 和 DevelopmentConfig 不要用同一个数据库!!!
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'data-dev.sqlite')
class TestingConfig(Config):
TESTING = True # True默认不发送邮件
# TestingConfig 和 DevelopmentConfig 不要用同一个数据库!!!
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'data-test.sqlite')
class ProductionConfig(Config):
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
'sqlite:///' + os.path.join(basedir, 'data.sqlite')
config = {
'development': DevelopmentConfig,
'testing': TestingConfig,
'production': ProductionConfig,
'default': DevelopmentConfig
}
总结:
终于解决了这两个bug。第一个bug的原因是TESTING 变量值设置错误。第二个bug原因是粗心大意填错了授权码。
原因都很简单,但还是写了这么多,是为了记录自己的思考过程。
and,谨慎修改代码!!!改动一个地方可能当时是跑通了,但是可能为日后别的功能埋下了雷。
and,改代码要遵循控制变量法,一次只改一处!
Attention:
在本文的代码注释中我总计强调了四次TestingConfig 和 DevelopmentConfig 不要用同一个数据库,是有原因的:
详情查看另一篇文章:偷懒用同一个数据库会在单元测试的时候踩大坑