Study_microblog笔记Part 8--个人主页及头像

为应用添加个人主页。个人主页用来展示用户的相关信息,其个人信息由本人录入。 我将为你展示如何动态地生成每个用户的主页,并提供一个编辑页面给他们来更新个人信息。

个人主页

第一步,让我们为其URL /user/ 新建一个对应的视图函数。
app/main/routers.py:

@app.route('/user/<username>')
@login_required
def user(username):
    user = User.query.filter_by(username=username).first_or_404()
    posts = [
        {'author': user, 'body': 'Test post #1'},
        {'author': user, 'body': 'Test post #2'}
    ]
    return render_template('user.html', user=user, posts=posts)

为这个用户初始化一个虚拟的用户动态列表,最后用传入的用户对象和用户动态列表渲染一个新的user.html模板。

{% extends "base.html" %}

{% block app_content %}
    <h1>User: {{ user.username }}</h1>
    <hr>
    {% for post in posts %}
    <p>
    {{ post.author.username }} says: <b>{{ post.body }}</b>
    </p>
    {% endfor %}
{% endblock %}

在顶部的导航栏中添加这个入口链接,以便用户可以轻松查看自己的个人资料:

<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
                <ul class="nav navbar-nav">
                    <li><a href="{{ url_for('main.index') }}">Home</a></li>
                    <li><a href="#">Explore</a></li>
                </ul>
                <ul class="nav navbar-nav navbar-right">
                    {% if current_user.is_anonymous %}
                    <li><a href="{{ url_for('auth.login') }}">Login</a></li>
                    {% else %}
                    <li><a href="{{ url_for('main.user', username=current_user.username) }}">Profile</a></li>
                    <li><a href="{{ url_for('auth.logout') }}">Logout</a></li>
                    {% endif %}
                </ul>
</div>

这里唯一有趣的变化是用来生成链接到个人主页的url_for()调用。 由于个人主页视图函数接受一个动态参数,所以url_for()函数接收一个值作为关键字参数。 由于这是一个指向当前登录个人主页的链接,我可以使用Flask-Login的current_user对象来生成正确的URL。

个人头像

Miguel Grinberg在头像服务中使用了Gravatar为所有用户提供图片服务,但遗憾的是在国内我们无法使用这个服务,而且在国内也没有找到类似的可以替代的服务。为了使用个人头像,参考类似项目做了一下变更。

  1. 用户模型
    对用户模型进行变更,增加_avatar,about_me,last_seen,并定义save(),set_avatar(),avatar()方法,用来上传自我简介,上次登录时间以及文件头像文件并做处理。
    app/models/user.py:
from datetime import datetime
from app import db,login
from werkzeug.security import generate_password_hash,check_password_hash
from flask_login import UserMixin

class User(UserMixin,db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), index=True, unique=True)
    email = db.Column(db.String(120), index=True, unique=True)
    password_hash = db.Column(db.String(128))
    about_me=db.Column(db.String(140))
    last_seen=db.Column(db.DateTime,default=datetime.utcnow)

    _avatar=db.Column(db.String(100))

    posts = db.relationship('Post', backref='author', lazy='dynamic')

    def __repr__(self):
        return '<User {}>'.format(self.username)

    def set_password(self,password):
        self.password_hash=generate_password_hash(password)

    def check_password(self,password):
        return check_password_hash(self.password_hash,password)

    
    def save(self):
        db.session.add(self)
        db.session.commit()

    def set_avatar(self, avatar):
        self._avatar = avatar


    def avatar(self,size=128):
        if self._avatar and size==128:
            return '/static/uploads/images/' + self._avatar
        elif self._avatar:
            name = self._avatar.split('.',1)
            new_name=name[0]+str(size)+'.'+name[1]
            return '/static/uploads/images/' + new_name
        return '/static/image/lijuan.jpg'

@login.user_loader
def load_user(id):
    return User.query.get(int(id))
 

每次修改完成后,不要忘记使用flask db migrate以及flask db upgrade来更新数据库迁移

  1. 将头像图片插入到个人主义模板当中。使用User类来返回头像的好处是,如果使用其他的头像服务,可以重写avatar()方法来返回头像图片,所有的模板将自动显示新的头像。
    个人主页的顶部有一个大头像,不止如此,底下的所有用户动态都会有一个小头像。 对于个人主页而言,所有的头像当然都是对应用户的。主页面上实现每个用户动态都用其作者的头像来装饰。

app/templates/user.html:

{% extends "base.html" %}

{% block content %}
    <table>
        <tr valign="top">
            <td><img src="{{ user.avatar(128) }}"></td>
            <td><h1>User: {{ user.username }}</h1></td>
        </tr>
    </table>
    <hr>
    {% for post in posts %}
    <table>
        <tr valign="top">
            <td><img src="{{ post.author.avatar(36) }}"></td>
            <td>{{ post.author.username }} says:<br>{{ post.body }}</td>
        </tr>
    </table>
    {% endfor %}
{% endblock %}
  1. Jinja子模板
    个人主页使用头像和文字组合的方式来展示了用户动态。 现在要在主页也使用类似的风格来布局。 我可以复制/粘贴来处理用户动态渲染的模板部分,但这实际上并不理想,因为之后如果想要对此布局进行更改,将不得不记住要更新两个模板。
    取而代之,创建一个只渲染一条用户动态的子模板,然后在user.html和index.html模板中引用它。 将其命名为app/templates/_post.html, _前缀只是一个命名约定,可以帮助识别哪些模板文件是子模板。
<table>
    <tr valign="top">
        <td><img src="{{ post.author.avatar(36) }}"></td>
        <td>{{ post.author.username }} says:<br>{{ post.body }}</td>
    </tr>
</table>

在user.html模板中使用了Jinja2的include语句来调用该子模板:

{% extends "base.html" %}

{% block app_content %}
     <table>
        <tr valign="top">
            <td><img src="{{ user.avatar(128) }}"></td>
            <td>
                <h1>User: {{ user.username }}</h1>
                {% if user.about_me %}
                <p>{{ user.about_me }}</p>
                {% endif %}

                {% if user.last_seen %}
                <p>Last seen on: {{ user.last_seen }}</p>
                {% endif %}

                {% if user == current_user %}
                <p><a href="{{ url_for('auth.edit_profile') }}">Edit your profile</a></p>
                {% endif %}
            </td>

        </tr>

    </table>
    <hr>
    {% for post in posts %}
        {% include '_post.html' %}
    {% endfor %}
{% endblock %}

4、需要给用户一个表单,让他们输入一些个人资料。 表单将允许用户更改他们的用户名,写一些个人介绍,以存储在新的about_me字段中,并且提供图片文件上传按钮,保存头像文件。 开始写一个表单类:
app/forms/auth.py:

#  .......
from wtforms.validators import DataRequired, ValidationError, Email, EqualTo,Length
from app.models import User

#  .........

class EditProfileForm(FlaskForm):
    username=StringField('Username',validators=[DataRequired()])
    about_me=TextAreaField('About me',validators=[Length(min=0,max=140)])
    avatar=FileField('Avatar')
    submit=SubmitField('Submit')

对于“about_me”字段,使用TextAreaField,这是一个多行输入文本框,用户可以在其中输入文本。 为了验证这个字段的长度,使用了Length,它将确保输入的文本在0到140个字符之间,因为这是我为数据库中的相应字段分配的空间。avatar使用了FileField,这是一个文件输入框,用户可以通过它来进行文件上传。
对表单进行渲染,app/templates/edit_profile.html:

{% extends "base.html" %}
{% import 'bootstrap/wtf.html' as wtf %}

{% block app_content %}
    <h1>Edit Profile</h1>
    <div class="row">
        <div class="col-md-4">
            {{ wtf.quick_form(form) }}
        </div>
    </div>
{% endblock %}

通过bootstrap/wtf进行渲染,是不是特简单?

  1. 视图函数,模型及表单准备好,html文件也准备好了,用视图函数连接起来!
from datetime import datetime
from app.utils import handle_upload,resize_avatar
# ...

auth=Blueprint('auth',__name__)
"""
在视图函数处理请求之前执行一段简单的代码,
一旦某个用户向服务器发送请求,就将当前时间写入到这个字段。
"""
@auth.before_request
def before_request():
    if current_user.is_authenticated:
        current_user.last_seen = datetime.utcnow()
        db.session.commit()

@auth.route('/edit_profile', methods=['GET', 'POST'])
@login_required
def edit_profile():
    user=current_user
    form = EditProfileForm()
    errors=[]
    if form.validate_on_submit():
        current_user.username = form.username.data
        current_user.about_me = form.about_me.data
        if request.files.get('avatar'):
            avatar = request.files['avatar']
            """
            通过自定义的handle_upload以及resize_avatar函数
            来对上传的头像存储及处理,考虑hash函数对名称进行定义,并回传不同像素的图像
            """
            ok,info = handle_upload(avatar,'image')
            if ok:
                size128=resize_avatar(info,128)
                size36=resize_avatar(info,36)
                current_user.set_avatar(size128)
            else:
                errors.append("Avatar upload failed")
        current_user.save()
        flash('Your changes have been saved.')
        return redirect(url_for('auth.edit_profile'))
    elif request.method == 'GET':
        form.username.data = current_user.username
        form.about_me.data = current_user.about_me
    return render_template('edit_profile.html', title='Edit Profile',
                           form=form)

在这里我们用到了两个工具函数,handle_upload(avatar,type)以及resize_avatar(info,size),我们把它定义在了工具函数中,app/utils.py:

from datetime import datetime
from random import randint
from hashlib import sha256,md5
from app import app
import os
from PIL import Image


def hash_name(name):
    degest=md5(name.lower().encode('utf-8')).hexdigest()
    return degest

def allowed_file(filename,type):
    return '.' in filename and \
            filename.rsplit('.', 1)[1] in app.config['ALLOWED_EXTENSIONS'][type]


def handle_upload(file,type):
    ''' type is the file type,for example:image.
    more file type to be added in the future.'''
    if file and allowed_file(file.filename,type):
        old_filename = file.filename
        file_suffix = old_filename.split('.')[-1]
        new_filename = hash_name(old_filename) + '.' + file_suffix
        print(new_filename)
        try:
            upload_path = os.path.join(app.config['UPLOAD_FOLDER'],type+'s/')
            print(upload_path)
            file.save(os.path.join(upload_path, new_filename))
        except FileNotFoundError:
            os.makedirs(upload_path)
            file.save(os.path.join(upload_path, new_filename))
        except Exception as e:
            return False,e
        # img = ImageStore(old_filename,new_filename)
        # img.save()
        return True,new_filename
    return False,"File type disallowd!"

def resize_avatar(old_file,size):
    upload_base = os.path.join(app.config['UPLOAD_FOLDER'],'image'+'s/')
    with Image.open(os.path.join(upload_base, old_file)) as img:
        image_width, image_height = img.size
        thumbnail_width = size
        thumbnail_height = size
        if image_width <= thumbnail_width and image_height <= thumbnail_height:
            return old_file
        # generate thumbnail if the avatar is too large
        if size==128:
            new_filename = hash_name(old_file) + '.png'
        else:
            new_filename=hash_name(old_file)+str(size)+'.png'
        try:
            img.thumbnail((thumbnail_width, thumbnail_height), Image.ANTIALIAS)
            img.save(os.path.join(upload_base, new_filename), "PNG")
        except IOError:
            print("Failed to create thumbnail from '" + old_file + "' to '" + new_filename + "'")
            return old_file
        return new_filename
    return old_file

现在把程序运行起来看一看吧!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值