Django搭建我的博客:扩展用户信息

可能你已经发现了,Django自带的User模型很是实用,以致于咱们没有写用户管理相关的任何模型。html

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

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

扩展User模型

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

编写userprofile/models.py以下:shell

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包含一个“信号调度程序”,它能够在框架中的某些位置发生操做时,通知其余应用程序。简而言之,信号容许某些发送者通知一组接收器已经发生了某个动做。当许多代码可能对同一事件感兴趣时,信号就特别有用。django

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

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

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

固然你也能够不使用信号来自动建立Profile表,而是采用手动方式实现。

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

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

重建数据库

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

(env) E:\django_project\my_blog> pip install Pillow
复制代码

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

(env) E:\django_project\my_blog>python manage.py makemigrations

Migrations for 'userprofile':
  userprofile\migrations\0001_initial.py
    - Create model Profile
复制代码
(env) E:\django_project\my_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_project\my_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请求数据")
复制代码

业务逻辑与之前写的处理表单的视图很是类似(还记得吗),就不赘述了。

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

  • user_id是外键自动生成的字段,用来表征两个数据表的关联。你能够在SQLiteStudio中查看它。
  • 留意redirect()是如何携带参数传递的。

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

/templates/userprofile/edit.html

{% extends "base.html" %} {% load staticfiles %}
{% block title %} 用户信息 {% endblock title %}
{% block content %}
<div class="container">
    <div class="row">
        <div class="col-12">
            <br>
            <div class="col-md-4">用户名: {{ user.username }}</div>
            <br>
            <form method="post" action=".">
                {% csrf_token %}
                <!-- phone -->
                <div class="form-group col-md-4">
                    <label for="phone">电话</label>
                    <input type="text" class="form-control" id="phone" name="phone" value="{{ profile.phone }}">
                </div>
                <!-- bio -->
                <div class="form-group col-md-4">
                    <label for="bio">简介</label>
                    <textarea type="text" class="form-control" id="bio" name="bio" rows="12">{{ profile.bio }}</textarea>
                </div>
                <!-- 提交按钮 -->
                <button type="submit" class="btn btn-primary">提交</button>
            </form>
        </div>
    </div>
</div>
{% endblock content %}
复制代码
  • 留意模板中如何分别调用UserProfile对象的
  • 行内文本经过value属性设置了初始值,而多行文本则直接设置{{ profile.bio }}

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

userprofile/urls.py

...
urlpatterns = [
    ...
    # 用户信息
    path('edit/<int:id>/', views.profile_edit, name='edit'),
]
复制代码

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

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

**固然最好再给我的信息添加一个入口。**修改/templates/header.html

/templates/header.html

...
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
    <a class="dropdown-item" href='{% url "userprofile:edit" user.id %}'>我的信息</a>
    ...
</div>
...
复制代码

修改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的数据已经堆叠在底部了:

总结

本章使用一对一连接的方式,扩展并更新了用户信息。

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

转载请告知做者并注明出处。

相关文章
相关标签/搜索