文件上传与邮件发送

# 文件上传与邮件发送

### 原生实现

- 模板文件

  ```html
  <form method="post" enctype="multipart/form-data">
      <input type="file" name="photo" /><br />
      <input type="submit" value="上传" />
  </form>
  ```

- 视图函数

  ```python
  import os

  # 配置上传文件保存目录
  app.config['UPLOADED_FOLDER'] = os.path.join(os.getcwd(), 'static/upload')

  @app.route('/upload/', methods=['GET', 'POST'])
  def upload():
      if request.method == 'POST':
          # 获取上传对象
          photo = request.files.get('photo')
          if photo:
              # 拼接保存路径名
              pathname = os.path.join(app.config['UPLOADED_FOLDER'], photo.filename)
              # 保存上传文件
              photo.save(pathname)
              return '上传成功'
          else:
              return '上传失败'
      return render_template('upload.html')
  ```

- 上传限制设置

  ```python
  # 允许上传的文件后缀
  ALLOWED_SUFFIX = set(['png', 'jpg', 'jpeg', 'gif'])

  # 判断是否是允许的后缀
  def allowed_file(filename):
      return '.' in filename and filename.rsplit('.', 1)[1] in ALLOWED_SUFFIX
      
  # 限制请求大小
  app.config['MAX_CONTENT_LENGTH'] = 1024 * 1024 * 8

  # 展示上传的图片
  @app.route('/uploaded/<filename>')
  def uploaded(filename):
      return send_from_directory(app.config['UPLOADED_FOLDER'], filename)
    
  @app.route('/upload/', methods=['GET', 'POST'])
  def upload():
      img_url = None

      if request.method == 'POST':
          # 获取上传对象
          photo = request.files.get('photo')
          if photo and allowed_file(photo.filename):
              # 拼接保存路径名
              pathname = os.path.join(app.config['UPLOADED_FOLDER'], photo.filename)
              # 保存上传文件
              photo.save(pathname)
              # 构造上传文件的url
              img_url = url_for('uploaded', filename=photo.filename)
      return render_template('upload.html', img_url=img_url)
  ```

### flask-uploads

- 说明:极大的的简化了文件上传相关的操作,使用非常方面。

- 安装:`pip install flask-uploads`

- 使用:

  - 配置

  ```python
  from flask_uploads import UploadSet, IMAGES
  from flask_uploads import configure_uploads
  from flask_uploads import patch_request_class
  import os

  app.config['UPLOADED_PHOTOS_DEST'] = os.getcwd()
  app.config['MAX_CONTENT_LENGTH'] = 8 * 1024 * 1024
  # 创建上传对象
  photos = UploadSet('photos', IMAGES)
  # 配置上传对象
  configure_uploads(app, photos)
  # 配置上传文件大小,默认为64M,
  # 若设置为None,则以MAX_CONTENT_LENGTH配置为准
  patch_request_class(app, size=None)
  ```

  - 视图函数

  ```python
  @app.route('/upload/', methods=['GET', 'POST'])
  def upload():
      img_url = None
      if request.method == 'POST':
          # 获取上传对象
          photo = request.files.get('photo')
          if photo:
              # 保存上传文件,返回文件名
              filename = photos.save(photo)
              # 根据文件名获取上传文件的URL
              img_url = photos.url(filename)
      return render_template('upload.html', img_url=img_url)
  ```

### 综合使用

- 要求:结合flask-bootstrap、flask-wtf、flask-uploads等完成文件上传

- 使用:

  - 配置

  ```python
  from flask_wtf import FlaskForm
  from flask_wtf.file import FileField, FileAllowed, FileRequired
  from wtforms import SubmitField
  from flask_uploads import UploadSet, IMAGES
  from flask_uploads import configure_uploads
  from flask_uploads import patch_request_class
  from flask_bootstrap import Bootstrap
  import os

  bootstrap = Bootstrap(app)

  app.config['SECRET_KEY'] = '123456'
  app.config['MAX_CONTENT_LENGTH'] = 8 * 1024 * 1024
  app.config['UPLOADED_PHOTOS_DEST'] = os.path.join(os.getcwd(), 'static/upload')

  photos = UploadSet('photos', IMAGES)
  configure_uploads(app, photos)
  patch_request_class(app, size=None)

  class UploadForm(FlaskForm):
      photo = FileField('头像', validators=[FileRequired(message='请选择文件'), 
                                          FileAllowed(photos, message='只能上传图片文件')])
      submit = SubmitField('上传')
  ```

  - 视图函数

  ```python
  @app.route('/upload/', methods=['GET', 'POST'])
  def upload():
      img_url = None
      form = UploadForm()
      if form.validate_on_submit():
          photo = form.photo.data
          filename = photos.save(photo)
          img_url = photos.url(filename)
      return render_template('upload.html', form=form, img_url=img_url)
  ```

  - 模板文件

  ```html
  {% extends 'bootstrap/base.html' %}

  {% from 'bootstrap/wtf.html' import quick_form %}

  {% block title %}完整的文件上传{% endblock %}

  {% block content %}
      <div class="container">
          {% if img_url %}
              <img src="{{ img_url }}">
          {% endif %}
          {{ quick_form(form) }}
      </div>
  {% endblock %}
  ```

  - 生成随机文件名

  ```python
  def random_string(length=32):
      import random
      base_str = 'abcdefghijklmnopqrstuvwxyz1234567890'
      return ''.join(random.choice(base_str) for i in range(length))
    
  @app.route('/upload/', methods=['GET', 'POST'])
  def upload():
      。。。
          # 提取文件后缀
          suffix = os.path.splitext(photo.filename)[1]
          # 生成随机文件名
          filename = random_string() + suffix
          # 保存文件
          photos.save(photo, name=filename)
      。。。
  ```

  - 生成缩略图:PIL模块(只支持py2,要支持py3需要安装pillow)

  ```python
  from PIL import Image

  @app.route('/upload/', methods=['GET', 'POST'])
  def upload():
      ...
          # 拼接完整文件路径名
          pathname = os.path.join(app.config['UPLOADED_PHOTOS_DEST'], filename)
          # 打开文件
          img = Image.open(pathname)
          # 设置大小
          img.thumbnail((64, 64))
          # 保存图片
          img.save(pathname)
      ...
  ```


### flask-mail

- 说明:专门用于邮件发送的扩展库,使用非常方便。

- 安装:`pip install flask-mail`

- 使用:

  ```python
  from flask_mail import Mail, Message
  import os

  # 邮件发送配置,一定要放在创建Mail对象之前
  app.config['MAIL_SERVER'] = 'smtp.1000phone.com'
  # 用户名
  app.config['MAIL_USERNAME'] = 'lijie@1000phone.com'
  # 密码
  app.config['MAIL_PASSWORD'] = os.getenv('MAIL_PASSWORD', '123456')

  # 创建发送邮件的对象
  mail = Mail(app)

  @app.route('/send/')
  def send():
      # 创建邮件消息对象
      msg = Message('账户激活',
                    recipients=['shuai_fmzj@163.com'],
                    sender=app.config['MAIL_USERNAME'])
      msg.html = '恭喜你,中奖了!!!'
      # 发送邮件
      mail.send(msg)
      return '邮件已发送'
  ```

- 封装函数发送邮件

  ```python
  def send_mail(subject, to, template, *args, **kwargs):
      if isinstance(to, list):
          recipients = to
      elif isinstance(to, str):
          recipients = to.split(',')
      else:
          raise Exception('邮件接收者参数类型有误')
      # 创建邮件消息对象
      msg = Message(subject,
                    recipients=recipients,
                    sender=app.config['MAIL_USERNAME'])
      # 将邮件模板渲染后作为邮件内容
      msg.html = render_template(template, *args, **kwargs)
      # 发送邮件
      mail.send(msg)
  ```

- 异步发送邮件

  ```python
  from flask import current_app

  # 异步发送邮件任务
  def async_send_mail(app, msg):
      # 邮件发送必须在程序上下文
      # 新的线程中没有上下文,因此需要手动创建
      with app.app_context():
          mail.send(msg)
          
  # 封装函数发送邮件
  def send_mail(subject, to, template, *args, **kwargs):
      if isinstance(to, list):
          recipients = to
      elif isinstance(to, str):
          recipients = to.split(',')
      else:
          raise Exception('邮件接收者参数类型有误')
      # 创建邮件消息对象
      msg = Message(subject,
                    recipients=recipients,
                    sender=app.config['MAIL_USERNAME'])
      # 将邮件模板渲染后作为邮件内容
      msg.html = render_template(template, *args, **kwargs)
      # 异步发送邮件
      # current_app是app的代理对象
      # 根据代理对象current_app找到原始的app
      app = current_app._get_current_object()
      # 创建线程
      thr = Thread(target=async_send_mail, args=(app, msg))
      # 启动线程
      thr.start()
      # 返回线程
      return thr        
  ```


###环境变量

- windows:
  - 设置:`set 环境变量名=值`
  - 获取:`set 环境变量名`
- linux:
  - 导出:`export 环境变量名=值`
  - 获取:`echo $环境变量名`
- 代码:
  - `os.getenv('环境变量名', '123456')`
原文地址:https://www.cnblogs.com/liangliangzz/p/10221980.html