Fork me on GitHub

Django学习笔记(16)——扩展Django自带User模型,实现用户注册与登录

一,项目题目:扩展Django自带User模型,实现用户注册与登录

  我们在开发一个网站的时候,无可避免的需要设计实现网站的用户系统。此时我们需要实现包括用户注册,登录,用户认证,注销,修改密码等功能。Django作为一个完美主义者的终极框架,当然也会想到用户的这些痛点,它内置了强大的用户认证系——auth,所以本文在不建立User模型的情况下实现用户的注册,登录和认证。另外对Django Auth自带的User模型进行扩展,运行用户添加更多的个人信息。

   我在之前的Django学习笔记(9)——开发用户注册与登录系统 中已经完成了这个注册与登录系统,那么下面在这里写主要是向基于Django自带的User模型重新建立自己用户的模型。

二,项目需求

2.1 实现的功能需求

  我们用Django2.0开发一个叫users的APP,来实现以下六项功能:

  1. 用户注册:注册完成后转到登录页面
  2. 用户登录:登录完成后转到用户资料页面
  3. 用户资料页面:查看用户注册信息,并提供编辑资料按钮
  4. 用户资料编辑:编辑完成后转到用户资料查看页面
  5. 用户密码重置
  6. 用户退出登录

2.2  扩展User模型的内容

  由于Django Auth自带的User模型字段有限,我们还需要自定义模型UserProfile对其扩展。

  (Auth组件的相关内容可以参考:Django学习笔记(13)——Django的用户认证组件,视图层和QuerySet API

  首先我们看一下Django Auth模块自带User模型所包含字段:

username:用户名

email: 电子邮件

password:密码

first_name:名

last_name:姓

is_active: 是否为活跃用户。默认是True

is_staff: 是否为员工。默认是False

is_superuser: 是否为管理员。默认是False

date_joined: 加入日期。系统自动生成。

  下面展示我们自定义的UserProfile模型

user: 与User是1对1关系

org:用户名

telephone: 电话

mod_date: 最后修改日期。系统自动生成

  

2.3  项目不足与改进

  整个用户注册和登录非常简单,比如没有邮箱验证,也不能通过第三方APP授权登录,我做的页面也比较丑陋、一个更好的方式是使用以及成熟的第三方Django Package(比如django-allauth)来实现用户注册和登录。

 

三,编码规范需求

编码规范需求:

    1. 代码规范遵守pep8 (https://python.org/dev/peps/pep-0008/)

    2. 函数有相应的注释

    3. 程序有文档说明文件(README.md)

    4. 程序的说明文档必须包含的内容:程序的开发环境(django版本)、程序的实
现的功能、程序的启动方式、登录用户信息、程序的运行效果

    5. 程序设计的流程图:

  

四,项目思路

  基本的项目开启什么的都在这里不再阐述了,我直接写项目的主要思路,基础的Django操作流程看我之前的博客就行。

4.1  创建模型(Model)

  到目前为止,我学习Django,第一步都是创建模型。为什么呢?因为大多数语言都遵循软件开发的设计模式MVC。

  这里我们建立一个名称为UserProfile的模型,代码如下:

from django.db import models

# Create your models here.
from django.contrib.auth.models import User

class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE,
                                related_name='profile')
    # 模型类中设置:blank=True,表示代码中创建数据库记录时该字段可传空白(空串,空字符串)
    org = models.CharField('Organization', max_length=128, blank=True)
    telephone = models.CharField('Telephone', max_length=50, blank=True)
    mod_data = models.DateTimeField('Last modified', auto_now=True)

    class Meta:
        verbose_name = 'User profile'

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

  注意:我们并没改变Django Auth 自带的User模型,也没有建立新的User模型。而UserProfile只是对User模型的扩展。找到users/models.py,并创建上面的UserProfile模型。由于我们引入了Django Auth自带的User模型,所以我们必须开始先把它import进来。

  自定义的UserProfile模型新增字段的意思如下:

user: 与User是1对1关系

org:用户名

telephone: 电话

mod_date: 最后修改日期。系统自动生成

  

4.2  配置URL

  我比较习惯先写URL,再写视图View,从URL配置上,你应该可以理解我们想实现的六个功能。

  下面是users/urls.py代码内容:

from django.urls import re_path, path
from users import views

app_name = 'users'

urlpatterns = [
    re_path(r'^register/$', views.register, name='register'),
    re_path(r'^login/$', views.login, name='login'),
    re_path(r'^user/(?P<pk>\d+)/profile/$', views.profile, name='profile'),
    re_path(r'^user/(?P<pk>\d+)/profile/update/$', views.profile_update, name='profile_update'),
    re_path(r'^user/(?P<pk>\d+)/pwd_change/$', views.pwd_change, name='pwd_change'),
    re_path(r"^logout/$", views.logout, name='logout'),
]

  上面,我们使用 name= '?' ,比如第一个:我们这是给动态链接 /register/取了个名字 ‘register’,这样我们就可以在HTML模板里面通过 {% url 'users:register' %} 来调用这个链接了。

4.3  编写视图函数(View)

  我们需要编写六个视图函数,正如上面url路径所指。

  由于这六个视图中,register , login,profile , pwdChange这个四个视图都需要用到表单,所以我们先创建表单代码。

  (表单代码我们可以分离出去,在users目录下创建MyForms.py文件,然后再在里面创建form表单,最后将其导入视图函数,这样代码显得工整,清晰,而且后期容易维护。还有MyForms.py可以通过clean方法自定义表单验证,非常便捷,而且逻辑更清晰)。

from django import forms
from django.contrib.auth.models import User
import re

def email_check(email):
    
    pattern = re.compile(r"\"?([-a-zA-Z0-9.'?{}]+@\w+\.\w+)\"?")
    return re.match(pattern, email)

class RegistrationForm(forms.Form):
    username = forms.CharField(label='Username', max_length=50)
    email = forms.EmailField(label='Email')
    password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
    password2 = forms.CharField(label='Password Confirmation', widget=forms.PasswordInput)

    #user clean methods to define custom validation rules

    def clean_username(self):
        username = self.cleaned_data.get('username')
        if len(username) < 3:
            raise forms.ValidationError("your username must be at least 3 characters log")
        elif len(username) > 20:
            raise forms.ValidationError("your username is too long")
        else:
            filter_result = User.objects.filter(username__exact=username)
            if len(filter_result) > 0:
                raise forms.ValidationError('your username already exists')
        return username

    def clean_email(self):
        email = self.cleaned_data.get('email')
        if email_check(email):
            filter_result = User.objects.filter(email__exact=email)
            if len(filter_result) > 0:
                raise forms.ValidationError("your email already exists")
        else:
            raise forms.ValidationError("Please enter a valid email")

        return email

    def clean_password1(self):
        password1 = self.cleaned_data.get('password1')
        if len(password1) < 3:
            raise forms.ValidationError("your password is too short")
        elif len(password1) > 20:
            raise forms.ValidationError("your password is too long")

        return password1

    def clean_password2(self):
        password1 = self.cleaned_data.get('password1')
        password2 = self.cleaned_data.get('password2')

        if password1 and password2 and password1 != password2:
            raise forms.ValidationError('Password mismatch Please enter again')

        return password2

class LoginForm(forms.Form):
    username = forms.CharField(label='Username', max_length=50)
    password = forms.CharField(label='Password', widget=forms.PasswordInput)

    # use clean methods to define custom validation rules

    def clean_username(self):
        username = self.cleaned_data.get('username')
        if email_check(username):
            filter_result = User.objects.filter(email__exact=username)
            if not filter_result:
                raise forms.ValidationError('This emial does not exist')
        else:
            filter_result = User.objects.filter(username__exact=username)
            if not filter_result:
                raise forms.ValidationError('This username does not exist Please register first')

        return username
    
    
class ProfileForm(forms.Form):
    first_name = forms.CharField(label='First Name', max_length=50, required=False)
    last_name = forms.CharField(label='Last Name', max_length=50, required=False)
    org = forms.CharField(label='Organization', max_length=50, required=False)
    telephone = forms.CharField(label='Telephone', max_length=50, required=False)


class PwdChangeForm(forms.Form):
    old_password = forms.CharField(label='Old Password', widget=forms.PasswordInput)

    password1 = forms.CharField(label='New Password', widget=forms.PasswordInput)
    password2 = forms.CharField(label='Password Confirmation', widget=forms.PasswordInput)

    # use clean methods to define custom validation rules

    def clean_password1(self):
        password1 = self.cleaned_data.get('password1')

        if len(password1) < 6:
            raise forms.ValidationError("your password is too short")
        elif len(password1) > 20:
            raise forms.ValidationError("your password is too long")

        return password1

    def clean_password2(self):
        password1 = self.cleaned_data.get('password1')
        password2 = self.cleaned_data.get('password2')

        if password1 and password2 and password1 != password2:
            raise forms.ValidationError("Password mismatch Please enter again")

        return password2

  

  上面的代码之所以非常长,是因为我们用clean方法加入了很多表单验证项。比如检查用户名是否过短,是否过长,是否已经存在等等。

  我们在之前,Django学习笔记(14)——AJAX与Form组件知识补充(局部钩子和全局钩子详解) ,一文中详细学习了局部钩子和全局钩子,也就是clean方法的使用,如果不懂的话可以先学习这一篇博文。毕竟真实的网站开发都是需要加上这些验证规则的。

  废话就说这么多,当然你也可以在View视图里面写Form验证规则。随你。下面直接看我的视图users/views.py的内容:

from django.shortcuts import render, HttpResponse, get_object_or_404
from .MyForms import RegistrationForm, LoginForm, ProfileForm, PwdChangeForm
from .models import UserProfile
from django.contrib.auth.models import User
from django.http import HttpResponseRedirect
from django.contrib import auth
from django.urls import reverse
from django.contrib.auth.decorators import login_required

# Create your views here.

@login_required
def profile(request, pk):
    user = get_object_or_404(User, pk=pk)
    return render(request, 'users/profile.html', {'user': user})

@login_required
def profile_update(request, pk):
    user = get_object_or_404(User, pk=pk)
    user_profile = get_object_or_404(UserProfile, user=user)

    if request.method == 'POST':
        form = ProfileForm(request.POST)

        if form.is_valid():
            user.first_name = form.cleaned_data['first_name']
            user.last_name = form.cleaned_data['last_name']
            user.save()

            user_profile.org = form.cleaned_data['org']
            user_profile.telephone = form.cleaned_data['telephone']
            user_profile.save()

            return HttpResponseRedirect(reverse('users:profile', args=[user.id]))
    else:
        default_data = {'first_name': user.first_name, 'last_name': user.last_name,
                        'org': user_profile.org, 'telephone': user_profile.telephone,}
        form = ProfileForm(default_data)

    return render(request, 'users/profile_update.html', {'form': form, 'user': user})


def register(request):
    if request.method == 'POST':
        
        form = RegistrationForm(request.POST)
        if form.is_valid():
            username = form.cleaned_data['username']
            email = form.cleaned_data['email']
            password = form.cleaned_data['password2']
            
            # 使用内置User自带create_user方法创建用户,不需要使用save()
            user = User.objects.create_user(username=username, password=password, email=email)

            # 如果直接使用objects.create()方法后不需要使用save()
            user_profile = UserProfile(user=user)
            user_profile.save()

            return HttpResponseRedirect("/accounts/login/")
    else:
        form = RegistrationForm()
    return render(request, 'users/registration.html', {'form': form})


def login(request):
    if request.method == 'POST':
        form = LoginForm(request.POST)
        if form.is_valid():
            username = form.cleaned_data['username']
            password = form.cleaned_data['password']

            user = auth.authenticate(username=username, password=password)

            if user is not None and user.is_active:
                auth.login(request, user)
                return HttpResponseRedirect(reverse('users:profile', args=[user.id]))
            else:
                # 登录失败
                return render(request, 'users/login.html', {'form': form, 'message':'Wrong password Please Try agagin'})
    else:
        form = LoginForm()

    return render(request, 'users/login.html', {'form': form})

@login_required
def logout(request):
    auth.logout(request)
    return HttpResponseRedirect("/accounts/login/")

@login_required
def pwd_change(request, pk):
    user = get_object_or_404(User, pk=pk)

    if request.method == "POST":
        form = PwdChangeForm(request.POST)

        if form.is_valid():
            password = form.cleaned_data['old_password']
            username = user.username

            user = auth.authenticate(username=username, password=password)

            if user is not None and user.is_active:
                new_password = form.cleaned_data['password2']
                user.set_password(new_password)
                user.save()
                return HttpResponseRedirect('/accounts/login/')

            else:
                return render(request, 'users/pwd_change.html', {'form': form,
                        'user': user, 'message': 'Old password is wrong Try again'})
    else:
        form = PwdChangeForm()

    return render(request, 'users/pwd_change.html', {'form': form, 'user': user})

  

 下面我们分别看看几个视图函数是如何工作的:

register视图函数:

  1. 当用户通过POST方法提交表单,我们先验证表单RegistrationForm的数据是否有效。如果有效,我们先用Django User模型自带的 create_user方法创建user对象,再创建 user_profile。用户通过一张表单提交数据,我们实际上分别存储在两张表里。
  2. 如果用户注册成功,我们通过HttpResponseRedirect方法转到登录页面。
  3. 如果用户没有提交表单或者不是通过POST方法提交表单,我们转到注册页面,生成一张空的RegistrationForm。

login视图函数:

  1. 当用户通过POST方法提交表单,我们先验证表单LoginForm的数据是否有效。如果有效,我们调用Django自带的auth.authenticate()来验证用户名和密码是否正确。如果正确且用户是活跃的,我们调用auth.login() 来进行登录。
  2. 如果用户登录失败,会重新转到登录页面,并返回错误信息。
  3. 如果用户登录成功,我们通过HttpResponseRedirect方法转到用户个人信息页面
  4. 如果用户没有提交表单或不是通过POST方法提交表单,我们转到登录页面,生成一个空的LoginForm。

profile视图函数:

  1. 我们先从url获取user的主键pk(id),利用get_object_or_404方法获取需要修改个人资料的用户对象user,然后利用user获取一一对应的 user_profile 对象。
  2. 当用户通过POST方法提交个人资料修改表单,我们先验证表单ProfileForm的数据是否有效。如果有效,我们将更新过的first_name 和 last_name 数据存入 user,再将更新过的 telephone 和 org 数据存入 user_profile,更新成功后返回个人信息页。
  3. 如果用户没有提交表单或者不是通过POST方法提交表单,我们先获取现有数据生成 default_data,再利用ProfileForm显示。

pwd_change视图函数:

  1. 首先我们用@login_required 装饰器确定用户是否已经登录,只有登录的才能修改个人密码。
  2. 我们先从url获取user的主键pk(id),利用 get_object_or_404 方法获取需要修改密码的用户对象user。
  3. 当用户通过POST方法提交密码修改表单,我们先验证表单PwdChangeForm的数据是否有效。如果老的密码是正确的,我们将更新过的密码通过set_password 方法数据存入 user。修改密码成功后返回登录页面。
  4. 如果用户没有提交表单或不是通过POST方法提交表单,我们生成一张空的PwdChangeForm。

4.4  编写HTML模板(Template)

  在users目录下创建/templates/users/文件夹,编写HTML模板。其目录结构如下:

   展示一个 login.html 代码:

{% extends 'base.html' %}
{% load staticfiles %}
{% block title %}<h1 class="my-con1">Login Page</h1>{% endblock %}
{% block css %}
    <link rel="stylesheet" href="{% static '/CSS/login.css' %}">
{% endblock %}


{% block content %}

    {% if message %}
    {{ message }}
    {% endif %}
    <div class="form-group">
        <form method="post" action="" enctype="multipart/form-data">
            {% csrf_token %}
            {% for field in form %}
                <div class="form-group ">
                    {{ field.errors }}
                    {{ field.label_tag }}{{ field }}
                    {% if field.help_text %}
                        <p class="help">{{ field.help_text|safe }}</p>
                    {% endif %}
                </div>
            {% endfor %}
            <div class="btn">
                <input type="submit" value="Login">
            </div>
        </form>
        <h2 class="my-href pull-right">
            <a href="/accounts/register" >Register Link</a>
        </h2>

    </div>

{% endblock %}

  

五,注意事项

5.1 Django网站开发是如何遵循软件设计MVC模式

  如果你要开发一个好的网站或者网络应用,你就必须了解经典的软件开发所遵循的MVC设计模式。Django作为最优秀的基于Python语言的网站开发框架,当然也遵循了这种设计模型。下面就学习一下什么是MVC框架以及Django网站开发是如何遵循这种软件开发设计模型的。

5.1.1  什么是MVC模式?它有什么优点?

  MVC是Model-View-Controller(模型-试图-控制器)模式

  • Model(模型)简而言之就是数据模型。模型不是数据本身(比如数据库里的数据),而是抽象的描述数据的构成和逻辑关系。通常模型包括了数据表的各个字段(比如人的年龄和出生日期)和相关关系(单对单,单对多关系等)。数据库里的表会根据模型的定义来生成创建。
  • View(视图)主要用于显示数据,用来展示用户可以看到内容或者提供用户可以输入或者操作的界面。数据来源于哪里?当然是数据库。那么用户输入的数据给谁?当然是给控制器了。
  • Controller(控制器)是应用程序中处理用户交互的部分。通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据(比如增加或者更新数据表)。

  如果把MVC比喻成一个粽子,那么View就是最外面一层的绿色玉米叶,是我们可以直接看到的。Controller就是中间那层熟糯米,而粽子的的核心自然然是最里面的那一层的肉馅Model模型了。

  MVC最大的优点是实现了软件或网络应用开发过程中数据,业务逻辑和界面的分离,使软件开发更清晰,也是维护变得更容易。这与静态网页设计中使用HTML和CSS实现了内容和样式的分离是同一个道理。

5.1.2  Django网站开发是如何遵循MVC设计模型的?

  Django网站开发全靠四件套:Model(模型),URL(链接),View(视图)和Template(模板)。他们看似与MVC设计模式不太一致,其实本质是相同的。但是Django的View和经典的View确实有非常大的不同。Django四件套与经典的MVC对应关系如下。

  • Django Model(模型):这个与经典MVC模式下的Model差不多。
  • Django URL+View(视图):这个合起来就与经典MVC下的Controller更像。原因在于Django的URL和View合起来才能向Template传递正确的数据。用户输入提供的数据也需要Django的View来处理。
  • Django Template(模板):这个与经典MVC模式下的View一致。Django模板用来呈现Django View传来的数据,也决定了用户界面的外观。Template里面也包含了表单,可以用来收集用户的输入。

5.1.3  Django网站开发应先写URL还是写View?

  使用Django开发网站的第一步绝对是定义模型(Model),如果写个不需要使用数据库的小应用,也完全可以不定义模型,直接写URL和View。当然先写URL还是View,其实都可以,完全取决于个人偏好。一般来说,从上向下思考问题的话,先写URL。不过不影响。

 

5.2  扩展Django自带的User Admin

  Django自带的User Admin 只能编辑管理基础字段如用户名和电子邮件。我们现在希望能扩展User Admin,在编辑 User的同时编辑我们User Profile 里的额外字段(如电话)。去实现这个功能,我们需要按照如下代码修改 users/admin.py:

from django.contrib import admin
from django.contrib.auth.models import User
from django.contrib.auth.admin import UserAdmin
from .models import UserProfile

admin.site.unregister(User)


class UserProfileInline(admin.StackedInline):
    model = UserProfile


class UserProfileAdmin(UserAdmin):
    inlines = [UserProfileInline, ]


admin.site.register(User, UserProfileAdmin)

  下面是实际效果,我们会发现UserAdmin后多了一栏 user Profiles。

进入后台的方法(前提是项目URL等东西要配置好):
 
1,启动项目
    python manage.py runserver
 
2,在浏览器中打开地址:
    127.0.0.1:8000/users/admin

   (我们进入admin后台,向下拉,会发现)

 

 

六,知识扩展

6.1:如何引入Bootstrap

  根目录下新建一个static目录,并将其解压后的Bootstrap-3.3.7目录,整体拷贝到static目录中,而且由于Bootstrap依赖于jQuery,所以我们需要引入jQuery,并且在static目录下,新建一个CSS和JS目录,作为以后的样式文件和JS文件的存放地,最后如下图所示:

  然后打开项目的settings文件,在最下面添加配置,用于指定静态文件的搜索目录:

STATIC_URL = '/static/'

STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static'),
]

  

6.1.1  创建 base.html 模板

  一个网站要有自己的统一风格和公共部分,可以将这部分内容集中到一个基础模板 base.html中。现在,在根目录下的template中建一个 base.html 文件作为站点的基础模板。

  在Bootstrap文档中,为我们提供了一个非常简单而且又实用的基础模板,代码如下:

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
    <title>Bootstrap 101 Template</title>

    <!-- Bootstrap -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet">

    <!-- HTML5 shim 和 Respond.js 是为了让 IE8 支持 HTML5 元素和媒体查询(media queries)功能 -->
    <!-- 警告:通过 file:// 协议(就是直接将 html 页面拖拽到浏览器中)访问页面时 Respond.js 不起作用 -->
    <!--[if lt IE 9]>
      <script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
      <script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
    <![endif]-->
  </head>
  <body>
    <h1>你好,世界!</h1>

    <!-- jQuery (Bootstrap 的所有 JavaScript 插件都依赖 jQuery,所以必须放在前边) -->
    <script src="https://cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js"></script>
    <!-- 加载 Bootstrap 的所有 JavaScript 插件。你也可以根据需要只加载单个插件。 -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"></script>
  </body>
</html>

  我们将其整体拷贝到 base.html文件中。因为我们将Bootstrap的目录都拷贝下来了,所以我们不需要cdn引入,我们可以将路径更改为本地的文件路径。

   更改后的代码如下:

{% load staticfiles %}

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->

    <!-- Bootstrap -->
    <link href="/static/bootstrap-3.3.7/dist/css/bootstrap.min.css" rel="stylesheet">

    <!-- HTML5 shim 和 Respond.js 是为了让 IE8 支持 HTML5 元素和媒体查询(media queries)功能 -->
    <!-- 警告:通过 file:// 协议(就是直接将 html 页面拖拽到浏览器中)访问页面时 Respond.js 不起作用 -->
    <!--[if lt IE 9]>
      <script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
      <script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
    <![endif]-->

  </head>
  <body>

    <!-- jQuery (Bootstrap 的所有 JavaScript 插件都依赖 jQuery,所以必须放在前边) -->
    <script src="/static/JS/jquery-3.2.1.js"></script>
    <!-- 加载 Bootstrap 的所有 JavaScript 插件。你也可以根据需要只加载单个插件。 -->
    <script src="/static/bootstrap-3.3.7/dist/js/bootstrap.min.js"></script>
  </body>
</html>

  

  注意:Bootstrap的所有JavaScript插件都依赖jQuery,所以必须将jQuery放在前面。

    <!-- jQuery (Bootstrap 的所有 JavaScript 插件都依赖 jQuery,所以必须放在前边) -->
    <script src="/static/JS/jquery-3.2.1.js"></script>

    <!-- 加载 Bootstrap 的所有 JavaScript 插件。你也可以根据需要只加载单个插件。 -->
    <script src="/static/bootstrap-3.3.7/dist/js/bootstrap.min.js"></script>

  

6.1.2  使用Bootstrap静态文件

  {%  static ‘相对路径’ %}  这个Django为我们提供的静态文件加载方法,可以将页面与静态文件链接起来。

  {%  block title%} AAA {%  endblock %} 设置了专门的 title。

  通过block css 引入了针对性的 css样式文件。

  更改后的 base.html 内容如下:

{% load staticfiles %}

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
      {% block title %}
          <title>base</title>
      {% endblock %}

    <!-- Bootstrap -->
    <link href="/static/bootstrap-3.3.7/dist/css/bootstrap.min.css" rel="stylesheet">

    <!-- HTML5 shim 和 Respond.js 是为了让 IE8 支持 HTML5 元素和媒体查询(media queries)功能 -->
    <!-- 警告:通过 file:// 协议(就是直接将 html 页面拖拽到浏览器中)访问页面时 Respond.js 不起作用 -->
    <!--[if lt IE 9]>
      <script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
      <script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
    <![endif]-->

        <link rel="stylesheet" href="/static/CSS/base.css">

      {% block css %}
      {% endblock %}
  </head>
  <body>



  <div class="container my-con">
        {% block content %}
        {% endblock %}
  </div>

    <!-- jQuery (Bootstrap 的所有 JavaScript 插件都依赖 jQuery,所以必须放在前边) -->
    <script src="/static/JS/jquery-3.2.1.js"></script>
    <!-- 加载 Bootstrap 的所有 JavaScript 插件。你也可以根据需要只加载单个插件。 -->
    <script src="/static/bootstrap-3.3.7/dist/js/bootstrap.min.js"></script>
  </body>
</html>

  

6.1.3  完成自己的HTML代码

  因为有四五个,这里只举其中一个例子来说。其他的大同小异。(注意:我的前端水平有点差,所以页面不是那么美观,见谅见谅啊)

  比如登录页面,首先我们继承 base.html的内容,然后将自己的后端逻辑代码填入其中,如下:

{% extends 'base.html' %}
{% load staticfiles %}
{% block title %}<h1 class="my-con1">Login Page</h1>{% endblock %}
{% block css %}
    <link rel="stylesheet" href="{% static '/CSS/login.css' %}">
{% endblock %}


{% block content %}

    {% if message %}
    {{ message }}
    {% endif %}
    <div class="form-group">
        <form method="post" action="" enctype="multipart/form-data">
            {% csrf_token %}
            {% for field in form %}
                <div class="form-group ">
                    {{ field.errors }}
                    {{ field.label_tag }}{{ field }}
                    {% if field.help_text %}
                        <p class="help">{{ field.help_text|safe }}</p>
                    {% endif %}
                </div>
            {% endfor %}
            <div class="btn">
                <input type="submit" value="Login">
            </div>
        </form>
        <h2 class="my-href pull-right">
            <a href="/accounts/register" >Register Link</a>
        </h2>

    </div>

{% endblock %}

   然后,我们写样式的话,需要在 static/CSS目录下新建一个样式文件,然后里面写样式(注意:我就写了简单的样式,哈哈哈哈哈)。

body{
    background: #e6e6e6;
}

.my-con1{
    text-align: center;
    color: steelblue;
}

.my-href{
    margin-top: 50px;
}

  这样一个简单的登录页面就出来了:

 

 

6.2:Model中的Meta选项

6.2.1,ModelForm的Meta类中定义的fields

  默认的Field是Model中定义的Field,如需更改,可在Form类内以同名字段覆盖,比如自定义widget和required属性等。

  下面来学习class Meta内嵌类的所有元数据选项(meta options),

6.2.2,可用的Meta选项

  abstract

Optional.abstract
    
    如果abstract = True ,这个model就是一个抽象基类

  

  app_label

Options.app_label
    
    如果一个model定义在默认的models.py之外(例如,如果你的APP的models在myapp.models
子模块下),你必须定义app_label 让DJango知道他属于哪一个APP

app_label = 'myapp'

  

  db_table

Options.db_table
    
    定义该model在数据中的表名称:
    
    db_table = 'music_album'

  

6.2.3,数据库中表的名称

  (注意:在MySQL中使用小写字母作为数据库表名称)

  为了节省时间,Django会自动的使用你的model class的名称和包含这个model的APP名称来构建数据库的表名称,一个model的数据库表名称是通过将‘app label’ (你在manage.py startapp 中使用的名称 和model的类名称,加上一个下划线在他们之间来构成)。

  例如,如果你有一个APP叫做bookstore(使用manage.py startapp bookstore创建),以及一个model定义为class Book这样将会创建一个名为bookstore_book的数据库表。

  如果想自定义数据库的表名称,需要在class Meta 使用db_table参数来自定义。

  如果你的数据库表名称是一个SQL保留字,或者它包含不允许出现在Python变量中的字符(比如连字符)这是没问题的,因为Django会自动给列名添加引号。

6.2.4,verbose_name

  Django模型中的verbose_name我们常常可能需要使用,比如将数据库里面的数据导出成csv文件。那么,csv文件的表头的名字可以通过取每个字段的verbose_name来获取,数据可以通过queryset语句来获取。这样制作出来的csv表就能像数据库一样,字段名和字段值一一对应了。

Options.verbose_name
    
    指明一个易于理解和表述的对象名称,单数形式:
    verbose_name = 'user_name'

    如果这个值没有设定,Django将会使用该model的类名的分词形式作为其对象的表述名
    CamelCase将会转换为camel case

  

6.2.5,常见的Django Model META类选项

from django.db import models

class Meta:
    # 按 Priority降序, order_date升序排列
    get_latest_by = ['-priority', 'order_date']
    # 自定义数据库里表格的名称
    db_table = 'music_album'
    # 按照什么排序
    ordering = ['pub_date']
    # 定义APP的标签
    app_label = 'myapp'
    # 声明此类是否抽象
    abstract = True
    # 添加授权
    permissions = (('Can deliver pizzas', 'Can deliver pizzas'))

  

6.3:URL命名及reverse() 方法

  假设我们需要在模板中通过链接指向一篇具体的文字,下面哪种方式更好?

方法1: 使用命名URL

<a href="{%  url  'article'  id %}Article</a>


方法2:使用常规URL——不建议

<a href="blog/article/id">Article</a>

  如果你还没有意识到方法1的好处,那么想想假设你需要把全部模板链接由 blog/article/id 改为blog/articles/id,那种方法更快?,改所有模板,还是改URL配置里的一个字母?

  可惜的是命名的URL一般只在模板里使用,不能直接在视图里使用。如果我们有了命名的URL,我们如何把它转化为常规的URL在视图里使用呢?Django提供的reverse()方法很容易实现这点。假设不同的APP(比如news和blog)里都有article这个命名URL,我们怎么样区分呢?我们只需要在article前面加上blog这个命名空间即可。

from django.urls import reverse

# output blog/article/id
reverse('blog:article', args=[id])

  而且,我们需要在urls.py 里面,给动态链接 取名字,比如:

from django.urls import re_path
from . import views

app_name = 'blog'
urlpatterns = [
    re_path(r'^articles/$', views.articles_content, name='articles'),

]

  这样我们就可以在HTML模板中通过{%   url 'arrticle'  id %} 来调用这个链接了。

 

6.4  Django创建对象的create和save方法

  Django的模型(Model)的本质是类,并不是一个具体的对象(object)。当你设计好模型后,你就可以对Model进行实例化从而创建一个具体的对象。Django对于创建对象提供了两种不同的save与create方法,下面来具体分析一下两者的不同。

  我们先来看一下下面的例子,我们已经设计好了一个Person的模型:

from django.db import models

class Person(models.Model):
    name = models.CharField(max_length=128)

    def __str__(self):
        return self.name

  

6.4.1  用save方法创建对象

  用save方法创建一个名为 james 的具体对象,我们可以这么做。记住你只有用了save()方法后,Django才会将这个对象的信息存储到数据库中。

james_obj = Person(name="LeBron james")
james_obj.save()

  

6.4.2  用create方法创建对象

  正因为用save方法创建对象有两步,而且程序员容易忘记加上 save(),Django提供了一个更便捷的create方法,如下:如果你使用create方法,无需再加上save(),create方法不仅创建了新的对象,而且直接将信息存储到数据库里。

james_obj = Person.objects.create(name="LeBron james")

  

6.4.3  save和create方法比较

  create只能用于创建新的对象,在数据库层总是执行 insert 的操作。save不仅用于创建新的对象,也能用于更新对象的现有数据,在数据库总是先执行update,找不到具体对象后再执行 insert 的操作,对于创建全新的对象,两者都可以。如果更新已有对象信息,只能用save()方法。

 

6.4.4  User自带的create_user方法

  如果你要用Auth自带的User模型创建新对象,你需要使用create_user方法,而不是create方法,如下所示。(create_user方法很有用,自动会给密码加hash )。

user1 = User.objects.create_user(username=username, username=password)

  

七,结果展示

注册页面展示

  当我们注册一个harden,注册成功后会自动跳转到登录页面。

登录页面展示

  下面我们登录harden的信息,登录成功后跳转到harden的用户信息页面。

用户信息页面展示

  进入详细信息页面,我们会发现下面会有三个按钮,分别是编辑,修改密码,退出按钮。首先我们点击编辑按钮。进入编辑页面。更新信息,结果如下:

  下面我们进入修改密码页面:

  当我们点击退出,就会重新进入登录页面。再次进入个人信息页面,我们就会发现新的信息。

   如果登录的用户名不存在,会出现下面报错信息:

  其他的就不多做演示了。

 

八,代码 

8.1  完整的项目代码 

  请移步小编的GitHub:传送门

 

 参考文献: https://blog.csdn.net/weixin_42134789/article/details/80194532

posted @ 2019-06-08 11:01  战争热诚  阅读(15435)  评论(3编辑  收藏  举报