Django community: Community blog posts RSS
This page, updated regularly, aggregates Community blog posts from the Django community.
-
Django-Rest-Framework 教程: 6. ViewSets 和 Routers
django-rst-framework为我们提供了ViewSet类, ViewSet为我们提供了默认的URL结构, 使得我们能更专注于API本身. ViewSet类与View类几乎是相同的, 但提供的是read或update, 而不是http动作get或put. ViewSet类在实例化后, 通过Router类, 最终将URL与ViewSet方法绑定. 接下来我们使用ViewSet代替之前的View. 1. 使用ViewSet 将UserList和UserDetail移除, 并替换为UserViewSet: # snippets/views.py class UserViewSet(viewsets.ReadOnlyModelViewSet): """ 这一viewset提供了`list`和`detail` """ queryset = User.objects.all() serializer_class = UserSerializer 我们使用ReadOnlyModelViewSet提供默认的只读权限, queryset和serializer_class设置还是未变. 将SnippetList, SnippetDetail和SnippetHighlight移除, 替换为SnippetViewSet: # snippets/views.py from rest_framework.decorators import link class SnippetViewSet(viewsets.ModelViewSet): """ 这一viewset提供了`list`, `create`, `retrieve`, `update` 和 `destroy` 但我们需要自己设置 `highlight`. """ queryset = Snippet.objects.all() serializer_class = SnippetSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly,) @link(renderer_classes=[renderers.StaticHTMLRenderer]) def highlight(self, request, *args, **kwargs): snippet = self.get_object() return Response(snippet.highlighted) def pre_save(self, obj): obj.owner = self.request.user 我们使用ModelViewSet类实现读写操作API. 我们使用了@link修饰器来创建自定义的highlight动作, 这一修饰器可以用于创建非标准路径. @link修饰器对应的是get request, 如果想对应post request, 可以使用@action修饰器. 2. 明确关联URL 为了更清楚ViewSet内部发生的事情, 我们首先在urls.py中使用明确指明的关联: # snippets/urls.py from snippets.views import SnippetViewSet, UserViewSet from rest_framework import renderers snippet_list = SnippetViewSet.as_view({ 'get': 'list', 'post': 'create' }) snippet_detail = SnippetViewSet.as_view({ 'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy' }) snippet_highlight = SnippetViewSet.as_view({ 'get': 'highlight' }, renderer_classes=[renderers.StaticHTMLRenderer]) user_list = UserViewSet.as_view({ 'get': 'list' }) user_detail = UserViewSet.as_view({ 'get': 'retrieve' }) urlpatterns = format_suffix_patterns(patterns('snippets.views', url(r'^$', 'api_root'), url(r'^snippets/$', snippet_list, name='snippet-list'), url(r'^snippets/(?P<pk>[0-9]+)/$', snippet_detail, name='snippet-detail'), url(r'^snippets/(?P<pk>[0-9]+)/highlight/$', snippet_highlight, name='snippet-highlight'), url(r'^users/$', user_list, name='user-list'), url(r'^users/(?P<pk>[0-9]+)/$', user_detail, name='user-detail') )) 3. 使用Router类自动关联URL Router类可以轻松的帮我们实现URL和ViewSet之间的关联, 我们先注释掉snippets/urls.py中所有内容, 然后重新改写tutorial/urls.py: # tutorial/urls.py from django.conf.urls import patterns, url, include from snippets import views from rest_framework.routers import DefaultRouter # 黄建router并注册viewset. router = DefaultRouter() router.register(r'snippets', views.SnippetViewSet) router.register(r'users', views.UserViewSet) # router会自动生成url # 我们只需要额外提供可浏览性登入API urlpatterns = patterns('', url(r'^', include(router.urls)), url(r'^api-auth/', … -
Django-Rest-Framework 教程: 4. 提高关联性和超链接API
到目前为止, API之间的关系是以主键形式体现的, (比如打开/users/1/, 可以看到snippets中为snippet的主键). 在本篇中, 我们将使用超链接的形式, 进一步提高API的关联程度和可发现性. 首先我们补充一些路径, 是整个API结构更为完整. 1. 根路径 现在, 我们已经有了users和snippets的路径, 但对于API本身却没有一个根路径. 我们使用@api_view修饰器来创建一个function based view作为根路径: # snippets/views.py from rest_framework import renderers from rest_framework.decorators import api_view from rest_framework.response import Response from rest_framework.reverse import reverse @api_view(('GET',)) def api_root(request, format=None): return Response({ 'users': reverse('user-list', request=request, format=format), 'snippets': reverse('snippet-list', request=request, format=format) }) 注意, 我们使用了django-rest-framework的reverse(), 而不是django自带的reverse(), 此时如果打开http://127.0.0.1/8000, 会报错, 因为我们还没有为url添加name, 稍后我们会添加. 在urls.py中添加对应的路径: # snippets/urls.py url(r'^$', views.api_root), 2. 高亮snippet路径 我们还需要提供高亮snippet的路径. 当然这一路径与其他不同, 我们希望使用HTML而不是JSON来呈现. Django-rest_framework为我们提供了两种方式呈现HTML, 一种是使用模板, 另一种则是已构建好的HTML文本. 由于在创建snippet时, 我们已经使用pygments将高亮的snippet转化为HTML文本储存在数据库中, 我们使用第二种方式. 由于我们返回的并不是一个object实例, 而是一个实例的某个属性, django-rest-framework没有提供该generic class based view. 因此我们需要使用基本的view, 并创建get()方法: # snippets/views.py from rest_framework import renderers from rest_framework.response import Response class SnippetHighlight(generics.GenericAPIView): queryset = Snippet.objects.all() renderer_classes = (renderers.StaticHTMLRenderer,) def get(self, request, *args, **kwargs): snippet = self.get_object() return Response(snippet.highlighted) 在urls.py中添加对应的路径: # snippets/urls.py url(r'^snippets/(?P<pk>[0-9]+)/highlight/$', views.SnippetHighlight.as_view()), 3. 使用超链接 处理各部件API之间的关系是一件头痛的事. django-rest_framework为我们提供了以下这些方法来表现关系: 使用主键 使用超链接 使用相关项的slug field 使用相关项的默认文本信息 将子项显示在母项中 其他表现方式 这次, 我们使用超链接的方式来体现user和snippet的关系. 为了实现这一方式, 我们需要改写序列器(serializers), 使用HyperlinkedModelSerializer代替原本的ModelSerializer. HyperlinkedModelSerializer相对于ModelSerializer具有以下不同: HyperlinkedModelSerializer不会自动包含pk field HyperlinkedModelSerializer会自动包括url field 关系使用的是HyperlinkedRelatedField而不是PrimaryKeyRelatedField # snippets/serializers.py class SnippetSerializer(serializers.HyperlinkedModelSerializer): owner = serializers.Field(source='owner.username') highlight = serializers.HyperlinkedIdentityField(view_name='snippet-highlight', format='html') class Meta: model = Snippet fields = ('url', 'highlight', 'owner', 'title', 'code', 'linenos', 'language', 'style') class UserSerializer(serializers.HyperlinkedModelSerializer): snippets = serializers.HyperlinkedRelatedField(many=True, view_name='snippet-detail') class Meta: model = User fields = ('url', 'username', 'snippets') 注意, 我们同时添加了 "highlight" field, 它与url field使用的一样, 是HyperlinkedRelatedField, 但指向的是snippet-highlight url而不是snippet-detail url. 由于我们在url中包含了格式信息, 我们使用format='html'参数为highlight指定.html后缀. 4. 为url添加name 编辑snippets/urls.py如下: # snippets/urls.py # API 路径 urlpatterns = … -
My Pelican enhancements
As written in my earlier blog post, I had some issues open for my blog: create a proper sitemap create a simple and responsive theme minify all the things To solve them, I created 2 Python packages and a theme for pelican installations. Refer to their PyPI or Github page for installation instructions. pelican-extended-sitemap Creates a sitemap.xml from your pages. PyPI-Link: https://pypi.python.org/pypi/pelican-extended-sitemap Github: https://github.com/dArignac/pelican-extended-sitemap Example: https://www.zoe.vc/sitemap.xml pelican-minification Automatic minification for all HTML and CSS files used in a Pelican project. PyPI-Link: https://pypi.python.org/pypi/pelican-minification Github: https://github.com/dArignac/pelican-minification pelican-theme-bootstrap3 A Bootstrap 3 based theme for Pelican sites. It's the theme you're currently viewing. PyPI-Link: not available as this does not work with the Pelican theme mechanism Github: https://github.com/dArignac/pelican-theme-bootstrap3 -
My Pelican enhancements
As written in my earlier blog post, I had some issues open for my blog: create a proper sitemap create a simple and responsive theme minify all the things To solve them, I created 2 Python packages and a theme for pelican installations. Refer to their PyPI or Github page for installation instructions. pelican-extended-sitemap Creates a sitemap.xml from your pages. PyPI-Link: https://pypi.python.org/pypi/pelican-extended-sitemap Github: https://github.com/dArignac/pelican-extended-sitemap Example: https://www.zoe.vc/sitemap.xml pelican-minification Automatic minification for all HTML and CSS files used in a Pelican project. PyPI-Link: https://pypi.python.org/pypi/pelican-minification Github: https://github.com/dArignac/pelican-minification pelican-theme-bootstrap3 A Bootstrap 3 based theme for Pelican sites. It's the theme you're currently viewing. PyPI-Link: not available as this does not work with the Pelican theme mechanism Github: https://github.com/dArignac/pelican-theme-bootstrap3 -
Django-Rest-Framework 教程: 3. 验证和权限
到目前为止, 我们的API并未指明哪些人有权限编辑或删除snippet, 接下来我们要实现: 为snippet增加创建者 特定用户才能创建snippet snippet创建者才能更新或删除该snippet 未授权用户只能查看 1. 为snippet model增加field 我们现为snippet model增加两个field, 一个用于储存创建者信息, 另一个则用作储存高亮HTML信息: # snippets/models.py from django.db import models from pygments.lexers import get_all_lexers from pygments.styles import get_all_styles LEXERS = [item for item in get_all_lexers() if item[1]] LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS]) STYLE_CHOICES = sorted((item, item) for item in get_all_styles()) class Snippet(models.Model): created = models.DateTimeField(auto_now_add=True) title = models.CharField(max_length=100, blank=True, default='') code = models.TextField() linenos = models.BooleanField(default=False) language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100) style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100) owner = models.ForeignKey('auth.User', related_name='snippets') highlighted = models.TextField() class Meta: ordering = ('created',) 当执行save()时, 我们使用pygments生成高亮后的HTML: # snippets/models.py from pygments.lexers import get_lexer_by_name from pygments.formatters.html import HtmlFormatter from pygments import highlight class Snippet(models.Model): ... def save(self, *args, **kwargs): """ 使用pygments创建高亮的HTML文本 """ lexer = get_lexer_by_name(self.language) linenos = self.linenos and 'table' or False options = self.title and {'title': self.title} or {} formatter = HtmlFormatter(style=self.style, linenos=linenos, full=True, **options) self.highlighted = highlight(self.code, lexer, formatter) super(Snippet, self).save(*args, **kwargs) 修改完毕之后我们删除原来的数据库, 然后重新创建数据库: python ./manage.py syncdb 你可能需要再创建几个用户, 可以通过以下命令创建: python ./manage.py createsuperuser 2. 为User model增加endpoints 在serializer.py中增加UserSerializer: # snippets/serializers.py from django.contrib.auth.models import User class UserSerializer(serializers.ModelSerializer): snippets = serializers.PrimaryKeyRelatedField(many=True) class Meta: model = User fields = ('id', 'username', 'snippets') 由于"snippets"是User的一个反向关系, 因此我们需要用field明确指明. 我们只需要为user model添加只读的API, 因此使用ListAPIView和RetrieveAPIView即可: # snippets/views.py from django.contrib.auth.models import User from snippets.serializers import UserSerializer class … -
Django-Rest-Framework 教程: 3. 使用 class based views
在上一篇Django-Rest-Framework 教程: 2. Requests 和 Responses中, 使用的是function based views. 在本篇中, 主要介绍怎样使用class based views. 1. 修改views.py 首先修改snippet_list view: # snippets/views.py from snippets.models import Snippet from snippets.serializers import SnippetSerializer from django.http import Http404 from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status class SnippetList(APIView): """ 展示所有存在的snippet, 或建立新的snippet """ def get(self, request, format=None): snippets = Snippet.objects.all() serializer = SnippetSerializer(snippets, many=True) return Response(serializer.data) def post(self, request, format=None): serializer = SnippetSerializer(data=request.DATA) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 使用class based view代替 snippet_list后, 两者的代码非常相似, 但对于不同Http动作, 分离效果更为优秀. 对于snippet_detail: # snippets/views.py class SnippetDetail(APIView): """ 展示, 更新或删除一个snippet """ def get_object(self, pk): try: return Snippet.objects.get(pk=pk) except Snippet.DoesNotExist: raise Http404 def get(self, request, pk, format=None): snippet = self.get_object(pk) serializer = SnippetSerializer(snippet) return Response(serializer.data) def put(self, request, pk, format=None): snippet = self.get_object(pk) serializer = SnippetSerializer(snippet, data=request.DATA) if serializer.is_valid(): serializer.save() return Response(serializer.data) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) def delete(self, request, pk, format=None): snippet = self.get_object(pk) snippet.delete() return Response(status=status.HTTP_204_NO_CONTENT) 更新urls.py: # snippets/urls.py from django.conf.urls import patterns, url from rest_framework.urlpatterns import format_suffix_patterns from snippets import views urlpatterns = patterns('', url(r'^snippets/$', views.SnippetList.as_view()), url(r'^snippets/(?P<pk>[0-9]+)/$', views.SnippetDetail.as_view()), ) urlpatterns = format_suffix_patterns(urlpatterns) class based view改造完成, 现在可以使用之前的测试方法进行测试了. 2. 使用 Mixins 使用class based views的一大好处是, 我们可以使用各种mixin. Django-rest-framework为我们提供了许多现成的mixins, 方便我们像使用model-backed API一样构建 "创建/获取/更新/删除" API. 我们试着使用Mixins改写原先的view. GenericAPIView为我们提供了view核心的功能, 而ListModelMixin和CreateModelMixin为我们提供了.list()和.create()功能. 我们将这些功能与http动作绑定: # snippets/views.py from snippets.models import … -
Install PyRun on current CentOS
Installing a current version of PyRun on CentOS 6.5 Final did not work for me, several issues occurred: ../bin/pyrun: error while loading shared libraries: libssl.so.1.0.0: cannot open shared object file: No such file or directory On current CentOS installations there is no libssl.so.1.0.0, instead after patching the heartbleed bug through yum there is libssl.so.1.0.1e. So simply symlink the lib and you're fine: ln -s /usr/lib64/libssl.so.1.0.1e /usr/lib64/libssl.so.1.0.0 But afterwards the PyRun installation still does not complete properly - there is another lib failing. Patch it by symlinking again: ../bin/pyrun: error while loading shared libraries: libcrypto.so.1.0.0: cannot open shared object file: No such file or directory ln -s /usr/lib64/libcrypto.so.1.0.1e /usr/lib64/libcrypto.so.1.0.0 Now, the whole thing is running. -
Install PyRun on current CentOS
Installing a current version of PyRun on CentOS 6.5 Final did not work for me, several issues occurred: ../bin/pyrun: error while loading shared libraries: libssl.so.1.0.0: cannot open shared object file: No such file or directory On current CentOS installations there is no libssl.so.1.0.0, instead after patching the heartbleed bug through yum there is libssl.so.1.0.1e. So simply symlink the lib and you're fine: ln -s /usr/lib64/libssl.so.1.0.1e /usr/lib64/libssl.so.1.0.0 But afterwards the PyRun installation still does not complete properly - there is another lib failing. Patch it by symlinking again: ../bin/pyrun: error while loading shared libraries: libcrypto.so.1.0.0: cannot open shared object file: No such file or directory ln -s /usr/lib64/libcrypto.so.1.0.1e /usr/lib64/libcrypto.so.1.0.0 Now, the whole thing is running. -
Django-Rest-Framework 教程: 2. Requests 和 Responses
接着上篇的内容, 本篇中, 我们进一步介绍Django-rest-framework的其他核心部件. 首先我们来做准备工作: Request django-rest-framework中引入了新的Request类, 该Request继承自Django的HttpRequest, 并提供了更多灵活的request处理功能. 比如request.DATA属性便是专门用作Web API的属性, 类似于request.POST. request.POST # 只处理form数据; 只接受HTML 'POST'动作. request.DATA # 处理特定数据; 接受HTML 'POST', 'PUT' 和 'PATCH' 动作. 状态码 (status codes) django-rest-framework为每一个状态码都提供了打包好的, 更为精确的状态码完整描述供我们使用, 比如HTTP_400_BAD_REQUEST. API views django-rest-framework为function_based_view和class_based_view分别提供了@api_view修饰器和APIView类. 这些wrapper代码, 保证了view接收Request实例, 并会自动添加context至Response中. 这些wrapper还能自动返回正确的状态码和详细信息. 1. 开始 接着之前的views.py, 在这里, 我们已经不需要JSONResponse了, 因此, 首先将其删除. 然后修改views.py中的snippet_list view: snippets/views.py from rest_framework import status from rest_framework.decorators import api_view from rest_framework.response import Response from snippets.models import Snippet from snippets.serializers import SnippetSerializer @api_view(['GET', 'POST']) def snippet_list(request): """ 展示所有存在的snippet, 或建立新的snippet """ if request.method == 'GET': snippets = Snippet.objects.all() serializer = SnippetSerializer(snippets, many=True) return Response(serializer.data) elif request.method == 'POST': serializer = SnippetSerializer(data=request.DATA) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 以上代码相对于教程1中的代码更为简洁. 可以看到代码与form API的使用十分相似, 我们还使用了内置的状态码, 使返回的response意义更为清晰. 对于单独的snippet编辑: @api_view(['GET', 'PUT', 'DELETE']) def snippet_detail(request, pk): """ 展示, 更新或删除一个snippet """ try: snippet = Snippet.objects.get(pk=pk) except Snippet.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) if request.method == 'GET': serializer = SnippetSerializer(snippet) return Response(serializer.data) elif request.method == 'PUT': serializer = SnippetSerializer(snippet, data=request.DATA) if serializer.is_valid(): serializer.save() return Response(serializer.data) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) elif request.method == 'DELETE': snippet.delete() return Response(status=status.HTTP_204_NO_CONTENT) 从以上代码中可以看到, 我们不再指明request和response中的内容类型. request.DATA即可用来处理json数据类型类型, 也可以处理yaml或其他数据类型. 同时, django-rest-framework也能根据不同情况, 再response呈现正确的数据类型. 2. 使用其他数据格式 为了展示如何多数据格式的支持, 接下来, 我们为url添加后缀: http://example.com/api/items/4.json 为snippets/view.py中的snippet_list和snippet_detail增加format参数 def snippet_list(request, format=None): ... def snippet_detail(request, pk, format=None): ... 更新urls.py: from django.conf.urls import patterns, url from rest_framework.urlpatterns import format_suffix_patterns urlpatterns = patterns('snippets.views', url(r'^snippets/$', 'snippet_list'), url(r'^snippets/(?P<pk>[0-9]+)$', … -
Django-Rest-Framework 教程: 1. 序列化 (Serialization)
在本篇中, 我们将通过建立一个代码黏贴板(pastebin), 来熟悉组成REST framework的各组成部分, 并了解这些部件是如何相互协调工作的. 1. 环境设置 首先我们使用virtualenvwrapper创建新的virtualenv, 并安装需要的代码库: mkvirtualenv env pip install django pip install djangorestframework pip install pygments # 用于代码高亮 2. Django项目设置 我们建立Django项目tutorial, 和app snippets django-admin.py startproject tutorial cd tutorial python manage.py startapp snippets 设置settings.py: # tutorial/settings.py DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', 'NAME': 'database_name', 'USER': 'database_user', 'PASSWORD': 'database_password', 'HOST': '', 'PORT': '' } } INSTALLED_APPS = ( ... 'rest_framework', 'snippets', ) 设置urls.py, 将新建的snippet app中的urls.py加入到其中: # tutorial/urls.py urlpatterns = patterns('', url(r'^', include('snippets.urls')), ) 3. 创建Model 我们建立Snippet Model用于储存代码: # snippet/models.py from django.db import models from pygments.lexers import get_all_lexers from pygments.styles import get_all_styles LEXERS = [item for item in get_all_lexers() if item[1]] LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS]) STYLE_CHOICES = sorted((item, item) for item in get_all_styles()) class Snippet(models.Model): created = models.DateTimeField(auto_now_add=True) title = models.CharField(max_length=100, blank=True, default='') code = models.TextField() linenos = models.BooleanField(default=False) language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100) style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100) class Meta: ordering = ('created',) 启动django服务器: python manage.py syncdb 4. 创建Serializer 第一步我们需要为我们的API提供序列化和反序列化的方法, 将snippet实例转为json等方式呈现数据. 我们可以使用Serializer达到这一目的, Serializer和django forms十分相似. 我们建立snippet/serializers.py文件: # snippet/serializers.py from django.forms import widgets from rest_framework import serializers from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES class SnippetSerializer(serializers.Serializer): pk = serializers.Field() # `Field` 是无类型, 只读的. title = serializers.CharField(required=False, max_length=100) code = serializers.CharField(widget=widgets.Textarea, max_length=100000) linenos = serializers.BooleanField(required=False) language = … -
Django-Rest-Framework 教程: 快速入门
本篇中, 我们会创建一个简单的API, 用来查看和编辑django默认的user和group数据. 1. 设置 我们创建django项目tutorial, 和app quickstart: # 创建新Django项目 django-admin.py startproject tutorial cd tutorial # 使用virtualenvwrapper创建Virtualenv mkvirtualenv env workon env # 在env中安装Django 和 Django REST framework pip install django pip install djangorestframework # 创建新app python manage.py startapp quickstart 然后根据自己的数据库配置设置数据库: # tutorial/settings.py DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', 'NAME': 'database_name', 'USER': 'database_user', 'PASSWORD': 'database_password', 'HOST': '', 'PORT': '' } } ... INSTALLED_APPS = ( ... 'quickstart', 'rest_framework', ) REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAdminUser',), 'PAGINATE_BY': 10 } 最后通过syncdb创建数据库 python manage.py syncdb 2. 序列化 接下来我们创建用于数据序列化的代码: # quickstart/serializers.py from django.contrib.auth.models import User, Group from rest_framework import serializers class UserSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = User fields = ('url', 'username', 'email', 'groups') class GroupSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Group fields = ('url', 'name') 值得注意的是, 我们使用的是HyperlinkedModelSerializer. 你可以使用主键或者其他关系, 但使用HyperlinkedModelSerializer是一个好的 RESTful 设计. 3. Views # quickstart/views.py from django.contrib.auth.models import User, Group from rest_framework import viewsets from quickstart.serializers import UserSerializer, GroupSerializer class UserViewSet(viewsets.ModelViewSet): """ 允许查看和编辑user 的 API endpoint """ queryset = User.objects.all() serializer_class = UserSerializer class GroupViewSet(viewsets.ModelViewSet): """ 允许查看和编辑group的 API endpoint """ queryset = Group.objects.all() serializer_class = GroupSerializer 在django_rest_framework中, 所有常见的行为都被归到了ViewSets中. 当然我们可以将这些行为分拆出来, 但使用ViewSets, 使view的逻辑更为清楚. 使用queryset和serializer_class代替model变量, 使我们能更加好的控制API行为, 这也是我们推荐的使用方式. 4. URLs # tutorial/urls.py from django.conf.urls import patterns, url, include from rest_framework import routers from quickstart import views router = routers.DefaultRouter() router.register(r'users', … -
理解OAuth 2.0
转自: http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html: OAuth是一个关于授权(authorization)的开放网络标准,在全世界得到广泛应用,目前的版本是2.0版。 本文对OAuth 2.0的设计思路和运行流程,做一个简明通俗的解释,主要参考材料为RFC 6749。 1. 应用场景 为了理解OAuth的适用场合,让我举一个假设的例子。 有一个"云冲印"的网站,可以将用户储存在Google的照片,冲印出来。用户为了使用该服务,必须让"云冲印"读取自己储存在Google上的照片。 问题是只有得到用户的授权,Google才会同意"云冲印"读取这些照片。那么,"云冲印"怎样获得用户的授权呢? 传统方法是,用户将自己的Google用户名和密码,告诉"云冲印",后者就可以读取用户的照片了。这样的做法有以下几个严重的缺点。 (1)"云冲印"为了后续的服务,会保存用户的密码,这样很不安全。 (2)Google不得不部署密码登录,而我们知道,单纯的密码登录并不安全。 (3)"云冲印"拥有了获取用户储存在Google所有资料的权力,用户没法限制"云冲印"获得授权的范围和有效期。 (4)用户只有修改密码,才能收回赋予"云冲印"的权力。但是这样做,会使得其他所有获得用户授权的第三方应用程序全部失效。 (5)只要有一个第三方应用程序被破解,就会导致用户密码泄漏,以及所有被密码保护的数据泄漏。 OAuth就是为了解决上面这些问题而诞生的。 2. 名词定义 在详细讲解OAuth 2.0之前,需要了解几个专用名词。它们对读懂后面的讲解,尤其是几张图,至关重要。 (1) Third-liarty alililication:第三方应用程序,本文中又称"客户端"(client),即上一节例子中的"云冲印"。 (2)HTTli service:HTTli服务提供商,本文中简称"服务提供商",即上一节例子中的Google。 (3)Resource Owner:资源所有者,本文中又称"用户"(user)。 (4)User Agent:用户代理,本文中就是指浏览器。 (5)Authorization server:认证服务器,即服务提供商专门用来处理认证的服务器。 (6)Resource server:资源服务器,即服务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。 知道了上面这些名词,就不难理解,OAuth的作用就是让"客户端"安全可控地获取"用户"的授权,与"服务商提供商"进行互动。 3. OAuth的思路 OAuth在"客户端"与"服务提供商"之间,设置了一个授权层(authorization layer)。"客户端"不能直接登录"服务提供商",只能登录授权层,以此将用户与客户端区分开来。"客户端"登录授权层所用的令牌(token),与用户的密码不同。用户可以在登录的时候,指定授权层令牌的权限范围和有效期。 "客户端"登录授权层以后,"服务提供商"根据令牌的权限范围和有效期,向"客户端"开放用户储存的资料。 4. 运行流程 OAuth 2.0的运行流程如下图,摘自RFC 6749。 (A)用户打开客户端以后,客户端要求用户给予授权。 (B)用户同意给予客户端授权。 (C)客户端使用上一步获得的授权,向认证服务器申请令牌。 (D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。 (E)客户端使用令牌,向资源服务器申请获取资源。 (F)资源服务器确认令牌无误,同意向客户端开放资源。 不难看出来,上面六个步骤之中,B是关键,即用户怎样才能给于客户端授权。有了这个授权以后,客户端就可以获取令牌,进而凭令牌获取资源。 下面一一讲解客户端获取授权的四种模式。 5. 客户端的授权模式 客户端必须得到用户的授权(authorization grant),才能获得令牌(access token)。OAuth 2.0定义了四种授权方式。 授权码模式(authorization code) 简化模式(implicit) 密码模式(resource owner password credentials) 客户端模式(client credentials) 6. 授权码模式 授权码模式(authorization code)是功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后台服务器,与"服务提供商"的认证服务器进行互动。 它的步骤如下: (A)用户访问客户端,后者将前者导向认证服务器。 (B)用户选择是否给予客户端授权。 (C)假设用户给予授权,认证服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个授权码。 (D)客户端收到授权码,附上早先的"重定向URI",向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。 (E)认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。 下面是上面这些步骤所需要的参数。 A步骤中,客户端申请认证的URI,包含以下参数: response_type:表示授权类型,必选项,此处的值固定为"code" client_id:表示客户端的ID,必选项 redirect_uri:表示重定向URI,可选项 scope:表示申请的权限范围,可选项 state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。 下面是一个例子。 GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1 Host: server.example.com C步骤中,服务器回应客户端的URI,包含以下参数: code:表示授权码,必选项。该码的有效期应该很短,通常设为10分钟,客户端只能使用该码一次,否则会被授权服务器拒绝。该码与客户端ID和重定向URI,是一一对应关系。 state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。 下面是一个例子。 HTTP/1.1 302 Found Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA &state=xyz D步骤中,客户端向认证服务器申请令牌的HTTP请求,包含以下参数: grant_type:表示使用的授权模式,必选项,此处的值固定为"authorization_code"。 code:表示上一步获得的授权码,必选项。 redirect_uri:表示重定向URI,必选项,且必须与A步骤中的该参数值保持一致。 client_id:表示客户端ID,必选项。 下面是一个例子。 POST /token HTTP/1.1 Host: server.example.com Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW Content-Type: application/x-www-form-urlencoded grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb E步骤中,认证服务器发送的HTTP回复,包含以下参数: access_token:表示访问令牌,必选项。 token_type:表示令牌类型,该值大小写不敏感,必选项,可以是bearer类型或mac类型。 expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。 refresh_token:表示更新令牌,用来获取下一次的访问令牌,可选项。 scope:表示权限范围,如果与客户端申请的范围一致,此项可省略。 下面是一个例子。 HTTP/1.1 200 OK Content-Type: application/json;charset=UTF-8 Cache-Control: no-store Pragma: no-cache { "access_token":"2YotnFZFEjr1zCsicMWpAA", "token_type":"example", "expires_in":3600, "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA", "example_parameter":"example_value" } 从上面代码可以看到,相关参数使用JSON格式发送(Content-Type: application/json)。此外,HTTP头信息中明确指定不得缓存。 7. 简化模式 简化模式(implicit grant type)不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了"授权码"这个步骤,因此得名。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。 它的步骤如下: (A)客户端将用户导向认证服务器。 (B)用户决定是否给于客户端授权。 (C)假设用户给予授权,认证服务器将用户导向客户端指定的"重定向URI",并在URI的Hash部分包含了访问令牌。 (D)浏览器向资源服务器发出请求,其中不包括上一步收到的Hash值。 (E)资源服务器返回一个网页,其中包含的代码可以获取Hash值中的令牌。 (F)浏览器执行上一步获得的脚本,提取出令牌。 (G)浏览器将令牌发给客户端。 下面是上面这些步骤所需要的参数。 A步骤中,客户端发出的HTTP请求,包含以下参数: response_type:表示授权类型,此处的值固定为"token",必选项。 client_id:表示客户端的ID,必选项。 redirect_uri:表示重定向的URI,可选项。 scope:表示权限范围,可选项。 state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。 下面是一个例子。 GET /authorize?response_type=token&client_id=s6BhdRkqt3&state=xyz &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1 Host: server.example.com C步骤中,认证服务器回应客户端的URI,包含以下参数: access_token:表示访问令牌,必选项。 token_type:表示令牌类型,该值大小写不敏感,必选项。 expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。 scope:表示权限范围,如果与客户端申请的范围一致,此项可省略。 state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。 下面是一个例子。 HTTP/1.1 302 Found Location: http://example.com/cb #access_token=2YotnFZFEjr1zCsicMWpAA &state=xyz&token_type=example&expires_in=3600 在上面的例子中,认证服务器用HTTP头信息的Location栏,指定浏览器重定向的网址。注意,在这个网址的Hash部分包含了令牌。 根据上面的D步骤,下一步浏览器会访问Location指定的网址,但是Hash部分不会发送。接下来的E步骤,服务提供商的资源服务器发送过来的代码,会提取出Hash中的令牌。 8. 密码模式 密码模式(Resource Owner Password Credentials Grant)中,用户向客户端提供自己的用户名和密码。客户端使用这些信息,向"服务商提供商"索要授权。 在这种模式中,用户必须把自己的密码给客户端,但是客户端不得储存密码。这通常用在用户对客户端高度信任的情况下,比如客户端是操作系统的一部分,或者由一个著名公司出品。而认证服务器只有在其他授权模式无法执行的情况下,才能考虑使用这种模式。 … -
Installing Python and Django on Windows
For my own reference I just wanted to document my first time installing Python and Django on Windows 8.  I have installed Python before on a Mac, but never on Windows. To begin with I installed the Python 2.7 (the version Django is compatible with at this point in time). First, I downloaded and installed Python. Python has an ‘installer’ so this was straightforward. I would recommend the install location to be short and simple c:/python27. It's a lot easier on your command prompt typing later on. Python is not added to the DOS path by default. I needed to tell windows the PATH environment variable to include paths to Python executable and additional scripts.  Under My Computer > Properties > Advanced System Settings > Environment Variables > I created a new variable called "PythonPath” and added the following… C:\Python27\;C:\Python27\Scripts; Above being my location to the Python executable. Now that I had Python installed, it was time to install Django. I download the Django files then opened the command prompt. Using the Command Prompt I changed directory (“cd”) into the extracted Django directory. I then typed the following... python setup.py install This installs Django into Python’s site-packages directory. The download … -
Resetting migration history Django South
Just something useful when it comes to resetting migration history using Django South. rm -r appname/migrations/ ./manage.py reset south ./manage.py convert_to_south appname I had to do this recently and I was somewhat apprehensive at first. No need to be tho. I just opened up terminal removed the migration folder from the app , used the reset command and then converted the app just like it was an existing one. Doing this does mean that South assumes your database is identical to the app models.py. -
Fix: vertualenv EnvironmentError: mysql_config not found
Have you ever had what should be a simple task just drive you insane? For me, it's setting up a new Django project using virtualenv on my home Mac running 10.8! For some reason this Mac likes to misbehave! I get the same error every time I try to install MySQL-python within virtualenv vertualenv EnvironmentError: mysql_config not found I cannot take any credit for this fix, after many searches I found the solution here. It's a simple fix just to edit the /bin/activate file from the virtualenv directory and add the following lines, _OLD_VIRTUAL_PATH="$PATH" PATH="$VIRTUAL_ENV/bin:$PATH" PATH="$PATH:/usr/local/mysql/bin/" export PATH That's it, just make sure you match your paths to your own configuration. Now MySQL-python installs under virtualenv as normal. -
Django 1.5 deployment on Amazon EC2 Ubuntu
Introduction Six months ago I began my Django journey. Since then I have setup, installed and utilised many different deployment mechanisms for projects. This tutorial demonstrates the installation and deployment solution I found to be the most fitting for my own Django projects. So, I’d like to share… Why Git? I’ve been using Git for deployment within our company for sometime. It was a natural fit since all our projects already used GIT for version control and our development team understands it. What’s best, it's completely free! Prerequisites One will assume you are already familiar, perhaps even using GIT and have a basic understanding of Apache, Ubuntu Server and Django. I tend to use Cloud computing such as Amazon AWS. I have tested this tutorial on a EC2 instance running Ubuntu 12. Installing our Server Software So as mentioned above I’ll be using an EC2 instance for this writeup. So first ssh in and do any updates. apt-get update apt-get upgrade Next, you will need to install Apache2, Python, and PIP using the following commands... apt-get -y install apache2-mpm-worker apache2-dev apt-get -y install python python-dev python-setuptools sudo apt-get install python-pip pip install --upgrade pip We will wget the latest mod_wsgi, … -
RESTful API 设计指南
转自http://www.ruanyifeng.com/blog/2014/05/restful_api.html 网络应用程序,分为前端和后端两个部分。当前的发展趋势,就是前端设备层出不穷(手机、平板、桌面电脑、其他专用设备......)。 因此,必须有一种统一的机制,方便不同的前端设备与后端进行通信。这导致API构架的流行,甚至出现"API First"的设计思想。RESTful API是目前比较成熟的一套互联网应用程序的API设计理论。我以前写过一篇《理解RESTful架构》,探讨如何理解这个概念。 今天,我将介绍RESTful API的设计细节,探讨如何设计一套合理、好用的API。我的主要参考资料是这篇《Principles of good RESTful API Design》。 1. 协议 API与用户的通信协议,总是使用HTTPs协议。 2. 域名 应该尽量将API部署在专用域名之下。 https://api.example.com 如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下。 https://example.org/api/ 3. 版本(Versioning) 应该将API的版本号放入URL。 https://api.example.com/v1/ 另一种做法是,将版本号放在HTTP头信息中,但不如放入URL方便和直观。 4. 路径(Endpoint) 路径又称"终点"(endpoint),表示API的具体网址。 在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库中的表都是同种记录的"集合"(collection),所以API中的名词也应该使用复数。 举例来说,有一个API提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样。 https://api.example.com/v1/zoos https://api.example.com/v1/animals https://api.example.com/v1/employees 5. HTTP动词 对于资源的具体操作类型,由HTTP动词表示。 常用的HTTP动词有下面五个(括号里是对应的SQL命令)。 GET(SELECT):从服务器取出资源(一项或多项)。 POST(CREATE):在服务器新建一个资源。 PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。 PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。 DELETE(DELETE):从服务器删除资源。 还有两个不常用的HTTP动词。 HEAD:获取资源的元数据。 OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。 下面是一些例子。 GET /zoos:列出所有动物园 POST /zoos:新建一个动物园 GET /zoos/ID:获取某个指定动物园的信息 PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息) PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息) DELETE /zoos/ID:删除某个动物园 GET /zoos/ID/animals:列出某个指定动物园的所有动物 DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物 6. 过滤信息(Filtering) 如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。下面是一些常见的参数。 ?limit=10:指定返回记录的数量 ?offset=10:指定返回记录的开始位置。 ?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。 ?animal_type_id=1:指定筛选条件 参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复。比如,GET /zoo/ID/animals 与 GET /animals?zoo_id=ID 的含义是相同的。 7. 状态码(Status Codes) 服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的HTTP动词)。 200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。 204 NO CONTENT - [DELETE]:用户删除数据成功。 400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。。 404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。 500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。 状态码的完全列表参见这里。 8. 错误处理(Error handling) 如果状态码是4xx,就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可。 { error: "Invalid API key" } 9. 返回结果 针对不同操作,服务器向用户返回的结果应该符合以下规范。 GET /collection:返回资源对象的列表(数组) GET /collection/resource:返回单个资源对象 POST /collection:返回新生成的资源对象 PUT /collection/resource:返回完整的资源对象 PATCH /collection/resource:返回完整的资源对象 DELETE /collection/resource:返回一个空文档 10. Hypermedia API RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。比如,当用户向api.example.com的根目录发出请求,会得到这样一个文档。 {"link": { "rel": "collection https://www.example.com/zoos", "href": "https://api.example.com/zoos", "title": "List of zoos", "type": "application/vnd.yourformat+json" }} 上面代码表示,文档中有一个link属性,用户读取这个属性就知道下一步该调用什么API了。rel表示这个API与当前网址的关系(collection关系,并给出该collection的网址),href表示API的路径,title表示API的标题,type表示返回类型。 Hypermedia API的设计被称为HATEOAS。Github的API就是这种设计,访问api.github.com会得到一个所有可用API的网址列表。 { "current_user_url": "https://api.github.com/user", "authorizations_url": "https://api.github.com/authorizations", // ... } 从上面可以看到,如果想获取当前用户的信息,应该去访问api.github.com/user,然后就得到了下面结果。 { "message": "Requires authentication", "documentation_url": "https://developer.github.com/v3" } 上面代码表示,服务器给出了提示信息,以及文档的网址。 11. 其他 (1)API的身份认证应该使用OAuth 2.0框架。 (2)服务器返回的数据格式,应该尽量使用JSON,避免使用XML -
Packaging a python library
Note This is about packaging libraries, not applications. For now there are no special considerations for C extensions here. I think the packaging best practices should be revisited, there are lots of good tools now-days that are either unused or underused. It's generally a good thing to re-evaluate best practices all the time. I assume here that your package is to be tested on multiple Python versions, with different combinations of dependency versions, settings etc. And few principles that I like to follow when packaging: If there's a tool that can help with testing use it. Don't waste time building a custom test runner if you can just use nose or py.test. They come with a large ecosystem of plugins that can improve your testing. When possible, prevent issues early. This is mostly a matter of strictness and exhaustive testing. Design things to prevent common mistakes. Collect all the coverage data. Record it. Identify regressions. Test all the possible configurations. The structure* This is fairly important, everything revolves around this. People layout packages like this: ├─ packagename │ ├─ __init__.py │ ├─ ... │ └─ tests │ └─ ... └─ setup.py I think this is actually a bad practice, a … -
Packaging a python library
Note This is about packaging libraries, not applications. ⸻ All the advice here is implemented in a project template (with full support for C extensions): cookiecutter-pylibrary (introduction). I think the packaging best practices should be revisited, there are lots of good tools now-days that are either unused or underused. It's generally a good thing to re-evaluate best practices all the time. I assume here that your package is to be tested on multiple Python versions, with different combinations of dependency versions, settings etc. And few principles that I like to follow when packaging: If there's a tool that can help with testing use it. Don't waste time building a custom test runner if you can just use py.test or nose. They come with a large ecosystem of plugins that can improve your testing. When possible, prevent issues early. This is mostly a matter of strictness and exhaustive testing. Design things to prevent common mistakes. Collect all the coverage data. Record it. Identify regressions. Test all the possible configurations. The structure * This is fairly important, everything revolves around this. I prefer this sort of layout: ├─ src │ └─ packagename │ ├─ __init__.py │ └─ ... ├─ tests │ └─ … -
Packaging a python library
Note This is about packaging libraries, not applications. ⸻ All the advice here is implemented in a project template (with full support for C extensions): cookiecutter-pylibrary (introduction). I think the packaging best practices should be revisited, there are lots of good tools now-days that are either unused or underused. It's generally a good thing to re-evaluate best practices all the time. I assume here that your package is to be tested on multiple Python versions, with different combinations of dependency versions, settings etc. And few principles that I like to follow when packaging: If there's a tool that can help with testing use it. Don't waste time building a custom test runner if you can just use py.test or nose. They come with a large ecosystem of plugins that can improve your testing. When possible, prevent issues early. This is mostly a matter of strictness and exhaustive testing. Design things to prevent common mistakes. Collect all the coverage data. Record it. Identify regressions. Test all the possible configurations. The structure * This is fairly important, everything revolves around this. I prefer this sort of layout: ├─ src │ └─ packagename │ ├─ __init__.py │ └─ ... ├─ tests │ └─ … -
Django 1.6 最佳实践: 如何正确使用Templates模板(二)
接着上一篇博文, 我们继续讲django中templates的使用最佳实践: 4. Templates的格式 尽管在浏览器中呈现或查看HTML时, 浏览器都会为你整齐排列好, 但我们在写template代码时, 还是应该尽量保持良好的格式. 原因就像之前的文章中提到的一样, 代码的可读性对于日后的维护和修改十分重要. 在写代码时, 应当随时注意代码的缩进, 空行和分段, 当然也可以使用编辑器或IDE的自动格式化来达到这一目的. 5. Templates中block.super的使用 在templates中除了使用常见的block, extends, include等tag组织template结构外, 还有一个非常好用的tag: {% block.super %}, block.super可以引入之前block的内容, 假设原始的base.html: {# templates/base.html #} ... {% block stylesheets %} <link rel="stylesheet" type="text/css" href="css/default.css" /> {% end block %} 如果我们希望继承自base.html的stylesheets block既保留default.css, 又加入project.css: {% extends "base.html" %} {% block stylesheets %} {% block.super %} <link rel="stylesheet" type="text/css" href="css/default.css" /> {% end block %} 如果希望完全代替原有block的内容则: {% extends "base.html" %} {% block stylesheets %} <link rel="stylesheet" type="text/css" href="css/default.css" /> {% end block %} 如果不使用block stylesheets, 则继承自base.html的template保留改block: 6. 其他需要注意的点 第一, 不要将样式文件与python代码捆绑到一起, 这点应该很容易理解, 尽量将这些文件归到css或JavaScript中. 第二, 一些命名规定: 尽量使用下划线(_)代替横杠(-), 大多数python开发人员都喜欢这样, 原因的话, 可能是下划线可以存在于python变量名中吧. block名尽量使用简明, 易懂的形式 在endblock中同样加入block名例如{% endblock javascript%} 被引用(include)的模板使用"_"开头 第三, templates应放入django_project_root中, 这样是最直观的做法. 但如果你的app是作为第三方插件的话, 那么templates就可以放入app文件夹中. 第四, 简单命名context: 在使用CBVs时, context都有默认的名字object_list或object, 与此同时, 我们还应当给出一个有意义, 简明的名字, 方便调用. 第五, 不要手写URL地址到template中, 而是应当使用{% url "url_name" %}的方式获得URL地址. 第六, debug template: 当templates很复杂时, 可以在settings.py中设置TEMPLATE_STRING_IF_INVALID = "INVALID EXPRESSION: %s", 这样当有变量没有呈现时, 便会在template原有位置显示INVALID EXPRESSION: "变量名", 是的debug变得较容易. 第七, 不到替换掉django的template引擎: 你可以在个别view中使用Jinjia2等模板引擎, 但最好不要将整个django templates引擎替换掉. 7. HTML错误页 即使是最稳定的网站或应用也有错误的时候, 这没有关系, 问题在于你如何处理这些错误. 但是将黄色的报错页直接显示给用户就不是一个好的做法了. 我们至少应该在templates中放置404.html和500.html页面. 我们建议使用文件服务器呈现这些页, 因为这样的话, 当你的Django程序出问题时, 文件服务器还能独立运行, 继续显示这些页面. 如果你使用PAAS的话, 请参照PAAS的说明文件. 在github上有很好的例子: https://github.com/404 https://github.com/500 你可以发现: 所有的CSS样式文件都是inline形式的, 不需要借助其他服务器或文件帮助 所有的图像都是以数据形式存在HTML中, 没有<img>标签或其他外部URL 所有的JavaScript代码都是包含在HTML中, 不使用外部的链接加在JavaScript -
Django 1.6 最佳实践: 如何正确使用Templates模板(一)
Django设计之初的基本原则之一是限制template的功能. 这一做法极大地限制了Django templates的复杂性, 也迫使我们将业务逻辑置于Python代码这边. 1. 最简原则 我们推荐在Django templates中使用最简原则. 尊重django template中的功能限制, 并将业务逻辑使用更优化的Python代码代替之. 在Django templates中使用最简原则, 可以是我们的django app更便于再利用. 试想, 当一个template中充满了大段繁琐的内嵌循环, 复杂逻辑和数据处理代码时, 如何能重复利用其中的业务逻辑? 更不要说如何在其他代码中利用这些业务逻辑了. 2. Templates结构的2种常见模式 在template结构中比较常见的是2种模式, 一种是2层结构, 即所有的其他页面都继承自同一base.html文件: templates/ base.html index.html # extends base.html articles/ article_detail.html # extends base.html article.form.html #extends base.html 还有一种则是3层结构. 这一结构中, 整个项目拥有一个基础的base.html文件, 而每一个app都拥有自己的base _<app>.html, app中其他页面则都继承自该app的base_<app>.html文件: templates/ base.html index.html # extends base.html articles/ base_articles.html # extends base.html article_detail.html # extends base_articles.html article_form.html # extends base_articles.html 三层结构的templates, 对于每一部分都需要不同布局的网站而言最为适合. 例如一个新闻网站, 其中本地新闻, 体育新闻, 和其他分类的新闻页面都采用不同样式的布局, 则可以采用三层结构的templates. 需要注意的是, 在templates中, 扁平比嵌套好. 复杂的template结构使得debug, 修改和扩展HTML, 加入CSS文件等异常艰辛. 如果使用扁平简单的template, 和你合作的前台设计师一定会十分感激你的. 3. 在template中限制处理量 在template中应当尽量减少处理的数据良, 因此最好每次循环一个queryset时, 你都应该问自己: 这个queryset有多大? 循环超过GB级的queryset永远是个错误 从数据库中查询的objects有多大? 需要的fields是否都包含在了其中? 每次循环中, 有多少处理步骤发生? 如果发现以上三个问题中有一个不符合最简原则, 那么可能你就应该重写这个template了. 以下我们以一个收据(voucher)model为例子, 详细说明一下常见的一些错误: # vouchers/models.py from django.core.urlresolvers import reverse from django.db import models from .managers import VoucherManager class Voucher(models.Model): name = models.CharField(max_length=100) email = models.EmailField() address = models.TextField() birth_date = models.DateField(blank=True) sent = models.BooleanField(default=False) redeemed = models.BooleanField(default=False) objects = VoucherManager() 错误1: 在templates中使用聚合函数 由于我们的收据model中有生日信息, 我们可能希望按照年龄段聚合显示收据的数量. 但是我们不应当在template中使用这些聚合处理, 因为这样会大大拖累页面显示的速度, 具体而言: 不应该在template的JavaScript中循环整个收据list 不应该在template中使用add来累加收据的数量 正确的做法应当将这一聚合过程放到python代码中, 然后只在template中呈现这些数据: {# templates/vouchers/ages.html #} {% extends "base.html" %} {% block content %} <table> <thead> <tr> <th>年龄层</th> <th>收据数量</th> </tr> </thead> <tbody> {% for age_bracket in age_brackets %} <tr> <td>{{ age_bracket.title }}</td> <td>{{ age_bracket.count }}</td> </tr> {% endfor %} </tbody> </table> {% end block content%} 数据聚合处理放入model manager中, 然后通过views.py调用并传到template中: # vouchers/managers.py from dateutil.relativedelta import relativedelta from django.utils import timezone from django.db import … -
Starting with Stripe.js
We all want to make money, so learn what it takes to start accepting money on your site with stripe. Learn the basics of have a user subscribe to your site in this first part of a multi-part stripe series.Watch Now... -
Django 1.6 session 序列化的安全问题
从Django 1.6开始默认的session序列化方法发生了变化,在1.5及之前版本的Django默认使用的 […] -
The State of Tastypie
The State of Tastypie