Python入门自学进阶-Web框架——30DjangoAdmin项目应用-自定义用户认证续

Posted kaoa000

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python入门自学进阶-Web框架——30DjangoAdmin项目应用-自定义用户认证续相关的知识,希望对你有一定的参考价值。

一、前面实现的是DjangoAdmin实现的自定义用户认证管理,现在自己来实现管理功能,即在mytestapp中增加用户认证管理功能。

在UserProfile的model中,对password字段增加help_text属性:

password = models.CharField(_('password'), max_length=128,help_text=mark_safe('<a href="password/">重置密码</a>.'))

然后在用户修改页面进行显示:    f f.help_text <span> f.errors </span>

然后点击这个重置密码链接,跳转到一个密码修改页面。

在urls中增加对应的路由项:

path('<str:app_name>/<str:table_name>/<int:id_num>/change/password/',views.passwd_reset,name='passwd_reset'),

编写视图函数passwd_reset:

def passwd_reset(req,app_name,table_name,id_num):
    admin_class = mytestapp_admin.enable_admins[app_name][table_name]
    model_form_class = myutils.create_model_form(req, admin_class)
    obj = admin_class.model.objects.get(id=id_num)
    errors =
    if req.method == "POST":
        _password = req.POST.get("password")
        _password2 = req.POST.get("password2")
        if _password == _password2:
            obj.set_password(_password)   # 借助模型中的函数
            obj.save()
            return redirect(req.path.rstrip("password/"))
        else:  # 两次密码输入不相同,报错
            errors['invalid_password'] = "two password not same"
    return render(req,'mytestapp/passwd_reset.html','user_obj':obj,'errors':errors)

 前端页面passwd_reset.html:

% extends 'base.html' %
% load tags %
% block mybody %
    <body>
        <nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow">
            <a class="navbar-brand col-md-3 col-lg-2 mr-0 px-3" href="#">我的客户管理系统</a>
            <button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-toggle="collapse" data-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
            <ul class="navbar-nav px-3">
                <li class="nav-item text-nowrap">
                    <a class="nav-link" href="#"> request.user.userprofile.name </a>
                </li>
            </ul>
        </nav>
        <div class="container-fluid" style="margin-top: 20px;margin-left: 50px;">
            <div class="d-flex p-2 bd-highlight">修改用户【 user_obj 】密码</div>
            <div class="d-inline-flex p-2 bd-highlight">
                <form method="post">% csrf_token %
                      <div class="form-group">
                        <label for="exampleInputEmail1">新密码</label>
                        <input type="password" class="form-control" name="password">
                      </div>
                      <div class="form-group">
                        <label for="exampleInputPassword1">重复密码</label>
                        <input type="password" class="form-control" name="password2">
                      </div>
                    <div>
                        <ul>
                            % for k,v in errors.items %
                            <li> k : v </li>
                            % endfor %
                        </ul>
                    </div>
                      <button type="submit" class="btn btn-primary">Submit</button>
                </form>
            </div>
        </div>
    </body>
% endblock %

二、在ModelForm中排除部分字段:

在modelform形成过程中,在Meta中使用exclude进行排除。

class Meta:
    model = admin_class.model
    fields = "__all__"
    exclude = ('qq',)

这样配置后,在前端就不会形成qq这个字段了。使用exclude排除,主要用在增加新记录时,如果某些字段值是数据库插入时自动生成的,如创建日期等,不需要在前端显示添加,或修改时,有些字段设置为readonly,在前端使用disabled进行了输入禁止,但是在提交时,这个字段不提交,就形成null值,与原值不相等导致校验失败,这时可以直接使用exclude排除这些字段,不显示就可以了,相当于update语句不修改这些字段。

做成配置的,在AdminClass中增加exclude_fields=['qq',]

在生成ModelForm模板中,

class Meta:
    model = admin_class.model
    fields = "__all__"
    if admin_class.exclude_fields:
        exclude = admin_class.exclude_fields

三、在显示记录列表中,是按照AdminClass中的list_display中的字段显示的,这些字段都是在数据库表中存在的,如果我们要添加一个数据库表中没有的字段,如何实现?如增加一个enrollment字段,内容显示报名,并是一个链接,点击后跳转到其他页面。

class CustomerAdmin(BaseAdmin):
    list_display = ['qq','name','phone','source','consultant','referral_from','consult_course','tags','status','enroll']
    list_per_page = 4
    list_filter = ['qq','source','status','consult_course','tags']
    list_search = ['qq','name']
    filter_horizontal = ['tags']
    readonly_fields = ['qq','consultant','tags']
    actions = ['delete_action',]

    def enroll(self):
        return "what"   # enroll字段显示的值
....

添加如上配置后,显示客户表时,会报错:

使用try捕获错误进行处理:

@register.simple_tag
def build_table_row(obj,admin_class,url_path):
    row_ele = ""
    for row_data in obj:
        row_ele = row_ele +'<tr><td colspan="6"><input  my_id="obj_checkbox" type="checkbox" value="%s"</td>'%(row_data.id)
        for index_ele,column in enumerate(admin_class.list_display):
            try:
                field_obj = row_data._meta.get_field(column)
                if field_obj.choices:
                    column_data = getattr(row_data,"get_%s_display"%column)()
                else:
                    column_data = getattr(row_data,column)
                field_obj1 = getattr(row_data,column)
                if hasattr(field_obj1,'values'):
                    s = ""
                    dic1 = field_obj1.values()
                    for i in range(dic1.count()):
                        for v in dic1[i].values():
                            s = s + str(v) + ';'
                        column_data = s
                if index_ele == 0:   # 若果是第一列,则加上a标签,可以跳转到修改页
                    row_ele += '<td colspan="6"><a href="%s%s/change/">%s</a></td>'%(url_path,row_data.id,column_data)
                else:
                    row_ele += "<td colspan='6'>%s</td>"%column_data
            except FieldDoesNotExist as e:
                if hasattr(admin_class,column):
                    column_func = getattr(admin_class,column)
                    admin_class.instance = obj
                    # 将obj传递给admin_class的instance,这样就能取到对应行的对象的id,这里obj是QuerySet对象

                    column_data = column_func()
                    row_ele += "<td colspan='6'>%s</td>" % column_data
        row_ele = row_ele + "</tr>"
    print(row_ele)
    return mark_safe(row_ele)

在admin_class中配置:

    def enroll(self):

        return "<a href='%s/enrollment/'>报名</a>"%self.instance[0].id
        # 因为instance是QuerySet对象,对于每一行,只有一个对象,取[0],这样,形成的连接就是id/enrollment/,在路由项中增加对应的路由,跳转到对应view函数处理

点击报名:

 

增加对应的路由项和视图及前端模板,完成报名的功能。 

 四、自定义用户登录验证

前面使用的是DjangoAdmin的登录界面:

 登陆后进入的是各种表的管理界面,现在要自定义登录界面,登陆后进入业务界面。

自己做一个登录界面:

 

% extends 'base.html' %
% block mybody %
    <div class="container" >
        <form class="form-signin col-sm-3">

          <h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
          <label for="inputEmail" class="sr-only">Email address</label>
          <input name="email" type="email" id="inputEmail" class="form-control" placeholder="Email address" required autofocus>
          <label for="inputPassword" class="sr-only">Password</label>
          <input name="password" type="password" id="inputPassword" class="form-control" placeholder="Password" required>
          <div class="checkbox mb-3">
            <label>
              <input type="checkbox" value="remember-me"> Remember me
            </label>
          </div>
          <button class="btn btn-lg btn-primary btn-block" type="submit">Login</button>

        </form>
    </div>
% endblock %

增加路由项:path('account/login/',views.acc_login),

编写视图函数acc_login:基本框架:

def acc_login(req):

    return render(req,'login.html')

此时,访问:http://127.0.0.1:8000/account/login/就可以访问到上面的自定义登录界面

修改完善:

前端

% extends 'base.html' %
% block mybody %
    <div class="container" >
        <form class="form-signin col-sm-3" method="post">% csrf_token %

          <h1 class="h3 mb-3 font-weight-normal">PlswCRM</h1>
          <label for="inputEmail" class="sr-only">Email address</label>
          <input name="email" type="email" id="inputEmail" class="form-control" placeholder="Email address" required autofocus>
          <label for="inputPassword" class="sr-only">Password</label>
          <input name="password" type="password" id="inputPassword" class="form-control" placeholder="Password" required>
          % if errors %
            <span style="color: red"> errors.error </span>
          % endif %
          <div class="checkbox mb-3">
            <label>
              <input type="checkbox" value="remember-me"> Remember me
            </label>
          </div>
          <button class="btn btn-lg btn-primary btn-block" type="submit">Login</button>

        </form>
    </div>
% endblock %

后端视图函数:

from django.shortcuts import render,redirect
from django.contrib.auth import authenticate,login
# 这里的authenticate和login是借助于django的认证和登录功能

def acc_login(req):

    errors=
    if req.method == "POST":
        _email = req.POST.get('email')
        _password = req.POST.get('password')
        user = authenticate(username=_email,password=_password)
        print(user)
        # authenticate接受用户名和密码,如果验证通过,则user为用户对象,否则为空
        if user:
            # 如果用户存在,即验证通过,需要将用户写入session中做保存,再次访问网站其他页面时使用
            # 这个写session的动作,可以使用login()来实现
            login(req,user)  # 利用Django的login,实现用户写入session,即保存登录
            # 验证、登录成功,转到首页
            return redirect("/plcrm/")
        else:
            errors['error'] = "username or password is wrong"
    return render(req,'login.html',"errors":errors,)

对需要登录验证的视图函数增加验证:

如前面的mytestapp的各个视图函数,访问前需要进行实现登录验证,这时还是借助Django的装饰器来实现:

from django.contrib.auth.decorators import login_required

# Create your views here.
@login_required   # 加上这个装饰器,就会在访问index函数时进行验证,如果没有登陆,则跳转到登录页面
def index(req):
    print(mytestapp_admin.enable_admins['plcrm']['customer'].model)
    return render(req,"mytestapp/index.html",'table_list':mytestapp_admin.enable_admins)

在没有登陆的状态下访问mytestapp:

自动跳转到accounts/login/路径,这个是Django默认的登录路径,而我们自定义的是account/login/,这个选项在settings.py中进行配置:

 LOGIN_URL = '/account/login/'

这时,就会自动跳转到自定义的/account/login/这个登录界面。

将其他的视图函数都加上@login_required,这样就实现了所有页面都需要登录认证的功能。

五、登录退出

在用户名称上增加下列框,

实现登录退出:<a class="dropdown-item" href="% url 'acc_logout' %">login out</a>

在路由项中增加:path('account/logout/',views.acc_logout,name='acc_logout'),

视图函数:

def acc_logout(req):
    logout(req)
    return redirect('/account/login/')

logout也是借助如django的功能。

以上是关于Python入门自学进阶-Web框架——30DjangoAdmin项目应用-自定义用户认证续的主要内容,如果未能解决你的问题,请参考以下文章

Python入门自学进阶-Web框架——18FormModelForm

Python入门自学进阶-Web框架——18FormModelForm

Python入门自学进阶-Web框架——20Django其他相关知识2

Python入门自学进阶-Web框架——2Django初识

Python入门自学进阶-Web框架——3Django的URL配置

Python入门自学进阶-Web框架——21DjangoAdmin项目应用