Django图书荐购云平台开发与实践 - 3用户信息模块

接下来开发用户信息模块,用户信息模块主要包括用户注册、用户登录、显示用户信息、用户密码的修改。
用户的注册、登陆、密码修改和找回功能在前面已经实现,只需要略做修改就可以了。参见(10) Django - Auth认证系统
由于用户信息表需要和图书馆信息表关联,这里一并要把图书馆信息表的模型给实现了。

# user 的 models.py 
from django.db import models
from django.contrib.auth.models import AbstractUser

class Library(models.Model):
    lib_id = models.AutoField('序号', primary_key=True)
    lib_name = models.CharField('图书馆名称', max_length=50)
    lib_address = models.CharField('地址', max_length=150)
    lib_contact = models.CharField('联系人', max_length=20)
    lib_phone = models.CharField('联系电话', max_length=20)
    lib_email = models.EmailField('Email', max_length=150)

    def __str__(self):
        return self.lib_name

    class Meta:
        verbose_name = '图书馆用户'
        verbose_name_plural = '图书馆用户'

class MyUser(AbstractUser):
    id_number = models.CharField('学号/工号', max_length=16)
    avatar = models.ImageField(upload_to='avatar', default='default.png', verbose_name="头像")
    qq = models.CharField('QQ号码', max_length=16)
    weChat = models.CharField('微信账号', max_length=100)
    mobile = models.CharField('手机号码', max_length=11)
    lib_id = models.ForeignKey(Library, on_delete=models.CASCADE, blank=True, null=True, verbose_name='所属馆')
    # 设置返回值
    def __str__(self):
        return self.username

    class Meta:
        verbose_name = '个人用户'
        verbose_name_plural = '个人用户'

定义好模型后,需要删除之前的全部数据表和所有app下migrations文件下的0001_initial.py文件,删除之前将表中数据导出保存好,最后重新执行makegirations和migrate命令创建好模型和数据库表。接着需要重写user下的__init__.py文件,以在后台显示修改后的MyUser模型。

#user 的 __init__.py
#设置App(user)的中文名
from django.apps import AppConfig
import os
# 修改app在admin后台显示名称
# default_app_config的值来自apps.py的类名
default_app_config = 'user.IndexConfig'

# 获取当前app的命名
def get_current_app_name(_file):
    return os.path.split(os.path.dirname(_file))[-1]

# 重写类IndexConfig
class IndexConfig(AppConfig):
    name = get_current_app_name(__file__)
    verbose_name = '用户管理'

最后在user的admin.py中注册新的模型即可。

#user 的 admin.py
from django.contrib import admin
from .models import MyUser, Library
from django.contrib.auth.admin import UserAdmin
from django.utils.translation import gettext_lazy as gl

@admin.register(MyUser)
class MyUserAdmin(UserAdmin):
    list_display = ['username', 'email', 'id_number', 'mobile', 'qq', 'weChat', 'lib_id']
    #修改用户时,在个人信息里添加 'id_number'、'mobile 、'qq'、'weChat'的信息录入
    #将源码的UserAdmin.fieldsets转换成列表格式
    fieldsets = list(UserAdmin.fieldsets)
    #重写UserAdmin的fieldsets,添加'id_number'、'mobile'、'qq'、'weChat'的信息录入
    fieldsets[1] = (gl('Personal info'), {'fields':('first_name','last_name','email','id_number','mobile','qq','weChat','lib_id')})

@admin.register(Library)
class LibraryAdmin(admin.ModelAdmin):
    #设置显示的字段
    list_display = ['lib_id','lib_name', 'lib_address', 'lib_contact', 'lib_phone', 'lib_email']
    #设置搜索字段
    search_fields = ['lib_id', 'lib_name']
    #设置每页最大记录数
    list_per_page = 20
    #设置显示字段中可被链接的字段
    list_display_links = ['lib_id', 'lib_name']
    #设置在列表展示页面可更改的字段
    # list_editable = ['cata_id']

准备工作就绪以后,打开admin后台管理,在这之前不要忘记创建superuser。可以看到后台用户管理模块里多里一个图书馆模块。点击增加图书馆,可以添加图书馆信息,这里添加了2个图书馆。


image.png

在个人用户修改信息的界面,可以看到有一个所属馆的下拉框,可以为个人用户添加所属馆的信息。


image.png

到这里,用户信息模块的准备工作就做的差不多了,下面开始逐一实现用户信息模块的其他功能。

会话控制

在实际使用中,还需要会话控制session。在验证登陆时,将用户名保存到session中,然后在base.html模板中做一个验证,判断用户是或否登陆,已经登陆则显示用户名和退出按钮,没有登陆的显示登陆按钮。

#base.html 的 网页头
      <!--网页头-->
      <div class="container clearfix">
          <div class="float-left">
              <img src="{% static 'images/logo.png' %}" >
          </div>
          <div class="float-right">
              {# 如果session中没有用户username,则显示登陆 #}
              {% if not request.session.username %}
              <a href="{% url 'login' %}"><button class="btn btn-success">登陆</button></a>
              {% else %}
              <a href="#"><button type="button" class="btn btn-outline-success">
                    {{ request.session.username }}
              </button></a>
              <a href="{% url 'logout' %}"><button type="button" class="btn btn-outline-success">退出</button></a>
              {% endif %}
            </div>
      </div>
#user 的views.py
def loginView(request):
    #设置标题和两外两个URL链接
    title = '登录'
    headers = '用户登录'
    unit_2 = '/user/register.html'
    unit_2_name = '立即注册'
    unit_1 = '/user/setpassword.html'
    unit_1_name = '修改密码'

    if request.method == 'POST':
        #用户提交登录,获取登录的用户名和密码
        username = request.POST.get('username', '')
        password = request.POST.get('password', '')
        #首先查找User数据表中有没有该用户,没有提示注册
        if MyUser.objects.filter(username=username):
            #如果有该用户,则检查用户名和密码是否正确,错误提示重新输入
            user = authenticate(username=username, password=password)
            if user:
                if user.is_active:##判断用户是否被激活,是则由内置函数login完成登录,跳转到主页
                    login(request, user)
                    #将用户的工号/学号写入session
                    request.session['username'] = user.username
                    return redirect('/book')
            else:
                tips = '账号密码错误,请重新输入'
        else:
            tips = '用户不存在,请注册'
    return render(request, 'user.html', locals())

def logoutView(request):
    logout(request)#退出登录
    try:
        del request.session['userid']
    except KeyError:
        pass
    return redirect('/user/login.html')#跳转到登录页面

代码和前面章节的基本相同,除了在登陆成功时将用户名写入session,以及在模板中对用户名做一个检查,最后在登出时删除session。

用户个人信息展示

这里将要实现一个个人信息展示页,其中展示了个人全部的信息,并且允许个人用户可以修改部分个人信息,如密码、联系方式等。有些信息不能修改,有些可以修改。个人信息展示比较简单,只要通过session做一个判断,如果用户登录,则从数据库获取用户的个人信息并展示给用户,如果session里没有用户登录信息,则跳转到登录页面。
下面是代码:

#定义URL
    path('userinfo.html', views.userinfoView, name='userinfo'),
#userinfo.html模板
{% extends 'base.html' %}
{% load staticfiles %}
<link rel="stylesheet" href="https://unpkg.com/mobi.css/dist/mobi.min.css">
<link rel="stylesheet" href="{% static 'css/mycss.css' %}">
{% block search %}
{% endblock %}
{% block body %}
<div class="container" style="margin-top:30px">
    <div class="row">
       <div class="col-sm-4">
           <div class="fakeimg border">
               <img class="img-fluid img-responsive" src="#" alt='头像'>
           </div>
           <hr class="d-sm-none">
       </div>
       <div class="col-sm-8">
           <h5 class="font-weight-bold">个人信息</h5>
           <form class="mx-auto" action="" method="POST">
            {% csrf_token %}
           <div class="form-group">
            <label for="usr">用户名</label>
            <input type="text" class="form-control" id="usr" placeholder="{{ user.username }}">
          </div>
          <div class="form-inline">
                <label for="first_name">姓</label>
                <input type="text" class="form-control" id="first_name" value="{{ user.first_name }}" disabled>
                <label for="last_name">名</label>
                <input type="text" class="form-control" id="last_name" value="{{ user.last_name }}" disabled>
                </div>
          <div class="form-group">
            <label for="id_number">工号/学号</label>
            <input type="text" class="form-control" id="id_number" placeholder="{{ user.id_number }}" disabled>
          </div>
          <div class="form-group">
                <label for="email">Email</label>
                <input type="text" class="form-control" id="email" value="{{ user.email }}">
         </div>
         <div class="form-group">
                <label for="qq">QQ</label>
                <input type="text" class="form-control" id="qq" value="{{ user.qq }}">
         </div>
         <div class="form-group">
                <label for="wechat">微信</label>
                <input type="text" class="form-control" id="wechat" value="{{ user.weChat }}">
         </div>
         <div class="form-group">
                <label for="mobile">手机</label>
                <input type="text" class="form-control" id="mobile" value="{{ user.mobile }}">
         </div>
         <div class="form-group">
                <label for="lib">所属馆</label>
                <input type="text" class="form-control" id="lib" value="{{ user.lib_id }}" disabled>
         </div>
         <div class="input-group mb-3 ">
                <button type="submit" class="btn btn-primary btn-block">保存</button>
            </div>
         </form>
           <div class="fakeimg">荐购历史</div>
           </div>
         </div>
       </div>
{% endblock %}
#user 的 views.py 的 userinfoView
def userinfoView(request):
    if not request.session.get('username'):
        return redirect('login.html')
    else:
        username = request.session.get('username')
        user = MyUser.objects.filter(username=username)[0]
        return render(request, 'userinfo.html', locals())

初步的个人信息页面展示如下:

image.png

可以看到左边这里有个头像框的预留位置,下面就要实现用户上传和修改头像的功能。

用户头像的上传与更改

要实现用户头像的上传首先需要做一些准备工作。
首先我们在重写user模型的时候,里面要添加一个字段,用来保存用户上传图片的地址信息。

avatar = models.ImageField(upload_to='avatar', default='default.png', verbose_name="头像")

上面MyUser模型完整的代码里已经包含了avatar这个字段,其中的参数upload_to表示图片上传到哪里,default参数可选,表示默认的图片,如果没有default参数,那必须加上null=True,blank=True参数,否则在执行数据迁移时会报错。


模型中定义好字段以后,我们还需要在项目的settings.py中设置用户上传图片的目录和路径。

#settings.py
#用户上传图片的路径, 'user'是app应用名,表示图片上传到user/media/avatar下
MEDIA_ROOT = os.path.join(BASE_DIR, 'user', "media/avatar")
#MEDIA_URL是用户从前台读取图片的地址,最后的'/'不能省
MEDIA_URL = "media/avatar/"

接下来我们在user应用下新建一个forms.py,用来定义表单模型。

from django import forms

#上传头像图片的表单
class AvataruploadForm(forms.Form):
    avatar = forms.ImageField()

然后到应用的urls.py里添加URL地址信息,userinfo用来展示用户信息和修改用户头像,alteruserinfo用来修改用户信息。

    path('userinfo.html', views.userinfoView, name='userinfo'),
    path('alteruserinfo.html', views.alterUserinfoView, name='alteruserinfo'),

接下来分别编写视图函数。

#展示个人信息页和修改头像,@login_required验证登录,未登录则跳转到登录页面
@login_required
def userinfoView(request):
    #获取用户名
    username= request.user.username
    #获取该用户的模型实例对象
    user = get_object_or_404(MyUser, username=username)
    #如果是POST请求,则说明用户是在修改头像
    if request.method == 'POST':
        #将用户的请求信息和上传的图片文件放入自定义的表单模型中
        form = AvataruploadForm(request.POST, request.FILES)
        #验证表单
        if form.is_valid():
            #获取用户上传的文件
            avatar = request.FILES['avatar']
            #设置保存的文件名
            imagename = get_random_str() + '.jpg'
            #保存文件的路径
            imagepath = os.path.join(settings.MEDIA_ROOT, imagename)
            #使用pillow压缩和保存图像文件
            img = Image.open(avatar)
            crop_im = img.resize((300, 300), Image.ANTIALIAS)
            crop_im.save(imagepath)
        
            try:
                #如果存在老的头像且不是默认头像就删除
                if request.user.avatar != 'default.png':
                    oldimage = '%s/%s' % (settings.MEDIA_ROOT, request.user.avatar)
                    os.remove(oldimage)
            except:
                pass
                
            #更新用户的头像在数据库中的地址
            user.avatar = imagename
            user.save()
            #跳转到用户信息页
            return redirect('userinfo.html')
    return render(request, 'userinfo.html', locals()) 

上面的展示用户信息和修改用户头像,每一步都说明的很清楚,接下来,实现修改用户信息。

修改用户信息

首先在forms.py中定义个人信息的数据模型表单及验证函数.

from django import forms
from .models import MyUser, Library
import re
#个人信息的数据库表单
class UserinfoForm(forms.ModelForm):
    #模型与表单设置
    class Meta:
        #绑定模型
        model = MyUser

        #选择转换哪些模型的字段为表单字段
        fields = ['first_name','last_name','id_number','email', 'qq','weChat','mobile','lib_id']
        #定义表单字段的css样式
        widgets = {
            'first_name': forms.widgets.TextInput(attrs={'class': 'form-control','autocomplete':'off'}),
            'last_name': forms.widgets.TextInput(attrs={'class': 'form-control','autocomplete':'off'}),
            'id_number': forms.widgets.TextInput(attrs={'class': 'form-control','autocomplete':'off'}),
            'email': forms.widgets.EmailInput(attrs={'class': 'form-control','autocomplete':'off'}),
            'qq': forms.widgets.TextInput(attrs={'class': 'form-control','autocomplete':'off'}),
            'weChat': forms.widgets.TextInput(attrs={'class': 'form-control','autocomplete':'off'}),
            'mobile': forms.widgets.TextInput(attrs={'class': 'form-control','autocomplete':'off'}),
           
        }

    # 函数必须以clean_开头,以模型的字段名结尾,如mobile是MyUser模型的字段
    def clean_mobile(self):  
        """
        通过正则表达式验证手机号码是否合法
        """
        mobile = self.cleaned_data['mobile']
        mobile_regex = r'^1[34578]\d{9}$'
        p = re.compile(mobile_regex)
        if p.match(mobile):
            return mobile
        else:
            raise forms.ValidationError('手机号码非法', code='invalid mobile')

    def clean_qq(self):
        qq = self.cleaned_data['qq']
        if qq.isdigit():
            return qq
        else:
            raise forms.ValidationError('QQ号码应全部为数字',  code='invalid qq')

    def clean_id_number(self):
        id_number = self.cleaned_data['id_number']
        if id_number.isdigit():
            return id_number
        else:
            raise forms.ValidationError('学号/工号应全部为数字',  code='invalid id_number')


继续编写修改个人信息的视图函数。

#修改个人信息,@login_required验证登录,未登录则跳转到登录页面
@login_required(login_url='/user/login.html')
def alterUserinfoView(request):
    #获取用户名
    username = request.user.username
    #获取该用户的实例化模型对象
    instance = MyUser.objects.get(username=username)
    if instance:
        #将实例化模型赋给自定义的表单模型
        user = UserinfoForm(instance=instance)
    #如果是POST请求,则说明用户想修改个人信息
    if request.method == 'POST':
        #将用户的POST请求信息和用户的实例化模型放入自定义表单中
        userinfo = UserinfoForm(request.POST, instance=instance)
        #验证表单,通过则保存表单数据
        if userinfo.is_valid():
            userinfo.save()
            #跳转到用户信息展示页
            return redirect('userinfo.html')
        else:
            #未通过验证,向用户展示错误消息
            error_msg = userinfo.errors.as_json()
  
    return render(request, 'alteruserinfo.html', locals())

到这里,用户信息模块基本上就实现的差不多了。
由于荐购系统需要实名制,因此在读者修改个人资料以后需要图书馆管理员进行后台认证,认证通过的读者才能进行荐购,而且不能再修改姓名、学号/工号和所属馆的资料。
权限的验证主要通过模板进行,如将读者分为普通读者和认证读者,认证读者有荐购权限,那么在模板中可以这样设置:

{# 对读者权限进行验证,如果有荐购权限,说明读者资料通过审核,不允许再修改姓名、工号和所属馆 #}
{% if perms.recommend.add_recommend %}
    <fieldset disabled >
{% else %}
    <fieldset>
{% endif %}
<div class="form-group">
      <label class="badge badge-secondary" for="name" >姓</label>
      {{ user.last_name }} 
</div>
<div class="form-group">
      <label class="badge badge-secondary" for="name" >名</label>
      {{ user.first_name }} 
</div>
...
</fieldset>

fieldset是bootstrap4中的禁用字段集,对 <fieldset> 添加 disabled 属性来禁用 <fieldset> 内的所有控件。

到这里,我们实现了用户的注册、登陆、修改密码、密码找回,还实现了用户的个人信息中心,用户可以修改头像以及部分个人信息。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 193,968评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,682评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,254评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,074评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,964评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,055评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,484评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,170评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,433评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,512评论 2 308
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,296评论 1 325
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,184评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,545评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,150评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,437评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,630评论 2 335

推荐阅读更多精彩内容