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类之间是一对一的关系,每个用户对应这一个关于他(她)的拓展信息。
浙公网安备 33010602011771号