当前位置: 首页 > news >正文

济南做网站公司排名销售市场规划方案

济南做网站公司排名,销售市场规划方案,西部数码网站管理助手 破解版,一起做网站女装夏季第六章 追踪用户行为在之前的章节里完成了小书签将外站图片保存至本站的功能#xff0c;并且实现了通过jQuery发送AJAX请求#xff0c;让用户可以对图片进行喜欢/不喜欢操作。这一章将学习如何创建一个用户关注系统和创建用户行为流数据#xff0c;还将学习Django的信号框架…第六章 追踪用户行为在之前的章节里完成了小书签将外站图片保存至本站的功能并且实现了通过jQuery发送AJAX请求让用户可以对图片进行喜欢/不喜欢操作。这一章将学习如何创建一个用户关注系统和创建用户行为流数据还将学习Django的信号框架使用和集成Redis数据库到Django中。主要的内容有通过中间模型建立多对多关系创建关注系统创建行为流应用显示用户最近的行为列表为模型添加通用关系优化QuerySet查找外键关联模型使用signal模块对数据库进行非规范化改造在Redis中存取内容1创建关注系统所谓关注系统就是指用户可以关注其他用户并且可以看到所关注用户的行为。关注关系在用户之间是多对多的关系一个用户可以关注很多用户也可以被很多用户关注。1.1通过中间模型创建多对多关系在之前的章节中通过ManyToManyField创建了多对多关系然后让Django创建了数据表。对于大多数情况直接使用多对多字段已经足够。在需要为多对多关系存储额外的信息时比如创建多对多关系的时间字段描述多对多关系性质的字段可能需要自定义一个模型作为多对多关系的中间模型。我们将创建一个中间模型用来建立用户之间的多对多关系原因是我们将使用内置的User模型但不想修改它想存储一个用户关注另外一个用户的时间在account应用的models.py中建立新Contact类CopyclassContact(models.Model):user_from models.ForeignKey(settings.AUTH_USER_MODEL, related_namerel_from_set, on_deletemodels.CASCADE)user_to models.ForeignKey(settings.AUTH_USER_MODEL, related_namerel_to_set, on_deletemodels.CASCADE)created models.DateTimeField(auto_now_addTrue, db_indexTrue)classMeta:ordering (-created,)def__str__(self):return{} follows {}.format(self.user_from, self.user_to)这个Contact类将用来记录用户关注关系包含如下字段:user_from发起关注的用户外键user_to被关注的用户外键created该关注关系创建的时间使用auto_now_addTrue自动记录时间数据库对于外键会自动创建索引这里还使用了db_indexTrue为created字段创建了索引。使用ORM的时候如果user1关注了user2实际操作的语句可以写成这样Copyuser1 User.objects.get(idn) user2 User.objects.get(idm) Contact.objects.create(user_fromuser1, user_touser2)基于Contact模型可以通过为两个外键字段设置的名称rel_from_set和rel_to_set作为管理器名称进行查询。为了从User模型中也可以进行查询User模型应该有一个多对多关系关联到其自己类似这样Copyfollowing models.ManyToManyField(self,throughContact,related_namefollowers,symmetricalFalse)在上边这行代码里我们throughContact告诉Django以Contact类作为中间表格建立多对多关系这是一个User模型与自己的多对多关系其中的self参数表示模型自己。当需要在多对多关系中记录额外数据时创建一个关联到两个模型的中间表格然后手动指定ManyToManyField的through参数将中间表格作为多对多关系的中间表。如果User模型是我们自定义的模型可以很方便的为其添加following字段但我们不想修改User类这里可以采用一个动态的方法为其添加字段。在account应用里的models.py里增加如下内容Copyfrom django.contrib.auth.models import User User.add_to_class(following,models.ManyToManyField(self, throughContact, related_namefollowers, symmetricalFalse))这里用了一个add_to_class()方法给User打了一个猴子补丁不推荐使用该方法。但是在这里使用主要考虑如下原因通过这个方法简化了查询通过使用user.followers.all()和user.following.all()可以迅速查询。如果通过一对一关系定义在Profile模型上查询就要复杂很多。通过这种方法添加的多对多字段实际是通过Contact模型生效不会实际修改数据库中的User数据表也无需建立自定义的User模型替换原User模型这里需要在此强调的是在大部分情况下需要为内置数据模型增加额外数据时优先通过一对一的方式如Profile模型进行扩展将额外信息和关系字段都添加在扩展的数据上其次是自定义新的数据模型取代原数据模型而不是直接通过猴子补丁。否则给后续开发和测试带来很大困难。关于自定义用户模型可以参考 https://docs.djangoproject.com/en/2.0/topics/auth/customizing/#specifying-a-custom-user-model。这里还有一个参数是symmetricalFalse对称参数当创建一个关联到自身的多对多字段的时候Django默认关系是对称的即A关注了B会自动添加B也关注A的记录这与实际情况不符所以必须设置为False。使用中间表格作为多对多关系的中间表时一些管理器的内置方法如add()create()remove()等无法使用必须编写直接操作中间表的代码。定义好中间表后执行数据迁移过程。现在模型已经建好我们需要建立展示用户关注关系的列表和详情视图。1.2创建用户关注关系的列表和详情视图在account应用的views.py里添加如下内容Copyfrom django.shortcuts import get_object_or_404 from django.contrib.auth.models import Userlogin_requireddefuser_list(request):users User.objects.filter(is_activeTrue)return render(request, account/user/list.html, {section: people, users: users})login_requireddefuser_detail(request, username):user get_object_or_404(User, usernameusername, is_activeTrue)return render(request, account/user/detail.html, {section: people, user: user})这是两个简单的展示所有用列表户和某个具体用户信息的视图如果用户较多还可以为user_list添加分页功能。user_detail使用了get_object_or_404方法如果找不到用户就会返回一个404错误。编辑account应用的urls.py文件为这两个视图配置URLCopy path(users/, views.user_list, nameuser_list),path(users/username/, views.user_detail, nameuser_detail),这里我们看到需要通过URL传参数给视图需要建立规范化URL为模型添加get_absolute_url()除了通过自定义的方法之外对于User这种内置的模型还有一种方法是设置ABSOLUTE_URL_OVERRIDES。修改项目的settings.py文件Copyfrom django.urls import reverse_lazyABSOLUTE_URL_OVERRIDES {auth.user: lambda u: reverse_lazy(user_detail,args[u.username])Django动态的为所有ABSOLUTE_URL_OVERRIDES中列出的模型添加get_absolute_url()方法这个方法按照设置中的结果返回规范化URL。这里通过一个匿名函数返回规范化URL这个匿名函数被绑定在对象上作为调用get_absolute_url()时候实际调用的函数。配置好了以后我们先来实验一下打开命令行模式Copy from django.contrib.auth.models import Useruser User.objects.latest(id)str(user.get_absolute_url()) /account/users/caidaye/可以看到解析出了地址之后需要建立模板在account应用的templates/account/目录下建立如下目录和文件结构Copy/user/detail.htmllist.html之后编写其中的list.htmlCopy{#list.html#} {% extends base.html %} {% load thumbnail %} {% block title %}People{% endblock %} {% block content %}h1People/h1dividpeople-list{% for user in users %}divclassuserahref{{ user.get_absolute_url }}{% thumbnail user.profile.photo 180x180 crop100% as im %}imgsrc{{ im.url }}{% endthumbnail %}/adivclassinfoahref{{ user.get_absolute_url }}classtitle{{ user.get_full_name }}/a/div/div{% endfor %}/div {% endblock %}这个模板中用一个循环列出了视图返回的所有活跃用户分别显示每个用户的名称和头像使用{% thumbnail %}显示缩略图。在base.html中添加这个模板的路径作为用户关注系统的链接首页Copyli {% ifsection people %}classselected{% endif %}ahref{% url user_list %}People/a/li之后启动网站到http://127.0.0.1:8000/account/users/可以看到显示出了用户列表页面示例如下如果无法显示缩略图记得在settings.py中设置THUMBNAIL_DEBUG True在命令行窗口中查看错误信息。编写account/user/detail.html来展示具体用户Copy{% extends base.html %} {% load thumbnail %} {% block title %}{{ user.get_full_name }}{% endblock %} {% block content %}h1{{ user.get_full_name }}/h1divclassprofile-info{% thumbnail user.profile.photo 180x180 crop100% as im %}imgsrc{{ im.url }}classuser-detail{% endthumbnail %}/div{% with total_followersuser.followers.count %}spanclasscountspanclasstotal{{ total_followers }}/span follower{{ total_followers|pluralize }} /spanahref#data-id{{ user.id }}data-action{% if request.user in user.followers.all %}un{% endif %}followclassfollow button{% if request.user not in user.followers.all %}Follow{% else %}Unfollow{% endif %}/adividimage-listclassimage-container{% include images/image/list_ajax.html with imagesuser.images_created.all %}/div{% endwith %} {% endblock %}在这个详情页面同样展示用户名称和使用{% thumbnail %}展示用户头像缩略图。此外还展示了关注该用户的人数以及提供了一个按钮供当前用户关注/取消关注该用户。和上一章类似我们将使用AJAX技术来完成关注/取消关注行为为此在a标签中增加了data-id和data-action属性用于保存用户ID和初始动作。还通过引入images/image/list_ajax.html展示了该用户上传的所有图片。启动站点点击某个具体的用户可以看到用户详情页面的示例如下1.3创建用户关注行为的AJAX视图编辑account应用的views.py文件Copyfrom django.http import JsonResponse from django.views.decorators.http import require_POST from common.decorators import ajax_required from .models import Contactajax_requiredrequire_POSTlogin_requireddefuser_follow(request):user_id request.POST.get(id)action request.POST.get(action)if user_id and action:try:user User.objects.get(iduser_id)if action follow:Contact.objects.get_or_create(user_fromrequest.user, user_touser)else:Contact.objects.filter(user_fromrequest.user, user_touser).delete()return JsonResponse({status: ok})except User.DoesNotExist:return JsonResponse({status: ko})return JsonResponse({status: ko})这个视图与之前喜欢/不喜欢图片的功能如出一辙。由于我们使用了自定义的中间表作为多对多字段中间表无法通过User模型直接使用管理器的add()和remove()方法因此这里直接操作Contact模型。编辑account应用的urls.py文件添加一行Copy path(users/follow/, views.user_follow, nameuser_follow),注意这一行一定要在user_detail的URL配置之前否则所有访问/users/follow/路径的请求都会被路由至user_detail视图。记住Django匹配URL的顺序是从上到下停在第一个匹配成功的地方。修改account应用的user/detail.html添加发送AJAX请求的JavaSCript代码Copy{% block domready %} $(a.follow).click(function (e) {e.preventDefault();$.post({% url user_follow %}, {id: $(this).data(id),action: $(this).data(action)},function (data) {if (data[status] ok) {let previous_action $(a.follow).data(action);// 切换 data-action 属性$(a.follow).data(action, previous_action follow ? unfollow : follow);// 切换按钮文字$(a.follow).text(previous_action follow ? unfollow : follow);// 更新关注人数let previous_followers parseInt($(span.count .total).text());$(span.count .total).text(previous_action follow ? previous_followers 1 : previous_followers - 1);}}); }); {% endblock %}这个函数的逻辑也和上一章的喜欢/不喜欢功能很相似。用户点击按钮时首先将用户ID和行为发送至视图根据返回的结果相应切换行为属性和显示的文字同时更新关注人数。尝试打开一个用户详情页面并且点击喜欢之后可以看到显示如下译者注这个函数和之前的AJAX函数一样更新关注人数的逻辑比较简单粗暴关注人数最好从数据库中取followers的总数。原书明显是为了让读者看到立竿见影的效果。2创建通用行为流应用许多社交网站向其用户展示其他用户的行为流供用户追踪其他用户最近在网站中做了什么。一个行为流是一个用户或者一组用户最近进行的所有活动的列表。例如Facebook界面的News Feed就是一个行为流。对于我们的网站来说X用户上传了Y图片或者X用户关注了Y用户都是行为流中的一个数据。我们也准备创建一个行为流应用让用户可以看到他们所关注的用户最近的所有活动。为了实现这个功能我们需要建立一个模型用于保存一个用户最近在网站上做过的所有事情及向模型中添加行为记录的方法。新建一个叫actions应用然后添加到settings.py里如下所示CopyINSTALLED_APPS [# ...actions.apps.ActionsConfig, ]在action应用中编辑models.pyCopyfrom django.db import modelsclassAction(models.Model):user models.ForeignKey(auth.user, related_nameactions, db_indexTrue, on_deletemodels.CASCADE)verb models.CharField(max_length255)created models.DateTimeField(auto_now_addTrue, db_indexTrue)classMeta:ordering (-created,)上边的代码建立了一个Action模型用于存放用户的所有行为记录模型的字段有这些user进行行为的主体即用户采用了ForeignKey关联至内置的User模型verb行为的动词描述用户进行了什么行为created记录用户执行行为的时间采用auto_now_addTrue自动记录创建该条数据的时间使用这个模型我们目前只能记录行为的主体和行为动词即用户X关注了...或者用户X上传了...还缺少行为的目标对象。显然我们还需要一个外键关联到用户操作的具体对象上这样才能够展示出类似用户X关注了用户Y这样的行为流。在之前我们已经知道一个ForeignKey字段只能关联到一个模型很显然无法满足我们的需求。目标对象必须可以是任意一个已经存在的模型的对象这个时候Django的content types框架就该登场了。2.1使用contenttypes框架django.contrib.conttenttypes模块中提供了一个contenttypes框架这个框架可以追踪当前项目内所有已激活的应用中的所有模型并且提供一个通用的接口可以操作模型。django.contrib.conttenttypes同时也是一个应用在默认设置中已经包含在INSTALLED_APPS中其他contrib包中的程序也使用这个框架比如内置认证模块和管理后台。conttenttypes应用中包含一个ContentType模型。这个模型的实例代表项目中一个实际的数据模型。当项目中每新建一个模型时ContentType的新实例会自动增加一个对应该新增模型。ContentType模型包含如下字段app_label数据模型所属的应用名称这个来自模型内的Meta类里的app_label属性。我们的Image模型就属于images应用model模型的名称name给人类阅读的名称这个来自模型内的Meta类的verbose_name属性。来看一下如何使用ContentType对象打开系统命令行窗口可以通过指定app_label和model属性在ContentType模型中查询得到一个具体对象Copy from django.contrib.contenttypes.models import ContentTypeimage_type ContentType.objects.get(app_labelimages, modelimage)image_type ContentType: image还可以对刚获得的ContentType对象调用model_class()方法查看类型Copy image_type.model_class() classimages.models.Image还可以直接通过具体的类名获取对应的ContentType对象Copy from images.models import ImageContentType.objects.get_for_model(Image) ContentType: image这是几个简单的例子还有更多的方法可以操作详情可以阅读官方文档https://docs.djangoproject.com/en/2.0/ref/contrib/contenttypes/。2.2为模型添加通用关系通常来说通过获取ContentType模型的实例就可以与整个项目中任何一个模型建立关系。为了建立通用关系需要如下三个字段一个关联到ContentType模型的ForeignKey这会用来反映与外键所在模型关联的具体模型。一个存储具体的模型的主键的字段通常采用PositiveIntegerField字段以匹配主键自增字段这个字段用于从相关的具体模型中确定一个对象。一个使用前两个字段用于管理通用关系的字段content types框架提供了一个GenericForeignKey专门用于管理通用关系。编辑actions应用的models.py文件Copyfrom django.db import models from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.fields import GenericForeignKeyclassAction(models.Model):user models.ForeignKey(auth.user, related_nameactions, db_indexTrue, on_deletemodels.CASCADE)verb models.CharField(max_length255)target_ct models.ForeignKey(ContentType, blankTrue, nullTrue, related_nametarget_obj,on_deletemodels.CASCADE)target_id models.PositiveIntegerField(nullTrue, blankTrue, db_indexTrue)target GenericForeignKey(target_ct, target_id)created models.DateTimeField(auto_now_addTrue, db_indexTrue)classMeta:ordering (-created,)我们将下列字段增加到了Action模型中target_ct一个外键字段关联到ContentType模型target_id一个PositiveIntegerField字段用于存储相关模型的主键target一个GenericForeignKey字段通过组合前两个字段得到Django并不会为GenericForeignKey创建数据表中的字段只有target_ct和target_id会被写入数据表。这两个字段都设置了blankTrue和nullTrue这样新增Action对象的时候不会强制要有关联的目标对象。如果确实需要的话建立通用关系比使用外键可以创建更灵活的关系。创建完模型之后执行数据迁移程序然后将Action模型添加到管理后台中编辑actions应用的admin.py文件Copyfrom django.contrib import admin from .models import Actionadmin.register(Action)classActionAdmin(admin.ModelAdmin):list_display (user, verb, target, created)list_filter (created,)search_fields (verb,)加入管理后台之后打开http://127.0.0.1:8000/admin/actions/action/add/可以看到如下界面这里可以看到只有target_id和target_ct出现GenericForeignKey并没有出现在表单中。target_ct字段允许选择项目中的所有模型可以使用limit_choices_to属性来限制可以选择的模型。在actions应用中新建utils.py文件在其中将编写一个函数用来快捷的建立新Action对象Copyfrom django.contrib.contenttypes.models import ContentType from .models import Actiondefcreate_action(user, verb, targetNone):action Action(useruser, verbverb, targettarget)action.save()这个create_action()函数的参数有一个target就是行为所关联的目标对象可以在任意地方导入该文件然后使用这个函数来快速为行为流添加新行为对象。2.3避免添加重复的行为有些时候用户可能在短期内连续点击同一类型的事件比如取消又关注关注再取消如果即使保存所有的行为会造成大量重复的数据。为了避免这种情况需要修改一下刚刚建立的utils.py文件中的create_action()函数Copyimport datetime from django.utils import timezone from django.contrib.contenttypes.models import ContentType from .models import Actiondefcreate_action(user, verb, targetNone):# 检查最后一分钟内的相同动作now timezone.now()last_minute now - datetime.timedelta(seconds60)similar_actions Action.objects.filter(user_iduser.id, verbverb, created__gtelast_minute)if target:target_ct ContentType.objects.get_for_model(target)similar_actions similar_actions.filter(target_cttarget_ct, target_idtarget.id)ifnot similar_actions:# 最后一分钟内找不到相似的记录action Action(useruser, verbverb, targettarget)action.save()returnTruereturnFalse我们修改了create_action()函数避免在一分钟内重复保存相同的动作并且返回一个布尔值以表示是否成功保存。这个函数的逻辑解释如下首先通过timezone.now()获取当前的时间这个方法与datetime.datetime.now()相同但是返回一个timezone-aware对象。Django使用USE_TZ设置控制是否支持时区使用startapp命令建立的项目默认USE_TZTrue使用last_minute变量保存之前一分钟然后获取之前一分钟到现在当前用户进行的所有动词相同的行为。如果没有找到任何相同的行为就直接创建Action对象并返回True否则返回False。2.4向行为流中添加行为现在需要编辑视图添加一些功能来创建行为流。我们将对下边的行为创建行为流任意用户上传了图片任意用户喜欢了一张图片任意用户创建账户任意用户关注其他用户编辑images应用的views.py文件Copyfrom actions.utils import create_action在image_create视图中在保存图片之后添加create_action()语句Copynew_item.save() create_action(request.user, bookmarked image, new_item)在image_like视图中在将用户添加到users_like关系之后添加create_action()语句Copyimage.users_like.add(request.user) create_action(request.user, likes, image)编辑account应用的views.py文件添加如下导入语句Copyfrom actions.utils import create_action在register视图里在创建Profile对象之后添加create_action()语句CopyProfile.objects.create(usernew_user) create_action(new_user, has created an account)在user_follow视图里也添加create_action()CopyContact.objects.get_or_create(user_fromrequest.user, user_touser) create_action(request.user, is following, user)从上边的代码中可以看到由于建立好了Aciton模型可以方便的添加各种行为。2.5展示用户行为流最后需要展示每个用户的行为流我们将在用户的登录后页面中展示行为流。编辑account应用的views.py文件修改dashboard视图如下Copyfrom actions.models import Actionlogin_requireddefdashboard(request):# 默认展示所有行为不包含当前用户actions Action.objects.exclude(userrequest.user)following_ids request.user.following.values_list(id, flatTrue)if following_ids:# 如果当前用户有关注的用户仅展示被关注用户的行为actions actions.objects.filter(user_id__infollowing_ids)actions actions[:10]return render(request, account/dashboard.html, {section: dashboard, actions: actions})在上边代码中首先从数据库中获取除了当前用户之外的全部行为流数据。如果当前用户有关注其他用户则在所有的行为流中筛选出属于关注用户的行为流。最后限制展示的数量为10条。在QuerySet中我们并没有使用order_by()方法因为默认已经按照ordering(-created)进行了排序。2.6优化QuerySet查询关联对象现在我们每次获取一个Action对象时都会去查询关联的User对象然后还会去查询User对象关联的Profile对象要查询两次。Django ORM提供了一种简便的方法获取相关联的对象而无需反复查询数据库。2.6.1使用select_related()Django提供了select_related()方法用于一对多字段查询关联对象。这个方法实际上会得到一个更加复杂的QuerySet然而却避免了反复查询关联对象。select_related()方法仅能用于ForeignKey和OneToOneField其实际生成的SQL语句是JOIN连表查询方法的参数则是SELECT语句之后的字段名。为了使用select_related()修改下边这行代码Copyactions actions[:10]将其修改成Copyactions actions.select_related(user, user__profile)[:10]我们使用user__profile在查询中将Profile数据表进行了连表查询。如果不给select_related()传任何参数会将所有该表外键关联的表格都进行连表操作。最好每次都指定具体要关联的表。进行连表操作的时候注意避免不需要的额外连表以减少查询时间。2.6.2使用prefetch_related()select_related()仅能用于一对一和一对多关系不能用于多对多ManyToMany和多对一关系反向的ForeignKey关系。Django提供了QuerySet的prefetch_related()方法用于多对多和多对一关系查询这个方法会对每个对象的关系进行一次单独查询然后再把结果连接起来。这个方法还支持查询GenericRelation和GenericForeignKey字段。编辑account应用的views.py文件为GenericForeignKey增加prefetch_related()方法Copy actions actions.select_related(user, user__profile).prefetch_related(target)[:10]现在我们就完成了优化查询的工作。2.7创建行为流模板现在来创建展示用户行为的页面在actions应用下创建templates目录添加如下文件结构Copyactions/action/detail.html编辑actions/action/detail.html模板添加如下内容Copy{% load thumbnail %} {% with useraction.user profileaction.user.profile %}divclassactiondivclassimages{% if profile.photo %}{% thumbnail user.profile.photo 80x80 crop100% as im %}ahref{{ user.get_absolute_url }}imgsrc{{ im.url }}alt{{ user.get_full_name }}classitem-img/a{% endthumbnail %}{% endif %}{% if action.target %}{% with targetaction.target %}{% if target.image %}{% thumbnail target.image 80x80 crop100% as im %}ahref{{ target.get_absolute_url }}imgsrc{{ im.url }}classitem-img/a{% endthumbnail %}{% endif %}{% endwith %}{% endif %}/divdivclassinfopspanclassdate{{ action.created|timesince }} ago/spanbr/ahref{{ user.get_absolute_url }}{{ user.first_name }}/a{{ action.verb }}{% if action.target %}{% with targetaction.target %}ahref{{ target.get_absolute_url }}{{ target }}/a{% endwith %}{% endif %}/p/div/div {% endwith %}这是展示Action对象的模板。首先我们使用{% with %}标签存储当前用户和当前用户的Profile对象然后如果Action对象存在关联的目标对象而且有图片就展示这个目标对象的图片最后展示执行这个行为的用户的链接动词和目标对象。然后编辑account应用里的dashboard.html把这个页面包含到content块的底部Copyh2Whats happening/h2dividaction-list{% for action in actions %}{% include actions/action/detail.html %}{% endfor %} /div启动站点打开http://127.0.0.1:8000/account/使用已经存在的用户登录然后进行一些行为。再更换另外一个用户登录关注之前的用户然后到登录后页面看一下行为流如下图所示我们就建立了一个完整的行为流应用可以方便的添加用户行为。还可以为这个页面添加之前的AJAX动态加载页面的效果。3使用signals非规范化数据有些时候你可能需要非规范化数据库。非规范化Denormalization是一种数据库方面的名词指通过向数据库中添加冗余数据以提高效率。非规范化只有在确实必要的情况下再考虑使用。使用非规范化数据的最大问题是如何保持非规范化数据始终更新。我们将通过一个例子展示如何通过非规范化数据提高查询效率缺点就是必须额外编写代码以保持数据更新。我们将非规范化Image模型并通过Django的信号功能保持数据更新。译者注规范化简单理解就是不会存储对象非必要的额外信息就像我们现在为止的所有设计来自于对象基础信息以外的额外信息如求和分组都通过设计良好的表间关系和查询手段获得而且这些基础信息都在对应的视图内得到操作和更新。非规范化是与规范化相反的手段添加冗余数据用于提高数据库的效率。这是结构化程序设计思想中的运行时间与占用空间关系在数据库结构方面的反映。3.1使用signal功能Django提供一个信号模块可以让receiver函数在某种动作发生的时候得到通知。信号功能在实现每当发生什么动作就执行一些代码的时候很有用也可以创建自定义的信号用于通知其他程序Django在django.db.models.signals中提供了一些信号功能其中有如下的信号pre_save和post_save在调用save()方法之前和之后发送信号pre_delete和post_delete在调用delete()方法之前和之后发送信号m2m_changed在多对多字段发生变动的时候发送信号这只是部分信号功能完整的内置信号功能见官方文档https://docs.djangoproject.com/en/2.0/ref/signals/。举个例子来看如何使用信号功能。如果在图片列表页想给图片按照受欢迎的程度排序可以使用聚合函数对喜欢该图片的用户合计总数代码是这样Copyfrom django.db.models import Count from images.models import Image images_by_popularity Image.objects.annotate(total_likesCount(users_like)).order_by(-total_likes)在性能上来说通过合计users_like字段生成临时表再进行排序的操作远没有直接通过一个字段排序的效率高。我们可以直接在Image模型上增加一个字段用于保存图片的被喜欢数合计这样虽然使数据库非规范化但显著的提高了查询效率。现在的问题是如何保持这个字段始终为最新值先到images应用的models.py中为Image模型增加一个字段total_likesCopyclassImage(models.Model):# ...total_likes models.PositiveIntegerField(db_indexTrue, default0)total_likes用来存储喜欢该图片的用户总数这个非规范化的字段在查询和排序的时候非常简便。在使用非规范化手段之前还有几种方法可以提高效率比如使用索引优化查询和使用缓存。添加完字段之后执行数据迁移程序。之后需要给m2m_changed信号设置一个receiver函数在images应用目录内新建一个signals.py文件添加如下代码Copyfrom django.db.models.signals import m2m_changed from django.dispatch import receiver from .models import Imagereceiver(m2m_changed, senderImage.users_like.through)defusers_like_changed(sender, instance, kwargs):instance.total_likes instance.users_like.count()instance.save()首先使用receiver装饰器将users_like_changed函数注册为一个事件的接收receiver函数然后将其设置为监听m2m_changed类型的信号并且设置信号来源为Image.users_like.through这表示来自于Image.users_like字段的变动会触发该接收函数。除了如此设置之外还可以采用Signal对象的connect()方法进行设置。DJango的信号是同步阻塞的不要将信号和异步任务的概念搞混。可以将二者有效结合让程序在收到某个信号的时候启动异步任务。配置好receiver接收函数之后还必须将函数导入到应用中这样就可以在每次发送信号的时候调用函数。推荐的做法是在应用配置类的ready()方法中导入接收函数。这就需要再了解一下应用配置类。3.2应用配置类Django允许为每个应用设置一个单独的应用配置类。当使用startapp命令创建一个应用时Django会在应用目录下创建一个apps.py文件并在其中自动设置一个名称为“首字母大写的应用名Config”并继承AppConfig类的应用配置类。使用应用配置类可以存储这个应用的元数据应用配置和提供自省功能。应用配置类的官方文档https://docs.djangoproject.com/en/2.0/ref/applications/。我们已经使用receiver装饰器注册好了信号接收函数这个函数应该在应用一启动的时候就可以进行调用所以要注册在应用配置类中其他类似的需要在应用初始化阶段就调用的功能也要注册在应用配置类中。编辑images应用的apps.py文件Copyfrom django.apps import AppConfigclassImagesConfig(AppConfig):name imagesdefready(self):# 导入信号接收函数import images.signals通过ready()方法导入之后在images应用加载的时候该函数就会被导入。启动程序选中一张图片并点击LIKE按钮然后到管理站点查看该图片例如http://127.0.0.1:8000/admin/images/image/1/change/可以看到新增的total_likes字段。还可以看到total_likes字段已经得到了更新如图所示现在可以用total_likes字段排序图片并且显示总数量避免复杂的查询。看一下本章开头的查询语句Copyfrom django.db.models import Countimages_by_popularity Image.objects.annotate(likesCount(users_like)).order_by(-likes)现在上边的查询可以改成下边这样Copyimages_by_popularity Image.objects.order_by(-total_likes)现在这个查询的开销要比原来小很多这是一个使用信号的例子。使用信号功能会让控制流变得更加难以追踪在很多情况下如果明确知道需要进行什么操作无需使用信号功能。对于已经存在表内的对象total_likes字段中还没有任何数据需要为所有对象设置当前的值通过python manage.py shell进入带有当前Django环境的Python命令行并输入下列命令Copy from images.models import Image for image in Image.objects.all():image.total_likes image.users_like.count()image.save()现在每个图片的total_likes字段已被更新。4使用Redis数据库Redis是一个先进的键值对数据库可以存储多种类型的数据并提供高速存取服务。Redis运行时的数据保存在内存中也可以定时将数据持久化到磁盘中或者通过日志输出。Redis相比普通的键值对存储具有一系列强力的命令支持不同的数据格式比如字符串、哈希值、列表、集合和有序集合甚至是位图或HyperLogLogs数据。尽管SQL数据库依然是保存结构化数据的最佳选择对于迅速变化的数据、反复使用的数据和缓存需求采用Redis有着独特的优势。本节来看一看如何通过Redis为我们的项目增加一个新功能。4.1安装Redis在https://redis.io/download下载最新的Redis数据库解压tar.gz文件进入redis目录然后使用make命令编译安装RedisCopycd redis-4.0.9 make在安装完成后在命令行中输入如下命令来初始化Redis服务Copysrc/redis-server可以看到如下输出Copy# Server initialized * Ready to accept connections说明Redis服务已经启动。Redis默认监听6379端口。可以使用--port参数指定端口例如redis-server --port 6655。保持Redis服务运行新开一个系统终端窗口启动Redis客户端Copysrc/redis-cli可以看到如下提示Copy127.0.0.1:6379说明已经进入Redis命令行模式可以直接执行Redis命令我们来试验一些命令译者注Redis官方未提供Windows版本可以在https://github.com/MicrosoftArchive/redis/releases找到Windows版安装好之后默认已经添加Redis服务默认端口号和Linux系统一样是6379。进入cmd输入redis-cli进入Redis命令行模式。使用SET命令保存一个键值对Copy127.0.0.1:6379 SET name Peter OK上边的命令创建了一个name键值是字符串Peter。OK表示这个键值对已被成功存储。可以使用GET命令取出该键值对Copy127.0.0.1:6379 GET name Peter使用EXIST命令检测某个键是否存在返回整数1表示True0表示False 127.0.0.1:6379 EXISTS name(integer) 1使用EXPIRE设置一个键值对的过期秒数。还可以使用EXPIREAT以UNIX时间戳的形式设置过期时间。过期时间对于将Redis作为缓存时很有用Copy127.0.0.1:6379 GET name Peter127.0.0.1:6379 EXPIRE name 2 (integer) 1等待超过2秒钟然后尝试获取该键Copy127.0.0.1:6379 GET name (nil)(nil)说明是一个null响应即没有找到该键。使用DEL命令可以删除键和值如下Copy127.0.0.1:6379 SET total 1 OK 127.0.0.1:6379 DEL total (integer) 1127.0.0.1:6379 GET total (nil)这是Redis的基础操作Redis对于各种数据类型有很多命令可以在https://redis.io/commands查看命令列表Redis所有支持的数据格式在https://redis.io/topics/data-types。译者注特别要看一下Redis中有序集合这个数据类型以下会使用到。4.2通过Python操作Redis同使用PostgreSQL一样在Python安装支持该数据库的模块redis-pyCopypip install redis2.10.6该模块的文档可以在https://redis-py.readthedocs.io/en/latest/找到。redis-py提供了两大功能模块StrictRedis和Redis功能完全一样。区别是前者只支持标准的Redis命令和语法后者进行了一些扩展。我们使用严格遵循标准Redis命令的StrictRedis模块打开Python命令行界面输入以下命令Copy import redisr redis.StrictRedis(hostlocalhost, port6379, db0)上述命令使用本机地址和端口和数据库编号实例化数据库连接对象在Redis内部数据库的编号是一个整数共有0-16号数据库默认客户端连接到的数据库是0号数据库可以通过修改redis.conf更改默认数据库。通过Python存入一个键值对Copy r.set(foo, bar) True返回True表示成功存入键值对通过get()方法取键值对Copy r.get(foo) bbar可以看到这些方法源自同名的标准Redis命令。了解Python中使用Redis之后需要把Redis集成到Django中来。编辑bookmarks应用的settings.py文件添加如下设置CopyREDIS_HOST localhost REDIS_PORT 6379 REDIS_DB 0这是Redis服务的相关设置。4.3在Redis中存储图片浏览次数我们需要存储一个图片被浏览过的总数。如果我们使用Django ORM来实现每次展示一个图片时需要通过视图执行SQL的UPDATE语句并写入磁盘。如果我们使用Redis只需要每次对保存在内存中的一个数字增加1相比之下Redis的速度要快很多。编辑images应用的views.py文件在最上边的导入语句后边添加如下内容Copyimport redis from django.conf import settingsr redis.StrictRedis(hostsettings.REDIS_HOST, portsettings.REDIS_PORT, dbsettings.REDIS_DB)通过上述语句在视图文件中实例化了一个Redis数据库连接对象等待其他视图的调用。编辑image_detail视图让其看起来如下Copylogin_requireddefimage_detail(request, id, slug):image get_object_or_404(Image, idid, slugslug)# 浏览数1total_views r.incr(image:{}:views.format(image.id))return render(request, images/image/detail.html,{section: images, image: image, total_views: total_views}) 这个视图使用了incr命令将该键对应的值增加1。如果键不存在会自动创建该键初始值为0然后将值加1。incr()方法返回增加1这个操作之后的结果也就是最新的浏览总数。然后用total_views存储浏览总数并传入模板。我们采用Redis的常用格式创建键名如object-type:id:field例如image:33:id)。Redis数据库的键常用冒号分割的字符串来创建类似于带有命名空间一样的键值这样的键名易于阅读而且在其名字中有共同的部分便于对应至具体对象和查找。编辑images/image/detail.html在span classcount之后追加Copyspan classcount{{ total_views }} view{{ total_views|pluralize }} /span打开一个图片的详情页面然后按F5刷新几次能够看到访问数“ * views”不断上升如下图所示现在我们就将Redis集成到Django中并用其显示数量了。4.4在Redis中存储排名现在用Redis来实现一个更复杂一些的功能创建一个排名按照图片的访问量将图片进行排名。为了实现这个功能将使用Redis的有序集合数据类型。有序集合是一个不重复的字符串集合其中的每一个字符串都对应一个分数按照分数的大小进行排序。编辑images应用里的views.py文件继续修改image_detail视图Copylogin_requireddefimage_detail(request, id, slug):image get_object_or_404(Image, idid, slugslug)total_views r.incr(image:{}:views.format(image.id))# 在有序集合image_ranking里把image.id的分数增加1r.zincrby(image_ranking, image.id, 1)return render(request, images/image/detail.html,{section: images, image: image, total_views: total_views})使用zincrby方法创建一个image_ranking有序集合对象在其中存储图片的id然后将对应的分数加1。这样就可以在每次图片被浏览之后更新该图片被浏览的次数以及所有图片被浏览的次数的排名。在当前的views.py文件里创建一个新的视图用于展示图片排名Copylogin_requireddefimage_ranking(request):# 获得排名前十的图片ID列表image_ranking r.zrange(image_ranking, 0, -1, descTrue)[:10]image_ranking_ids [int(id) foridin image_ranking]# 取排名最高的图片然后排序most_viewed list(Image.objects.filter(id__inimage_ranking_ids))most_viewed.sort(keylambda x: image_ranking_ids.index(x.id))return render(request, images/image/ranking.html, {section: images, most_viewed: most_viewed}) 这个image_ranking视图工作逻辑如下使用zrange()命令从有序集合中取元素后边的两个参数表示开始和结束索引给出0到-1的范围表示取全部元素descTrue表示将这些元素降序排列。最后使用[:10]切片列表前10个元素。使用列表推导式取得了键名对应的整数构成的列表存入image_ranking_ids中。然后查询id属于该列表中的所有Image对象。由于要按照image_ranking_ids中的顺序对查询结果进行排序所以使用list()将查询结果列表化。按照每个Image对象的id在image_ranking_ids中的顺序对查询结果组成的列表进行排序。在images/image/模板目录内创建ranking.html添加下列代码Copy{% extends base.html %} {% block title %}Images Ranking {% endblock %}{% block content %}h1Images Ranking/h1ol{% for image in most_viewed %}liahref{{ image.get_absolute_url }}{{ image.title }}/a/li{% endfor %}/ol {% endblock %}这个页面很简单迭代most_viewed中的每个Image对象展示图片内容、名称和对应的详情链接。最后为新的视图配置URL编辑images应用的urls.py文件增加一行Copypath(ranking/, views.image_ranking, nameranking),译者注原书此处有误name参数的值设置成了create按作者的一贯写法应该为ranking。之后启动站点访问不同图片的详情页反复刷新拖杆次然后打开http://127.0.0.1:8000/images/ranking/即可看到排名页面4.5进一步使用RedisRedis无法替代SQL数据库但其使用内存存储的特性可以用来完成模型具体任务把Redis加入到你的工具库里在必要的时候就可以使用它。下边是一些适合使用Redis的场景计数从我们的例子可以看出使用Redis管理计数非常便捷incr()和incrby()方法可以方便的实现计数功能。存储最新的项目使用lpush()和rpush()可以向一个队列的开头和末尾追加数据lpop()和rpop()则是从队列开始和末尾弹出元素。如果操作造成队列长度改变还可以用ltrim()保持队列长度。队列除了上边的pop和push系列方法Redis还提供了阻塞队列的方法缓存expire()和expireat()方法让用户可以把Redis当做缓存来使用还可以找到一些第三方开发的将Redis配置为Django缓存后端的模块。订阅/发布Redis提供订阅/发布消息模式可以向一些频道发送消息订阅该频道的Redis客户端可以接受到该消息。排名和排行榜Redis的有序集合可以方便的创建排名相关的数据。实时跟踪Redis的高速I/O可以用在实时追踪并更新数据方面。总结这一章里完成了两大任务一个是用户之间的互相关注系统一个是用户行为流系统。还学习了使用Django的信号功能和将Redis集成至Django。在下一章我们将开始一个新的项目创建一个电商网站。将学习创建商品品类通过session创建购物车以及使用Celery启动异步任务。如有不懂还要咨询下方小卡片博主也希望和志同道合的测试人员一起学习进步在适当的年龄选择适当的岗位尽量去发挥好自己的优势。我的自动化测试开发之路一路走来都离不每个阶段的计划因为自己喜欢规划和总结测试开发视频教程、学习笔记领取传送门
http://www.w-s-a.com/news/786375/

相关文章:

  • 营销型网站定制珠海建站网站
  • 企业网站代码wordpress页面重定向循环
  • 厦门网站建设哪家便宜用wordpress做企业网站
  • 网站备案有幕布python 做网站速度
  • 旅游网站模板psd网站后台维护主要做什么
  • 晋江做任务的网站网站如何设置关键词
  • 呼伦贝尔网站建设呼伦贝尔ps网页设计心得体会
  • 字母logo设计网站动画设计方案及内容
  • 怎样做网站建设方案wordpress 附件预览
  • 网站内容编辑wordpress cron原理
  • 户外商品网站制作建筑网络图片
  • 注册了网站怎么建设做网站是学什么专业
  • 济南建设网站哪里好网站色哦优化8888
  • 什么网站做简历最好外贸公司网站大全
  • 衡水网站托管企业二级网站怎么做
  • 丹阳网站建设公司旅游类网站开发开题报告范文
  • 地方门户网站建设苏州网站优化建设
  • 谁用fun域名做网站了网络营销的三种方式
  • 织梦网站上传天津网站建设电话咨询
  • 论坛网站搭建深圳网
  • 天津建立网站营销设计window7用jsp做的网站要什么工具
  • 英文网站wordpress所有图片
  • 我做的网站怎么打开很慢网络营销典型企业
  • 新增备案网站python3网站开发
  • 诊断网站seo现状的方法与通信工程专业做项目的网站
  • 南京 微网站 建站alexa排名查询统计
  • 天津网站建设企业系统wordpress已发布不显示不出来
  • 大连网站前端制作公司局域网视频网站建设
  • 张家界建设局网站电话wordpress网站怎么建
  • 淄博网站建设有实力装修培训机构哪家最好