-
艾永亮:力压腾讯音乐与苹果音乐,Spotify是如何炼成的?
2020-08-17 08:17:46根据艾媒咨询数据,QQ、酷狗、酷我app月活跃用户数量分列市场前三,而腾讯音乐手握QQ音乐和酷狗音乐两张王牌,打造了一个月活6.57亿用户的巨大音乐流量池。 瑞典的音乐流媒体平台Spotify,月活跃用户仅有2.86亿,不...你用什么软件听歌呢?
不少人回答QQ音乐、酷狗音乐、网易云和itunes。
根据艾媒咨询数据,QQ、酷狗、酷我app月活跃用户数量分列市场前三,而腾讯音乐手握QQ音乐和酷狗音乐两张王牌,打造了一个月活6.57亿用户的巨大音乐流量池。
瑞典的音乐流媒体平台Spotify,月活跃用户仅有2.86亿,不到腾讯音乐的一半,市值却是腾讯音乐的两倍(腾讯音乐的市值仅为263.38亿美金,Spotify的市值却高达483.97亿美金)。
Spotify付费用户人群有1.3亿,付费率高达45%,腾讯音乐仅有6.2%,而苹果iTunes无论是付费用户还是活跃用户都只有它的一半。
这样一个力压腾讯音乐与苹果iTunes的产品到底是如何打造的?
01
主要策略是增长
而不是最大化收入
面对一个非生活必需品,我们该如何推向用户?
太多的企业家习惯性上来就一顿介绍,把所有的优点都讲一遍,恨不得你马上掏钱。
每月一看报表,大量的投入、极低的成交量让人陷入了沉思。到底哪里出了问题?
是状态不好?是穿着不宜?还是产品的卖点没讲明白?
太多的企业家过于急功近利,恨不得马上能从用户兜里把钱一张张往外掏。
2008年刚刚成立的Spotify所在的行业就普遍存在这一现象。此时的iTunes已经问世5年,牢牢占据着美国数字音乐销量近69%的份额。按理说,Spotify没有一点点胜算。
与iTunes每首歌单独付费不同的是,Spotify为所有用户提供免费音乐库,前提是用户需要接受广告推送。这对于2008年的音乐行业来说,确实前所未有,让不少的用户体验到了“听歌自由”。
“我们的主要策略还是增长,而不是最大化收入。”
作为一个行业的新入局者,Spotify没有选择与竞争对手拼价格、拼曲库,而是凭借免费的模式,获得了大量的用户。Spotify其实玩的是市场份额的游戏,先吸引用户使用产品,扩充自己的用户群体,直至成为行业主导者,最后再考虑盈利。
等你拥有了庞大的用户群体,还会怕没有变现的方式吗?
但是对于很多企业来说,总是太着急,恨不得把让用户第一眼就掏出钱包。
Spotify为用户提供了超预期的体验,让他们无法拒绝在试用免费服务后成为付费用户。不过用户并不是为了某一首歌买单,而是9.99美元包月无限量供应,对于很多用户来说,这是非常划算的买卖。
Spotify创新性在音乐行业运用“广告+付费”相结合的模式,让广告商成为音乐付费者,而互联网的强大覆盖范围让9.99美元的价值被无限放大,最终实现规模效应,这可比单独一首首卖歌赚得多的多。
事实也是如此,Spotify只用了1年时间,就把付费用户数从0上升至100万。
Tips:用户价值高于商业价值,当满足用户需求之后,企业也将获得超额回报。
02
音乐不是核心
情绪才是
这是一句常常被说到的话,也是常常说完就忘的话。
用户需要的不是钻头,而是墙上的孔。
产品是在满足用户需求的基础上诞生的,而很多时候,我们眼里只有产品,追求更好的材料、更新的技术、更加花里胡哨的功能,却忘了用户最基本的需求。
那用户听音乐背后的需求到底是什么?
2014年,Spotify通过深入地研究了15亿份用户生成的播放列表,发现用户听歌的每一个时刻都有明显的情感变化移动轨迹,音乐反映着人的情绪。
Spotify开始针对这一有趣现象建立不同的歌单。当你在工作日的早上打开软件,你可以选择“工作”、“完美一天”、“为新一天做好准备”等歌单,当你心情不好的时候,你可以选择“悲伤”、“伤心”等歌单。
但是这远远还不够,Spotify不单单在挖掘用户的情绪,还想我们变得更好。
当你情绪低落,在播放悲伤的歌曲时,软件会逐渐给你推送放松、幸福的音乐,帮助你恢复。当你选择“工作”歌单时,在一开始可能会感到压力,但随后会变得更专注。当你选择“健身”的歌单时,刚开始会感到慵懒与抗拒,然后变得更加积极…
音乐不再是产品,倾听及其附加于其上的情绪、行为数据才是。Spotify提供了一个解决方案,把自己定位为:一个帮助人们感觉良好的平台。
在满足用户需求的前提下,它是否有商业的空间呢?
“比如,某个早晨你在播放轻音乐或者舒缓音乐的歌单,那么我们推测你有可能正在做瑜伽或者在冥想…因此,我们将根据这个情景来推送符合这个时刻、节奏和心绪的广告内容。”
Spotify推出了自动化广告投放服务,允许广告商根据“寒冷”、“通勤”、“学习”、“女生节”等用户情绪或者活动数据来推送精准广告。Spotify还尽职地概述了如何利用这些时刻“化腐朽为神奇”——将不好的产品成功推广出去。
Tips:别忘了产品最终是解决用户什么需求,回到需求本身,你会发现更多的盈利机会。
03
既是音乐推荐官
也是连接者
“它是我最钟意的音乐应用了,尤其是它的【每周新发现】。让我觉得它是那么的了解我,它比任何人都要了解我的音乐喜好,每一周的推荐音乐都令我十分满意。”
对于Spotify 的用户来说,每周一都会收到【每周新发现】的歌单,包含了30首各种风格,但你从未欣赏过的歌曲,神奇的是每一首对你来说都是珍品。
Spotify并不像传统的版权方,一直兜售自己旗下歌手的音乐。这30首并不是它随意的拼凑,而是通过寻找与你爱好相同的人,听过相同的音乐,且评价相似,然后推断出你对没有听过的音乐的喜好。
推出歌单后也并不是就此结束,它会根据用户对歌曲的保存情况、跳过和删除情况,以及因为某首歌而放弃整个歌单的情况,及时调整并更新播放列表。
在信息过载的今天,它是用户的优质音乐推荐官。通过复杂的运算,这30首歌在你想要的时候,巧合地出现在你的面前,给用户营造一种“懂我”的美好体验。
你在Spotify上贡献的数据越多,转移成本就越高,越离不开它。
自2015年6月发布【每周新发现】的6个月内,Spotify的访问量就超过了17亿次。
传统的推荐系统对新音乐和新歌手并不友好,如果不是热门歌手,几乎不会推荐他们的音乐。新歌手们面临的最大挑战是:自己的音乐如何被听到。
Spotify会找到各种风格的创作者,给他们工具、给他们平台、帮助他们做他们想做的事情、让更多人听到他们的声音。Spotify通过对大量数据的洞察,分析音乐本身可能被不同人群喜欢的可能,以【每周新发现】的形式推荐给对应的用户,这也是为什么你会被推荐一个从未听说过的艺术家的作品的原因。
除此之外,Spotify还会为每一个类型的创作者,按照知名度的不同去帮他们建立自己的粉丝群。
在这个新世界,音乐没有界限,一个由创作者、制作人、版权商和歌迷组成的网络就此形成。
Tips:一个好产品应当是所有参与者都能满足需求,都能获益,这才是可持续发展。
结语
音乐,一个离我们很近、又很远的行业。从摇篮曲到童谣,课间广播,旅游的激情曲,工作的振奋剂,深夜的精神寄托,婚礼上的伴奏,最后到葬礼,几乎伴随着我们的一生。
这个需求是漫长且不断变化的,从未被满足。Spotify回归到产品本身,满足人们情感的寄托,一切的创新都是从用户需求出发,而不是成型的产品,或者商业需求。
当满足了用户的需求,提供他们所需要的产品,创作者也得到尊重与关注,企业也将获得超额回报。
这样一个超级产品,它让用户的情绪得到安抚,听到自己想听的音乐,默默鼓励着用户积极向上,就像一位老友聆听你的烦恼,不忘让你坚强。
如果你的企业在经营过程中遇到任何问题,请关注“艾老思”公众H,让我们一起解决,一起做出下一个超级产品。
-
-
-
spotify是如何设计产品的
2021-02-21 06:21:59他们的产品广为用户和艺术家喜爱,并且像病毒一样传播开来:他们有超过2000万活跃用户,500万付费用户,并且用户数量增速迅猛。举一组数字说明问题,Spotify在美国这样一个已经充斥着不少音频传播软件提供商的海外... -
第2章 用Django REST framework实现豆瓣API应用
2020-11-13 09:59:11比如想要开发一个面向喜欢重金属音乐的用户群体的音乐推荐软件,就需要获取豆瓣中重金属类目下的音乐数据信息,以此了解哪些音乐评分较高。 几年前,豆瓣这些数据的API,都是免费提供给广大开发者的,但是随着近些...第2章 用Django REST framework实现豆瓣API应用
活跃在互联网上的年轻人中,不论是文艺青年还是非文艺青年,可能都会去逛豆瓣网(以后简称为豆瓣),因此大家对豆瓣并不陌生。豆瓣上多年以来囤积的海量数据,对于无数与文艺相关的项目是非常重要的内容。比如想要开发一个面向喜欢重金属音乐的用户群体的音乐推荐软件,就需要获取豆瓣中重金属类目下的音乐数据信息,以此了解哪些音乐评分较高。
几年前,豆瓣这些数据的API,都是免费提供给广大开发者的,但是随着近些年数据资产的价值越来越被重视,豆瓣向外提供数据查询的API开始收费,包括电影、图书、音乐等所有类目。
开发者想要获得豆瓣上的数据,需要向豆瓣付费,才可以有权限调用相关的API,而本章就是开发一个这样的业务模型。
2.1 豆瓣API功能介绍
豆瓣图书的API功能原理是用户通过输入图书的ISBN号(书号)、书名、作者、出版社等部分信息,就可获取到该图书在豆瓣上的所有信息。当然,API中除了要包含检索信息之外,还要包含开发者的apikey,用来记录开发者访问API的次数,以此向开发者收费。目前豆瓣图书的API是0.3元/100次。
2.2 Django REST framework序列化
序列化(Serialization)是指将对象的状态信息转换为可以存储或传输形式的过程。在客户端与服务端传输的数据形式主要分为两种:XML和JSON。在Django中的序列化就是指将对象状态的信息转换为JSON数据,以达到将数据信息传送给前端的目的。
序列化是开发API不可缺少的一个环节,Django本身也有一套做序列化的方案,这个方案可以说已经做得很好了,但是若跟Django REST framework相比,还是不够极致,速度不够快。
2.2.1 Postman的使用
Postman是一款非常流行的API调试工具,其使用简单、方便,而且功能强大。
通过Postman可以便捷地向API发送GET、POST、PUT和DELETE请求,几乎是资深或者伪资深开发人员调试API的首选。当然,这并不是Postman在开发领域如此受欢迎的唯一理由。Postman最早是以Chrome浏览器插件的形式存在,可以从Chrome应用商店搜索、下载并安装,后来因为一些原因,Chrome应用商店在国内无法访问,2018年Postman停止了对Chrome浏览器的支持,提供了独立安装包,不再依赖Chrome,同时支持Linux、Windows和Mac OS系统。
测试人员做接口测试会有更多选择,例如Jmeter和soapUI等,因为测试人员就是完成产品的测试,而开发人员不需要有更多的选择,毕竟开发人员是创新者、创造者。Postman的下载地址是https://www.getpostman.com/apps。
2.2.2 用serializers.Serializer方式序列化
还记得我们在第1章中新建的Django项目book吗?下面我们来一起在这个项目中一步一步地通过Serializer序列化组件,完成豆瓣API核心功能的开发。
(1)打开项目book。
(2)安装Django REST framework及其依赖包markdown和django-filter。命令如下:
pip install djangorestframework markdown django-filter
(3)在settings中注册,代码如下:
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contentTypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'users.apps.UsersConfig', 'rest_framework' ]
(4)设计users的models.py,重构用户表UserProfile,增加字段APIkey和money。当然,为了演示核心功能,可以建立一张最简单的表,大家可以根据个人喜好增加一些业务字段来丰富项目功能。
from django.db import models from django.contrib.auth.models import AbstractUser # Create your models here. class UserProfile(AbstractUser): """ 用户 """ APIkey=models.CharField(max_length=30,verbose_name='APIkey',default='abcdefghigklmn') money=models.IntegerField(default=10,verbose_name='余额') class Meta: verbose_name='用户' verbose_name_plural = verbose_name def __str__(self): return self.username
(5)在settings中配置用户表的继承代码:
AUTH_USER_MODEL='users.UserProfile'
(6)在users的models.py文件中新建书籍信息表book,为了演示方便,我们姑且将作者字段并入书籍信息表,读者在实际项目中可根据业务模式灵活设计数据表model:
from datetime import datetime from django.db import models class Book(models.Model): """ 书籍信息 """ title=models.CharField(max_length=30,verbose_name='书名',default='') isbn=models.CharField(max_length=30,verbose_name='isbn',default='') author=models.CharField(max_length=20,verbose_name='作者',default='') publish=models.CharField(max_length=30,verbose_name='出版社',default='') rate=models.FloatField(default=0,verbose_name='豆瓣评分') add_time = models.DateTimeField(default=datetime.now, verbose_name='添加时间') class Meta: verbose_name='书籍信息' verbose_name_plural = verbose_name def __str__(self): return self.title
(7)执行数据更新命令:
python manage.py makemigrations python manage.py migrate
(8)建立一个超级用户,用户名为admin,邮箱为1@1.com,密码为admin1234。
python manage.py createsuperuser Username: admin 邮箱: 1@1.com Password: Password (again):
(9)通过PyCharm的Databases操作面板,直接在book表内增加一条记录,title为一个书名,isbn为777777,author为一个作者,publish为一个出版社,rate为6.6,add_time为154087130331。
(10)准备工作已经完成,接下来是我们的“正片”开始啦。在users目录下新建py文件serializers,将序列化的类代码写入其中:
from rest_framework import serializers from .models import UserProfile,Book class BookSerializer(serializers.Serializer): title=serializers.CharField(required=True,max_length=100) isbn=serializers.CharField(required=True,max_length=100) author=serializers.CharField(required=True,max_length=100) publish=serializers.CharField(required=True,max_length=100) rate=serializers.FloatField(default=0)
(11)在users/views中编写视图代码:
from .serializers import BookSerializer from rest_framework.views import APIView from rest_framework.response import Response from .models import UserProfile,Book class BookAPIView1(APIView): """ 使用Serializer """ def get(self, request, format=None): APIKey=self.request.query_params.get("apikey", 0) developer=UserProfile.objects.filter(APIkey=APIKey).first() if developer: balance=developer.money if balance>0: isbn = self.request.query_params.get("isbn", 0) books = Book.objects.filter(isbn=int(isbn)) books_serializer = BookSerializer(books, many=True) developer.money-=1 developer.save() return Response(books_serializer.data) else: return Response("兄弟,又到了需要充钱的时候!好开心啊!") else: return Response("查无此人啊")
(12)在urls中配置路由如下:
from django.contrib import admin from django.urls import path from users.views import BookAPIView1 urlpatterns = [ path('admin/', admin.site.urls), path('apibook1/',BookAPIView1.as_view(),name='book1'), ]
至此,我们可以运行book项目,使用Postman访问API来测试一下啦。我们用Postman的GET方式访问API:
http://127.0.0.1:8000/apibook1/?apikey=abcdefghigklmn&isbn=777777
我们获得了想要的JSON数据:
[ { "title": "一个书名", "isbn": "777777", "author": "一个作者", "publish": "一个出版社", "rate": 6.6 } ]
然后到数据库中查看一下,发现用户admin的money被减去了1,变成了9。当我们用Postman故意填错apikey时,访问:
http://127.0.0.1:8000/apibook1/?apikey=abcdefghigklmn33&isbn=777777
API返回的数据为:
"查无此人啊"
当我们连续访问10次:
http://127.0.0.1:8000/apibook1/?apikey=abcdefghigklmn&isbn=777777
API返回的数据为:
"兄弟,又到了需要充钱的时候!好开心啊!"
至此,一个简单的模仿豆瓣图书API的功能就实现了。在实际的项目中,这样的实现方式虽然原理很清晰,但是存在着很明显的短板,比如被查询的表的字段不可能只有几个,我们在真正调用豆瓣图书API的时候就会发现,即使只查询一本书的信息,由于有很多的字段和外键字段,返回的数据量也会非常大。如果使用Serializer进行序列化,那么工作量实在太大,严重影响了开发效率。
所以,这里使用Serializer进行序列化,目的是让大家通过这种序列化方式更加轻松地理解Django REST framework的序列化原理。在实际生产环境中,更加被广泛应用的序列化方式是采用了Django REST framework的ModelSerializer。
2.2.3 用serializers.ModelSerializer方式序列化
在上一节中,我们通过使用Django REST framework的Serializer序列化,实现了一个模仿豆瓣图书API的功能,在这一节,我们将要使用Django REST framework的ModelSerializer来实现这个功能。因为都是在book项目中,所以上一节中介绍的很多步骤我们没有必要重复。我们现在要做的,首先是到数据库中的UserProfile表中,将用户admin的money从0修改回10,不然API只能返回提醒充值的数据。
在users/Serializer.py中,写book的ModelSerializer序列化类:
from rest_framework import serializers from .models import UserProfile,Book class BookModelSerializer(serializers.ModelSerializer): class Meta: model = Book fields="__all__" #将整个表的所有字段都序列化
在users/views.py中,编写基于BookModelSerializer的图书API视图类:
BookModelSerializer的图书API视图类: from .serializers import BookModelSerializer from rest_framework.views import APIView from rest_framework.response import Response from .models import UserProfile,Book class BookAPIView2(APIView): """ 使用ModelSerializer """ def get(self, request, format=None): APIKey=self.request.query_params.get("apikey", 0) developer=UserProfile.objects.filter(APIkey=APIKey).first() if developer: balance=developer.money if balance>0: isbn = self.request.query_params.get("isbn", 0) books = Book.objects.filter(isbn=int(isbn)) books_serializer = BookModelSerializer(books, many=True) developer.money-=1 developer.save() return Response(books_serializer.data) else: return Response("兄弟,又到了需要充钱的时候!好开心啊!") else: return Response("查无此人啊")
注意:使用ModelSerializer序列化对应的视图类与使用Serializer进行序列化对应的视图类,除了序列化的方式不同,其他的代码都是相同的。
在urls中配置路由代码:
from django.contrib import admin from django.urls import path from users.views import BookAPIView1,BookAPIView2 urlpatterns = [ path('admin/', admin.site.urls), path('apibook1/',BookAPIView1.as_view(),name='book1'), path('apibook2/',BookAPIView2.as_view(),name='book2'), ]
使用Postman对API进行测试,用GET的方式访问:
http://127.0.0.1:8000/apibook2/?apikey=abcdefghigklmn&isbn=777777
返回书籍所有的字段数据:
[ { "id": 1, "title": "一个书名", "isbn": "777777", "author": "一个作者", "publish": "一个出版社", "rate": 6.6, "add_time": null } ]
注意:这里的add_time字段为null,是因为这个项目使用了Django默认的db.sqlite3数据库。由于db.sqlite3在存储时间字段的时候,是以时间戳的格式保存的,所以直接使用Django REST framework的Serializer进行序列化失败。在实际项目中,我们会选择MySQL等主流数据库,就不会出现这种情况了。
可以看出,对于一条有很多字段的数据记录来说,使用ModelSerializer的序列化方式,可以一句话将所有字段序列化,非常方便。当然,ModelSerializer也可以像Serializer一样对某几个特定字段进行序列化,写法也很简单,只需要对原本的BookModelSerializer修改一行代码:
class BookModelSerializer(serializers.ModelSerializer): class Meta: model = Book # fields="__all__" #将整个表的所有字段都序列化 fields = ('title', 'isbn', 'author') #指定序列化某些字段
使用Postman对API进行测试,用GET的方式访问:
http://127.0.0.1:8000/apibook2/?apikey=abcdefghigklmn&isbn=777777
返回的数据就成了:
[ { "title": "一个书名", "isbn": "777777", "author": "一个作者" } ]
至此,我们对Django REST framework的两种序列化方式做一个总结:Serializer和ModelSerializer两种序列化方式中,前者比较容易理解,适用于新手;后者则在商业项目中被使用的更多,在实际开发中建议大家多使用后者。
记得笔者初学Django REST framework时,一直很困惑于用哪种序列化方式更好。因为许多教材中都将Django REST framework的Serializer和ModelSerializer,与Django的Form和ModelForm做对比,虽然二者相似,在优劣选择上却是不同的。Form虽然没有ModelForm效率高,但是ModelForm的使用增加了项目的耦合度,不符合项目解耦原则,所以Form比ModelForm更优(除了字段量过大的情况);而ModelSerializer有Serializer所有的优点,同时并没有比Serializer明显的不足之外,所以ModelSerializer比Serializer更优。
2.3 Django REST framework视图三层封装
其实,Django REST framework中最令人困惑的并不是Serializer与ModelSerializer的选择,而是三层封装的视图使用哪种好?到底应该怎样选择?视图层层封装,层层嵌套,令人混乱不堪,再加上版本更替,许多教程与实际项目中的演示对不上号,学习起来更是晦涩难懂。
最无奈的是,就算硬着头皮将文档教程“啃”下来,但依然困惑于实现一个功能,到底应该封装几层,使用哪种视图方式,根本不敢贸然选择,犹豫不决之间又浪费了许多时间。因此业内有十个抛弃Django REST framework的人里九个是因为视图封装之说。我们将会在下一节聊一聊视图的三层封装,如果你也是一个对Django REST framework视图的三层封装如鲠在喉的程序员,可要打起精神来。
2.3.1 用mixins.ListModelMixin+GenericAPIView的方式实现视图封装
在users/views.py中,使用mixins.ListModelMixin+GenericAPIView编写基于Book ModelSerializer的图书API视图类。代码如下:
from .serializers import BookModelSerializer from rest_framework.response import Response from .models import UserProfile,Book from rest_framework import mixins from rest_framework import generics ookMixinView1(mixins.ListModelMixin,generics.GenericAPIView): queryset=Book.objects.all() serializer_class = BookModelSerializer def get(self,request,*args,**kwargs): #如果这里不加get函数,代表默认不 支持get访问这个api,所以必须加上 APIKey = self.request.query_params.get("apikey", 0) developer = UserProfile.objects.filter(APIkey=APIKey).first() if developer: balance=developer.money if balance>0: isbn = self.request.query_params.get("isbn", 0) developer.money -= 1 developer.save() self.queryset = Book.objects.filter(isbn=int(isbn)) return self.list(request, *args, **kwargs) else: return Response("兄弟,又到了需要充钱的时候!好开心啊!") else: return Response("查无此人啊")
在urls中配置路由代码如下:
from django.contrib import admin from django.urls import path from users.views import BookAPIView1,BookAPIView2 from users.views import BookMixinView1 urlpatterns = [ path('admin/', admin.site.urls), path('apibook1/',BookAPIView1.as_view(),name='book1'), path('apibook2/',BookAPIView2.as_view(),name='book2'), path('apibook3/',BookMixinView1.as_view(),name='book3'), ]
这时,我们再使用Postman对API进行测试,用GET的方式访问:
http://127.0.0.1:8000/apibook3/?apikey=abcdefghigklmn&isbn=777777
我们获得了跟使用APIView编写的API视图同样的效果,也获得了以下数据:
[ { "title": "一个书名", "isbn": "777777", "author": "一个作者" } ]
2.3.2 用generics.ListAPIView的方式实现视图封装
在users/views.py中,使用generics.ListAPIView编写基于BookModelSerializer的图书API视图类,代码如下:
from .serializers import BookModelSerializer from rest_framework.response import Response from .models import UserProfile,Book from rest_framework import mixins from rest_framework import generics class BookMixinView2(generics.ListAPIView): queryset = Book.objects.all() serializer_class = BookModelSerializer def get(self,request,*args,**kwargs): APIKey = self.request.query_params.get("apikey", 0) developer = UserProfile.objects.filter(APIkey=APIKey).first() if developer: balance=developer.money if balance>0: isbn = self.request.query_params.get("isbn", 0) developer.money -= 1 developer.save() self.queryset = Book.objects.filter(isbn=int(isbn)) return self.list(request, *args, **kwargs) else: return Response("兄弟,又到了需要充钱的时候!好开心啊!") else: return Response("查无此人啊")
在urls中配置路由代码:
from django.contrib import admin from django.urls import path from users.views import BookAPIView1,BookAPIView2 from users.views import BookMixinView1,BookMixinView2 urlpatterns = [ path('admin/', admin.site.urls), path('apibook1/',BookAPIView1.as_view(),name='book1'), path('apibook2/',BookAPIView2.as_view(),name='book2'), path('apibook3/',BookMixinView1.as_view(),name='book3'), path('apibook4/',BookMixinView2.as_view(),name='book4'), ]
使用Postman对API进行测试,用GET的方式访问:
http://127.0.0.1:8000/apibook4/?apikey=abcdefghigklmn&isbn=777777
我们获得了跟使用APIView编写的API视图同样的效果,也获得了以下数据:
[ { "title": "一个书名", "isbn": "777777", "author": "一个作者" } ]
注意:使用mixins.ListModelMixin+generics.GenericAPIView对APIView进行一次封装,至少需要加一个get函数:
def get(self,request,*args,**kwargs): return self.list(request,*args,**kwargs)
而使用generics.ListAPIView则可以不用加这个函数,因为generics.ListAPIView相对于mixins.ListModelMixin+generics.GenericAPIView而言,所谓的封装,就是封装了一个get函数罢了。
2.3.3 用viewsets+Router的方式实现视图封装
在users/views.py中,使用viewsets.ModelViewSet编写基于BookModelSerializer的图书API视图类,代码如下:
from .serializers import BookModelSerializer from rest_framework.response import Response from .models import UserProfile,Book from rest_framework import viewsets from rest_framework.permissions import BasePermission class IsDeveloper(BasePermission): message='查无此人啊' def has_permission(self,request,view): APIKey = request.query_params.get("apikey", 0) developer = UserProfile.objects.filter(APIkey=APIKey).first() if developer: return True else: print(self.message) return False class EnoughMoney(BasePermission): message = "兄弟,又到了需要充钱的时候!好开心啊!" def has_permission(self,request,view): APIKey = request.query_params.get("apikey", 0) developer = UserProfile.objects.filter(APIkey=APIKey).first() balance = developer.money if balance > 0: developer.money -= 1 developer.save() return True else: return False class BookModelViewSet(viewsets.ModelViewSet): authentication_classes = [] permission_classes = [IsDeveloper, EnoughMoney] queryset = Book.objects.all() serializer_class = BookModelSerializer def get_queryset(self): isbn = self.request.query_params.get("isbn", 0) books = Book.objects.filter(isbn=int(isbn)) queryset=books return queryset
在urls中配置路由代码:
from django.contrib import admin from django.urls import path from users.views import BookAPIView2 from users.views import BookMixinView1,BookMixinView2 from users.views import BookModelViewSet from rest_framework.routers import DefaultRouter from django.conf.urls import include router=DefaultRouter() router.register(r'apibook5',BookModelViewSet) urlpatterns = [ path('admin/', admin.site.urls), path('apibook2/',BookAPIView2.as_view(),name='book2'), path('apibook3/',BookMixinView1.as_view(),name='book3'), path('apibook4/',BookMixinView2.as_view(),name='book4'), path('',include(router.urls)) ]
使用Postman对API进行测试,用GET的方式访问:
http://127.0.0.1:8000/apibook5/?apikey=abcdefghigklmn&isbn=777777
获得了以下数据:
[ { "title": "一个书名", "isbn": "777777", "author": "一个作者" } ]
当我们连续10次用get访问API后,得到以下提示信息:
{ "detail": "兄弟,又到了需要充钱的时候!好开心啊!" }
注意:Django REST framework的权限组件有一个原则,即没有认证就没有权限!所以我们可以看见,在视图类BookModelViewSet中不但加入了permission_classes=[IsDeveloper,EnoughMoney],还加入了authentication_classes=[]这样一个空列表。这一行代码是必须加的,如果不加,虽然权限组件依然起作用,但是在权限通不过的时候,detail将不会显示我们自定义的message的内容,而永远只是提示认证未通过。
2.3.4 小结
在这一章中,我们使用Django REST framework三层封装(APIView、mixins和viewsets)分别实现了一遍豆瓣图书API的功能。对比这3种视图封装方式,大家觉得哪一种更优一些呢?
相信有很多Django REST framework的学习者,在学到视图封装的时候,都会认为APIView这一层封装的知识点还是比较好领会的,一切的麻烦都是从mixins开始,mixins虽然只是在APIView的基础上又做了一层封装,但是根据不同的method,又分成了mixins.ListModelMixin、generics.GenericAPIView和generics.ListAPIView,这样非常容易让人认为所谓Django REST framework的三层视图封装,指的是APIView、mixins和Generics.ListAPIView,然后当发现文档后面的viewsets时,概念瞬间就凌乱了,甚至学完viewsets,脑中依然一片混乱。
当读者发现有这么多方式可以实现同一个功能时,非常容易在选择时犹豫不决,再加上Django REST framework的许多教程中,经常会出现一些诸如“像魔法一样”“非常强大”、“很简单”这类故弄玄虚的词,令人更加困扰。
那么,到底该怎样选择视图封装呢?我们马上就将得到一个相对确切的答案。
首先,我们来剖析视图的封装层数。要知道,我们经常说到的Django REST framework的“三层视图封装”,并不是仅仅封装了三层,下面解剖一个viewsets.ModelViewSet看一下:
class ModelViewSet(mixins.CreateModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, mixins.ListModelMixin, GenericViewSet): class GenericViewSet(ViewSetMixin, generics.GenericAPIView): class GenericAPIView(views.APIView):
可以看出,从APIView到views.ModelViewSet,mixins只是个过程,mixins存在的价值,更多的是为了帮助Django REST framework学习者,更加容易地理解视图封装的原理。但事实上似乎并没有起到帮助作用。可以说,我们在今后的项目中,只需要优先在APIView和viewsets中选择即可。至于mixins就好像是斐波那契数列一样,几乎永远不会缺席于应聘Django REST framework技术岗位的笔试题中,但在实际项目中却很少能用得上。
APIView和viewsets应该怎样选择呢?Django REST framework的官方文档中也有介绍过二者的取舍问题,但帮助不大。以我们在本章中实现的豆瓣图书API这个功能来看,viewsets虽然对APIView做了封装,但结果反而是代码更多了,逻辑更麻烦了,显然,像这类情况,我们应该选择APIView。总结一下,当视图要实现的功能中,存在数据运算、拼接的业务逻辑时,比如本章例子中,API成功访问一次,用户表中的money记录减少1,可以一律选择APIView的方式来写视图类,除此以外,优先使用viewsets的方式来写视图类,毕竟使用viewsets+Router在常规功能上效率极高。
-
呱呱视频聊天室 v8.2 bulid20170323
2019-11-04 21:54:19从08年正式面市至今,呱呱经历了用户爆炸式增长的发展历程,截止2012年2月,呱呱拥有超过6000万注册用户,960万活跃用户,占据了领先的市场地位。呱呱以国内领先的p2p技术提供给用户无延迟的“面对面”视频沟通体验... -
SuperCollider-3.11.0-macOS-signed.zip 亲测可用:用于音频合成和算法合成的平台
2020-04-02 12:38:13现在,它由一个活跃而热情的社区维护和开发。 功能语言-sclang单一继承面向对象和函数式语言类似于Smalltalk或Ruby,语法类似于C或Javascript动态类型恒定时间消息查找和实时垃圾收集用作一流对象闭包是词法,作用... -
呱呱美女视频聊天室2013免费下载 v6.0 官方最新版.exe
2019-07-17 07:09:19从08年正式面市至今,呱呱经历了用户爆炸式增长的发展历程,截止2012年2月,呱呱拥有超过6000万注册用户,960万活跃用户,占据了领先的市场地位。呱呱以国内领先的p2p技术提供给用户无延迟的“面对面”视频沟通体验... -
呱呱视频聊天室 v6.4.1812 正式版.zip
2019-07-17 09:33:32从08年正式面市至今,呱呱经历了用户爆炸式增长的发展历程,截止2012年2月,呱呱拥有超过6000万注册用户,960万活跃用户,占据了领先的市场地位。呱呱以国内领先的p2p技术提供给用户无延迟的“面对面”视频沟通体验... -
Java开源的下一代社区平台Symphony.zip
2019-07-19 04:36:02一个好玩的产品或说是细节特性然并卵,需要做的是一个能够持续提供用户价值的产品/特性 虽然直到目前 B3log 系产品用户不多,但我们已经初步证明了:Java 用来实现博客、论坛没有什么不好的 使用开源软件,了解... -
《本地YouTube下载器》作者自己也承认youtube-dl要比《本地YouTube下载器》更好用一些,但《本地YouTube下载器》是一个脚本,无需安装Python开发环境,可以在浏览器直接使用,对普通用户极其友好,所以懒得折腾的非...
-
PowerShadow(影子系统)
2008-10-25 07:53:32TEMP、TMP变量:如你在解压压缩包时、安装软件时都会用到这个变量,也就是缓存路径——压缩包解压的数据和安装程序解包的数据都会暂存于此。当你在影子模式下时,这些操作都会被PowerShadow截获并缓存在系统根目录... -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-