资讯专栏INFORMATION COLUMN

Django搭建个人博客:扩展用户信息

Eastboat / 1304人阅读

摘要:博客网站的用户信息并不复杂,因此扩展就足够了。可以在这个基础上,扩展为一个美观详细的用户信息页面。当然最好再给个人信息添加一个入口。没有对用户的登录状态进行检查。总结本章使用一对一链接的方式,扩展并更新了用户信息。

可能你已经发现了,Django自带的User模型非常实用,以至于我们没有写用户管理相关的任何模型。

但是自带的User毕竟可用的字段较少。比方说非常重要的电话号码、头像等都没有。解决的方法有很多,你可以不使用User,自己从零写用户模型;也可以对User模型进行扩展。

博客网站的用户信息并不复杂,因此扩展User就足够了。

扩展User模型

扩展User模型又有不同的方法。在大多数情况下,使用模型一对一链接的方法是比较适合的。

编写userprofile/models.py如下:

userprofile/models.py

from django.db import models
from django.contrib.auth.models import User
# 引入内置信号
from django.db.models.signals import post_save
# 引入信号接收器的装饰器
from django.dispatch import receiver


# 用户扩展信息
class Profile(models.Model):
    # 与 User 模型构成一对一的关系
    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="profile")
    # 电话号码字段
    phone = models.CharField(max_length=20, blank=True)
    # 头像
    avatar = models.ImageField(upload_to="avatar/%Y%m%d/", blank=True)
    # 个人简介
    bio = models.TextField(max_length=500, blank=True)

    def __str__(self):
        return "user {}".format(self.user.username)


# 信号接收函数,每当新建 User 实例时自动调用
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
    if created:
        Profile.objects.create(user=instance)


# 信号接收函数,每当更新 User 实例时自动调用
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
    instance.profile.save()

每个Profile模型对应唯一的一个User模型,形成了对User的外接扩展,因此你可以在Profile添加任何想要的字段。这种方法的好处是不需要对User进行任何改动,从而拥有完全自定义的数据表。模型本身没有什么新的知识,比较神奇的是用到的信号机制

Django包含一个“信号调度程序”,它可以在框架中的某些位置发生操作时,通知其他应用程序。简而言之,信号允许某些发送者通知一组接收器已经发生了某个动作。当许多代码可能对同一事件感兴趣时,信号就特别有用。

这里引入的post_save就是一个内置信号,它可以在模型调用save()方法后发出信号。

有了信号之后还需要定义接收器,告诉Django应该把信号发给谁。装饰器receiver就起到接收器的作用。每当User有更新时,就发送一个信号启动post_save相关的函数。

通过信号的传递,实现了每当User创建/更新时,Profile也会自动的创建/更新。

当然你也可以不使用信号来自动创建Profile表,而是采用手动方式实现。

为什么删除User表不需要信号?答案是两者的关系采用了models.CASCADE级联删除,已经带有关联删除的功能了。

avatar字段用来存放头像,暂且不管它,下一章讲解。

重建数据库

前面讲过,每次改动模型后都需要进行数据的迁移。由于avatar字段为图像字段,需要安装第三方库Pillow来支持:

(env) E:django_projectmy_blog> pip install Pillow

安装成功后,通过makemigrationsmigrate迁移数据:

(env) E:django_projectmy_blog>python manage.py makemigrations

Migrations for "userprofile":
  userprofilemigrations001_initial.py
    - Create model Profile
(env) E:django_projectmy_blog>python manage.py migrate

Operations to perform:
  Apply all migrations: admin, article, auth, contenttypes, sessions, userprofile
Running migrations:
  Applying userprofile.0001_initial... OK

迁移好数据后,如果你试图登录用户,会得到报错。这是因为之前创建的User数据都没有对应的Profile模型,违背了现有的模型。一种解决办法就是干脆删除旧的数据,因此就需要用到Django的shell命令。

shell是Django提供的互动解释器,你可以在这个指令模式中试验代码是否能够正确执行,是相当方便的工具。

在虚拟环境中输入python manage.py shell就可以进入shell:

(env) E:django_projectmy_blog>python manage.py shell
Python 3.7.0 (v3.7.0:1bf9cc5093, Jun 27 2018, 04:59:51) [MSC v.1914 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>

看到>>>表示成功进入shell。

输入下面两行指令就可以轻松删除User数据库:

>>> from django.contrib.auth.models import User
>>> User.objects.all().delete()

注意因为前面写的article模型中,与User的外键也采用了models.CASCADE级联删除模式,因此随着User的删除,相关的文章也一并删除了

输入exit()退出shell,输入指令python manage.py createsuperuser重新创建管理员账户

对新手来说,修改数据库经常会导致各种头疼的问题,比如说字段失效、新字段为null、赋值错误、外键链接出错等等,最终导致整个业务逻辑报错。因此我的建议是,在设计数据库时尽量考虑周全,避免频繁修改模型。

如果实在要修改,并且已经导致数据库混乱了,不妨删除掉/app/migrations/目录下最新的几个文件,清空相关数据库,重新迁移数据。

接下来编写MTV模式的剩余部分。

表单、视图和模板

有了扩展的Profile模型后,需要新建一个表单类去编辑它的内容:

userprofile/forms.py

...
# 引入 Profile 模型
from .models import Profile

...
class ProfileForm(forms.ModelForm):
    class Meta:
        model = Profile
        fields = ("phone", "avatar", "bio")

然后在userprofile/views.py中写处理用户信息的视图函数:

userprofile/views.py

...

# 别忘了引入模块
from .forms import ProfileForm
from .models import Profile

...

# 编辑用户信息
@login_required(login_url="/userprofile/login/")
def profile_edit(request, id):
    user = User.objects.get(id=id)
    # user_id 是 OneToOneField 自动生成的字段
    profile = Profile.objects.get(user_id=id)

    if request.method == "POST":
        # 验证修改数据者,是否为用户本人
        if request.user != user:
            return HttpResponse("你没有权限修改此用户信息。")

        profile_form = ProfileForm(data=request.POST)
        if profile_form.is_valid():
            # 取得清洗后的合法数据
            profile_cd = profile_form.cleaned_data
            profile.phone = profile_cd["phone"]
            profile.bio = profile_cd["bio"]
            profile.save()
            # 带参数的 redirect()
            return redirect("userprofile:edit", id=id)
        else:
            return HttpResponse("注册表单输入有误。请重新输入~")

    elif request.method == "GET":
        profile_form = ProfileForm()
        context = { "profile_form": profile_form, "profile": profile, "user": user }
        return render(request, "userprofile/edit.html", context)
    else:
        return HttpResponse("请使用GET或POST请求数据")
2019/05/13更新:实际上GET方法中不需要将profile_form这个表单对象传递到模板中去,因为模板中已经用Bootstrap写好了表单,所以profile_form并没有用到。感谢读者YipCyun指正。

业务逻辑与以前写的处理表单的视图非常相似(还记得吗),就不赘述了。

需要注意下面几个小地方:

user_id是外键自动生成的字段,用来表征两个数据表的关联。你可以在SQLiteStudio中查看它。

留意redirect()是如何携带参数传递的。

然后就是新建模板文件/templates/userprofile/edit.html

/templates/userprofile/edit.html

{% extends "base.html" %} {% load staticfiles %}
{% block title %} 用户信息 {% endblock title %}
{% block content %}

用户名: {{ user.username }}

{% csrf_token %}
{% endblock content %}

留意模板中如何分别调用UserProfile对象的

行内文本通过value属性设置了初始值,而多行文本则直接设置{{ profile.bio }}

最后配置熟悉的userprofile/urls.py

userprofile/urls.py

...
urlpatterns = [
    ...
    # 用户信息
    path("edit//", views.profile_edit, name="edit"),
]

启动服务器,输入地址查看功能是否正常。注意旧的用户都删除了(id=1的用户已经没有了),这里的/必须为新创建的用户的id

页面虽然简陋,但是方法是类似的。可以在这个基础上,扩展为一个美观、详细的用户信息页面。

当然最好再给个人信息添加一个入口。修改/templates/header.html

/templates/header.html

...

...
修改article视图

在前面新建article的章节中,由于没有用户管理的知识,存在一些问题:

new_article.author = User.objects.get(id=1)强行把作者指定为id=1的用户,这显然是不对的。

没有对用户的登录状态进行检查。

因此稍加修改def article_create()

/article/views.py

...
from django.contrib.auth.decorators import login_required

...

# 检查登录
@login_required(login_url="/userprofile/login/")
def article_create(request):
    ...
    # 指定目前登录的用户为作者
    new_article.author = User.objects.get(id=request.user.id)
    ...

重启服务器,文章正确匹配到登录的用户,又可以愉快的写文章了。

实际上,删除文章article_delete()、更新文章article_update()都应该对用户身份进行检查。就请读者尝试修改吧。
配置admin

前面我们已经尝试过将article配置到admin后台,方法是非常简单的,直接在admin.py中写入admin.site.register(Profile)就可以了。但是这样写会导致UserProfile是两个分开的表,不方便不说,强迫症的你怎么能受得了。

我们希望能够在admin中将UserProfile合并为一张完整的表格。方法如下:

/userprofile/admin.py

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User

from .models import Profile

# 定义一个行内 admin
class ProfileInline(admin.StackedInline):
    model = Profile
    can_delete = False
    verbose_name_plural = "UserProfile"

# 将 Profile 关联到 User 中
class UserAdmin(BaseUserAdmin):
    inlines = (ProfileInline,)

# 重新注册 User
admin.site.unregister(User)
admin.site.register(User, UserAdmin)

打开admin中的User表,发现Profile的数据已经堆叠在底部了:

本章勘误
2019/06/19 新增:感谢读者 @shenhanlin 对本问题的反馈。

本章中用到了信号来实现UserProfile的同步创建,但是也产生了一个BUG:在后台中创建User时如果填写了Profile任何内容,则系统报错且保存不成功;其他情况下均正常。

BUG产生原因:在后台中创建并保存User时调用了信号接收函数,创建了Profile表;但如果此时管理员填写了内联的Profile表,会导致此表也会被创建并保存。最终结果就是同时创建了两个具有相同UserProfile表,违背了”一对一“外键的原则。

解决的办法也不难。因为博客项目的需求较简单,其实没有必要用到信号。

修改model,把两个信号接收函数去除:

userprofile/models.py

from django.db import models
from django.contrib.auth.models import User

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="profile")
    phone = models.CharField(max_length=20, blank=True)
    avatar = models.ImageField(upload_to="avatar/%Y%m%d/", blank=True)
    bio = models.TextField(max_length=500, blank=True)

    def __str__(self):
        return "user {}".format(self.user.username)
    
# 下面的信号接收函数全部注释掉
...

修改view,使得Profile表根据是否已经存在而动态的创建、获取:

userprofile/views.py

...

# 编辑用户信息
@login_required(login_url="/userprofile/login/")
def profile_edit(request, id):
    user = User.objects.get(id=id)

    # 旧代码
    # profile = Profile.objects.get(user_id=id)
    # 修改后的代码
    if Profile.objects.filter(user_id=id).exists():
        profile = Profile.objects.get(user_id=id)
    else:
        profile = Profile.objects.create(user=user)

    ...

即如果Profile已经存在了就获取它,如果不存在就创建一个新的。这样修改后应该就可以顺利的创建新用户了。

除了上面的方法,还有别的手段解决此问题:

取消Profile在后台的内联,避免创建User的同时创建此表。

覆写User表的save()方法,跳过后台的自动保存。(不推荐)

虽然博主做了不正确的示范,但是信号确实是很重要的概念,就当蜻蜓点水的介绍给大家。

总结

本章使用一对一链接的方式,扩展并更新了用户信息。读者可以根据自身需求,添加任何需要的字段内容。

下一章将学习对图片的简单处理。

有疑问请在杜赛的个人网站留言,我会尽快回复。

或Email私信我:dusaiphoto@foxmail.com

项目完整代码:Django_blog_tutorial

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/42756.html

相关文章

  • Django搭建个人博客:结束和开始

    摘要:教程看到这里,你已经学会如下内容搭建开发环境博文管理用户管理发表评论若干小功能搭建简单的小博客,以上的功能够用了。教程为了起步平缓,没有展开这方面的内容。陌生人,祝你学业进步事业有成欢迎常到杜赛的个人网站做客 教程看到这里,你已经学会如下内容: 搭建开发环境 博文管理 用户管理 发表评论 若干小功能 搭建简单的小博客,以上的功能够用了。 相信你的志向不止于此。毕竟程序员面试个个造火...

    zqhxuyuan 评论0 收藏0
  • Django搭建个人博客:重置用户密码

    摘要:本章讲如何帮助健忘症患者,重置用户密码。实际上不仅内置了密码重置,还包括登录登出密码修改等功能。总结本章学习了使用第三方库,高效完成了重置密码的功能。有疑问请在杜赛的个人网站留言,我会尽快回复。 随着技术的发展,验证用户身份的手段越来越多,指纹、面容、声纹应有尽有,但密码依然是最重要的手段。 互联网处处都有密码的身影,甚至变成了现代人的一种负担。像笔者这样的,动辄几十个账号密码,忘记其...

    mumumu 评论0 收藏0
  • Django搭建个人博客:设置文章的栏目

    摘要:而文章分类一个重要的途径就是设置栏目。修改文件栏目的栏目标题创建时间文章栏目的一对多外键栏目的有两个字段,名称和创建日期。修改文章的栏目功能,也就完成了。对个人博客来说,栏目数据的变动通常是很少的。 博客的文章类型通常不止一种:有时候你会写高深莫测的技术文章,有时候又纯粹只记录一下当天的心情。 因此对文章的分类就显得相当的重要了,既方便博主对文章进行分类归档,也方便用户有针对性的阅读。...

    keelii 评论0 收藏0
  • Django搭建个人博客:自动化测试

    摘要:修改某一个组件可能会导致另一个组件出现意想不到的,但是在人工测试时却很难检查出来,总不能每写几行代码就把整个网站统统检查一遍吧。比如说有个功能,限制每个用户每天发表评论不能超过条,人工测试就显得比较麻烦,特别是需要反复调试的时候。 测试是伴随着开发进行的,开发有多久,测试就要多久。本教程已经进行了30多章了,都是如何测试的?当然是runserver啦!每当开发新功能后,都需要运行服务器...

    smallStone 评论0 收藏0
  • Django搭建个人博客:使用Markdown语法书写文章

    摘要:重新打开一个命令行窗口,进入虚拟环境,安装是一种通用语法高亮显示器,可以帮助我们自动生成美化代码块的样式文件。 上一章我们实现了文章详情页面。为了让文章正文能够进行标题、加粗、引用、代码块等不同的排版(像在Office中那样!),我们将使用Markdown语法。 安装Markdown Markdown是一种轻量级的标记语言,它允许人们使用易读易写的纯文本格式编写文档,然后转换成有效的或...

    沈建明 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<