2.6 维护个人信息

  当用户完成网站注册和登录之后,大多数网站要求用户提供更多的个人信息,因此学习网站上维护用户个人信息的功能是很有必要的。同时,也可以在该功能编写过程中体验另外一种前后端传递数据的方式,如下图所示。

2.6.1 个人信息的数据模型和表单类

  所有的个人信息都要保存到数据库中,因此要编辑./account/models.py文件,创建个人信息相关的数据模型类,在这个文件中增加下面代码中的类。

 1 class UserInfo(models.Model):
 2     user = models.OneToOneField(User,on_delete=models.CASCADE,unique=True)
 3     school = models.CharField(max_length=100, blank=True)
 4     company = models.CharField(max_length=100, blank=True)
 5     profession = models.CharField(max_length=100, blank=True)
 6     address = models.CharField(max_length=100, blank=True)
 7     aboutme = models.TextField(blank=True) #①
 8     
 9     def __str__(self):
10         return "user:{}".format(self.user.username)

语句①中的TextField属性,它将对应HTML中的Textarea。在多个字段中都有blank=True,其含义是该项数据可以为空,即在前端界面中允许用户不填写。

数据模型类创建完成,下面迁移数据,让数据库具有相应的数据库表。

数据模型类创建之后,后面的开发步骤类似于前文所学习过的“注册”过程,依次是“编写表单类”、“创建视图函数”、“设置URL”、“设置前端模板”,下面就按部就班地逐一展开。

  表单类还是要写在./account/forms.py文件中,下面是此文件中增加的代码。

1 from .models import UserProfile,UserInfo
2 
3 class UserInfoForm(forms.ModelForm):
4     class Meta:
5         model = UserInfo
6         fields = ("school","company","profession","address","aboutme")

上面的表单类是针对UserInfo数据模型类的,对于来自auth_user数据库表的信息,也要写一个表单类来对应,下面的UserForm类要完成对该表的数据进行修改。

1 class UserForm(forms.ModelForm):
2     class Meta:
3         model = User
4         fields = ("email",)

特别注意:在UserForm的fields值中不包括username,因为用户的username一旦确定就不能随便更改。在用户编辑个人信息时,username的值不再是可修改的,或者说在本项目中约定用username为用户的唯一标识,通常唯一标识就不让用户修改了。

2.6.2 展示个人信息

  按照习惯的流程,接下来就是编写视图函数了。这里要编写的视图函数应该能够处理两种请求,一种是展示用户信息的GET请求,另一种是前端提交用户信息的POST请求。

  要展示用户的个人信息,必须看一下目前的数据库中都有哪些与个人信息相关的内容。

  • account_userinfo表,其中记录了school、company、profession、address、aboutme字段内容。
  • account_userprofile表,其中记录了phone、birth字段内容。
  • auth_user表,这是Django默认的,其中记录了password、list_login、si_supperuser、first_name、last_name、email、username等字段内容。

  此外,关于用户信息还有Django默认的其他表,如果auth_user_group等,此处我们暂时用不到。在下面的例子中,只从上述三个数据库表中获取某些字段的内容,用来组成用户的个人信息。

  在./account/views.py文件中增加如下代码,这是与个人信息相关的视图函数。

 1 from django.contrib.auth.decorators import login_required #①
 2 from .models import UserProfile,UserInfo
 3 from django.contrib.auth.models import User
 4 
 5 @login_required(login_url = '/account/login') #②
 6 def myself(request):
 7     user = User.objects.get(username=request.user.username)
 8     userprofile = UserProfile.objects.get(user=user)
 9     userinfo = UserInfo.objects.get(user=user)
10     return render(request,"account/myself.html",{"user":user,"userinfo":userinfo,"userprofile":userprofile})

  因为只有登录的用户才能看到自己的个人信息,所以要在该视图函数被执行时判断用户是否登录,此处使用了Django自带的装饰器函数,所以在前面使用了语句①引入装饰器函数login_required。在具体使用时,向装饰器函数提供一个参数,语句②中的参数login_url='/account/login/'将没有登录的用户转到登录页面。

  与此视图函数对应的模板应该放在./templates/account目录中,命名为myself.html,其代码如下。

 1 {% extends "base.html" %}
 2 {% block title %} my information{% endblock %}
 3 {% block content %}
 4 <div class="row text-center vertical-middle-sm">
 5     <h1>My Information</h1>
 6     <div class="row">
 7         <div class="col-md-6">
 8             <div class="row">
 9                 <div class="col-md-4 text-right"><span>用户名:</span></div>
10                 <div class="col-md-8 text-left">{{user.username}}</div>
11             </div>
12             <div class="row">
13                 <div class="col-md-4 text-right"><span>邮箱:</span></div>
14                 <div class="col-md-8 text-left">{{user.email}}</div>
15             </div>
16             <div class="row">
17                 <div class="col-md-4 text-right"><span>生日:</span></div>
18                 <div class="col-md-8 text-left">{{userprofile.birth}}</div>
19             </div>
20             <div class="row">
21                 <div class="col-md-4 text-right"><span>电话:</span></div>
22                 <div class="col-md-8 text-left">{{userprofile.phone}}</div>
23             </div>
24             <div class="row">
25                 <div class="col-md-4 text-right"><span>毕业学校:</span></div>
26                 <div class="col-md-8 text-left">{{userinfo.school}}</div>
27             </div>
28             <div class="row">
29                 <div class="col-md-4 text-right"><span>工作单位:</span></div>
30                 <div class="col-md-8 text-left">{{userinfo.company}}</div>
31             </div>
32             <div class="row">
33                 <div class="col-md-4 text-right"><span>职业:</span></div>
34                 <div class="col-md-8 text-left">{{userinfo.profession}}</div>
35             </div>
36             <div class="row">
37                 <div class="col-md-4 text-right"><span>地址:</span></div>
38                 <div class="col-md-8 text-left">{{userinfo.address}}</div>
39             </div>
40             <div class="row">
41                 <div class="col-md-4 text-right"><span>个人介绍:</span></div>
42                 <div class="col-md-8 text-left">{{userinfo.aboutme}}</div> 43 </div> 44 </div> 45 <div class="col-md-6"> 46 <p>picture</p> 47 </div> 48 </div> 49 </div> 50 {% endblock %}

下一步,就是完成URL的配置,编辑./account/urls.py文件,增加相应的URL。

1 path('my-information/',views.myself, name="my_information"),

将Django服务启动,然后在浏览器的地址栏中输入http://127.0.0.1:8000/account/my-information/,如果不处于登录状态,会自动跳转到登录界面,这就是在视图函数中判断用户是否登录的装饰器函数起作用了。

从报错信息中可以看出,UserProfile或者UserInfo实例化时出现了错误,因为没有找到相应的用户数据,为什么?

  我们的项目是一个学习项目,开始并没有做非常完整的设计,随着学习的深入不断增加一些应用,随着这些应用的增加,数据库表发生了变化。但是,新增的UserInfo没有为原来注册的用户增加记录(注意:UserInfo中已经写上了user=models.OneToOneField(User,unique=True)).

  做简单的修改,在注册的视图函数register()的注册流程业务中增加一行代码,如下所示:

UserInfo.objects.create(user=new_user)把这行放在new_profile.save()后面,其效果是在保存用户注册信息后,同时在account_userinfo数据库表中写入该用户的ID信息。

增加上述代码之后,重新注册一个账户,注册之后登录,然后访问http://127.0.0.1:8000/account/my-information/

除展示外,网站还应该允许用户对个人信息进行编辑。

2.6.3 编辑个人信息

在展示出来的个人信息中,还有不少选项没有填写的呢,应允许用户继续填写。此外,对已经填写的部分,也应允许修改,但前面已经说过了,本项目中的用户名不能修改。

下面还是先编写视图函数,在./account/views.py中创建一个新的视图函数myself_edit(),其代码如下。

 1 from django.http import HttpResponseRedirect #
 2 from .forms import UserProfileForm,UserInfoForm,UserForm
 3 
 4 @login_required(login_url = '/account/login')
 5 def myself_edit(request):
 6     user = User.objects.get(username=request.user.username)
 7     userprofile = UserProfile.objects.get(user=user)
 8     userinfo = UserInfo.objects.get(user=user)
 9 
10     if request.method == "POST":
11         user_form = UserForm(request.POST)
12         userprofile_form = UserProfileForm(request.POST)
13         userinfo_form = UserInfoForm(request.POST)
14         if user_form.is_valid() * userprofile_form.is_valid() * userinfo_form.is_valid():
15             user_cd = user_form.cleaned_data
16             userprofile_cd = userprofile_form.cleaned_data
17             userinfo_cd = userinfo_form.cleaned_data
18             print(user_cd["email"])
19             user.email = user_cd['email']
20             userprofile.birth = userprofile_cd['birth']
21             userprofile.phone = userprofile_cd['phone']
22             userinfo.school = userinfo_cd['school']
23             userinfo.company = userinfo_cd['company']
24             userinfo.profession = userinfo_cd['profession']
25             userinfo.address = userinfo_cd['address']
26             userinfo.aboutme = userinfo_cd['aboutme']
27             user.save()
28             userprofile.save()
29             userinfo.save()
30         return HttpResponseRedirect('/account/my-information/') #
31     else:
32         user_form = UserForm(instance=request.user)
33         userprofile_form = UserProfileForm(initial={"birth":userprofile.birth,"phone":userprofile.phone})
34         userinfo_form = UserInfoForm(initial={"school":userinfo.school,"company":userinfo.company,
35                                               "profession":userinfo.profession},"address":userinfo.address,"aboutme":userinfo.aboutme)
36 return render(request,"account/myself_edit.html",{"user_form":user_form,"userprofile_form":userprofile_form,
37                                                   "userinfo_form":userinfo_form})

语句①中引入HttpResponseRedirect,其作用是实现URL的转向,当前的URL是http://127.0.0.1:8000/account/edit-my-information/(这个路径在urls.py中配置),当用户提交了个人信息并被后端程序验证通过和保存后,就执行语句②,页面转到http://127.0.0.1:8000/account/my-information/,用户能够看到自己的修改结果。

  先把已有的用户信息读出来,然后判断用户请求是POST还是GET。如果是GET,则显示表单,并且将用户已有信息也显示在其中;如果是POST,则接收用户提交的表单信息,然后更新各个数据模型实例属性的值,最后不要忘记保存。

  编写完视图函数后再编写相应的模板代码,在./templates/account目录中创建文件myself_edit.html,其代码如下。

 1 {% extends "base.html" %}
 2 {% block title %}my information{% endblock %}
 3 {% block content %}
 4 <div class="row text-center vertical-middle-sm">
 5     <h1>Edit My Information</h1>
 6     <div class="row">
 7         <div class="col-md-6">
 8             <form class="form-horizontal" action="." method="post">{% crsf_token %}
 9                 <div class="row">
10                     <div class="col-md-4 text-right"><span>用户名:</span></div>
11                     <div class="col-md-8 text-left">{{user.username}}</div>
12                 </div>
13                 <div class="row">
14                     <div class="col-md-4 text-right"><span>邮箱:</span></div>
15                     <div class="col-md-8 text-left">{{user_form.email}}</div>
16                 </div>
17                 <div class="row">
18                     <div class="col-md-4 text-right"><span>生日:</span></div>
19                     <div class="col-md-8 text-left">{{userprofile_form.birth}}</div>
20                 </div>
21                 <div class="row">
22                     <div class="col-md-4 text-right"><span>电话:</span></div>
23                     <div class="col-md-8 text-left">{{userprofile_form.phone}}</div>
24                 </div>
25                 <div class="row">
26                     <div class="col-md-4 text-right"><span>毕业学校:</span></div>
27                     <div class="col-md-8 text-left">{{userinfo_form.school}}</div>
28                 </div>
29                 <div class="row">
30                     <div class="col-md-4 text-right"><span>工作单位:</span></div>
31                     <div class="col-md-8 text-left">{{userinfo_form.company}}</div>
32                 </div>
33                 <div class="row">
34                     <div class="col-md-4 text-right"><span>职业:</span></div>
35                     <div class="col-md-8 text-left">{{userinfo_form.profession}}</div>
36                 </div>
37                 <div class="row">
38                     <div class="col-md-4 text-right"><span>地址:</span></div>
39                     <div class="col-md-8 text-left">{{userinfo_form.address}}</div>
40                 </div>
41                 <div class="row">
42                     <div class="col-md-4 text-right"><span>个人介绍:</span></div>
43                     <div class="col-md-8 text-left">{{userinfo_form.aboutme}}</div>
44                 </div>
45                 <div class="row">
46                     <input type="submit" class="btn btn-primary btn-lg" value="Submit">
47                 </div>
48             </form>
49         </div>
50         <div class="col-md-6">
51             <p>picture</p>
52         </div>
53     </div>
54 </div>
55 {% endblock %}

配置URL,在./account/urls.py中增加一行代码。

1 path('edit-my-information/',views.myself_edit, name="edit_my_information"),

在保证服务器运行和有一个账户登录之后,在浏览器的地址输入http://127.0.0.1:8000/account/edit-my-information/

完善个人信息,然后点击Submit按钮,会跳转到个人信息展示界面,如下图所示。

编辑./templates/header.html文件,找到“修改密码”那一行,在其下增加“个人信息”的下拉菜单代码。

1 <li><a href="{% url 'account:my_information' %}">个人信息</a> </li>

保存后刷新页面,其效果如下图所示。

再编辑./templates/account/myself.html文件,在所展示的信息下面增加一个按钮,通过按钮实现编辑功能,在个人信息列表内容下面增加如下代码。

1 <a href="{% url 'account:edit_my_information' %}"><button class="btn btn-primary
2             btn-lg">edit my information</button> </a>

保存后刷新页面,其效果如下图所示。

单击edit my information按钮,就能够实现编辑当前用户信息的功能。

2.6.4 上传和裁剪头像图片

   在展示和编辑个人信息的过程中,界面的右侧有一个地方,写了picture,仅仅占据了一个div的位置罢了。现在就把它完善,打算在那里上传用户的头像图片。

  为了显示那里是头像,先传一张图片上去。在./static/images目录中放一张图片,下面的代码中会用到,放了一张名为newton.jpg的图片。编辑./templates/account/myself.html文件,找到写有pictures的代码块,将原来的<p>picture</p>部分用下面的代码替换。

{% load staticfiles %} #①
<div style="margin-right:100px"><img name="user_face" src="{% static'images/newton.jpg %}"
            class="img-circle" width="270px" id="my_photo"></div> #②
            <div style="margin-right:100px"><button class="btn btn-primary btn-lg" id="upload_image"
            onclick="upload_image_layer()">upload my photo</button></div>#③

因为要引用静态文件,所以语句①是必须有的。语句②用于显示图片。语句③用于显示一个按钮,不过这个按钮预留了操作“onclick="upload_image_layer()”",后面会编写这个函数。刷新界面后,就可以看到作为头像的图片,如下图所示,只不过这还不是上传的。

  占领位置不是目的,目的是实现图片上传。

1、部署上传和裁剪头像图片的插件

  很多网站都有头像图片上传和裁剪头像图片的功能,这里使用实现这个功能的插件(网上找的),完成头像图片的上传和展示。

  到https://github.com/qiwsir/DajngoPracticeProject下载ImgCrop文件包。以此为例来说明使用方法。

  下载文件包后,观察其目录和文件结构,如下图所示

 

 从上面的文件结构中可以看出,这是一个纯粹前端的工具,那么就把各个文件按照已经部署好的项目架构分门别类地放置。

  • 复制ImgCrop/css/style.css到本项目的./static/css中,并更名为imagecrop.css。为了达到后期的展示效果,将文件中的样式修改一下,目的是将显示区域左对齐。

  • 复制ImgCrop/jquery-1.11.1.min.js到本项目的./static/js中,虽然原来已经有一个jquery.js文件,但是还是要复制这个文件,因为版本不同。所选用的JavaScript插件因为比较古老,所以使用这个版本的Jquery。

  • 复制复制ImgCrop/js/cropbox-min.js到本项目的./static/js中。

完成上述文件的复制后,开始在本项目中实现头像图片的裁剪。

先在./account/views.py中增加一个非常简单的视图函数,代码如下。

1 def my_image(request):
2     return render(request,'account/imagecrop.html',)

然后在./templates/account目录中创建imagecrop.html文件,并且将裁剪头像插件文件夹中的index.html文件里面的部分代码复制到imagecrop.html文件中,同时增加CSS和JavaScript文件,最终代码如下。

 1 {% load staticfiles %}
 2 <link rel="stylesheet" href="{% static 'css/imagecrop.css' %}">
 3 <div class="container">
 4   <div class="imageBox">
 5     <div class="thumbBox"></div>
 6     <div class="spinner" style="display: none"></div>
 7   </div>
 8   <div class="action">
 9     <!-- <input type="file" id="file" style=" width: 200px">-->
10     <div class="new-contentarea tc"> <a href="javascript:void(0)" class="upload-img">
11       <label for="upload-file">请先选择图片...</label>
12       </a>
13       <input type="file" class="" name="upload-file" id="upload-file" />
14     </div>
15     <input type="button" id="btnCrop" class="Btnsty_peyton" value="OK">
16     <input type="button" id="btnZoomIn" class="Btnsty_peyton" value="+"  >
17     <input type="button" id="btnZoomOut" class="Btnsty_peyton" value="-" >
18   </div>
19   <div class="cropped"></div>
20 </div>
21 
22 <script src="{% static 'js/jquery-1.11.1.min.js' %}"></script>
23 <script type="text/javascript" src="{% static 'js/cropbox-min.js' %}"></script>
24 <script type="text/javascript">
25 
26 $(window).load(function() {
27     //$('#btnCrop').click();$("#idName").css("cssText","background-color:red!important");
28 
29     //$(".imageBox").css("cssText","background-position:88px 88px!important");$(".imageBox").css("cssText","background-size:222px 222px!important");
30     var options =
31     {
32         thumbBox: '.thumbBox',
33         spinner: '.spinner',
34         imgSrc: ''
35     }
36     var cropper = $('.imageBox').cropbox(options);
37     var img="";
38     $('#upload-file').on('change', function(){
39         var reader = new FileReader();
40         reader.onload = function(e) {
41             options.imgSrc = e.target.result;
42             cropper = $('.imageBox').cropbox(options);
43             getImg();
44         }
45         reader.readAsDataURL(this.files[0]);
46         this.files = [];
47         //getImg();
48     })
49     $('#btnCrop').on('click', function(){
50         //alert("图片上传喽");
51         $.ajax({
52             url: '{% url "account:my_image" %}',
53             type: 'POST',
54             data: {"img":img},
55             success: function(e){
56               location.href = "{% url 'account:my_information' %}"
57             },
58         });
59     })
60     function getImg(){
61         img = cropper.getDataURL();
62         $('.cropped').html('');
63         $('.cropped').append('<img src="'+img+'" align="absmiddle" style="width:180px;margin-top:4px;border-radius:180px;box-shadow:0px 0px 12px #7E7E7E;"><p>180px*180px</p>');
64         $('.cropped').append('<img src="'+img+'" align="absmiddle" style="width:128px;margin-top:4px;border-radius:128px;box-shadow:0px 0px 12px #7E7E7E;"><p>128px*128px</p>');
65         $('.cropped').append('<img src="'+img+'" align="absmiddle" style="width:64px;margin-top:4px;border-radius:64px;box-shadow:0px 0px 12px #7E7E7E;" ><p>64px*64px</p>');
66         }
67 
68     $(".imageBox").on("mouseup",function(){
69          getImg();
70           });
71 
72 
73     $('#btnZoomIn').on('click', function(){
74         cropper.zoomIn();
75     })
76     $('#btnZoomOut').on('click', function(){
77         cropper.zoomOut();
78     })
79 });
80 </script>

 

这么多代码,没有几行是自己写的,都是从那个插件的index.html文件中复制过来的。为了显示真的是复制的,原来文件中注释掉的内容都没有删掉,可以对照看一下。

特别提醒的是开头引入的CSS和JavaScript文件,不要把路径搞错。还有一个需要注意的地方,就是<input type="button" id="btnCrop" class="Btnsty_peyton" value="OK">这行代码,原文件中没有增加id,如果直接复制过来,在操作的时候单击OK按钮,可能会没有显示。

最后在./account/urls.py中配置URL,增加下面一行代码。

path('my-image/',view.my_image, name="my_image"),

重新运行Django服务,在浏览器的地址栏中输入http://127.0.0.1:8000/account/my-image/,会看到如下图所示的界面。

单击“请先选择图片”按钮,选择一张图片,看一下效果。在单击“ok”按钮,该图片还不能上传到服务器上,只是弹出一个提示语。

为了实现图片的真正上传,还要做不少工作,下面逐项完成。

2、修改数据模型

上传的文件也要存储在数据库中,前面建立了一个UserInfo的用户个人信息模型类,当时没有考虑上传头像问题,现在需要在那个类中增加一个字段。

编辑./account/models.py文件,在UserInfo类中增加如下字段。

photo = models.ImageField(blank=True)

在ImageField(blank=True)中设置参数blank=True,意为用户的头像可以为空。

每次完成models.py的编辑,都要重新做迁移数据的操作,依次执行下述指令。

注意:此处有坑

需要安装Pillow,下面我们在命令行安装它。

安装之前最好是先升级pip

然后再进行数据迁移,如下图所示。

 可以查看一下account_userinfo的表结构是否已经发生更改。

数据模型修改好之后,不要忘记./account/forms.py中的UserInfoForm表单类也要在fields的值中增加photo,如下所示。

1 fields = ("school","company","profession","address","aboutme","photo")

3、编写视图函数

下面编写的视图函数专门用于处理显示、上传和裁剪头像。前面编写了my_image()视图函数,主要是为了显示上传和裁剪的效果,现在要对这个函数进行修改,从而实现存储前端传过来的图片的功能。

 1 @login_required(login_url='/account/login/')
 2 def my_image(request):
 3     if request.method == 'POST':
 4         img = request.POST['img'] #①
 5         userinfo = UserInfo.objects.get(user=request.user.id)
 6         userinfo.photo = img
 7         userinfo.save()
 8         return HttpResponse("1")
 9     else:
10         return render(request, 'account/imagecrop.html', )

语句①得到前端以POST方式提交的图片信息,已经规定前端提交的类字典数据中有img这个键,否则会报错。

4、再次编写前端代码

上传和裁剪功能已经创建完成,数据提交给后端的函数还没有。

下面检查刚才已经做好的上传和裁剪头像功能,上传一张图片,然后点击OK按钮,会弹出一个提示框,如下图所示。

 

显然我们需要的功能不是弹出一个提示框,而是将裁剪的图片上传。为了实现这个功能,这里使用一种新的从前端向后端提交数据的方式,也是在网站开发中被被普遍使用的Ajax

编辑./templates/account/imagecrop.html文件中的JavaScript代码,修改单击OK按钮后实现的功能,代码如下:

 1     $('#btnCrop').on('click', function(){
 2         //alert("图片上传喽");
 3         $.ajax({
 4             url: '{% url "account:my_image" %}',
 5             type: 'POST',
 6             data: {"img":img},
 7             success: function(e){
 8               location.href = "{% url 'account:my_information' %}"
 9             },
10         });
11     })

这段代码就是Ajax的 一种写法(使用jQuery),请观察其结构

  • url:声明提交地址
  • type:声明提交方式,有GET和POST两种,此处使用POST
  • data:声明提交的数据内容
  • success:提交成功后,根据反馈结果实现页面跳转。

保存文件后刷新界面,选择一张图片,单击OK按钮上传图片。

这里应该报错,也一定要报错,只有报错才能显示Django的特点,如下图所示。

打开Google,输入“Django 403 forbidden”,会看到很多都在说是跟CSRF有关。前面已经提到过这个问题,在没有使用Ajax提交数据时,都是在表单代码中增加{% csrf_token %},才能实现数据的提交。现在使用Ajax方式提交,没有使用增加代码的方式,也没有使用被的替代方法,所以Django从安全角度考虑,就拒绝了提交数据。

解决的方法有很多,这里使用其中一种,将一个名为csrf.js的文件引入进来,可以在https://github.com/qiwsir/DjangoPracticeProject/blob/master/mysite_2/static/js/csrf.js下载,将这个文件放在本项目的./static/js目录中,并且在./templates/account/imagecrop.html里面引用。

1 </div>
2 
3 <script src="{% static 'js/jquery-1.11.1.min.js' %}"></script>
4 <script type="text/javascript" src="{% static 'js/cropbox-min.js' %}"></script>
5 <script type="text/javascript" src="{% static 'js/csrf.js' %}"></script>
6 <script type="text/javascript">

保存文件后刷新页面,上传一张图片,单击OK按钮,发现界面跳转到指定页面了。

看来图片应该被上传了。看看是数据库中保存的内容是否发生了变化,如下图所示。

 

数据库中的确保存了图片的信息。注意,我们并没有在服务器某个位置保存图片文件,而是通过所使用的插件,将实体图片转换为代码格式,保存到数据库中。

在显示图片的位置还没有将刚刚上传的图片显示出来,还要修改./templates/account/myself.html文件,对显示图片的部分代码进行如下修改。

 1  <div class="col-md-6">
 2             {% load staticfiles %}
 3             <div style="margin-right:100px">
 4                 {% if userinfo.photo %}
 5                 <img src="{{ userinfo.photo | striptags }}" class="img-circle" id="my_photo" name="user_face"> #①
 6                 {% else %}
 7                 <img name="user_face" src="{% static 'images/newton.jpg' %}" class="img-circle" id="my_photo">
 8                 {% endif %}
 9             </div>
10             <div style="margin-right:100px"><button class="btn btn-primary btn-lg" id="upload_image"
11             onclick="upload_image_layer()">upload my photo</button></div>
12         </div>

这段代码对userinfo.photo进行判断,如果用户还没上传图片,则显示默认的newton.jpg;如果用户已经上传了图片,则显示上传的图片内容。

在显示上传图片内容时,语句①使用了{{ userinfo.photo | striptags }}这种方式,这是Django模板中常用的一种方式。“|”符号是管道符,是将userinfo.photo这个从数据库中读出来的对象内容进行过滤。按什么规则过滤,要看管道符后面的内容了,striptags的意思是去除HTML标签,就是将userinfo.photo所引用的对象内容中的HTML标签去掉,然后将结果返回。

再刷新一下刚刚跳转过来的界面,比刚才漂亮了,如下图所示。

这样就实现了用户头像图片的裁剪功能。

2.6.5 优化头像上传功能

头像上传和裁剪实现了,但是使用该功能的入口还没有做,所以还要继续优化。

入口就是单击upload my photo按钮之后,会弹出一个提示框,在这个提示框中完成上传和裁剪图片的功能。

提示框是web开发中常用的功能,所以一定有现成的工具可以使用,在这里使用来自http://layer.layui.com/网站的提示框。下载后,根据本项目的静态文件部署规范,将提示框各个文件放到相应的目录,还可以到 https://github.com/qiwsir/DjangoPracticeProject下载提示框。

具体部署方法就是将layer.js文件和skin目录复制到本项目的./static/js目录里面,然后编辑./templates/account/myself.html文件,在{% endblock %}前面用下面的方式将插件引入。

1 <script type="text/javascript" src='{% static"js/jquery.js" %}'></script>
2 <script type="text/javascript" src="{% static 'js/layer.js '%}"></script>

写一个JavaScriptd代码,实现让系统弹出上传和裁剪头像的层。

 1 <script>
 2     function upload_image_layer(){
 3         layer.open(
 4         title:"上传头像",
 5         area: ['650px', '600px'],
 6         type:2,
 7         content:"{% url 'account:my_image' %}",
 8         })
 9    }
10 </script>

刷新页面之后,看看效果,应该弹出一个相当不错的提示框,如下图所示。

这时,单击提示框中的OK按钮,就能将图片上传,但是会遇到下一个麻烦。麻烦的来源在./templates/account/imagecrop.html中,在图片上传后,直接转到了http://127.0.0.1:8000/account/my-information/,以前这么做可以,但是现在因为将imagecropt.html页面嵌入了弹出的提示框,所以就不能再如从前一样跳转了。想要实现的是上传之后自动关闭提示框,并刷新提示框的父页面,在该页面中显示所上传的头像,因此要对./templates/account/imagecrop.html中的JavaScript代码进行一些改写。

 1     $('#btnCrop').on('click', function(){
 2         //alert("图片上传喽");
 3         $.ajax({
 4             url: '{% url "account:my_image" %}',
 5             type: 'POST',
 6             data: {"img":img},
 7             success: function(e){
 8               //location.href = "{% url 'account:my_information' %}"
 9               //相应的视图函数中的return HttpResponse("1"),其数值在这里被应用
10               if(e == "1"){
11                 parent.location.reload();
12               } else {
13                 alert("sorry,you are not lucky.the picture can't been uploaded.")
14               }
15             },
16         });
17     })

再次刷新http://127.0.0.1:8000/account/my-information/,上传头像图片,看一下效果,已经能够关闭提示框并刷新页面了。

2.6.6 对个人信息进行管理

  用admin账号登录http://127.0.0.1:8000/admin/,在管理后台中,可以对Users和User profiles这两类个人信息进行管理,但是新增加的User infos还没有被列出来,如下图所示。

  要增加User infos,需要再次编辑./account/admin.py文件,增加如下代码。

1 from .models import UserProfile,UserInfo
2 
3 class UserInfoAdmin(admin.ModelAdmin):
4     list_display = ('user','school','company','profession','address','aboutme','photo')
5     list_filter = ("school","company","profession")
6 
7 admin.site.register(UserInfo, UserInfoAdmin)

增加代码之后,再次刷新上述页面,效果如下图所示。

单击User infos选项,可以查看所有用户的相关个人信息,如下图所示。

虽界面美观度稍差,但功能是齐全的,也可以在这里完善某个用户的个人信息。

  回顾本节的学习内容,主要是前端知识,即HTML/CSS/JS部分,且以JavaScript为主。对于一个网站来说,后端固然重要,但是前端是跟用户打交道的,要提高其友好性,就不得不大量使用JavaScript脚本或者组件(插件)。前端知识不能或缺。

2.6.7 知识点

1、Ajax

  Ajax的全称是Asynchronous JavaScript and XML,译为“异步JavaScript和XML”。为什么要异步?在互联网的“石器”时代,网页都是HTML的静态页面,假设两个页面只有很小的差别,也必须完全刷新,才能访问另一个页面。另外,在向服务器提交网页端表单时,也有类似重复提交的情况。为此,就需要有某种异步的技术,实现局部信息请求。Google公司在一系列的项目中使用了Ajax技术,并让这种技术变得流行。本书中对此技术的应用实例还是初级阶段,建议读者深入学习。

2、模型:主键

  在关系型数据库中,“主键”是常见的概念,它的作用就是保证整个数据库表中记录的唯一性。在Django的数据模型类中,虽没明确规定哪个字段是主键,但主键是必须的,所以Django默认在每个数据模型中都隐藏这一个名为id的字段,并且这个字段是AutoField类型(自增整数型)。当完成数据迁移后,数据库表中都会有一个id字段,它就是这个表的主键。

  在某些情况下,或许要自定义主键,方法就是在数据模型类的某个字段的属性参数中声明primary_key = True,这样该字段就被指定为主键了,而那个没有显示出来的默认主键就被忽略了。

  一个数据模型类中只能有一个字段被指定为主键。请不要把允许有重复的字段作为主键。

3、模型:唯一性

  在数据模型类中,如果要确定某个字段的值不能重复、是唯一的,那么可以在该字段的属性参数中声明unique=True。注意,唯一不等于该字段就是主键。

4、模型:一对一

  定义数据库表之间的关系是关系型数据库中非常重要的操作,Django对此功能的实现是通过在数据模型类中定义字段属性来实现的。比如,在本节的UserInfo数据模型类中就定义了user=models.OneToOneField(User,unique=True),UserInfo与User类之间是一对一的关系,每个用户对应这一个关于他(她)的拓展信息。

 

posted @ 2019-04-25 17:24  taoziya  阅读(418)  评论(0)    收藏  举报