├── README.md ├── __init__.py ├── apps.py ├── authentication.py ├── authtoken ├── __init__.py ├── admin.py ├── apps.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── drf_create_token.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20160226_1747.py │ └── __init__.py ├── models.py ├── serializers.py └── views.py ├── checks.py ├── compat.py ├── decorators.py ├── documentation.py ├── exceptions.py ├── fields.py ├── filters.py ├── generics.py ├── locale ├── ach │ └── LC_MESSAGES │ │ └── django.mo ├── ar │ └── LC_MESSAGES │ │ └── django.mo ├── be │ └── LC_MESSAGES │ │ └── django.mo ├── ca │ └── LC_MESSAGES │ │ └── django.mo ├── ca_ES │ └── LC_MESSAGES │ │ └── django.mo ├── cs │ └── LC_MESSAGES │ │ └── django.mo ├── da │ └── LC_MESSAGES │ │ └── django.mo ├── de │ └── LC_MESSAGES │ │ └── django.mo ├── el │ └── LC_MESSAGES │ │ └── django.mo ├── el_GR │ └── LC_MESSAGES │ │ └── django.mo ├── en │ └── LC_MESSAGES │ │ └── django.mo ├── en_AU │ └── LC_MESSAGES │ │ └── django.mo ├── en_CA │ └── LC_MESSAGES │ │ └── django.mo ├── en_US │ └── LC_MESSAGES │ │ └── django.mo ├── es │ └── LC_MESSAGES │ │ └── django.mo ├── et │ └── LC_MESSAGES │ │ └── django.mo ├── fa │ └── LC_MESSAGES │ │ └── django.mo ├── fa_IR │ └── LC_MESSAGES │ │ └── django.mo ├── fi │ └── LC_MESSAGES │ │ └── django.mo ├── fr │ └── LC_MESSAGES │ │ └── django.mo ├── fr_CA │ └── LC_MESSAGES │ │ └── django.mo ├── gl │ └── LC_MESSAGES │ │ └── django.mo ├── gl_ES │ └── LC_MESSAGES │ │ └── django.mo ├── he_IL │ └── LC_MESSAGES │ │ └── django.mo ├── hu │ └── LC_MESSAGES │ │ └── django.mo ├── id │ └── LC_MESSAGES │ │ └── django.mo ├── it │ └── LC_MESSAGES │ │ └── django.mo ├── ja │ └── LC_MESSAGES │ │ └── django.mo ├── ko_KR │ └── LC_MESSAGES │ │ └── django.mo ├── lv │ └── LC_MESSAGES │ │ └── django.mo ├── mk │ └── LC_MESSAGES │ │ └── django.mo ├── nb │ └── LC_MESSAGES │ │ └── django.mo ├── nl │ └── LC_MESSAGES │ │ └── django.mo ├── nn │ └── LC_MESSAGES │ │ └── django.mo ├── no │ └── LC_MESSAGES │ │ └── django.mo ├── pl │ └── LC_MESSAGES │ │ └── django.mo ├── pt │ └── LC_MESSAGES │ │ └── django.mo ├── pt_BR │ └── LC_MESSAGES │ │ └── django.mo ├── pt_PT │ └── LC_MESSAGES │ │ └── django.mo ├── ro │ └── LC_MESSAGES │ │ └── django.mo ├── ru │ └── LC_MESSAGES │ │ └── django.mo ├── sk │ └── LC_MESSAGES │ │ └── django.mo ├── sl │ └── LC_MESSAGES │ │ └── django.mo ├── sv │ └── LC_MESSAGES │ │ └── django.mo ├── tr │ └── LC_MESSAGES │ │ └── django.mo ├── tr_TR │ └── LC_MESSAGES │ │ └── django.mo ├── uk │ └── LC_MESSAGES │ │ └── django.mo ├── vi │ └── LC_MESSAGES │ │ └── django.mo ├── zh_CN │ └── LC_MESSAGES │ │ └── django.mo ├── zh_Hans │ └── LC_MESSAGES │ │ └── django.mo ├── zh_Hant │ └── LC_MESSAGES │ │ └── django.mo └── zh_TW │ └── LC_MESSAGES │ └── django.mo ├── metadata.py ├── mixins.py ├── models.py ├── negotiation.py ├── pagination.py ├── parsers.py ├── permissions.py ├── relations.py ├── renderers.py ├── request.py ├── response.py ├── reverse.py ├── routers.py ├── schemas ├── __init__.py ├── generators.py ├── inspectors.py ├── utils.py └── views.py ├── serializers.py ├── settings.py ├── static └── rest_framework │ ├── css │ ├── bootstrap-theme.min.css │ ├── bootstrap-tweaks.css │ ├── bootstrap.min.css │ ├── default.css │ ├── font-awesome-4.0.3.css │ └── prettify.css │ ├── docs │ ├── css │ │ ├── base.css │ │ ├── highlight.css │ │ └── jquery.json-view.min.css │ ├── img │ │ ├── favicon.ico │ │ └── grid.png │ └── js │ │ ├── api.js │ │ ├── highlight.pack.js │ │ └── jquery.json-view.min.js │ ├── fonts │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 │ ├── img │ ├── glyphicons-halflings-white.png │ ├── glyphicons-halflings.png │ └── grid.png │ └── js │ ├── ajax-form.js │ ├── bootstrap.min.js │ ├── coreapi-0.1.1.js │ ├── csrf.js │ ├── default.js │ ├── jquery-3.3.1.min.js │ └── prettify-min.js ├── status.py ├── templates └── rest_framework │ ├── admin.html │ ├── admin │ ├── detail.html │ ├── dict_value.html │ ├── list.html │ ├── list_value.html │ └── simple_list_value.html │ ├── api.html │ ├── base.html │ ├── docs │ ├── auth │ │ ├── basic.html │ │ ├── session.html │ │ └── token.html │ ├── document.html │ ├── error.html │ ├── index.html │ ├── interact.html │ ├── langs │ │ ├── javascript-intro.html │ │ ├── javascript.html │ │ ├── python-intro.html │ │ ├── python.html │ │ ├── shell-intro.html │ │ └── shell.html │ ├── link.html │ └── sidebar.html │ ├── filters │ ├── base.html │ ├── ordering.html │ └── search.html │ ├── horizontal │ ├── checkbox.html │ ├── checkbox_multiple.html │ ├── dict_field.html │ ├── fieldset.html │ ├── form.html │ ├── input.html │ ├── list_field.html │ ├── list_fieldset.html │ ├── radio.html │ ├── select.html │ ├── select_multiple.html │ └── textarea.html │ ├── inline │ ├── checkbox.html │ ├── checkbox_multiple.html │ ├── dict_field.html │ ├── fieldset.html │ ├── form.html │ ├── input.html │ ├── list_field.html │ ├── list_fieldset.html │ ├── radio.html │ ├── select.html │ ├── select_multiple.html │ └── textarea.html │ ├── login.html │ ├── login_base.html │ ├── pagination │ ├── numbers.html │ └── previous_and_next.html │ ├── raw_data_form.html │ ├── schema.js │ └── vertical │ ├── checkbox.html │ ├── checkbox_multiple.html │ ├── dict_field.html │ ├── fieldset.html │ ├── form.html │ ├── input.html │ ├── list_field.html │ ├── list_fieldset.html │ ├── radio.html │ ├── select.html │ ├── select_multiple.html │ └── textarea.html ├── templatetags ├── __init__.py └── rest_framework.py ├── test.py ├── throttling.py ├── urlpatterns.py ├── urls.py ├── utils ├── __init__.py ├── breadcrumbs.py ├── encoders.py ├── field_mapping.py ├── formatting.py ├── html.py ├── humanize_datetime.py ├── json.py ├── mediatypes.py ├── model_meta.py ├── representation.py ├── serializer_helpers.py └── urls.py ├── validators.py ├── versioning.py ├── views.py └── viewsets.py /README.md: -------------------------------------------------------------------------------- 1 | # 项目介绍 2 | 本项目是对[django rest_framework框架](https://github.com/encode/django-rest-framework)的源码分析,方便对rest_framework进行源码解读,加深对rest_framework框架的理解。我将用在关键部分代码添加注释的方式对源码进行分析说明。 3 | 4 | 我将在个人博客上配合详细文字说明对源码分析的思路进行介绍,如果你也在学习rest_framework,请将本项目下载到你的Python安装文件下的\Lib\site-packages\目录下,然后根据博客上的说明,配合pycharm设置断点进行进行调试解读。 5 | 6 | ## 博客地址: 7 | 8 | ###### [一、django rest_framework源码之总体流程剖析](https://www.cnblogs.com/chenhuabin/p/9978468.html) 9 | 10 | ###### [二、django rest_framework源码之认证流程剖析](https://www.cnblogs.com/chenhuabin/p/9982833.html) 11 | 12 | ###### [三、django rest_framework源码之权限流程剖析](https://www.cnblogs.com/chenhuabin/p/9983722.html) 13 | 14 | ###### [四、django rest_framework源码之频率控制剖析](https://www.cnblogs.com/chenhuabin/p/9985898.html) 15 | 16 | ###### [五、django rest_framework源码之版本控制剖析](https://www.cnblogs.com/chenhuabin/p/9987467.html) 17 | 18 | ###### [六、django rest_framework源码之解析器剖析](https://www.cnblogs.com/chenhuabin/p/9988724.html) 19 | 20 | ###### [七、django rest_framework源码之视图](https://www.cnblogs.com/chenhuabin/p/9991293.html) 21 | 22 | 其他尚未完成部分将在后续进行更新…… 23 | 24 | 25 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | ______ _____ _____ _____ __ 3 | | ___ \ ___/ ___|_ _| / _| | | 4 | | |_/ / |__ \ `--. | | | |_ _ __ __ _ _ __ ___ _____ _____ _ __| |__ 5 | | /| __| `--. \ | | | _| '__/ _` | '_ ` _ \ / _ \ \ /\ / / _ \| '__| |/ / 6 | | |\ \| |___/\__/ / | | | | | | | (_| | | | | | | __/\ V V / (_) | | | < 7 | \_| \_\____/\____/ \_/ |_| |_| \__,_|_| |_| |_|\___| \_/\_/ \___/|_| |_|\_| 8 | """ 9 | 10 | __title__ = 'Django REST framework' 11 | __version__ = '3.8.2' 12 | __author__ = 'Tom Christie' 13 | __license__ = 'BSD 2-Clause' 14 | __copyright__ = 'Copyright 2011-2018 Tom Christie' 15 | 16 | # Version synonym 17 | VERSION = __version__ 18 | 19 | # Header encoding (see RFC5987) 20 | HTTP_HEADER_ENCODING = 'iso-8859-1' 21 | 22 | # Default datetime input and output formats 23 | ISO_8601 = 'iso-8601' 24 | 25 | default_app_config = 'rest_framework.apps.RestFrameworkConfig' 26 | -------------------------------------------------------------------------------- /apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class RestFrameworkConfig(AppConfig): 5 | name = 'rest_framework' 6 | verbose_name = "Django REST framework" 7 | 8 | def ready(self): 9 | # Add System checks 10 | from .checks import pagination_system_check # NOQA 11 | -------------------------------------------------------------------------------- /authtoken/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = 'rest_framework.authtoken.apps.AuthTokenConfig' 2 | -------------------------------------------------------------------------------- /authtoken/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from rest_framework.authtoken.models import Token 4 | 5 | 6 | class TokenAdmin(admin.ModelAdmin): 7 | list_display = ('key', 'user', 'created') 8 | fields = ('user',) 9 | ordering = ('-created',) 10 | 11 | 12 | admin.site.register(Token, TokenAdmin) 13 | -------------------------------------------------------------------------------- /authtoken/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | from django.utils.translation import ugettext_lazy as _ 3 | 4 | 5 | class AuthTokenConfig(AppConfig): 6 | name = 'rest_framework.authtoken' 7 | verbose_name = _("Auth Token") 8 | -------------------------------------------------------------------------------- /authtoken/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/authtoken/management/__init__.py -------------------------------------------------------------------------------- /authtoken/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/authtoken/management/commands/__init__.py -------------------------------------------------------------------------------- /authtoken/management/commands/drf_create_token.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model 2 | from django.core.management.base import BaseCommand, CommandError 3 | 4 | from rest_framework.authtoken.models import Token 5 | 6 | UserModel = get_user_model() 7 | 8 | 9 | class Command(BaseCommand): 10 | help = 'Create DRF Token for a given user' 11 | 12 | def create_user_token(self, username, reset_token): 13 | user = UserModel._default_manager.get_by_natural_key(username) 14 | 15 | if reset_token: 16 | Token.objects.filter(user=user).delete() 17 | 18 | token = Token.objects.get_or_create(user=user) 19 | return token[0] 20 | 21 | def add_arguments(self, parser): 22 | parser.add_argument('username', type=str) 23 | 24 | parser.add_argument( 25 | '-r', 26 | '--reset', 27 | action='store_true', 28 | dest='reset_token', 29 | default=False, 30 | help='Reset existing User token and create a new one', 31 | ) 32 | 33 | def handle(self, *args, **options): 34 | username = options['username'] 35 | reset_token = options['reset_token'] 36 | 37 | try: 38 | token = self.create_user_token(username, reset_token) 39 | except UserModel.DoesNotExist: 40 | raise CommandError( 41 | 'Cannot create the Token: user {0} does not exist'.format( 42 | username) 43 | ) 44 | self.stdout.write( 45 | 'Generated token {0} for user {1}'.format(token.key, username)) 46 | -------------------------------------------------------------------------------- /authtoken/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.conf import settings 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='Token', 17 | fields=[ 18 | ('key', models.CharField(primary_key=True, serialize=False, max_length=40)), 19 | ('created', models.DateTimeField(auto_now_add=True)), 20 | ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL, related_name='auth_token', on_delete=models.CASCADE)), 21 | ], 22 | options={ 23 | }, 24 | bases=(models.Model,), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /authtoken/migrations/0002_auto_20160226_1747.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.conf import settings 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('authtoken', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterModelOptions( 16 | name='token', 17 | options={'verbose_name_plural': 'Tokens', 'verbose_name': 'Token'}, 18 | ), 19 | migrations.AlterField( 20 | model_name='token', 21 | name='created', 22 | field=models.DateTimeField(verbose_name='Created', auto_now_add=True), 23 | ), 24 | migrations.AlterField( 25 | model_name='token', 26 | name='key', 27 | field=models.CharField(verbose_name='Key', max_length=40, primary_key=True, serialize=False), 28 | ), 29 | migrations.AlterField( 30 | model_name='token', 31 | name='user', 32 | field=models.OneToOneField(to=settings.AUTH_USER_MODEL, verbose_name='User', related_name='auth_token', on_delete=models.CASCADE), 33 | ), 34 | ] 35 | -------------------------------------------------------------------------------- /authtoken/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/authtoken/migrations/__init__.py -------------------------------------------------------------------------------- /authtoken/models.py: -------------------------------------------------------------------------------- 1 | import binascii 2 | import os 3 | 4 | from django.conf import settings 5 | from django.db import models 6 | from django.utils.encoding import python_2_unicode_compatible 7 | from django.utils.translation import ugettext_lazy as _ 8 | 9 | 10 | @python_2_unicode_compatible 11 | class Token(models.Model): 12 | """ 13 | The default authorization token model. 14 | """ 15 | key = models.CharField(_("Key"), max_length=40, primary_key=True) 16 | user = models.OneToOneField( 17 | settings.AUTH_USER_MODEL, related_name='auth_token', 18 | on_delete=models.CASCADE, verbose_name=_("User") 19 | ) 20 | created = models.DateTimeField(_("Created"), auto_now_add=True) 21 | 22 | class Meta: 23 | # Work around for a bug in Django: 24 | # https://code.djangoproject.com/ticket/19422 25 | # 26 | # Also see corresponding ticket: 27 | # https://github.com/encode/django-rest-framework/issues/705 28 | abstract = 'rest_framework.authtoken' not in settings.INSTALLED_APPS 29 | verbose_name = _("Token") 30 | verbose_name_plural = _("Tokens") 31 | 32 | def save(self, *args, **kwargs): 33 | if not self.key: 34 | self.key = self.generate_key() 35 | return super(Token, self).save(*args, **kwargs) 36 | 37 | def generate_key(self): 38 | return binascii.hexlify(os.urandom(20)).decode() 39 | 40 | def __str__(self): 41 | return self.key 42 | -------------------------------------------------------------------------------- /authtoken/serializers.py: -------------------------------------------------------------------------------- 1 | from django.utils.translation import ugettext_lazy as _ 2 | 3 | from rest_framework import serializers 4 | from rest_framework.compat import authenticate 5 | 6 | 7 | class AuthTokenSerializer(serializers.Serializer): 8 | username = serializers.CharField(label=_("Username")) 9 | password = serializers.CharField( 10 | label=_("Password"), 11 | style={'input_type': 'password'}, 12 | trim_whitespace=False 13 | ) 14 | 15 | def validate(self, attrs): 16 | username = attrs.get('username') 17 | password = attrs.get('password') 18 | 19 | if username and password: 20 | user = authenticate(request=self.context.get('request'), 21 | username=username, password=password) 22 | 23 | # The authenticate call simply returns None for is_active=False 24 | # users. (Assuming the default ModelBackend authentication 25 | # backend.) 26 | if not user: 27 | msg = _('Unable to log in with provided credentials.') 28 | raise serializers.ValidationError(msg, code='authorization') 29 | else: 30 | msg = _('Must include "username" and "password".') 31 | raise serializers.ValidationError(msg, code='authorization') 32 | 33 | attrs['user'] = user 34 | return attrs 35 | -------------------------------------------------------------------------------- /authtoken/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework import parsers, renderers 2 | from rest_framework.authtoken.models import Token 3 | from rest_framework.authtoken.serializers import AuthTokenSerializer 4 | from rest_framework.compat import coreapi, coreschema 5 | from rest_framework.response import Response 6 | from rest_framework.schemas import ManualSchema 7 | from rest_framework.views import APIView 8 | 9 | 10 | class ObtainAuthToken(APIView): 11 | throttle_classes = () 12 | permission_classes = () 13 | parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,) 14 | renderer_classes = (renderers.JSONRenderer,) 15 | serializer_class = AuthTokenSerializer 16 | if coreapi is not None and coreschema is not None: 17 | schema = ManualSchema( 18 | fields=[ 19 | coreapi.Field( 20 | name="username", 21 | required=True, 22 | location='form', 23 | schema=coreschema.String( 24 | title="Username", 25 | description="Valid username for authentication", 26 | ), 27 | ), 28 | coreapi.Field( 29 | name="password", 30 | required=True, 31 | location='form', 32 | schema=coreschema.String( 33 | title="Password", 34 | description="Valid password for authentication", 35 | ), 36 | ), 37 | ], 38 | encoding="application/json", 39 | ) 40 | 41 | def post(self, request, *args, **kwargs): 42 | serializer = self.serializer_class(data=request.data, 43 | context={'request': request}) 44 | serializer.is_valid(raise_exception=True) 45 | user = serializer.validated_data['user'] 46 | token, created = Token.objects.get_or_create(user=user) 47 | return Response({'token': token.key}) 48 | 49 | 50 | obtain_auth_token = ObtainAuthToken.as_view() 51 | -------------------------------------------------------------------------------- /checks.py: -------------------------------------------------------------------------------- 1 | from django.core.checks import Tags, Warning, register 2 | 3 | 4 | @register(Tags.compatibility) 5 | def pagination_system_check(app_configs, **kwargs): 6 | errors = [] 7 | # Use of default page size setting requires a default Paginator class 8 | from rest_framework.settings import api_settings 9 | if api_settings.PAGE_SIZE and not api_settings.DEFAULT_PAGINATION_CLASS: 10 | errors.append( 11 | Warning( 12 | "You have specified a default PAGE_SIZE pagination rest_framework setting," 13 | "without specifying also a DEFAULT_PAGINATION_CLASS.", 14 | hint="The default for DEFAULT_PAGINATION_CLASS is None. " 15 | "In previous versions this was PageNumberPagination. " 16 | "If you wish to define PAGE_SIZE globally whilst defining " 17 | "pagination_class on a per-view basis you may silence this check.", 18 | id="rest_framework.W001" 19 | ) 20 | ) 21 | return errors 22 | -------------------------------------------------------------------------------- /decorators.py: -------------------------------------------------------------------------------- 1 | """ 2 | The most important decorator in this module is `@api_view`, which is used 3 | for writing function-based views with REST framework. 4 | 5 | There are also various decorators for setting the API policies on function 6 | based views, as well as the `@detail_route` and `@list_route` decorators, which are 7 | used to annotate methods on viewsets that should be included by routers. 8 | """ 9 | from __future__ import unicode_literals 10 | 11 | import types 12 | import warnings 13 | 14 | from django.utils import six 15 | 16 | from rest_framework.views import APIView 17 | 18 | 19 | def api_view(http_method_names=None, exclude_from_schema=False): 20 | """ 21 | Decorator that converts a function-based view into an APIView subclass. 22 | Takes a list of allowed methods for the view as an argument. 23 | """ 24 | http_method_names = ['GET'] if (http_method_names is None) else http_method_names 25 | 26 | def decorator(func): 27 | 28 | WrappedAPIView = type( 29 | six.PY3 and 'WrappedAPIView' or b'WrappedAPIView', 30 | (APIView,), 31 | {'__doc__': func.__doc__} 32 | ) 33 | 34 | # Note, the above allows us to set the docstring. 35 | # It is the equivalent of: 36 | # 37 | # class WrappedAPIView(APIView): 38 | # pass 39 | # WrappedAPIView.__doc__ = func.doc <--- Not possible to do this 40 | 41 | # api_view applied without (method_names) 42 | assert not(isinstance(http_method_names, types.FunctionType)), \ 43 | '@api_view missing list of allowed HTTP methods' 44 | 45 | # api_view applied with eg. string instead of list of strings 46 | assert isinstance(http_method_names, (list, tuple)), \ 47 | '@api_view expected a list of strings, received %s' % type(http_method_names).__name__ 48 | 49 | allowed_methods = set(http_method_names) | {'options'} 50 | WrappedAPIView.http_method_names = [method.lower() for method in allowed_methods] 51 | 52 | def handler(self, *args, **kwargs): 53 | return func(*args, **kwargs) 54 | 55 | for method in http_method_names: 56 | setattr(WrappedAPIView, method.lower(), handler) 57 | 58 | WrappedAPIView.__name__ = func.__name__ 59 | WrappedAPIView.__module__ = func.__module__ 60 | 61 | WrappedAPIView.renderer_classes = getattr(func, 'renderer_classes', 62 | APIView.renderer_classes) 63 | 64 | WrappedAPIView.parser_classes = getattr(func, 'parser_classes', 65 | APIView.parser_classes) 66 | 67 | WrappedAPIView.authentication_classes = getattr(func, 'authentication_classes', 68 | APIView.authentication_classes) 69 | 70 | WrappedAPIView.throttle_classes = getattr(func, 'throttle_classes', 71 | APIView.throttle_classes) 72 | 73 | WrappedAPIView.permission_classes = getattr(func, 'permission_classes', 74 | APIView.permission_classes) 75 | 76 | WrappedAPIView.schema = getattr(func, 'schema', 77 | APIView.schema) 78 | 79 | if exclude_from_schema: 80 | warnings.warn( 81 | "The `exclude_from_schema` argument to `api_view` is deprecated. " 82 | "Use the `schema` decorator instead, passing `None`.", 83 | DeprecationWarning 84 | ) 85 | WrappedAPIView.exclude_from_schema = exclude_from_schema 86 | 87 | return WrappedAPIView.as_view() 88 | return decorator 89 | 90 | 91 | def renderer_classes(renderer_classes): 92 | def decorator(func): 93 | func.renderer_classes = renderer_classes 94 | return func 95 | return decorator 96 | 97 | 98 | def parser_classes(parser_classes): 99 | def decorator(func): 100 | func.parser_classes = parser_classes 101 | return func 102 | return decorator 103 | 104 | 105 | def authentication_classes(authentication_classes): 106 | def decorator(func): 107 | func.authentication_classes = authentication_classes 108 | return func 109 | return decorator 110 | 111 | 112 | def throttle_classes(throttle_classes): 113 | def decorator(func): 114 | func.throttle_classes = throttle_classes 115 | return func 116 | return decorator 117 | 118 | 119 | def permission_classes(permission_classes): 120 | def decorator(func): 121 | func.permission_classes = permission_classes 122 | return func 123 | return decorator 124 | 125 | 126 | def schema(view_inspector): 127 | def decorator(func): 128 | func.schema = view_inspector 129 | return func 130 | return decorator 131 | 132 | 133 | def action(methods=None, detail=None, url_path=None, url_name=None, **kwargs): 134 | """ 135 | Mark a ViewSet method as a routable action. 136 | 137 | Set the `detail` boolean to determine if this action should apply to 138 | instance/detail requests or collection/list requests. 139 | """ 140 | methods = ['get'] if (methods is None) else methods 141 | methods = [method.lower() for method in methods] 142 | 143 | assert detail is not None, ( 144 | "@action() missing required argument: 'detail'" 145 | ) 146 | 147 | def decorator(func): 148 | func.bind_to_methods = methods 149 | func.detail = detail 150 | func.url_path = url_path if url_path else func.__name__ 151 | func.url_name = url_name if url_name else func.__name__.replace('_', '-') 152 | func.kwargs = kwargs 153 | return func 154 | return decorator 155 | 156 | 157 | def detail_route(methods=None, **kwargs): 158 | """ 159 | Used to mark a method on a ViewSet that should be routed for detail requests. 160 | """ 161 | warnings.warn( 162 | "`detail_route` is pending deprecation and will be removed in 3.10 in favor of " 163 | "`action`, which accepts a `detail` bool. Use `@action(detail=True)` instead.", 164 | PendingDeprecationWarning, stacklevel=2 165 | ) 166 | 167 | def decorator(func): 168 | func = action(methods, detail=True, **kwargs)(func) 169 | if 'url_name' not in kwargs: 170 | func.url_name = func.url_path.replace('_', '-') 171 | return func 172 | return decorator 173 | 174 | 175 | def list_route(methods=None, **kwargs): 176 | """ 177 | Used to mark a method on a ViewSet that should be routed for list requests. 178 | """ 179 | warnings.warn( 180 | "`list_route` is pending deprecation and will be removed in 3.10 in favor of " 181 | "`action`, which accepts a `detail` bool. Use `@action(detail=False)` instead.", 182 | PendingDeprecationWarning, stacklevel=2 183 | ) 184 | 185 | def decorator(func): 186 | func = action(methods, detail=False, **kwargs)(func) 187 | if 'url_name' not in kwargs: 188 | func.url_name = func.url_path.replace('_', '-') 189 | return func 190 | return decorator 191 | -------------------------------------------------------------------------------- /documentation.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import include, url 2 | 3 | from rest_framework.renderers import ( 4 | CoreJSONRenderer, DocumentationRenderer, SchemaJSRenderer 5 | ) 6 | from rest_framework.schemas import SchemaGenerator, get_schema_view 7 | from rest_framework.settings import api_settings 8 | 9 | 10 | def get_docs_view( 11 | title=None, description=None, schema_url=None, public=True, 12 | patterns=None, generator_class=SchemaGenerator, 13 | authentication_classes=api_settings.DEFAULT_AUTHENTICATION_CLASSES, 14 | permission_classes=api_settings.DEFAULT_PERMISSION_CLASSES, 15 | renderer_classes=None): 16 | 17 | if renderer_classes is None: 18 | renderer_classes = [DocumentationRenderer, CoreJSONRenderer] 19 | 20 | return get_schema_view( 21 | title=title, 22 | url=schema_url, 23 | description=description, 24 | renderer_classes=renderer_classes, 25 | public=public, 26 | patterns=patterns, 27 | generator_class=generator_class, 28 | authentication_classes=authentication_classes, 29 | permission_classes=permission_classes, 30 | ) 31 | 32 | 33 | def get_schemajs_view( 34 | title=None, description=None, schema_url=None, public=True, 35 | patterns=None, generator_class=SchemaGenerator, 36 | authentication_classes=api_settings.DEFAULT_AUTHENTICATION_CLASSES, 37 | permission_classes=api_settings.DEFAULT_PERMISSION_CLASSES): 38 | renderer_classes = [SchemaJSRenderer] 39 | 40 | return get_schema_view( 41 | title=title, 42 | url=schema_url, 43 | description=description, 44 | renderer_classes=renderer_classes, 45 | public=public, 46 | patterns=patterns, 47 | generator_class=generator_class, 48 | authentication_classes=authentication_classes, 49 | permission_classes=permission_classes, 50 | ) 51 | 52 | 53 | def include_docs_urls( 54 | title=None, description=None, schema_url=None, public=True, 55 | patterns=None, generator_class=SchemaGenerator, 56 | authentication_classes=api_settings.DEFAULT_AUTHENTICATION_CLASSES, 57 | permission_classes=api_settings.DEFAULT_PERMISSION_CLASSES, 58 | renderer_classes=None): 59 | docs_view = get_docs_view( 60 | title=title, 61 | description=description, 62 | schema_url=schema_url, 63 | public=public, 64 | patterns=patterns, 65 | generator_class=generator_class, 66 | authentication_classes=authentication_classes, 67 | renderer_classes=renderer_classes, 68 | permission_classes=permission_classes, 69 | ) 70 | schema_js_view = get_schemajs_view( 71 | title=title, 72 | description=description, 73 | schema_url=schema_url, 74 | public=public, 75 | patterns=patterns, 76 | generator_class=generator_class, 77 | authentication_classes=authentication_classes, 78 | permission_classes=permission_classes, 79 | ) 80 | urls = [ 81 | url(r'^$', docs_view, name='docs-index'), 82 | url(r'^schema.js$', schema_js_view, name='schema-js') 83 | ] 84 | return include((urls, 'api-docs'), namespace='api-docs') 85 | -------------------------------------------------------------------------------- /locale/ach/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/ach/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/ar/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/ar/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/be/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/be/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/ca/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/ca/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/ca_ES/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/ca_ES/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/cs/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/cs/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/da/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/da/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/de/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/de/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/el/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/el/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/el_GR/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/el_GR/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/en/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/en/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/en_AU/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/en_AU/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/en_CA/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/en_CA/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/en_US/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/en_US/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/es/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/es/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/et/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/et/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/fa/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/fa/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/fa_IR/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/fa_IR/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/fi/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/fi/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/fr/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/fr/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/fr_CA/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/fr_CA/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/gl/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/gl/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/gl_ES/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/gl_ES/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/he_IL/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/he_IL/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/hu/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/hu/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/id/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/id/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/it/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/it/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/ja/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/ja/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/ko_KR/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/ko_KR/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/lv/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/lv/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/mk/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/mk/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/nb/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/nb/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/nl/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/nl/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/nn/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/nn/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/no/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/no/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/pl/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/pl/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/pt/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/pt/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/pt_BR/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/pt_BR/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/pt_PT/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/pt_PT/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/ro/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/ro/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/ru/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/ru/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/sk/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/sk/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/sl/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/sl/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/sv/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/sv/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/tr/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/tr/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/tr_TR/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/tr_TR/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/uk/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/uk/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/vi/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/vi/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/zh_CN/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/zh_CN/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/zh_Hans/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/zh_Hans/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/zh_Hant/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/zh_Hant/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /locale/zh_TW/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/locale/zh_TW/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /metadata.py: -------------------------------------------------------------------------------- 1 | """ 2 | The metadata API is used to allow customization of how `OPTIONS` requests 3 | are handled. We currently provide a single default implementation that returns 4 | some fairly ad-hoc information about the view. 5 | 6 | Future implementations might use JSON schema or other definitions in order 7 | to return this information in a more standardized way. 8 | """ 9 | from __future__ import unicode_literals 10 | 11 | from collections import OrderedDict 12 | 13 | from django.core.exceptions import PermissionDenied 14 | from django.http import Http404 15 | from django.utils.encoding import force_text 16 | 17 | from rest_framework import exceptions, serializers 18 | from rest_framework.request import clone_request 19 | from rest_framework.utils.field_mapping import ClassLookupDict 20 | 21 | 22 | class BaseMetadata(object): 23 | def determine_metadata(self, request, view): 24 | """ 25 | Return a dictionary of metadata about the view. 26 | Used to return responses for OPTIONS requests. 27 | """ 28 | raise NotImplementedError(".determine_metadata() must be overridden.") 29 | 30 | 31 | class SimpleMetadata(BaseMetadata): 32 | """ 33 | This is the default metadata implementation. 34 | It returns an ad-hoc set of information about the view. 35 | There are not any formalized standards for `OPTIONS` responses 36 | for us to base this on. 37 | """ 38 | label_lookup = ClassLookupDict({ 39 | serializers.Field: 'field', 40 | serializers.BooleanField: 'boolean', 41 | serializers.NullBooleanField: 'boolean', 42 | serializers.CharField: 'string', 43 | serializers.UUIDField: 'string', 44 | serializers.URLField: 'url', 45 | serializers.EmailField: 'email', 46 | serializers.RegexField: 'regex', 47 | serializers.SlugField: 'slug', 48 | serializers.IntegerField: 'integer', 49 | serializers.FloatField: 'float', 50 | serializers.DecimalField: 'decimal', 51 | serializers.DateField: 'date', 52 | serializers.DateTimeField: 'datetime', 53 | serializers.TimeField: 'time', 54 | serializers.ChoiceField: 'choice', 55 | serializers.MultipleChoiceField: 'multiple choice', 56 | serializers.FileField: 'file upload', 57 | serializers.ImageField: 'image upload', 58 | serializers.ListField: 'list', 59 | serializers.DictField: 'nested object', 60 | serializers.Serializer: 'nested object', 61 | }) 62 | 63 | def determine_metadata(self, request, view): 64 | metadata = OrderedDict() 65 | metadata['name'] = view.get_view_name() 66 | metadata['description'] = view.get_view_description() 67 | metadata['renders'] = [renderer.media_type for renderer in view.renderer_classes] 68 | metadata['parses'] = [parser.media_type for parser in view.parser_classes] 69 | if hasattr(view, 'get_serializer'): 70 | actions = self.determine_actions(request, view) 71 | if actions: 72 | metadata['actions'] = actions 73 | return metadata 74 | 75 | def determine_actions(self, request, view): 76 | """ 77 | For generic class based views we return information about 78 | the fields that are accepted for 'PUT' and 'POST' methods. 79 | """ 80 | actions = {} 81 | for method in {'PUT', 'POST'} & set(view.allowed_methods): 82 | view.request = clone_request(request, method) 83 | try: 84 | # Test global permissions 85 | if hasattr(view, 'check_permissions'): 86 | view.check_permissions(view.request) 87 | # Test object permissions 88 | if method == 'PUT' and hasattr(view, 'get_object'): 89 | view.get_object() 90 | except (exceptions.APIException, PermissionDenied, Http404): 91 | pass 92 | else: 93 | # If user has appropriate permissions for the view, include 94 | # appropriate metadata about the fields that should be supplied. 95 | serializer = view.get_serializer() 96 | actions[method] = self.get_serializer_info(serializer) 97 | finally: 98 | view.request = request 99 | 100 | return actions 101 | 102 | def get_serializer_info(self, serializer): 103 | """ 104 | Given an instance of a serializer, return a dictionary of metadata 105 | about its fields. 106 | """ 107 | if hasattr(serializer, 'child'): 108 | # If this is a `ListSerializer` then we want to examine the 109 | # underlying child serializer instance instead. 110 | serializer = serializer.child 111 | return OrderedDict([ 112 | (field_name, self.get_field_info(field)) 113 | for field_name, field in serializer.fields.items() 114 | if not isinstance(field, serializers.HiddenField) 115 | ]) 116 | 117 | def get_field_info(self, field): 118 | """ 119 | Given an instance of a serializer field, return a dictionary 120 | of metadata about it. 121 | """ 122 | field_info = OrderedDict() 123 | field_info['type'] = self.label_lookup[field] 124 | field_info['required'] = getattr(field, 'required', False) 125 | 126 | attrs = [ 127 | 'read_only', 'label', 'help_text', 128 | 'min_length', 'max_length', 129 | 'min_value', 'max_value' 130 | ] 131 | 132 | for attr in attrs: 133 | value = getattr(field, attr, None) 134 | if value is not None and value != '': 135 | field_info[attr] = force_text(value, strings_only=True) 136 | 137 | if getattr(field, 'child', None): 138 | field_info['child'] = self.get_field_info(field.child) 139 | elif getattr(field, 'fields', None): 140 | field_info['children'] = self.get_serializer_info(field) 141 | 142 | if (not field_info.get('read_only') and 143 | not isinstance(field, (serializers.RelatedField, serializers.ManyRelatedField)) and 144 | hasattr(field, 'choices')): 145 | field_info['choices'] = [ 146 | { 147 | 'value': choice_value, 148 | 'display_name': force_text(choice_name, strings_only=True) 149 | } 150 | for choice_value, choice_name in field.choices.items() 151 | ] 152 | 153 | return field_info 154 | -------------------------------------------------------------------------------- /mixins.py: -------------------------------------------------------------------------------- 1 | """ 2 | Basic building blocks for generic class based views. 3 | 4 | We don't bind behaviour to http method handlers yet, 5 | which allows mixin classes to be composed in interesting ways. 6 | """ 7 | from __future__ import unicode_literals 8 | 9 | from rest_framework import status 10 | from rest_framework.response import Response 11 | from rest_framework.settings import api_settings 12 | 13 | 14 | class CreateModelMixin(object): 15 | """ 16 | Create a model instance. 17 | """ 18 | def create(self, request, *args, **kwargs): 19 | serializer = self.get_serializer(data=request.data) 20 | serializer.is_valid(raise_exception=True) 21 | self.perform_create(serializer) 22 | headers = self.get_success_headers(serializer.data) 23 | return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) 24 | 25 | def perform_create(self, serializer): 26 | serializer.save() 27 | 28 | def get_success_headers(self, data): 29 | try: 30 | return {'Location': str(data[api_settings.URL_FIELD_NAME])} 31 | except (TypeError, KeyError): 32 | return {} 33 | 34 | 35 | class ListModelMixin(object): 36 | """ 37 | List a queryset. 38 | """ 39 | def list(self, request, *args, **kwargs): 40 | queryset = self.filter_queryset(self.get_queryset()) 41 | 42 | page = self.paginate_queryset(queryset) 43 | if page is not None: 44 | serializer = self.get_serializer(page, many=True) 45 | return self.get_paginated_response(serializer.data) 46 | 47 | serializer = self.get_serializer(queryset, many=True) 48 | return Response(serializer.data) 49 | 50 | 51 | class RetrieveModelMixin(object): 52 | """ 53 | Retrieve a model instance. 54 | """ 55 | def retrieve(self, request, *args, **kwargs): 56 | instance = self.get_object() 57 | serializer = self.get_serializer(instance) 58 | return Response(serializer.data) 59 | 60 | 61 | class UpdateModelMixin(object): 62 | """ 63 | Update a model instance. 64 | """ 65 | def update(self, request, *args, **kwargs): 66 | partial = kwargs.pop('partial', False) 67 | instance = self.get_object() 68 | serializer = self.get_serializer(instance, data=request.data, partial=partial) 69 | serializer.is_valid(raise_exception=True) 70 | self.perform_update(serializer) 71 | 72 | if getattr(instance, '_prefetched_objects_cache', None): 73 | # If 'prefetch_related' has been applied to a queryset, we need to 74 | # forcibly invalidate the prefetch cache on the instance. 75 | instance._prefetched_objects_cache = {} 76 | 77 | return Response(serializer.data) 78 | 79 | def perform_update(self, serializer): 80 | serializer.save() 81 | 82 | def partial_update(self, request, *args, **kwargs): 83 | kwargs['partial'] = True 84 | return self.update(request, *args, **kwargs) 85 | 86 | 87 | class DestroyModelMixin(object): 88 | """ 89 | Destroy a model instance. 90 | """ 91 | def destroy(self, request, *args, **kwargs): 92 | instance = self.get_object() 93 | self.perform_destroy(instance) 94 | return Response(status=status.HTTP_204_NO_CONTENT) 95 | 96 | def perform_destroy(self, instance): 97 | instance.delete() 98 | -------------------------------------------------------------------------------- /models.py: -------------------------------------------------------------------------------- 1 | # Just to keep things like ./manage.py test happy 2 | -------------------------------------------------------------------------------- /negotiation.py: -------------------------------------------------------------------------------- 1 | """ 2 | Content negotiation deals with selecting an appropriate renderer given the 3 | incoming request. Typically this will be based on the request's Accept header. 4 | """ 5 | from __future__ import unicode_literals 6 | 7 | from django.http import Http404 8 | 9 | from rest_framework import HTTP_HEADER_ENCODING, exceptions 10 | from rest_framework.settings import api_settings 11 | from rest_framework.utils.mediatypes import ( 12 | _MediaType, media_type_matches, order_by_precedence 13 | ) 14 | 15 | 16 | class BaseContentNegotiation(object): 17 | def select_parser(self, request, parsers): 18 | raise NotImplementedError('.select_parser() must be implemented') 19 | 20 | def select_renderer(self, request, renderers, format_suffix=None): 21 | raise NotImplementedError('.select_renderer() must be implemented') 22 | 23 | 24 | class DefaultContentNegotiation(BaseContentNegotiation): 25 | settings = api_settings 26 | 27 | def select_parser(self, request, parsers): 28 | """ 29 | Given a list of parsers and a media type, return the appropriate 30 | parser to handle the incoming request. 31 | """ 32 | for parser in parsers: 33 | if media_type_matches(parser.media_type, request.content_type): 34 | return parser 35 | return None 36 | 37 | def select_renderer(self, request, renderers, format_suffix=None): 38 | """ 39 | Given a request and a list of renderers, return a two-tuple of: 40 | (renderer, media type). 41 | """ 42 | # Allow URL style format override. eg. "?format=json 43 | format_query_param = self.settings.URL_FORMAT_OVERRIDE 44 | format = format_suffix or request.query_params.get(format_query_param) 45 | 46 | if format: 47 | renderers = self.filter_renderers(renderers, format) 48 | 49 | accepts = self.get_accept_list(request) 50 | 51 | # Check the acceptable media types against each renderer, 52 | # attempting more specific media types first 53 | # NB. The inner loop here isn't as bad as it first looks :) 54 | # Worst case is we're looping over len(accept_list) * len(self.renderers) 55 | for media_type_set in order_by_precedence(accepts): 56 | for renderer in renderers: 57 | for media_type in media_type_set: 58 | if media_type_matches(renderer.media_type, media_type): 59 | # Return the most specific media type as accepted. 60 | media_type_wrapper = _MediaType(media_type) 61 | if ( 62 | _MediaType(renderer.media_type).precedence > 63 | media_type_wrapper.precedence 64 | ): 65 | # Eg client requests '*/*' 66 | # Accepted media type is 'application/json' 67 | full_media_type = ';'.join( 68 | (renderer.media_type,) + 69 | tuple('{0}={1}'.format( 70 | key, value.decode(HTTP_HEADER_ENCODING)) 71 | for key, value in media_type_wrapper.params.items())) 72 | return renderer, full_media_type 73 | else: 74 | # Eg client requests 'application/json; indent=8' 75 | # Accepted media type is 'application/json; indent=8' 76 | return renderer, media_type 77 | 78 | raise exceptions.NotAcceptable(available_renderers=renderers) 79 | 80 | def filter_renderers(self, renderers, format): 81 | """ 82 | If there is a '.json' style format suffix, filter the renderers 83 | so that we only negotiation against those that accept that format. 84 | """ 85 | renderers = [renderer for renderer in renderers 86 | if renderer.format == format] 87 | if not renderers: 88 | raise Http404 89 | return renderers 90 | 91 | def get_accept_list(self, request): 92 | """ 93 | Given the incoming request, return a tokenized list of media 94 | type strings. 95 | """ 96 | header = request.META.get('HTTP_ACCEPT', '*/*') 97 | return [token.strip() for token in header.split(',')] 98 | -------------------------------------------------------------------------------- /permissions.py: -------------------------------------------------------------------------------- 1 | """ 2 | Provides a set of pluggable permission policies. 3 | """ 4 | from __future__ import unicode_literals 5 | 6 | from django.http import Http404 7 | 8 | from rest_framework import exceptions 9 | 10 | SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS') 11 | 12 | 13 | class BasePermission(object): 14 | """ 15 | A base class from which all permission classes should inherit. 16 | """ 17 | 18 | def has_permission(self, request, view): 19 | """ 20 | Return `True` if permission is granted, `False` otherwise. 21 | """ 22 | return True 23 | 24 | def has_object_permission(self, request, view, obj): 25 | """ 26 | Return `True` if permission is granted, `False` otherwise. 27 | """ 28 | return True 29 | 30 | 31 | class AllowAny(BasePermission): 32 | """ 33 | Allow any access. 34 | This isn't strictly required, since you could use an empty 35 | permission_classes list, but it's useful because it makes the intention 36 | more explicit. 37 | """ 38 | 39 | def has_permission(self, request, view): 40 | return True 41 | 42 | 43 | class IsAuthenticated(BasePermission): 44 | """ 45 | Allows access only to authenticated users. 46 | """ 47 | 48 | def has_permission(self, request, view): 49 | return request.user and request.user.is_authenticated 50 | 51 | 52 | class IsAdminUser(BasePermission): 53 | """ 54 | Allows access only to admin users. 55 | """ 56 | 57 | def has_permission(self, request, view): 58 | return request.user and request.user.is_staff 59 | 60 | 61 | class IsAuthenticatedOrReadOnly(BasePermission): 62 | """ 63 | The request is authenticated as a user, or is a read-only request. 64 | """ 65 | 66 | def has_permission(self, request, view): 67 | return ( 68 | request.method in SAFE_METHODS or 69 | request.user and 70 | request.user.is_authenticated 71 | ) 72 | 73 | 74 | class DjangoModelPermissions(BasePermission): 75 | """ 76 | The request is authenticated using `django.contrib.auth` permissions. 77 | See: https://docs.djangoproject.com/en/dev/topics/auth/#permissions 78 | 79 | It ensures that the user is authenticated, and has the appropriate 80 | `add`/`change`/`delete` permissions on the model. 81 | 82 | This permission can only be applied against view classes that 83 | provide a `.queryset` attribute. 84 | """ 85 | 86 | # Map methods into required permission codes. 87 | # Override this if you need to also provide 'view' permissions, 88 | # or if you want to provide custom permission codes. 89 | perms_map = { 90 | 'GET': [], 91 | 'OPTIONS': [], 92 | 'HEAD': [], 93 | 'POST': ['%(app_label)s.add_%(model_name)s'], 94 | 'PUT': ['%(app_label)s.change_%(model_name)s'], 95 | 'PATCH': ['%(app_label)s.change_%(model_name)s'], 96 | 'DELETE': ['%(app_label)s.delete_%(model_name)s'], 97 | } 98 | 99 | authenticated_users_only = True 100 | 101 | def get_required_permissions(self, method, model_cls): 102 | """ 103 | Given a model and an HTTP method, return the list of permission 104 | codes that the user is required to have. 105 | """ 106 | kwargs = { 107 | 'app_label': model_cls._meta.app_label, 108 | 'model_name': model_cls._meta.model_name 109 | } 110 | 111 | if method not in self.perms_map: 112 | raise exceptions.MethodNotAllowed(method) 113 | 114 | return [perm % kwargs for perm in self.perms_map[method]] 115 | 116 | def _queryset(self, view): 117 | assert hasattr(view, 'get_queryset') \ 118 | or getattr(view, 'queryset', None) is not None, ( 119 | 'Cannot apply {} on a view that does not set ' 120 | '`.queryset` or have a `.get_queryset()` method.' 121 | ).format(self.__class__.__name__) 122 | 123 | if hasattr(view, 'get_queryset'): 124 | queryset = view.get_queryset() 125 | assert queryset is not None, ( 126 | '{}.get_queryset() returned None'.format(view.__class__.__name__) 127 | ) 128 | return queryset 129 | return view.queryset 130 | 131 | def has_permission(self, request, view): 132 | # Workaround to ensure DjangoModelPermissions are not applied 133 | # to the root view when using DefaultRouter. 134 | if getattr(view, '_ignore_model_permissions', False): 135 | return True 136 | 137 | if not request.user or ( 138 | not request.user.is_authenticated and self.authenticated_users_only): 139 | return False 140 | 141 | queryset = self._queryset(view) 142 | perms = self.get_required_permissions(request.method, queryset.model) 143 | 144 | return request.user.has_perms(perms) 145 | 146 | 147 | class DjangoModelPermissionsOrAnonReadOnly(DjangoModelPermissions): 148 | """ 149 | Similar to DjangoModelPermissions, except that anonymous users are 150 | allowed read-only access. 151 | """ 152 | authenticated_users_only = False 153 | 154 | 155 | class DjangoObjectPermissions(DjangoModelPermissions): 156 | """ 157 | The request is authenticated using Django's object-level permissions. 158 | It requires an object-permissions-enabled backend, such as Django Guardian. 159 | 160 | It ensures that the user is authenticated, and has the appropriate 161 | `add`/`change`/`delete` permissions on the object using .has_perms. 162 | 163 | This permission can only be applied against view classes that 164 | provide a `.queryset` attribute. 165 | """ 166 | perms_map = { 167 | 'GET': [], 168 | 'OPTIONS': [], 169 | 'HEAD': [], 170 | 'POST': ['%(app_label)s.add_%(model_name)s'], 171 | 'PUT': ['%(app_label)s.change_%(model_name)s'], 172 | 'PATCH': ['%(app_label)s.change_%(model_name)s'], 173 | 'DELETE': ['%(app_label)s.delete_%(model_name)s'], 174 | } 175 | 176 | def get_required_object_permissions(self, method, model_cls): 177 | kwargs = { 178 | 'app_label': model_cls._meta.app_label, 179 | 'model_name': model_cls._meta.model_name 180 | } 181 | 182 | if method not in self.perms_map: 183 | raise exceptions.MethodNotAllowed(method) 184 | 185 | return [perm % kwargs for perm in self.perms_map[method]] 186 | 187 | def has_object_permission(self, request, view, obj): 188 | # authentication checks have already executed via has_permission 189 | queryset = self._queryset(view) 190 | model_cls = queryset.model 191 | user = request.user 192 | 193 | perms = self.get_required_object_permissions(request.method, model_cls) 194 | 195 | if not user.has_perms(perms, obj): 196 | # If the user does not have permissions we need to determine if 197 | # they have read permissions to see 403, or not, and simply see 198 | # a 404 response. 199 | 200 | if request.method in SAFE_METHODS: 201 | # Read permissions already checked and failed, no need 202 | # to make another lookup. 203 | raise Http404 204 | 205 | read_perms = self.get_required_object_permissions('GET', model_cls) 206 | if not user.has_perms(read_perms, obj): 207 | raise Http404 208 | 209 | # Has read permissions. 210 | return False 211 | 212 | return True 213 | -------------------------------------------------------------------------------- /response.py: -------------------------------------------------------------------------------- 1 | """ 2 | The Response class in REST framework is similar to HTTPResponse, except that 3 | it is initialized with unrendered data, instead of a pre-rendered string. 4 | 5 | The appropriate renderer is called during Django's template response rendering. 6 | """ 7 | from __future__ import unicode_literals 8 | 9 | from django.template.response import SimpleTemplateResponse 10 | from django.utils import six 11 | from django.utils.six.moves.http_client import responses 12 | 13 | from rest_framework.serializers import Serializer 14 | 15 | 16 | class Response(SimpleTemplateResponse): 17 | """ 18 | An HttpResponse that allows its data to be rendered into 19 | arbitrary media types. 20 | """ 21 | 22 | def __init__(self, data=None, status=None, 23 | template_name=None, headers=None, 24 | exception=False, content_type=None): 25 | """ 26 | Alters the init arguments slightly. 27 | For example, drop 'template_name', and instead use 'data'. 28 | 29 | Setting 'renderer' and 'media_type' will typically be deferred, 30 | For example being set automatically by the `APIView`. 31 | """ 32 | super(Response, self).__init__(None, status=status) 33 | 34 | if isinstance(data, Serializer): 35 | msg = ( 36 | 'You passed a Serializer instance as data, but ' 37 | 'probably meant to pass serialized `.data` or ' 38 | '`.error`. representation.' 39 | ) 40 | raise AssertionError(msg) 41 | 42 | self.data = data 43 | self.template_name = template_name 44 | self.exception = exception 45 | self.content_type = content_type 46 | 47 | if headers: 48 | for name, value in six.iteritems(headers): 49 | self[name] = value 50 | 51 | @property 52 | def rendered_content(self): 53 | renderer = getattr(self, 'accepted_renderer', None) 54 | accepted_media_type = getattr(self, 'accepted_media_type', None) 55 | context = getattr(self, 'renderer_context', None) 56 | 57 | assert renderer, ".accepted_renderer not set on Response" 58 | assert accepted_media_type, ".accepted_media_type not set on Response" 59 | assert context is not None, ".renderer_context not set on Response" 60 | context['response'] = self 61 | 62 | media_type = renderer.media_type 63 | charset = renderer.charset 64 | content_type = self.content_type 65 | 66 | if content_type is None and charset is not None: 67 | content_type = "{0}; charset={1}".format(media_type, charset) 68 | elif content_type is None: 69 | content_type = media_type 70 | self['Content-Type'] = content_type 71 | 72 | ret = renderer.render(self.data, accepted_media_type, context) 73 | if isinstance(ret, six.text_type): 74 | assert charset, ( 75 | 'renderer returned unicode, and did not specify ' 76 | 'a charset value.' 77 | ) 78 | return bytes(ret.encode(charset)) 79 | 80 | if not ret: 81 | del self['Content-Type'] 82 | 83 | return ret 84 | 85 | @property 86 | def status_text(self): 87 | """ 88 | Returns reason text corresponding to our HTTP response status code. 89 | Provided for convenience. 90 | """ 91 | return responses.get(self.status_code, '') 92 | 93 | def __getstate__(self): 94 | """ 95 | Remove attributes from the response that shouldn't be cached. 96 | """ 97 | state = super(Response, self).__getstate__() 98 | for key in ( 99 | 'accepted_renderer', 'renderer_context', 'resolver_match', 100 | 'client', 'request', 'json', 'wsgi_request' 101 | ): 102 | if key in state: 103 | del state[key] 104 | state['_closable_objects'] = [] 105 | return state 106 | -------------------------------------------------------------------------------- /reverse.py: -------------------------------------------------------------------------------- 1 | """ 2 | Provide urlresolver functions that return fully qualified URLs or view names 3 | """ 4 | from __future__ import unicode_literals 5 | 6 | from django.urls import NoReverseMatch 7 | from django.urls import reverse as django_reverse 8 | from django.utils import six 9 | from django.utils.functional import lazy 10 | 11 | from rest_framework.settings import api_settings 12 | from rest_framework.utils.urls import replace_query_param 13 | 14 | 15 | def preserve_builtin_query_params(url, request=None): 16 | """ 17 | Given an incoming request, and an outgoing URL representation, 18 | append the value of any built-in query parameters. 19 | """ 20 | if request is None: 21 | return url 22 | 23 | overrides = [ 24 | api_settings.URL_FORMAT_OVERRIDE, 25 | ] 26 | 27 | for param in overrides: 28 | if param and (param in request.GET): 29 | value = request.GET[param] 30 | url = replace_query_param(url, param, value) 31 | 32 | return url 33 | 34 | 35 | def reverse(viewname, args=None, kwargs=None, request=None, format=None, **extra): 36 | """ 37 | If versioning is being used then we pass any `reverse` calls through 38 | to the versioning scheme instance, so that the resulting URL 39 | can be modified if needed. 40 | """ 41 | scheme = getattr(request, 'versioning_scheme', None) 42 | if scheme is not None: 43 | try: 44 | url = scheme.reverse(viewname, args, kwargs, request, format, **extra) 45 | except NoReverseMatch: 46 | # In case the versioning scheme reversal fails, fallback to the 47 | # default implementation 48 | url = _reverse(viewname, args, kwargs, request, format, **extra) 49 | else: 50 | url = _reverse(viewname, args, kwargs, request, format, **extra) 51 | 52 | return preserve_builtin_query_params(url, request) 53 | 54 | 55 | def _reverse(viewname, args=None, kwargs=None, request=None, format=None, **extra): 56 | """ 57 | Same as `django.urls.reverse`, but optionally takes a request 58 | and returns a fully qualified URL, using the request to get the base URL. 59 | """ 60 | if format is not None: 61 | kwargs = kwargs or {} 62 | kwargs['format'] = format 63 | url = django_reverse(viewname, args=args, kwargs=kwargs, **extra) 64 | if request: 65 | return request.build_absolute_uri(url) 66 | return url 67 | 68 | 69 | reverse_lazy = lazy(reverse, six.text_type) 70 | -------------------------------------------------------------------------------- /schemas/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | rest_framework.schemas 3 | 4 | schemas: 5 | __init__.py 6 | generators.py # Top-down schema generation 7 | inspectors.py # Per-endpoint view introspection 8 | utils.py # Shared helper functions 9 | views.py # Houses `SchemaView`, `APIView` subclass. 10 | 11 | We expose a minimal "public" API directly from `schemas`. This covers the 12 | basic use-cases: 13 | 14 | from rest_framework.schemas import ( 15 | AutoSchema, 16 | ManualSchema, 17 | get_schema_view, 18 | SchemaGenerator, 19 | ) 20 | 21 | Other access should target the submodules directly 22 | """ 23 | from rest_framework.settings import api_settings 24 | 25 | from .generators import SchemaGenerator 26 | from .inspectors import AutoSchema, DefaultSchema, ManualSchema # noqa 27 | 28 | 29 | def get_schema_view( 30 | title=None, url=None, description=None, urlconf=None, renderer_classes=None, 31 | public=False, patterns=None, generator_class=SchemaGenerator, 32 | authentication_classes=api_settings.DEFAULT_AUTHENTICATION_CLASSES, 33 | permission_classes=api_settings.DEFAULT_PERMISSION_CLASSES): 34 | """ 35 | Return a schema view. 36 | """ 37 | # Avoid import cycle on APIView 38 | from .views import SchemaView 39 | generator = generator_class( 40 | title=title, url=url, description=description, 41 | urlconf=urlconf, patterns=patterns, 42 | ) 43 | return SchemaView.as_view( 44 | renderer_classes=renderer_classes, 45 | schema_generator=generator, 46 | public=public, 47 | authentication_classes=authentication_classes, 48 | permission_classes=permission_classes, 49 | ) 50 | -------------------------------------------------------------------------------- /schemas/utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | utils.py # Shared helper functions 3 | 4 | See schemas.__init__.py for package overview. 5 | """ 6 | from rest_framework.mixins import RetrieveModelMixin 7 | 8 | 9 | def is_list_view(path, method, view): 10 | """ 11 | Return True if the given path/method appears to represent a list view. 12 | """ 13 | if hasattr(view, 'action'): 14 | # Viewsets have an explicitly defined action, which we can inspect. 15 | return view.action == 'list' 16 | 17 | if method.lower() != 'get': 18 | return False 19 | if isinstance(view, RetrieveModelMixin): 20 | return False 21 | path_components = path.strip('/').split('/') 22 | if path_components and '{' in path_components[-1]: 23 | return False 24 | return True 25 | -------------------------------------------------------------------------------- /schemas/views.py: -------------------------------------------------------------------------------- 1 | """ 2 | views.py # Houses `SchemaView`, `APIView` subclass. 3 | 4 | See schemas.__init__.py for package overview. 5 | """ 6 | from rest_framework import exceptions, renderers 7 | from rest_framework.response import Response 8 | from rest_framework.settings import api_settings 9 | from rest_framework.views import APIView 10 | 11 | 12 | class SchemaView(APIView): 13 | _ignore_model_permissions = True 14 | schema = None # exclude from schema 15 | renderer_classes = None 16 | schema_generator = None 17 | public = False 18 | 19 | def __init__(self, *args, **kwargs): 20 | super(SchemaView, self).__init__(*args, **kwargs) 21 | if self.renderer_classes is None: 22 | if renderers.BrowsableAPIRenderer in api_settings.DEFAULT_RENDERER_CLASSES: 23 | self.renderer_classes = [ 24 | renderers.CoreJSONRenderer, 25 | renderers.BrowsableAPIRenderer, 26 | ] 27 | else: 28 | self.renderer_classes = [renderers.CoreJSONRenderer] 29 | 30 | def get(self, request, *args, **kwargs): 31 | schema = self.schema_generator.get_schema(request, self.public) 32 | if schema is None: 33 | raise exceptions.PermissionDenied() 34 | return Response(schema) 35 | -------------------------------------------------------------------------------- /static/rest_framework/css/bootstrap-tweaks.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | This CSS file contains some tweaks specific to the included Bootstrap theme. 4 | It's separate from `style.css` so that it can be easily overridden by replacing 5 | a single block in the template. 6 | 7 | */ 8 | 9 | .form-actions { 10 | background: transparent; 11 | border-top-color: transparent; 12 | padding-top: 0; 13 | text-align: right; 14 | } 15 | 16 | #generic-content-form textarea { 17 | font-family:Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace; 18 | font-size: 80%; 19 | } 20 | 21 | .navbar-inverse .brand a { 22 | color: #999999; 23 | } 24 | .navbar-inverse .brand:hover a { 25 | color: white; 26 | text-decoration: none; 27 | } 28 | 29 | /* custom navigation styles */ 30 | .navbar { 31 | width: 100%; 32 | position: fixed; 33 | left: 0; 34 | top: 0; 35 | } 36 | 37 | .navbar { 38 | background: #2C2C2C; 39 | color: white; 40 | border: none; 41 | border-top: 5px solid #A30000; 42 | border-radius: 0px; 43 | } 44 | 45 | .navbar .nav li, .navbar .nav li a, .navbar .brand:hover { 46 | color: white; 47 | } 48 | 49 | .nav-list > .active > a, .nav-list > .active > a:hover { 50 | background: #2C2C2C; 51 | } 52 | 53 | .navbar .dropdown-menu li a, .navbar .dropdown-menu li { 54 | color: #A30000; 55 | } 56 | 57 | .navbar .dropdown-menu li a:hover { 58 | background: #EEEEEE; 59 | color: #C20000; 60 | } 61 | 62 | ul.breadcrumb { 63 | margin: 70px 0 0 0; 64 | } 65 | 66 | .breadcrumb li.active a { 67 | color: #777; 68 | } 69 | 70 | .pagination>.disabled>a, 71 | .pagination>.disabled>a:hover, 72 | .pagination>.disabled>a:focus { 73 | cursor: not-allowed; 74 | pointer-events: none; 75 | } 76 | 77 | .pager>.disabled>a, 78 | .pager>.disabled>a:hover, 79 | .pager>.disabled>a:focus { 80 | pointer-events: none; 81 | } 82 | 83 | .pager .next { 84 | margin-left: 10px; 85 | } 86 | 87 | /*=== dabapps bootstrap styles ====*/ 88 | 89 | html { 90 | width:100%; 91 | background: none; 92 | } 93 | 94 | /*body, .navbar .container-fluid { 95 | max-width: 1150px; 96 | margin: 0 auto; 97 | }*/ 98 | 99 | body { 100 | background: url("../img/grid.png") repeat-x; 101 | background-attachment: fixed; 102 | } 103 | 104 | #content { 105 | margin: 0; 106 | padding-bottom: 60px; 107 | } 108 | 109 | /* sticky footer and footer */ 110 | html, body { 111 | height: 100%; 112 | } 113 | 114 | .wrapper { 115 | position: relative; 116 | top: 0; 117 | left: 0; 118 | padding-top: 60px; 119 | margin: -60px 0; 120 | min-height: 100%; 121 | } 122 | 123 | .form-switcher { 124 | margin-bottom: 0; 125 | } 126 | 127 | .well { 128 | -webkit-box-shadow: none; 129 | -moz-box-shadow: none; 130 | box-shadow: none; 131 | } 132 | 133 | .well .form-actions { 134 | padding-bottom: 0; 135 | margin-bottom: 0; 136 | } 137 | 138 | .well form { 139 | margin-bottom: 0; 140 | } 141 | 142 | .nav-tabs { 143 | border: 0; 144 | } 145 | 146 | .nav-tabs > li { 147 | float: right; 148 | } 149 | 150 | .nav-tabs li a { 151 | margin-right: 0; 152 | } 153 | 154 | .nav-tabs > .active > a { 155 | background: #F5F5F5; 156 | } 157 | 158 | .nav-tabs > .active > a:hover { 159 | background: #F5F5F5; 160 | } 161 | 162 | .tabbable.first-tab-active .tab-content { 163 | border-top-right-radius: 0; 164 | } 165 | 166 | footer { 167 | position: absolute; 168 | bottom: 0; 169 | left: 0; 170 | clear: both; 171 | z-index: 10; 172 | height: 60px; 173 | width: 95%; 174 | margin: 0 2.5%; 175 | } 176 | 177 | footer p { 178 | text-align: center; 179 | color: gray; 180 | border-top: 1px solid #DDDDDD; 181 | padding-top: 10px; 182 | } 183 | 184 | footer a { 185 | color: gray !important; 186 | font-weight: bold; 187 | } 188 | 189 | footer a:hover { 190 | color: gray; 191 | } 192 | 193 | .page-header { 194 | border-bottom: none; 195 | padding-bottom: 0px; 196 | margin: 0; 197 | } 198 | 199 | /* custom general page styles */ 200 | .hero-unit h1, .hero-unit h2 { 201 | color: #A30000; 202 | } 203 | 204 | body a { 205 | color: #A30000; 206 | } 207 | 208 | body a:hover { 209 | color: #c20000; 210 | } 211 | 212 | .request-info { 213 | clear:both; 214 | } 215 | 216 | .horizontal-checkbox label { 217 | padding-top: 0; 218 | } 219 | 220 | .horizontal-checkbox label { 221 | padding-top: 0 !important; 222 | } 223 | 224 | .horizontal-checkbox input { 225 | float: left; 226 | width: 20px; 227 | margin-top: 3px; 228 | } 229 | 230 | .modal-footer form { 231 | margin-left: 5px; 232 | margin-right: 5px; 233 | } 234 | -------------------------------------------------------------------------------- /static/rest_framework/css/default.css: -------------------------------------------------------------------------------- 1 | /* The navbar is fixed at >= 980px wide, so add padding to the body to prevent 2 | content running up underneath it. */ 3 | 4 | h1 { 5 | font-weight: 300; 6 | } 7 | 8 | h2, h3 { 9 | font-weight: 300; 10 | } 11 | 12 | .resource-description, .response-info { 13 | margin-bottom: 2em; 14 | } 15 | 16 | .version:before { 17 | content: "v"; 18 | opacity: 0.6; 19 | padding-right: 0.25em; 20 | } 21 | 22 | .version { 23 | font-size: 70%; 24 | } 25 | 26 | .format-option { 27 | font-family: Menlo, Consolas, "Andale Mono", "Lucida Console", monospace; 28 | } 29 | 30 | .button-form { 31 | float: right; 32 | margin-right: 1em; 33 | } 34 | 35 | td.nested { 36 | padding: 0 !important; 37 | } 38 | 39 | td.nested > table { 40 | margin: 0; 41 | } 42 | 43 | form select, form input, form textarea { 44 | width: 90%; 45 | } 46 | 47 | form select[multiple] { 48 | height: 150px; 49 | } 50 | 51 | /* To allow tooltips to work on disabled elements */ 52 | .disabled-tooltip-shield { 53 | position: absolute; 54 | top: 0; 55 | right: 0; 56 | bottom: 0; 57 | left: 0; 58 | } 59 | 60 | .errorlist { 61 | margin-top: 0.5em; 62 | } 63 | 64 | pre { 65 | overflow: auto; 66 | word-wrap: normal; 67 | white-space: pre; 68 | font-size: 12px; 69 | } 70 | 71 | .page-header { 72 | border-bottom: none; 73 | padding-bottom: 0px; 74 | } 75 | 76 | #filtersModal form input[type=submit] { 77 | width: auto; 78 | } 79 | 80 | #filtersModal .modal-body h2 { 81 | margin-top: 0 82 | } 83 | -------------------------------------------------------------------------------- /static/rest_framework/css/prettify.css: -------------------------------------------------------------------------------- 1 | .com { color: #93a1a1; } 2 | .lit { color: #195f91; } 3 | .pun, .opn, .clo { color: #93a1a1; } 4 | .fun { color: #dc322f; } 5 | .str, .atv { color: #D14; } 6 | .kwd, .prettyprint .tag { color: #1e347b; } 7 | .typ, .atn, .dec, .var { color: teal; } 8 | .pln { color: #48484c; } 9 | 10 | .prettyprint { 11 | padding: 8px; 12 | background-color: #f7f7f9; 13 | border: 1px solid #e1e1e8; 14 | } 15 | .prettyprint.linenums { 16 | -webkit-box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0; 17 | -moz-box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0; 18 | box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0; 19 | } 20 | 21 | /* Specify class=linenums on a pre to get line numbering */ 22 | ol.linenums { 23 | margin: 0 0 0 33px; /* IE indents via margin-left */ 24 | } 25 | ol.linenums li { 26 | padding-left: 12px; 27 | color: #bebec5; 28 | line-height: 20px; 29 | text-shadow: 0 1px 0 #fff; 30 | } -------------------------------------------------------------------------------- /static/rest_framework/docs/css/highlight.css: -------------------------------------------------------------------------------- 1 | /* 2 | This is the GitHub theme for highlight.js 3 | 4 | github.com style (c) Vasily Polovnyov 5 | 6 | */ 7 | 8 | .hljs { 9 | display: block; 10 | overflow-x: auto; 11 | padding: 0.5em; 12 | color: #333; 13 | -webkit-text-size-adjust: none; 14 | } 15 | 16 | .hljs-comment, 17 | .diff .hljs-header, 18 | .hljs-javadoc { 19 | color: #998; 20 | font-style: italic; 21 | } 22 | 23 | .hljs-keyword, 24 | .css .rule .hljs-keyword, 25 | .hljs-winutils, 26 | .nginx .hljs-title, 27 | .hljs-subst, 28 | .hljs-request, 29 | .hljs-status { 30 | color: #333; 31 | font-weight: bold; 32 | } 33 | 34 | .hljs-number, 35 | .hljs-hexcolor, 36 | .ruby .hljs-constant { 37 | color: #008080; 38 | } 39 | 40 | .hljs-string, 41 | .hljs-tag .hljs-value, 42 | .hljs-phpdoc, 43 | .hljs-dartdoc, 44 | .tex .hljs-formula { 45 | color: #d14; 46 | } 47 | 48 | .hljs-title, 49 | .hljs-id, 50 | .scss .hljs-preprocessor { 51 | color: #900; 52 | font-weight: bold; 53 | } 54 | 55 | .hljs-list .hljs-keyword, 56 | .hljs-subst { 57 | font-weight: normal; 58 | } 59 | 60 | .hljs-class .hljs-title, 61 | .hljs-type, 62 | .vhdl .hljs-literal, 63 | .tex .hljs-command { 64 | color: #458; 65 | font-weight: bold; 66 | } 67 | 68 | .hljs-tag, 69 | .hljs-tag .hljs-title, 70 | .hljs-rule .hljs-property, 71 | .django .hljs-tag .hljs-keyword { 72 | color: #000080; 73 | font-weight: normal; 74 | } 75 | 76 | .hljs-attribute, 77 | .hljs-variable, 78 | .lisp .hljs-body, 79 | .hljs-name { 80 | color: #008080; 81 | } 82 | 83 | .hljs-regexp { 84 | color: #009926; 85 | } 86 | 87 | .hljs-symbol, 88 | .ruby .hljs-symbol .hljs-string, 89 | .lisp .hljs-keyword, 90 | .clojure .hljs-keyword, 91 | .scheme .hljs-keyword, 92 | .tex .hljs-special, 93 | .hljs-prompt { 94 | color: #990073; 95 | } 96 | 97 | .hljs-built_in { 98 | color: #0086b3; 99 | } 100 | 101 | .hljs-preprocessor, 102 | .hljs-pragma, 103 | .hljs-pi, 104 | .hljs-doctype, 105 | .hljs-shebang, 106 | .hljs-cdata { 107 | color: #999; 108 | font-weight: bold; 109 | } 110 | 111 | .hljs-deletion { 112 | background: #fdd; 113 | } 114 | 115 | .hljs-addition { 116 | background: #dfd; 117 | } 118 | 119 | .diff .hljs-change { 120 | background: #0086b3; 121 | } 122 | 123 | .hljs-chunk { 124 | color: #aaa; 125 | } 126 | -------------------------------------------------------------------------------- /static/rest_framework/docs/css/jquery.json-view.min.css: -------------------------------------------------------------------------------- 1 | .json-view{position:relative} 2 | .json-view .collapser{width:20px;height:18px;display:block;position:absolute;left:-1.7em;top:-.2em;z-index:5;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAD1JREFUeNpiYGBgOADE%2F3Hgw0DM4IRHgSsDFOzFInmMAQnY49ONzZRjDFiADT7dMLALiE8y4AGW6LoBAgwAuIkf%2F%2FB7O9sAAAAASUVORK5CYII%3D);background-repeat:no-repeat;background-position:center center;opacity:.5;cursor:pointer} 3 | .json-view .collapsed{-ms-transform:rotate(-90deg);-moz-transform:rotate(-90deg);-khtml-transform:rotate(-90deg);-webkit-transform:rotate(-90deg);-o-transform:rotate(-90deg);transform:rotate(-90deg)} 4 | .json-view .bl{display:block;padding-left:20px;margin-left:-20px;position:relative} 5 | .json-view{font-family:monospace} 6 | .json-view ul{list-style-type:none;padding-left:2em;border-left:1px dotted;margin:.3em} 7 | .json-view ul li{position:relative} 8 | .json-view .comments,.json-view .dots{display:none;-moz-user-select:none;-ms-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;user-select:none} 9 | .json-view .comments{padding-left:.8em;font-style:italic;color:#888} 10 | .json-view .bool,.json-view .null,.json-view .num,.json-view .undef{font-weight:700;color:#1A01CC} 11 | .json-view .str{color:#800} -------------------------------------------------------------------------------- /static/rest_framework/docs/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/static/rest_framework/docs/img/favicon.ico -------------------------------------------------------------------------------- /static/rest_framework/docs/img/grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/static/rest_framework/docs/img/grid.png -------------------------------------------------------------------------------- /static/rest_framework/docs/js/jquery.json-view.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jquery.json-view - jQuery collapsible JSON plugin 3 | * @version v1.0.0 4 | * @link http://github.com/bazh/jquery.json-view 5 | * @license MIT 6 | */ 7 | !function(e){"use strict";var n=function(n){var a=e("",{"class":"collapser",on:{click:function(){var n=e(this);n.toggleClass("collapsed");var a=n.parent().children(".block"),p=a.children("ul");n.hasClass("collapsed")?(p.hide(),a.children(".dots, .comments").show()):(p.show(),a.children(".dots, .comments").hide())}}});return n&&a.addClass("collapsed"),a},a=function(a,p){var t=e.extend({},{nl2br:!0},p),r=function(e){return e.toString()?e.toString().replace(/&/g,"&").replace(/"/g,""").replace(//g,">"):""},s=function(n,a){return e("",{"class":a,html:r(n)})},l=function(a,p){switch(e.type(a)){case"object":p||(p=0);var c=e("",{"class":"block"}),d=Object.keys(a).length;if(!d)return c.append(s("{","b")).append(" ").append(s("}","b"));c.append(s("{","b"));var i=e("
    ",{"class":"obj collapsible level"+p});return e.each(a,function(a,t){d--;var r=e("
  • ").append(s('"',"q")).append(a).append(s('"',"q")).append(": ").append(l(t,p+1));-1===["object","array"].indexOf(e.type(t))||e.isEmptyObject(t)||r.prepend(n()),d>0&&r.append(","),i.append(r)}),c.append(i),c.append(s("...","dots")),c.append(s("}","b")),c.append(1===Object.keys(a).length?s("// 1 item","comments"):s("// "+Object.keys(a).length+" items","comments")),c;case"array":p||(p=0);var d=a.length,c=e("",{"class":"block"});if(!d)return c.append(s("[","b")).append(" ").append(s("]","b"));c.append(s("[","b"));var i=e("
      ",{"class":"obj collapsible level"+p});return e.each(a,function(a,t){d--;var r=e("
    • ").append(l(t,p+1));-1===["object","array"].indexOf(e.type(t))||e.isEmptyObject(t)||r.prepend(n()),d>0&&r.append(","),i.append(r)}),c.append(i),c.append(s("...","dots")),c.append(s("]","b")),c.append(1===a.length?s("// 1 item","comments"):s("// "+a.length+" items","comments")),c;case"string":if(a=r(a),/^(http|https|file):\/\/[^\s]+$/i.test(a))return e("").append(s('"',"q")).append(e("",{href:a,text:a})).append(s('"',"q"));if(t.nl2br){var o=/\n/g;o.test(a)&&(a=(a+"").replace(o,"
      "))}var u=e("",{"class":"str"}).html(a);return e("").append(s('"',"q")).append(u).append(s('"',"q"));case"number":return s(a.toString(),"num");case"undefined":return s("undefined","undef");case"null":return s("null","null");case"boolean":return s(a?"true":"false","bool")}};return l(a)};return e.fn.jsonView=function(n,p){var t=e(this);if(p=e.extend({},{nl2br:!0},p),"string"==typeof n)try{n=JSON.parse(n)}catch(r){}return t.append(e("
      ",{"class":"json-view"}).append(a(n,p))),t}}(jQuery); -------------------------------------------------------------------------------- /static/rest_framework/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/static/rest_framework/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /static/rest_framework/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/static/rest_framework/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /static/rest_framework/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/static/rest_framework/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /static/rest_framework/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/static/rest_framework/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /static/rest_framework/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/static/rest_framework/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /static/rest_framework/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/static/rest_framework/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /static/rest_framework/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/static/rest_framework/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /static/rest_framework/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/static/rest_framework/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /static/rest_framework/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/static/rest_framework/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /static/rest_framework/img/grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/static/rest_framework/img/grid.png -------------------------------------------------------------------------------- /static/rest_framework/js/ajax-form.js: -------------------------------------------------------------------------------- 1 | function replaceDocument(docString) { 2 | var doc = document.open("text/html"); 3 | 4 | doc.write(docString); 5 | doc.close(); 6 | } 7 | 8 | function doAjaxSubmit(e) { 9 | var form = $(this); 10 | var btn = $(this.clk); 11 | var method = ( 12 | btn.data('method') || 13 | form.data('method') || 14 | form.attr('method') || 'GET' 15 | ).toUpperCase(); 16 | 17 | if (method === 'GET') { 18 | // GET requests can always use standard form submits. 19 | return; 20 | } 21 | 22 | var contentType = 23 | form.find('input[data-override="content-type"]').val() || 24 | form.find('select[data-override="content-type"] option:selected').text(); 25 | 26 | if (method === 'POST' && !contentType) { 27 | // POST requests can use standard form submits, unless we have 28 | // overridden the content type. 29 | return; 30 | } 31 | 32 | // At this point we need to make an AJAX form submission. 33 | e.preventDefault(); 34 | 35 | var url = form.attr('action'); 36 | var data; 37 | 38 | if (contentType) { 39 | data = form.find('[data-override="content"]').val() || '' 40 | 41 | if (contentType === 'multipart/form-data') { 42 | // We need to add a boundary parameter to the header 43 | // We assume the first valid-looking boundary line in the body is correct 44 | // regex is from RFC 2046 appendix A 45 | var boundaryCharNoSpace = "0-9A-Z'()+_,-./:=?"; 46 | var boundaryChar = boundaryCharNoSpace + ' '; 47 | var re = new RegExp('^--([' + boundaryChar + ']{0,69}[' + boundaryCharNoSpace + '])[\\s]*?$', 'im'); 48 | var boundary = data.match(re); 49 | if (boundary !== null) { 50 | contentType += '; boundary="' + boundary[1] + '"'; 51 | } 52 | // Fix textarea.value EOL normalisation (multipart/form-data should use CR+NL, not NL) 53 | data = data.replace(/\n/g, '\r\n'); 54 | } 55 | } else { 56 | contentType = form.attr('enctype') || form.attr('encoding') 57 | 58 | if (contentType === 'multipart/form-data') { 59 | if (!window.FormData) { 60 | alert('Your browser does not support AJAX multipart form submissions'); 61 | return; 62 | } 63 | 64 | // Use the FormData API and allow the content type to be set automatically, 65 | // so it includes the boundary string. 66 | // See https://developer.mozilla.org/en-US/docs/Web/API/FormData/Using_FormData_Objects 67 | contentType = false; 68 | data = new FormData(form[0]); 69 | } else { 70 | contentType = 'application/x-www-form-urlencoded; charset=UTF-8' 71 | data = form.serialize(); 72 | } 73 | } 74 | 75 | var ret = $.ajax({ 76 | url: url, 77 | method: method, 78 | data: data, 79 | contentType: contentType, 80 | processData: false, 81 | headers: { 82 | 'Accept': 'text/html; q=1.0, */*' 83 | }, 84 | }); 85 | 86 | ret.always(function(data, textStatus, jqXHR) { 87 | if (textStatus != 'success') { 88 | jqXHR = data; 89 | } 90 | 91 | var responseContentType = jqXHR.getResponseHeader("content-type") || ""; 92 | 93 | if (responseContentType.toLowerCase().indexOf('text/html') === 0) { 94 | replaceDocument(jqXHR.responseText); 95 | 96 | try { 97 | // Modify the location and scroll to top, as if after page load. 98 | history.replaceState({}, '', url); 99 | scroll(0, 0); 100 | } catch (err) { 101 | // History API not supported, so redirect. 102 | window.location = url; 103 | } 104 | } else { 105 | // Not HTML content. We can't open this directly, so redirect. 106 | window.location = url; 107 | } 108 | }); 109 | 110 | return ret; 111 | } 112 | 113 | function captureSubmittingElement(e) { 114 | var target = e.target; 115 | var form = this; 116 | 117 | form.clk = target; 118 | } 119 | 120 | $.fn.ajaxForm = function() { 121 | var options = {} 122 | 123 | return this 124 | .unbind('submit.form-plugin click.form-plugin') 125 | .bind('submit.form-plugin', options, doAjaxSubmit) 126 | .bind('click.form-plugin', options, captureSubmittingElement); 127 | }; 128 | -------------------------------------------------------------------------------- /static/rest_framework/js/csrf.js: -------------------------------------------------------------------------------- 1 | function getCookie(name) { 2 | var cookieValue = null; 3 | 4 | if (document.cookie && document.cookie != '') { 5 | var cookies = document.cookie.split(';'); 6 | 7 | for (var i = 0; i < cookies.length; i++) { 8 | var cookie = jQuery.trim(cookies[i]); 9 | 10 | // Does this cookie string begin with the name we want? 11 | if (cookie.substring(0, name.length + 1) == (name + '=')) { 12 | cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); 13 | break; 14 | } 15 | } 16 | } 17 | 18 | return cookieValue; 19 | } 20 | 21 | function csrfSafeMethod(method) { 22 | // these HTTP methods do not require CSRF protection 23 | return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); 24 | } 25 | 26 | function sameOrigin(url) { 27 | // test that a given url is a same-origin URL 28 | // url could be relative or scheme relative or absolute 29 | var host = document.location.host; // host + port 30 | var protocol = document.location.protocol; 31 | var sr_origin = '//' + host; 32 | var origin = protocol + sr_origin; 33 | 34 | // Allow absolute or scheme relative URLs to same origin 35 | return (url == origin || url.slice(0, origin.length + 1) == origin + '/') || 36 | (url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') || 37 | // or any other URL that isn't scheme relative or absolute i.e relative. 38 | !(/^(\/\/|http:|https:).*/.test(url)); 39 | } 40 | 41 | var csrftoken = getCookie(window.drf.csrfCookieName); 42 | 43 | $.ajaxSetup({ 44 | beforeSend: function(xhr, settings) { 45 | if (!csrfSafeMethod(settings.type) && sameOrigin(settings.url)) { 46 | // Send the token to same-origin, relative URLs only. 47 | // Send the token only if the method warrants CSRF protection 48 | // Using the CSRFToken value acquired earlier 49 | xhr.setRequestHeader(window.drf.csrfHeaderName, csrftoken); 50 | } 51 | } 52 | }); 53 | -------------------------------------------------------------------------------- /static/rest_framework/js/default.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | // JSON highlighting. 3 | prettyPrint(); 4 | 5 | // Bootstrap tooltips. 6 | $('.js-tooltip').tooltip({ 7 | delay: 1000, 8 | container: 'body' 9 | }); 10 | 11 | // Deal with rounded tab styling after tab clicks. 12 | $('a[data-toggle="tab"]:first').on('shown', function(e) { 13 | $(e.target).parents('.tabbable').addClass('first-tab-active'); 14 | }); 15 | 16 | $('a[data-toggle="tab"]:not(:first)').on('shown', function(e) { 17 | $(e.target).parents('.tabbable').removeClass('first-tab-active'); 18 | }); 19 | 20 | $('a[data-toggle="tab"]').click(function() { 21 | document.cookie = "tabstyle=" + this.name + "; path=/"; 22 | }); 23 | 24 | // Store tab preference in cookies & display appropriate tab on load. 25 | var selectedTab = null; 26 | var selectedTabName = getCookie('tabstyle'); 27 | 28 | if (selectedTabName) { 29 | selectedTabName = selectedTabName.replace(/[^a-z-]/g, ''); 30 | } 31 | 32 | if (selectedTabName) { 33 | selectedTab = $('.form-switcher a[name=' + selectedTabName + ']'); 34 | } 35 | 36 | if (selectedTab && selectedTab.length > 0) { 37 | // Display whichever tab is selected. 38 | selectedTab.tab('show'); 39 | } else { 40 | // If no tab selected, display rightmost tab. 41 | $('.form-switcher a:first').tab('show'); 42 | } 43 | 44 | $(window).load(function() { 45 | $('#errorModal').modal('show'); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /status.py: -------------------------------------------------------------------------------- 1 | """ 2 | Descriptive HTTP status codes, for code readability. 3 | 4 | See RFC 2616 - https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html 5 | And RFC 6585 - https://tools.ietf.org/html/rfc6585 6 | And RFC 4918 - https://tools.ietf.org/html/rfc4918 7 | """ 8 | from __future__ import unicode_literals 9 | 10 | 11 | def is_informational(code): 12 | return 100 <= code <= 199 13 | 14 | 15 | def is_success(code): 16 | return 200 <= code <= 299 17 | 18 | 19 | def is_redirect(code): 20 | return 300 <= code <= 399 21 | 22 | 23 | def is_client_error(code): 24 | return 400 <= code <= 499 25 | 26 | 27 | def is_server_error(code): 28 | return 500 <= code <= 599 29 | 30 | 31 | HTTP_100_CONTINUE = 100 32 | HTTP_101_SWITCHING_PROTOCOLS = 101 33 | HTTP_200_OK = 200 34 | HTTP_201_CREATED = 201 35 | HTTP_202_ACCEPTED = 202 36 | HTTP_203_NON_AUTHORITATIVE_INFORMATION = 203 37 | HTTP_204_NO_CONTENT = 204 38 | HTTP_205_RESET_CONTENT = 205 39 | HTTP_206_PARTIAL_CONTENT = 206 40 | HTTP_207_MULTI_STATUS = 207 41 | HTTP_300_MULTIPLE_CHOICES = 300 42 | HTTP_301_MOVED_PERMANENTLY = 301 43 | HTTP_302_FOUND = 302 44 | HTTP_303_SEE_OTHER = 303 45 | HTTP_304_NOT_MODIFIED = 304 46 | HTTP_305_USE_PROXY = 305 47 | HTTP_306_RESERVED = 306 48 | HTTP_307_TEMPORARY_REDIRECT = 307 49 | HTTP_400_BAD_REQUEST = 400 50 | HTTP_401_UNAUTHORIZED = 401 51 | HTTP_402_PAYMENT_REQUIRED = 402 52 | HTTP_403_FORBIDDEN = 403 53 | HTTP_404_NOT_FOUND = 404 54 | HTTP_405_METHOD_NOT_ALLOWED = 405 55 | HTTP_406_NOT_ACCEPTABLE = 406 56 | HTTP_407_PROXY_AUTHENTICATION_REQUIRED = 407 57 | HTTP_408_REQUEST_TIMEOUT = 408 58 | HTTP_409_CONFLICT = 409 59 | HTTP_410_GONE = 410 60 | HTTP_411_LENGTH_REQUIRED = 411 61 | HTTP_412_PRECONDITION_FAILED = 412 62 | HTTP_413_REQUEST_ENTITY_TOO_LARGE = 413 63 | HTTP_414_REQUEST_URI_TOO_LONG = 414 64 | HTTP_415_UNSUPPORTED_MEDIA_TYPE = 415 65 | HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE = 416 66 | HTTP_417_EXPECTATION_FAILED = 417 67 | HTTP_422_UNPROCESSABLE_ENTITY = 422 68 | HTTP_423_LOCKED = 423 69 | HTTP_424_FAILED_DEPENDENCY = 424 70 | HTTP_428_PRECONDITION_REQUIRED = 428 71 | HTTP_429_TOO_MANY_REQUESTS = 429 72 | HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE = 431 73 | HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS = 451 74 | HTTP_500_INTERNAL_SERVER_ERROR = 500 75 | HTTP_501_NOT_IMPLEMENTED = 501 76 | HTTP_502_BAD_GATEWAY = 502 77 | HTTP_503_SERVICE_UNAVAILABLE = 503 78 | HTTP_504_GATEWAY_TIMEOUT = 504 79 | HTTP_505_HTTP_VERSION_NOT_SUPPORTED = 505 80 | HTTP_507_INSUFFICIENT_STORAGE = 507 81 | HTTP_511_NETWORK_AUTHENTICATION_REQUIRED = 511 82 | -------------------------------------------------------------------------------- /templates/rest_framework/admin/detail.html: -------------------------------------------------------------------------------- 1 | {% load rest_framework %} 2 | 3 | 4 | {% for key, value in results|items %} 5 | {% if key in details %} 6 | 7 | {% endif %} 8 | {% endfor %} 9 | 10 |
      {{ key|capfirst }}{{ value|format_value }}
      11 | -------------------------------------------------------------------------------- /templates/rest_framework/admin/dict_value.html: -------------------------------------------------------------------------------- 1 | {% load rest_framework %} 2 | 3 | 4 | {% for k, v in value|items %} 5 | 6 | 7 | 8 | 9 | {% endfor %} 10 | 11 |
      {{ k|format_value }}{{ v|format_value }}
      12 | -------------------------------------------------------------------------------- /templates/rest_framework/admin/list.html: -------------------------------------------------------------------------------- 1 | {% load rest_framework %} 2 | 3 | 4 | {% for column in columns%}{% endfor %} 5 | 6 | 7 | {% for row in results %} 8 | 9 | {% for key, value in row|items %} 10 | {% if key in columns %} 11 | 14 | {% endif %} 15 | {% endfor %} 16 | 19 | 20 | {% endfor %} 21 | 22 |
      {{ column|capfirst }}
      12 | {{ value|format_value }} 13 | 17 | 18 |
      23 | -------------------------------------------------------------------------------- /templates/rest_framework/admin/list_value.html: -------------------------------------------------------------------------------- 1 | {% load rest_framework %} 2 | 3 | 4 | {% for item in value %} 5 | 6 | 7 | 8 | 9 | {% endfor %} 10 | 11 |
      {{ forloop.counter0 }}{{ item|format_value }}
      12 | -------------------------------------------------------------------------------- /templates/rest_framework/admin/simple_list_value.html: -------------------------------------------------------------------------------- 1 | {% load rest_framework %} 2 | {% for item in value %}{% if not forloop.first%},{% endif %} {{item|format_value}}{% endfor %} 3 | -------------------------------------------------------------------------------- /templates/rest_framework/api.html: -------------------------------------------------------------------------------- 1 | {% extends "rest_framework/base.html" %} 2 | 3 | {# Override this template in your own templates directory to customize #} 4 | -------------------------------------------------------------------------------- /templates/rest_framework/docs/auth/basic.html: -------------------------------------------------------------------------------- 1 | {% load rest_framework %} 2 | 3 | 4 | 39 | -------------------------------------------------------------------------------- /templates/rest_framework/docs/auth/session.html: -------------------------------------------------------------------------------- 1 | {% load rest_framework %} 2 | 3 | 4 | 36 | -------------------------------------------------------------------------------- /templates/rest_framework/docs/auth/token.html: -------------------------------------------------------------------------------- 1 | {% load rest_framework %} 2 | 3 | 4 | 37 | -------------------------------------------------------------------------------- /templates/rest_framework/docs/document.html: -------------------------------------------------------------------------------- 1 | {% load rest_framework %} 2 | 3 |
      4 |
      5 |

      {{ document.title }}

      6 | {% if document.description %} 7 |

      {% render_markdown document.description %}

      8 | {% endif %} 9 |
      10 |
      11 | {% for html in lang_intro_htmls %} 12 | {% include html %} 13 | {% endfor %} 14 |
      15 |
      16 | {% if document|data %} 17 | {% for section_key, section in document|data|items %} 18 | {% if section_key %} 19 |

      {{ section_key }} 20 |

      21 | {% endif %} 22 | 23 | {% for link_key, link in section|schema_links|items %} 24 | {% include "rest_framework/docs/link.html" %} 25 | {% endfor %} 26 | {% endfor %} 27 | 28 | {% for link_key, link in document.links|items %} 29 | {% include "rest_framework/docs/link.html" %} 30 | {% endfor %} 31 | {% endif %} 32 | -------------------------------------------------------------------------------- /templates/rest_framework/docs/error.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Error Rendering Schema 10 | 11 | 12 | 13 | 14 | 15 |

      Error

      16 | 17 |
      18 | {{ data }}
      19 | 
      20 | 21 | 22 | {% if debug is True %} 23 |
      24 |

      Additional Information

      25 |

      Note: You are seeing this message because DEBUG==True.

      26 | 27 |

      Seeing this page is usually a configuration error: are your 28 | DEFAULT_AUTHENTICATION_CLASSES or DEFAULT_PERMISSION_CLASSES 29 | being applied unexpectedly?

      30 | 31 |

      Your response status code is: {{ response.status_code }}

      32 | 33 |

      401 Unauthorised.

      34 |
        35 |
      • Do you have SessionAuthentication enabled?
      • 36 |
      • Are you logged in?
      • 37 |
      38 | 39 | 40 |

      403 Forbidden.

      41 |
        42 |
      • Do you have sufficient permissions to access this view?
      • 43 |
      • Is you schema non-empty? (An empty schema will lead to a permission denied error being raised.)
      • 44 |
      45 | 46 | 47 |

      Most commonly the intended solution is to disable authentication and permissions 48 | when including the docs urls:

      49 | 50 |
      51 |    url(r'^docs/', include_docs_urls(title='Your API',
      52 |                                     authentication_classes=[],
      53 |                                     permission_classes=[])),
      54 | 
      55 | 56 | 57 |

      Overriding this template

      58 | 59 |

      If you wish access to your docs to be authenticated you may override this template 60 | at rest_framework/docs/error.html.

      61 | 62 |

      The available context is: data the error dict above, request, 63 | response and the debug flag.

      64 | 65 | {% endif %} 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /templates/rest_framework/docs/index.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | {{ document.title }} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {% if code_style %}{% endif %} 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | {% include "rest_framework/docs/sidebar.html" %} 28 | 29 |
      30 |
      31 |
      32 | {% include "rest_framework/docs/document.html" %} 33 |
      34 |
      35 |
      36 | 37 | {% include "rest_framework/docs/auth/token.html" %} 38 | {% include "rest_framework/docs/auth/basic.html" %} 39 | {% include "rest_framework/docs/auth/session.html" %} 40 | 41 | 42 | 43 | 44 | 45 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /templates/rest_framework/docs/interact.html: -------------------------------------------------------------------------------- 1 | {% load rest_framework %} 2 | 3 | 4 | 52 | -------------------------------------------------------------------------------- /templates/rest_framework/docs/langs/javascript-intro.html: -------------------------------------------------------------------------------- 1 | {% load rest_framework %} 2 | {% load static %} 3 |
      {% code html %}
      4 | 
      5 | {% endcode %}
      6 | -------------------------------------------------------------------------------- /templates/rest_framework/docs/langs/javascript.html: -------------------------------------------------------------------------------- 1 | {% load rest_framework %} 2 |
      {% code javascript %}var coreapi = window.coreapi  // Loaded by `coreapi.js`
       3 | var schema = window.schema    // Loaded by `schema.js`
       4 | 
       5 | // Initialize a client
       6 | var client = new coreapi.Client()
       7 | 
       8 | // Interact with the API endpoint
       9 | var action = [{% if section_key %}"{{ section_key }}", {% endif %}"{{ link_key }}"]
      10 | {% if link.fields %}var params = {
      11 | {% for field in link.fields %}    {{ field.name }}: ...{% if not loop.last %},{% endif %}
      12 | {% endfor %}}
      13 | {% endif %}client.action(schema, action{% if link.fields %}, params{% endif %}).then(function(result) {
      14 |     // Return value is in 'result'
      15 | }){% endcode %}
      16 | -------------------------------------------------------------------------------- /templates/rest_framework/docs/langs/python-intro.html: -------------------------------------------------------------------------------- 1 | {% load rest_framework %} 2 |
      {% code bash %}# Install the Python client library
      3 | $ pip install coreapi{% endcode %}
      4 | -------------------------------------------------------------------------------- /templates/rest_framework/docs/langs/python.html: -------------------------------------------------------------------------------- 1 | {% load rest_framework %} 2 |
      {% code python %}import coreapi
       3 | 
       4 | # Initialize a client & load the schema document
       5 | client = coreapi.Client()
       6 | schema = client.get("{{ document.url }}"{% if schema_format %}, format="{{ schema_format }}"{% endif %})
       7 | 
       8 | # Interact with the API endpoint
       9 | action = [{% if section_key %}"{{ section_key }}", {% endif %}"{{ link_key }}"]
      10 | {% if link.fields %}params = {
      11 | {% for field in link.fields %}    "{{ field.name }}": ...{% if not loop.last %},{% endif %}
      12 | {% endfor %}}
      13 | {% endif %}result = client.action(schema, action{% if link.fields %}, params=params{% endif %}){% endcode %}
      14 | -------------------------------------------------------------------------------- /templates/rest_framework/docs/langs/shell-intro.html: -------------------------------------------------------------------------------- 1 | {% load rest_framework %} 2 |
      {% code bash %}# Install the command line client
      3 | $ pip install coreapi-cli{% endcode %}
      4 | -------------------------------------------------------------------------------- /templates/rest_framework/docs/langs/shell.html: -------------------------------------------------------------------------------- 1 | {% load rest_framework %} 2 |
      {% code bash %}# Load the schema document
      3 | $ coreapi get {{ document.url }}{% if schema_format %} --format {{ schema_format }}{% endif %}
      4 | 
      5 | # Interact with the API endpoint
      6 | $ coreapi action {% if section_key %}{{ section_key }} {% endif %}{{ link_key }}{% for field in link.fields %} -p {{ field.name }}=...{% endfor %}{% endcode %}
      7 | -------------------------------------------------------------------------------- /templates/rest_framework/docs/link.html: -------------------------------------------------------------------------------- 1 | {% load rest_framework %} 2 | 101 | 102 | {% include "rest_framework/docs/interact.html" with link=link %} 103 | -------------------------------------------------------------------------------- /templates/rest_framework/docs/sidebar.html: -------------------------------------------------------------------------------- 1 | {% load rest_framework %} 2 | 45 | -------------------------------------------------------------------------------- /templates/rest_framework/filters/base.html: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /templates/rest_framework/filters/ordering.html: -------------------------------------------------------------------------------- 1 | {% load rest_framework %} 2 | {% load i18n %} 3 |

      {% trans "Ordering" %}

      4 |
      5 | {% for key, label in options %} 6 | {% if key == current %} 7 | 8 | {{ label }} 9 | 10 | {% else %} 11 | {{ label }} 12 | {% endif %} 13 | {% endfor %} 14 |
      15 | -------------------------------------------------------------------------------- /templates/rest_framework/filters/search.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 |

      {% trans "Search" %}

      3 |
      4 |
      5 |
      6 | 7 | 8 | 9 | 10 |
      11 |
      12 |
      13 | -------------------------------------------------------------------------------- /templates/rest_framework/horizontal/checkbox.html: -------------------------------------------------------------------------------- 1 |
      2 | {% if field.label %} 3 | 6 | {% endif %} 7 | 8 |
      9 | 10 | 11 | {% if field.errors %} 12 | {% for error in field.errors %} 13 | {{ error }} 14 | {% endfor %} 15 | {% endif %} 16 | 17 | {% if field.help_text %} 18 | {{ field.help_text|safe }} 19 | {% endif %} 20 |
      21 |
      22 | -------------------------------------------------------------------------------- /templates/rest_framework/horizontal/checkbox_multiple.html: -------------------------------------------------------------------------------- 1 | {% load rest_framework %} 2 | 3 |
      4 | {% if field.label %} 5 | 8 | {% endif %} 9 | 10 |
      11 | {% if style.inline %} 12 | {% for key, text in field.choices|items %} 13 | 17 | {% endfor %} 18 | {% else %} 19 | {% for key, text in field.choices|items %} 20 |
      21 | 25 |
      26 | {% endfor %} 27 | {% endif %} 28 | 29 | {% if field.errors %} 30 | {% for error in field.errors %} 31 | {{ error }} 32 | {% endfor %} 33 | {% endif %} 34 | 35 | {% if field.help_text %} 36 | {{ field.help_text|safe }} 37 | {% endif %} 38 |
      39 |
      40 | -------------------------------------------------------------------------------- /templates/rest_framework/horizontal/dict_field.html: -------------------------------------------------------------------------------- 1 |
      2 | {% if field.label %} 3 | 6 | {% endif %} 7 | 8 |
      9 |

      Dictionaries are not currently supported in HTML input.

      10 |
      11 |
      12 | -------------------------------------------------------------------------------- /templates/rest_framework/horizontal/fieldset.html: -------------------------------------------------------------------------------- 1 | {% load rest_framework %} 2 |
      3 | {% if field.label %} 4 |
      5 | 6 | {{ field.label }} 7 | 8 |
      9 | {% endif %} 10 | 11 | {% for nested_field in field %} 12 | {% if not nested_field.read_only %} 13 | {% render_field nested_field style=style %} 14 | {% endif %} 15 | {% endfor %} 16 |
      17 | -------------------------------------------------------------------------------- /templates/rest_framework/horizontal/form.html: -------------------------------------------------------------------------------- 1 | {% load rest_framework %} 2 | {% for field in form %} 3 | {% if not field.read_only %} 4 | {% render_field field style=style %} 5 | {% endif %} 6 | {% endfor %} 7 | -------------------------------------------------------------------------------- /templates/rest_framework/horizontal/input.html: -------------------------------------------------------------------------------- 1 |
      2 | {% if field.label %} 3 | 6 | {% endif %} 7 | 8 |
      9 | 10 | 11 | {% if field.errors %} 12 | {% for error in field.errors %} 13 | {{ error }} 14 | {% endfor %} 15 | {% endif %} 16 | 17 | {% if field.help_text %} 18 | {{ field.help_text|safe }} 19 | {% endif %} 20 |
      21 |
      22 | -------------------------------------------------------------------------------- /templates/rest_framework/horizontal/list_field.html: -------------------------------------------------------------------------------- 1 |
      2 | {% if field.label %} 3 | 6 | {% endif %} 7 | 8 |
      9 |

      Lists are not currently supported in HTML input.

      10 |
      11 |
      12 | -------------------------------------------------------------------------------- /templates/rest_framework/horizontal/list_fieldset.html: -------------------------------------------------------------------------------- 1 | {% load rest_framework %} 2 | 3 |
      4 | {% if field.label %} 5 |
      6 | 7 | {{ field.label }} 8 | 9 |
      10 | {% endif %} 11 | 12 |

      Lists are not currently supported in HTML input.

      13 |
      14 | -------------------------------------------------------------------------------- /templates/rest_framework/horizontal/radio.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% load rest_framework %} 3 | 4 | {% trans "None" as none_choice %} 5 | 6 |
      7 | {% if field.label %} 8 | 11 | {% endif %} 12 | 13 |
      14 | {% if style.inline %} 15 | {% if field.allow_null or field.allow_blank %} 16 | 20 | {% endif %} 21 | 22 | {% for key, text in field.choices|items %} 23 | 27 | {% endfor %} 28 | {% else %} 29 | {% if field.allow_null or field.allow_blank %} 30 |
      31 | 35 |
      36 | {% endif %} 37 | {% for key, text in field.choices|items %} 38 |
      39 | 43 |
      44 | {% endfor %} 45 | {% endif %} 46 | 47 | {% if field.errors %} 48 | {% for error in field.errors %} 49 | {{ error }} 50 | {% endfor %} 51 | {% endif %} 52 | 53 | {% if field.help_text %} 54 | {{ field.help_text|safe }} 55 | {% endif %} 56 |
      57 |
      58 | -------------------------------------------------------------------------------- /templates/rest_framework/horizontal/select.html: -------------------------------------------------------------------------------- 1 | {% load rest_framework %} 2 | 3 |
      4 | {% if field.label %} 5 | 8 | {% endif %} 9 | 10 |
      11 | 25 | 26 | {% if field.errors %} 27 | {% for error in field.errors %} 28 | {{ error }} 29 | {% endfor %} 30 | {% endif %} 31 | 32 | {% if field.help_text %} 33 | {{ field.help_text|safe }} 34 | {% endif %} 35 |
      36 |
      37 | -------------------------------------------------------------------------------- /templates/rest_framework/horizontal/select_multiple.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% load rest_framework %} 3 | 4 | {% trans "No items to select." as no_items %} 5 | 6 |
      7 | {% if field.label %} 8 | 11 | {% endif %} 12 | 13 |
      14 | 27 | 28 | {% if field.errors %} 29 | {% for error in field.errors %} 30 | {{ error }} 31 | {% endfor %} 32 | {% endif %} 33 | 34 | {% if field.help_text %} 35 | {{ field.help_text|safe }} 36 | {% endif %} 37 |
      38 |
      39 | -------------------------------------------------------------------------------- /templates/rest_framework/horizontal/textarea.html: -------------------------------------------------------------------------------- 1 |
      2 | {% if field.label %} 3 | 6 | {% endif %} 7 | 8 |
      9 | 10 | 11 | {% if field.errors %} 12 | {% for error in field.errors %} 13 | {{ error }} 14 | {% endfor %} 15 | {% endif %} 16 | 17 | {% if field.help_text %} 18 | {{ field.help_text|safe }} 19 | {% endif %} 20 |
      21 |
      22 | -------------------------------------------------------------------------------- /templates/rest_framework/inline/checkbox.html: -------------------------------------------------------------------------------- 1 |
      2 |
      3 | 7 |
      8 |
      9 | -------------------------------------------------------------------------------- /templates/rest_framework/inline/checkbox_multiple.html: -------------------------------------------------------------------------------- 1 | {% load rest_framework %} 2 | 3 |
      4 | {% if field.label %} 5 | 6 | {% endif %} 7 | 8 | {% for key, text in field.choices|items %} 9 |
      10 | 14 |
      15 | {% endfor %} 16 |
      17 | -------------------------------------------------------------------------------- /templates/rest_framework/inline/dict_field.html: -------------------------------------------------------------------------------- 1 |
      2 | {% if field.label %} 3 | 6 | {% endif %} 7 | 8 |

      Dictionaries are not currently supported in HTML input.

      9 |
      10 | -------------------------------------------------------------------------------- /templates/rest_framework/inline/fieldset.html: -------------------------------------------------------------------------------- 1 | {% load rest_framework %} 2 | {% for nested_field in field %} 3 | {% if not nested_field.read_only %} 4 | {% render_field nested_field style=style %} 5 | {% endif %} 6 | {% endfor %} 7 | -------------------------------------------------------------------------------- /templates/rest_framework/inline/form.html: -------------------------------------------------------------------------------- 1 | {% load rest_framework %} 2 | {% for field in form %} 3 | {% if not field.read_only %} 4 | {% render_field field style=style %} 5 | {% endif %} 6 | {% endfor %} 7 | -------------------------------------------------------------------------------- /templates/rest_framework/inline/input.html: -------------------------------------------------------------------------------- 1 |
      2 | {% if field.label %} 3 | 6 | {% endif %} 7 | 8 | 9 |
      10 | -------------------------------------------------------------------------------- /templates/rest_framework/inline/list_field.html: -------------------------------------------------------------------------------- 1 |
      2 | {% if field.label %} 3 | 6 | {% endif %} 7 | 8 |

      Lists are not currently supported in HTML input.

      9 |
      10 | -------------------------------------------------------------------------------- /templates/rest_framework/inline/list_fieldset.html: -------------------------------------------------------------------------------- 1 | Lists are not currently supported in HTML input. 2 | -------------------------------------------------------------------------------- /templates/rest_framework/inline/radio.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% load rest_framework %} 3 | {% trans "None" as none_choice %} 4 | 5 |
      6 | {% if field.label %} 7 | 10 | {% endif %} 11 | 12 | {% if field.allow_null or field.allow_blank %} 13 |
      14 | 18 |
      19 | {% endif %} 20 | 21 | {% for key, text in field.choices|items %} 22 |
      23 | 27 |
      28 | {% endfor %} 29 |
      30 | -------------------------------------------------------------------------------- /templates/rest_framework/inline/select.html: -------------------------------------------------------------------------------- 1 | {% load rest_framework %} 2 | 3 |
      4 | {% if field.label %} 5 | 8 | {% endif %} 9 | 10 | 24 |
      25 | -------------------------------------------------------------------------------- /templates/rest_framework/inline/select_multiple.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% load rest_framework %} 3 | {% trans "No items to select." as no_items %} 4 | 5 |
      6 | {% if field.label %} 7 | 10 | {% endif %} 11 | 12 | 25 |
      26 | -------------------------------------------------------------------------------- /templates/rest_framework/inline/textarea.html: -------------------------------------------------------------------------------- 1 |
      2 | {% if field.label %} 3 | 6 | {% endif %} 7 | 8 | 9 |
      10 | -------------------------------------------------------------------------------- /templates/rest_framework/login.html: -------------------------------------------------------------------------------- 1 | {% extends "rest_framework/login_base.html" %} 2 | 3 | {# Override this template in your own templates directory to customize #} 4 | -------------------------------------------------------------------------------- /templates/rest_framework/login_base.html: -------------------------------------------------------------------------------- 1 | {% extends "rest_framework/base.html" %} 2 | {% load rest_framework %} 3 | 4 | {% block body %} 5 | 6 |
      7 |
      8 |
      9 |
      10 |
      11 | {% block branding %}

      Django REST framework

      {% endblock %} 12 |
      13 |
      14 | 15 |
      16 |
      17 |
      18 | {% csrf_token %} 19 | 20 | 21 |
      22 |
      23 | 24 | 29 | {% if form.username.errors %} 30 |

      31 | {{ form.username.errors|striptags }} 32 |

      33 | {% endif %} 34 |
      35 |
      36 | 37 |
      38 |
      39 | 40 | 41 | {% if form.password.errors %} 42 |

      43 | {{ form.password.errors|striptags }} 44 |

      45 | {% endif %} 46 |
      47 |
      48 | 49 | {% if form.non_field_errors %} 50 | {% for error in form.non_field_errors %} 51 |
      {{ error }}
      52 | {% endfor %} 53 | {% endif %} 54 | 55 |
      56 | 57 |
      58 |
      59 |
      60 |
      61 |
      62 |
      63 |
      64 | 65 | {% endblock %} 66 | -------------------------------------------------------------------------------- /templates/rest_framework/pagination/numbers.html: -------------------------------------------------------------------------------- 1 |
        2 | {% if previous_url %} 3 |
      • 4 | 5 | 6 | 7 |
      • 8 | {% else %} 9 |
      • 10 | 11 | 12 | 13 |
      • 14 | {% endif %} 15 | 16 | {% for page_link in page_links %} 17 | {% if page_link.is_break %} 18 |
      • 19 | 20 |
      • 21 | {% else %} 22 | {% if page_link.is_active %} 23 |
      • 24 | {{ page_link.number }} 25 |
      • 26 | {% else %} 27 |
      • 28 | {{ page_link.number }} 29 |
      • 30 | {% endif %} 31 | {% endif %} 32 | {% endfor %} 33 | 34 | {% if next_url %} 35 |
      • 36 | 37 | 38 | 39 |
      • 40 | {% else %} 41 |
      • 42 | 43 | 44 | 45 |
      • 46 | {% endif %} 47 |
      48 | -------------------------------------------------------------------------------- /templates/rest_framework/pagination/previous_and_next.html: -------------------------------------------------------------------------------- 1 |
        2 | {% if previous_url %} 3 | 6 | {% else %} 7 | 10 | {% endif %} 11 | 12 | {% if next_url %} 13 | 16 | {% else %} 17 | 20 | {% endif %} 21 |
      22 | -------------------------------------------------------------------------------- /templates/rest_framework/raw_data_form.html: -------------------------------------------------------------------------------- 1 | {% load rest_framework %} 2 | {{ form.non_field_errors }} 3 | {% for field in form %} 4 |
      5 | {{ field.label_tag|add_class:"col-sm-2 control-label" }} 6 |
      7 | {{ field|add_class:"form-control" }} 8 | {{ field.help_text|safe }} 9 |
      10 |
      11 | {% endfor %} 12 | -------------------------------------------------------------------------------- /templates/rest_framework/schema.js: -------------------------------------------------------------------------------- 1 | var codec = new window.coreapi.codecs.CoreJSONCodec() 2 | var coreJSON = window.atob('{{ schema }}') 3 | window.schema = codec.decode(coreJSON) 4 | -------------------------------------------------------------------------------- /templates/rest_framework/vertical/checkbox.html: -------------------------------------------------------------------------------- 1 |
      2 |
      3 | 7 |
      8 | 9 | {% if field.errors %} 10 | {% for error in field.errors %} 11 | {{ error }} 12 | {% endfor %} 13 | {% endif %} 14 | 15 | {% if field.help_text %} 16 | {{ field.help_text|safe }} 17 | {% endif %} 18 |
      19 | -------------------------------------------------------------------------------- /templates/rest_framework/vertical/checkbox_multiple.html: -------------------------------------------------------------------------------- 1 | {% load rest_framework %} 2 | 3 |
      4 | {% if field.label %} 5 | 6 | {% endif %} 7 | 8 | {% if style.inline %} 9 |
      10 | {% for key, text in field.choices|items %} 11 | 15 | {% endfor %} 16 |
      17 | {% else %} 18 | {% for key, text in field.choices|items %} 19 |
      20 | 24 |
      25 | {% endfor %} 26 | {% endif %} 27 | 28 | {% if field.errors %} 29 | {% for error in field.errors %} 30 | {{ error }} 31 | {% endfor %} 32 | {% endif %} 33 | 34 | {% if field.help_text %} 35 | {{ field.help_text|safe }} 36 | {% endif %} 37 |
      38 | -------------------------------------------------------------------------------- /templates/rest_framework/vertical/dict_field.html: -------------------------------------------------------------------------------- 1 |
      2 | {% if field.label %} 3 | 4 | {% endif %} 5 | 6 |

      Dictionaries are not currently supported in HTML input.

      7 |
      8 | -------------------------------------------------------------------------------- /templates/rest_framework/vertical/fieldset.html: -------------------------------------------------------------------------------- 1 | {% load rest_framework %} 2 | 3 |
      4 | {% if field.label %} 5 | 6 | {{ field.label }} 7 | 8 | {% endif %} 9 | 10 | {% for nested_field in field %} 11 | {% if not nested_field.read_only %} 12 | {% render_field nested_field style=style %} 13 | {% endif %} 14 | {% endfor %} 15 |
      16 | -------------------------------------------------------------------------------- /templates/rest_framework/vertical/form.html: -------------------------------------------------------------------------------- 1 | {% load rest_framework %} 2 | {% for field in form %} 3 | {% if not field.read_only %} 4 | {% render_field field style=style %} 5 | {% endif %} 6 | {% endfor %} 7 | -------------------------------------------------------------------------------- /templates/rest_framework/vertical/input.html: -------------------------------------------------------------------------------- 1 |
      2 | {% if field.label %} 3 | 4 | {% endif %} 5 | 6 | 7 | 8 | {% if field.errors %} 9 | {% for error in field.errors %} 10 | {{ error }} 11 | {% endfor %} 12 | {% endif %} 13 | 14 | {% if field.help_text %} 15 | {{ field.help_text|safe }} 16 | {% endif %} 17 |
      18 | -------------------------------------------------------------------------------- /templates/rest_framework/vertical/list_field.html: -------------------------------------------------------------------------------- 1 |
      2 | {% if field.label %} 3 | 4 | {% endif %} 5 | 6 |

      Lists are not currently supported in HTML input.

      7 |
      8 | -------------------------------------------------------------------------------- /templates/rest_framework/vertical/list_fieldset.html: -------------------------------------------------------------------------------- 1 |
      2 | {% if field.label %} 3 | 4 | {{ field.label }} 5 | 6 | {% endif %} 7 | 8 |

      Lists are not currently supported in HTML input.

      9 |
      10 | -------------------------------------------------------------------------------- /templates/rest_framework/vertical/radio.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% load rest_framework %} 3 | {% trans "None" as none_choice %} 4 | 5 |
      6 | {% if field.label %} 7 | 10 | {% endif %} 11 | 12 | {% if style.inline %} 13 |
      14 | {% if field.allow_null or field.allow_blank %} 15 | 19 | {% endif %} 20 | 21 | {% for key, text in field.choices|items %} 22 | 26 | {% endfor %} 27 |
      28 | {% else %} 29 | {% if field.allow_null or field.allow_blank %} 30 |
      31 | 35 |
      36 | {% endif %} 37 | 38 | {% for key, text in field.choices|items %} 39 |
      40 | 44 |
      45 | {% endfor %} 46 | {% endif %} 47 | 48 | {% if field.errors %} 49 | {% for error in field.errors %} 50 | {{ error }} 51 | {% endfor %} 52 | {% endif %} 53 | 54 | {% if field.help_text %} 55 | {{ field.help_text|safe }} 56 | {% endif %} 57 |
      58 | -------------------------------------------------------------------------------- /templates/rest_framework/vertical/select.html: -------------------------------------------------------------------------------- 1 | {% load rest_framework %} 2 | 3 |
      4 | {% if field.label %} 5 | 8 | {% endif %} 9 | 10 | 24 | 25 | {% if field.errors %} 26 | {% for error in field.errors %} 27 | {{ error }} 28 | {% endfor %} 29 | {% endif %} 30 | 31 | {% if field.help_text %} 32 | {{ field.help_text|safe }} 33 | {% endif %} 34 |
      35 | -------------------------------------------------------------------------------- /templates/rest_framework/vertical/select_multiple.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% load rest_framework %} 3 | {% trans "No items to select." as no_items %} 4 | 5 |
      6 | {% if field.label %} 7 | 10 | {% endif %} 11 | 12 | 25 | 26 | {% if field.errors %} 27 | {% for error in field.errors %}{{ error }}{% endfor %} 28 | {% endif %} 29 | 30 | {% if field.help_text %} 31 | {{ field.help_text|safe }} 32 | {% endif %} 33 |
      34 | -------------------------------------------------------------------------------- /templates/rest_framework/vertical/textarea.html: -------------------------------------------------------------------------------- 1 |
      2 | {% if field.label %} 3 | 6 | {% endif %} 7 | 8 | 9 | 10 | {% if field.errors %} 11 | {% for error in field.errors %}{{ error }}{% endfor %} 12 | {% endif %} 13 | 14 | {% if field.help_text %} 15 | {{ field.help_text|safe }} 16 | {% endif %} 17 |
      18 | -------------------------------------------------------------------------------- /templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/templatetags/__init__.py -------------------------------------------------------------------------------- /urlpatterns.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.conf.urls import include, url 4 | 5 | from rest_framework.compat import ( 6 | URLResolver, get_regex_pattern, is_route_pattern, path, register_converter 7 | ) 8 | from rest_framework.settings import api_settings 9 | 10 | 11 | def _get_format_path_converter(suffix_kwarg, allowed): 12 | if allowed: 13 | if len(allowed) == 1: 14 | allowed_pattern = allowed[0] 15 | else: 16 | allowed_pattern = '(?:%s)' % '|'.join(allowed) 17 | suffix_pattern = r"\.%s/?" % allowed_pattern 18 | else: 19 | suffix_pattern = r"\.[a-z0-9]+/?" 20 | 21 | class FormatSuffixConverter: 22 | regex = suffix_pattern 23 | 24 | def to_python(self, value): 25 | return value.strip('./') 26 | 27 | def to_url(self, value): 28 | return '.' + value + '/' 29 | 30 | converter_name = 'drf_format_suffix' 31 | if allowed: 32 | converter_name += '_' + '_'.join(allowed) 33 | 34 | return converter_name, FormatSuffixConverter 35 | 36 | 37 | def apply_suffix_patterns(urlpatterns, suffix_pattern, suffix_required, suffix_route=None): 38 | ret = [] 39 | for urlpattern in urlpatterns: 40 | if isinstance(urlpattern, URLResolver): 41 | # Set of included URL patterns 42 | regex = get_regex_pattern(urlpattern) 43 | namespace = urlpattern.namespace 44 | app_name = urlpattern.app_name 45 | kwargs = urlpattern.default_kwargs 46 | # Add in the included patterns, after applying the suffixes 47 | patterns = apply_suffix_patterns(urlpattern.url_patterns, 48 | suffix_pattern, 49 | suffix_required, 50 | suffix_route) 51 | 52 | # if the original pattern was a RoutePattern we need to preserve it 53 | if is_route_pattern(urlpattern): 54 | assert path is not None 55 | route = str(urlpattern.pattern) 56 | new_pattern = path(route, include((patterns, app_name), namespace), kwargs) 57 | else: 58 | new_pattern = url(regex, include((patterns, app_name), namespace), kwargs) 59 | 60 | ret.append(new_pattern) 61 | else: 62 | # Regular URL pattern 63 | regex = get_regex_pattern(urlpattern).rstrip('$').rstrip('/') + suffix_pattern 64 | view = urlpattern.callback 65 | kwargs = urlpattern.default_args 66 | name = urlpattern.name 67 | # Add in both the existing and the new urlpattern 68 | if not suffix_required: 69 | ret.append(urlpattern) 70 | 71 | # if the original pattern was a RoutePattern we need to preserve it 72 | if is_route_pattern(urlpattern): 73 | assert path is not None 74 | assert suffix_route is not None 75 | route = str(urlpattern.pattern).rstrip('$').rstrip('/') + suffix_route 76 | new_pattern = path(route, view, kwargs, name) 77 | else: 78 | new_pattern = url(regex, view, kwargs, name) 79 | 80 | ret.append(new_pattern) 81 | 82 | return ret 83 | 84 | 85 | def format_suffix_patterns(urlpatterns, suffix_required=False, allowed=None): 86 | """ 87 | Supplement existing urlpatterns with corresponding patterns that also 88 | include a '.format' suffix. Retains urlpattern ordering. 89 | 90 | urlpatterns: 91 | A list of URL patterns. 92 | 93 | suffix_required: 94 | If `True`, only suffixed URLs will be generated, and non-suffixed 95 | URLs will not be used. Defaults to `False`. 96 | 97 | allowed: 98 | An optional tuple/list of allowed suffixes. eg ['json', 'api'] 99 | Defaults to `None`, which allows any suffix. 100 | """ 101 | suffix_kwarg = api_settings.FORMAT_SUFFIX_KWARG 102 | if allowed: 103 | if len(allowed) == 1: 104 | allowed_pattern = allowed[0] 105 | else: 106 | allowed_pattern = '(%s)' % '|'.join(allowed) 107 | suffix_pattern = r'\.(?P<%s>%s)/?$' % (suffix_kwarg, allowed_pattern) 108 | else: 109 | suffix_pattern = r'\.(?P<%s>[a-z0-9]+)/?$' % suffix_kwarg 110 | 111 | if path and register_converter: 112 | converter_name, suffix_converter = _get_format_path_converter(suffix_kwarg, allowed) 113 | register_converter(suffix_converter, converter_name) 114 | 115 | suffix_route = '<%s:%s>' % (converter_name, suffix_kwarg) 116 | else: 117 | suffix_route = None 118 | 119 | return apply_suffix_patterns(urlpatterns, suffix_pattern, suffix_required, suffix_route) 120 | -------------------------------------------------------------------------------- /urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | Login and logout views for the browsable API. 3 | 4 | Add these to your root URLconf if you're using the browsable API and 5 | your API requires authentication: 6 | 7 | urlpatterns = [ 8 | ... 9 | url(r'^auth/', include('rest_framework.urls')) 10 | ] 11 | 12 | You should make sure your authentication settings include `SessionAuthentication`. 13 | """ 14 | from __future__ import unicode_literals 15 | 16 | import django 17 | from django.conf.urls import url 18 | from django.contrib.auth import views 19 | 20 | if django.VERSION < (1, 11): 21 | login = views.login 22 | login_kwargs = {'template_name': 'rest_framework/login.html'} 23 | logout = views.logout 24 | else: 25 | login = views.LoginView.as_view(template_name='rest_framework/login.html') 26 | login_kwargs = {} 27 | logout = views.LogoutView.as_view() 28 | 29 | 30 | app_name = 'rest_framework' 31 | urlpatterns = [ 32 | url(r'^login/$', login, login_kwargs, name='login'), 33 | url(r'^logout/$', logout, name='logout'), 34 | ] 35 | -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenHuabin321/rest_framework_source_code_analysis/22f72df3ae10a0d3d938e571c512b4d67e041ae9/utils/__init__.py -------------------------------------------------------------------------------- /utils/breadcrumbs.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.urls import get_script_prefix, resolve 4 | 5 | 6 | def get_breadcrumbs(url, request=None): 7 | """ 8 | Given a url returns a list of breadcrumbs, which are each a 9 | tuple of (name, url). 10 | """ 11 | from rest_framework.reverse import preserve_builtin_query_params 12 | from rest_framework.views import APIView 13 | 14 | def breadcrumbs_recursive(url, breadcrumbs_list, prefix, seen): 15 | """ 16 | Add tuples of (name, url) to the breadcrumbs list, 17 | progressively chomping off parts of the url. 18 | """ 19 | try: 20 | (view, unused_args, unused_kwargs) = resolve(url) 21 | except Exception: 22 | pass 23 | else: 24 | # Check if this is a REST framework view, 25 | # and if so add it to the breadcrumbs 26 | cls = getattr(view, 'cls', None) 27 | initkwargs = getattr(view, 'initkwargs', {}) 28 | if cls is not None and issubclass(cls, APIView): 29 | # Don't list the same view twice in a row. 30 | # Probably an optional trailing slash. 31 | if not seen or seen[-1] != view: 32 | c = cls(**initkwargs) 33 | c.suffix = getattr(view, 'suffix', None) 34 | name = c.get_view_name() 35 | insert_url = preserve_builtin_query_params(prefix + url, request) 36 | breadcrumbs_list.insert(0, (name, insert_url)) 37 | seen.append(view) 38 | 39 | if url == '': 40 | # All done 41 | return breadcrumbs_list 42 | 43 | elif url.endswith('/'): 44 | # Drop trailing slash off the end and continue to try to 45 | # resolve more breadcrumbs 46 | url = url.rstrip('/') 47 | return breadcrumbs_recursive(url, breadcrumbs_list, prefix, seen) 48 | 49 | # Drop trailing non-slash off the end and continue to try to 50 | # resolve more breadcrumbs 51 | url = url[:url.rfind('/') + 1] 52 | return breadcrumbs_recursive(url, breadcrumbs_list, prefix, seen) 53 | 54 | prefix = get_script_prefix().rstrip('/') 55 | url = url[len(prefix):] 56 | return breadcrumbs_recursive(url, [], prefix, []) 57 | -------------------------------------------------------------------------------- /utils/encoders.py: -------------------------------------------------------------------------------- 1 | """ 2 | Helper classes for parsers. 3 | """ 4 | from __future__ import absolute_import, unicode_literals 5 | 6 | import datetime 7 | import decimal 8 | import json # noqa 9 | import uuid 10 | 11 | from django.db.models.query import QuerySet 12 | from django.utils import six, timezone 13 | from django.utils.encoding import force_text 14 | from django.utils.functional import Promise 15 | 16 | from rest_framework.compat import coreapi 17 | 18 | 19 | class JSONEncoder(json.JSONEncoder): 20 | """ 21 | JSONEncoder subclass that knows how to encode date/time/timedelta, 22 | decimal types, generators and other basic python objects. 23 | """ 24 | def default(self, obj): 25 | # For Date Time string spec, see ECMA 262 26 | # https://ecma-international.org/ecma-262/5.1/#sec-15.9.1.15 27 | if isinstance(obj, Promise): 28 | return force_text(obj) 29 | elif isinstance(obj, datetime.datetime): 30 | representation = obj.isoformat() 31 | if representation.endswith('+00:00'): 32 | representation = representation[:-6] + 'Z' 33 | return representation 34 | elif isinstance(obj, datetime.date): 35 | return obj.isoformat() 36 | elif isinstance(obj, datetime.time): 37 | if timezone and timezone.is_aware(obj): 38 | raise ValueError("JSON can't represent timezone-aware times.") 39 | representation = obj.isoformat() 40 | return representation 41 | elif isinstance(obj, datetime.timedelta): 42 | return six.text_type(obj.total_seconds()) 43 | elif isinstance(obj, decimal.Decimal): 44 | # Serializers will coerce decimals to strings by default. 45 | return float(obj) 46 | elif isinstance(obj, uuid.UUID): 47 | return six.text_type(obj) 48 | elif isinstance(obj, QuerySet): 49 | return tuple(obj) 50 | elif isinstance(obj, six.binary_type): 51 | # Best-effort for binary blobs. See #4187. 52 | return obj.decode('utf-8') 53 | elif hasattr(obj, 'tolist'): 54 | # Numpy arrays and array scalars. 55 | return obj.tolist() 56 | elif (coreapi is not None) and isinstance(obj, (coreapi.Document, coreapi.Error)): 57 | raise RuntimeError( 58 | 'Cannot return a coreapi object from a JSON view. ' 59 | 'You should be using a schema renderer instead for this view.' 60 | ) 61 | elif hasattr(obj, '__getitem__'): 62 | try: 63 | return dict(obj) 64 | except Exception: 65 | pass 66 | elif hasattr(obj, '__iter__'): 67 | return tuple(item for item in obj) 68 | return super(JSONEncoder, self).default(obj) 69 | -------------------------------------------------------------------------------- /utils/formatting.py: -------------------------------------------------------------------------------- 1 | """ 2 | Utility functions to return a formatted name and description for a given view. 3 | """ 4 | from __future__ import unicode_literals 5 | 6 | import re 7 | 8 | from django.utils.encoding import force_text 9 | from django.utils.html import escape 10 | from django.utils.safestring import mark_safe 11 | 12 | from rest_framework.compat import apply_markdown 13 | 14 | 15 | def remove_trailing_string(content, trailing): 16 | """ 17 | Strip trailing component `trailing` from `content` if it exists. 18 | Used when generating names from view classes. 19 | """ 20 | if content.endswith(trailing) and content != trailing: 21 | return content[:-len(trailing)] 22 | return content 23 | 24 | 25 | def dedent(content): 26 | """ 27 | Remove leading indent from a block of text. 28 | Used when generating descriptions from docstrings. 29 | 30 | Note that python's `textwrap.dedent` doesn't quite cut it, 31 | as it fails to dedent multiline docstrings that include 32 | unindented text on the initial line. 33 | """ 34 | content = force_text(content) 35 | lines = [line for line in content.splitlines()[1:] if line.lstrip()] 36 | 37 | # unindent the content if needed 38 | if lines: 39 | whitespace_counts = min([len(line) - len(line.lstrip(' ')) for line in lines]) 40 | tab_counts = min([len(line) - len(line.lstrip('\t')) for line in lines]) 41 | if whitespace_counts: 42 | whitespace_pattern = '^' + (' ' * whitespace_counts) 43 | content = re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', content) 44 | elif tab_counts: 45 | whitespace_pattern = '^' + ('\t' * tab_counts) 46 | content = re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', content) 47 | return content.strip() 48 | 49 | 50 | def camelcase_to_spaces(content): 51 | """ 52 | Translate 'CamelCaseNames' to 'Camel Case Names'. 53 | Used when generating names from view classes. 54 | """ 55 | camelcase_boundary = '(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))' 56 | content = re.sub(camelcase_boundary, ' \\1', content).strip() 57 | return ' '.join(content.split('_')).title() 58 | 59 | 60 | def markup_description(description): 61 | """ 62 | Apply HTML markup to the given description. 63 | """ 64 | if apply_markdown: 65 | description = apply_markdown(description) 66 | else: 67 | description = escape(description).replace('\n', '
      ') 68 | description = '

      ' + description + '

      ' 69 | return mark_safe(description) 70 | -------------------------------------------------------------------------------- /utils/html.py: -------------------------------------------------------------------------------- 1 | """ 2 | Helpers for dealing with HTML input. 3 | """ 4 | import re 5 | 6 | from django.utils.datastructures import MultiValueDict 7 | 8 | 9 | def is_html_input(dictionary): 10 | # MultiDict type datastructures are used to represent HTML form input, 11 | # which may have more than one value for each key. 12 | return hasattr(dictionary, 'getlist') 13 | 14 | 15 | def parse_html_list(dictionary, prefix=''): 16 | """ 17 | Used to support list values in HTML forms. 18 | Supports lists of primitives and/or dictionaries. 19 | 20 | * List of primitives. 21 | 22 | { 23 | '[0]': 'abc', 24 | '[1]': 'def', 25 | '[2]': 'hij' 26 | } 27 | --> 28 | [ 29 | 'abc', 30 | 'def', 31 | 'hij' 32 | ] 33 | 34 | * List of dictionaries. 35 | 36 | { 37 | '[0]foo': 'abc', 38 | '[0]bar': 'def', 39 | '[1]foo': 'hij', 40 | '[1]bar': 'klm', 41 | } 42 | --> 43 | [ 44 | {'foo': 'abc', 'bar': 'def'}, 45 | {'foo': 'hij', 'bar': 'klm'} 46 | ] 47 | """ 48 | ret = {} 49 | regex = re.compile(r'^%s\[([0-9]+)\](.*)$' % re.escape(prefix)) 50 | for field, value in dictionary.items(): 51 | match = regex.match(field) 52 | if not match: 53 | continue 54 | index, key = match.groups() 55 | index = int(index) 56 | if not key: 57 | ret[index] = value 58 | elif isinstance(ret.get(index), dict): 59 | ret[index][key] = value 60 | else: 61 | ret[index] = MultiValueDict({key: [value]}) 62 | return [ret[item] for item in sorted(ret)] 63 | 64 | 65 | def parse_html_dict(dictionary, prefix=''): 66 | """ 67 | Used to support dictionary values in HTML forms. 68 | 69 | { 70 | 'profile.username': 'example', 71 | 'profile.email': 'example@example.com', 72 | } 73 | --> 74 | { 75 | 'profile': { 76 | 'username': 'example', 77 | 'email': 'example@example.com' 78 | } 79 | } 80 | """ 81 | ret = MultiValueDict() 82 | regex = re.compile(r'^%s\.(.+)$' % re.escape(prefix)) 83 | for field in dictionary: 84 | match = regex.match(field) 85 | if not match: 86 | continue 87 | key = match.groups()[0] 88 | value = dictionary.getlist(field) 89 | ret.setlist(key, value) 90 | 91 | return ret 92 | -------------------------------------------------------------------------------- /utils/humanize_datetime.py: -------------------------------------------------------------------------------- 1 | """ 2 | Helper functions that convert strftime formats into more readable representations. 3 | """ 4 | from rest_framework import ISO_8601 5 | 6 | 7 | def datetime_formats(formats): 8 | format = ', '.join(formats).replace( 9 | ISO_8601, 10 | 'YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z]' 11 | ) 12 | return humanize_strptime(format) 13 | 14 | 15 | def date_formats(formats): 16 | format = ', '.join(formats).replace(ISO_8601, 'YYYY[-MM[-DD]]') 17 | return humanize_strptime(format) 18 | 19 | 20 | def time_formats(formats): 21 | format = ', '.join(formats).replace(ISO_8601, 'hh:mm[:ss[.uuuuuu]]') 22 | return humanize_strptime(format) 23 | 24 | 25 | def humanize_strptime(format_string): 26 | # Note that we're missing some of the locale specific mappings that 27 | # don't really make sense. 28 | mapping = { 29 | "%Y": "YYYY", 30 | "%y": "YY", 31 | "%m": "MM", 32 | "%b": "[Jan-Dec]", 33 | "%B": "[January-December]", 34 | "%d": "DD", 35 | "%H": "hh", 36 | "%I": "hh", # Requires '%p' to differentiate from '%H'. 37 | "%M": "mm", 38 | "%S": "ss", 39 | "%f": "uuuuuu", 40 | "%a": "[Mon-Sun]", 41 | "%A": "[Monday-Sunday]", 42 | "%p": "[AM|PM]", 43 | "%z": "[+HHMM|-HHMM]" 44 | } 45 | for key, val in mapping.items(): 46 | format_string = format_string.replace(key, val) 47 | return format_string 48 | -------------------------------------------------------------------------------- /utils/json.py: -------------------------------------------------------------------------------- 1 | """ 2 | Wrapper for the builtin json module that ensures compliance with the JSON spec. 3 | 4 | REST framework should always import this wrapper module in order to maintain 5 | spec-compliant encoding/decoding. Support for non-standard features should be 6 | handled by users at the renderer and parser layer. 7 | """ 8 | 9 | from __future__ import absolute_import 10 | 11 | import functools 12 | import json # noqa 13 | 14 | 15 | def strict_constant(o): 16 | raise ValueError('Out of range float values are not JSON compliant: ' + repr(o)) 17 | 18 | 19 | @functools.wraps(json.dump) 20 | def dump(*args, **kwargs): 21 | kwargs.setdefault('allow_nan', False) 22 | return json.dump(*args, **kwargs) 23 | 24 | 25 | @functools.wraps(json.dumps) 26 | def dumps(*args, **kwargs): 27 | kwargs.setdefault('allow_nan', False) 28 | return json.dumps(*args, **kwargs) 29 | 30 | 31 | @functools.wraps(json.load) 32 | def load(*args, **kwargs): 33 | kwargs.setdefault('parse_constant', strict_constant) 34 | return json.load(*args, **kwargs) 35 | 36 | 37 | @functools.wraps(json.loads) 38 | def loads(*args, **kwargs): 39 | kwargs.setdefault('parse_constant', strict_constant) 40 | return json.loads(*args, **kwargs) 41 | -------------------------------------------------------------------------------- /utils/mediatypes.py: -------------------------------------------------------------------------------- 1 | """ 2 | Handling of media types, as found in HTTP Content-Type and Accept headers. 3 | 4 | See https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7 5 | """ 6 | from __future__ import unicode_literals 7 | 8 | from django.http.multipartparser import parse_header 9 | from django.utils.encoding import python_2_unicode_compatible 10 | 11 | from rest_framework import HTTP_HEADER_ENCODING 12 | 13 | 14 | def media_type_matches(lhs, rhs): 15 | """ 16 | Returns ``True`` if the media type in the first argument <= the 17 | media type in the second argument. The media types are strings 18 | as described by the HTTP spec. 19 | 20 | Valid media type strings include: 21 | 22 | 'application/json; indent=4' 23 | 'application/json' 24 | 'text/*' 25 | '*/*' 26 | """ 27 | lhs = _MediaType(lhs) 28 | rhs = _MediaType(rhs) 29 | return lhs.match(rhs) 30 | 31 | 32 | def order_by_precedence(media_type_lst): 33 | """ 34 | Returns a list of sets of media type strings, ordered by precedence. 35 | Precedence is determined by how specific a media type is: 36 | 37 | 3. 'type/subtype; param=val' 38 | 2. 'type/subtype' 39 | 1. 'type/*' 40 | 0. '*/*' 41 | """ 42 | ret = [set(), set(), set(), set()] 43 | for media_type in media_type_lst: 44 | precedence = _MediaType(media_type).precedence 45 | ret[3 - precedence].add(media_type) 46 | return [media_types for media_types in ret if media_types] 47 | 48 | 49 | @python_2_unicode_compatible 50 | class _MediaType(object): 51 | def __init__(self, media_type_str): 52 | self.orig = '' if (media_type_str is None) else media_type_str 53 | self.full_type, self.params = parse_header(self.orig.encode(HTTP_HEADER_ENCODING)) 54 | self.main_type, sep, self.sub_type = self.full_type.partition('/') 55 | 56 | def match(self, other): 57 | """Return true if this MediaType satisfies the given MediaType.""" 58 | for key in self.params: 59 | if key != 'q' and other.params.get(key, None) != self.params.get(key, None): 60 | return False 61 | 62 | if self.sub_type != '*' and other.sub_type != '*' and other.sub_type != self.sub_type: 63 | return False 64 | 65 | if self.main_type != '*' and other.main_type != '*' and other.main_type != self.main_type: 66 | return False 67 | 68 | return True 69 | 70 | @property 71 | def precedence(self): 72 | """ 73 | Return a precedence level from 0-3 for the media type given how specific it is. 74 | """ 75 | if self.main_type == '*': 76 | return 0 77 | elif self.sub_type == '*': 78 | return 1 79 | elif not self.params or list(self.params) == ['q']: 80 | return 2 81 | return 3 82 | 83 | def __str__(self): 84 | ret = "%s/%s" % (self.main_type, self.sub_type) 85 | for key, val in self.params.items(): 86 | ret += "; %s=%s" % (key, val.decode('ascii')) 87 | return ret 88 | -------------------------------------------------------------------------------- /utils/model_meta.py: -------------------------------------------------------------------------------- 1 | """ 2 | Helper function for returning the field information that is associated 3 | with a model class. This includes returning all the forward and reverse 4 | relationships and their associated metadata. 5 | 6 | Usage: `get_field_info(model)` returns a `FieldInfo` instance. 7 | """ 8 | from collections import OrderedDict, namedtuple 9 | 10 | FieldInfo = namedtuple('FieldResult', [ 11 | 'pk', # Model field instance 12 | 'fields', # Dict of field name -> model field instance 13 | 'forward_relations', # Dict of field name -> RelationInfo 14 | 'reverse_relations', # Dict of field name -> RelationInfo 15 | 'fields_and_pk', # Shortcut for 'pk' + 'fields' 16 | 'relations' # Shortcut for 'forward_relations' + 'reverse_relations' 17 | ]) 18 | 19 | RelationInfo = namedtuple('RelationInfo', [ 20 | 'model_field', 21 | 'related_model', 22 | 'to_many', 23 | 'to_field', 24 | 'has_through_model', 25 | 'reverse' 26 | ]) 27 | 28 | 29 | def get_field_info(model): 30 | """ 31 | Given a model class, returns a `FieldInfo` instance, which is a 32 | `namedtuple`, containing metadata about the various field types on the model 33 | including information about their relationships. 34 | """ 35 | opts = model._meta.concrete_model._meta 36 | 37 | pk = _get_pk(opts) 38 | fields = _get_fields(opts) 39 | forward_relations = _get_forward_relationships(opts) 40 | reverse_relations = _get_reverse_relationships(opts) 41 | fields_and_pk = _merge_fields_and_pk(pk, fields) 42 | relationships = _merge_relationships(forward_relations, reverse_relations) 43 | 44 | return FieldInfo(pk, fields, forward_relations, reverse_relations, 45 | fields_and_pk, relationships) 46 | 47 | 48 | def _get_pk(opts): 49 | pk = opts.pk 50 | rel = pk.remote_field 51 | 52 | while rel and rel.parent_link: 53 | # If model is a child via multi-table inheritance, use parent's pk. 54 | pk = pk.remote_field.model._meta.pk 55 | rel = pk.remote_field 56 | 57 | return pk 58 | 59 | 60 | def _get_fields(opts): 61 | fields = OrderedDict() 62 | for field in [field for field in opts.fields if field.serialize and not field.remote_field]: 63 | fields[field.name] = field 64 | 65 | return fields 66 | 67 | 68 | def _get_to_field(field): 69 | return getattr(field, 'to_fields', None) and field.to_fields[0] 70 | 71 | 72 | def _get_forward_relationships(opts): 73 | """ 74 | Returns an `OrderedDict` of field names to `RelationInfo`. 75 | """ 76 | forward_relations = OrderedDict() 77 | for field in [field for field in opts.fields if field.serialize and field.remote_field]: 78 | forward_relations[field.name] = RelationInfo( 79 | model_field=field, 80 | related_model=field.remote_field.model, 81 | to_many=False, 82 | to_field=_get_to_field(field), 83 | has_through_model=False, 84 | reverse=False 85 | ) 86 | 87 | # Deal with forward many-to-many relationships. 88 | for field in [field for field in opts.many_to_many if field.serialize]: 89 | forward_relations[field.name] = RelationInfo( 90 | model_field=field, 91 | related_model=field.remote_field.model, 92 | to_many=True, 93 | # manytomany do not have to_fields 94 | to_field=None, 95 | has_through_model=( 96 | not field.remote_field.through._meta.auto_created 97 | ), 98 | reverse=False 99 | ) 100 | 101 | return forward_relations 102 | 103 | 104 | def _get_reverse_relationships(opts): 105 | """ 106 | Returns an `OrderedDict` of field names to `RelationInfo`. 107 | """ 108 | reverse_relations = OrderedDict() 109 | all_related_objects = [r for r in opts.related_objects if not r.field.many_to_many] 110 | for relation in all_related_objects: 111 | accessor_name = relation.get_accessor_name() 112 | reverse_relations[accessor_name] = RelationInfo( 113 | model_field=None, 114 | related_model=relation.related_model, 115 | to_many=relation.field.remote_field.multiple, 116 | to_field=_get_to_field(relation.field), 117 | has_through_model=False, 118 | reverse=True 119 | ) 120 | 121 | # Deal with reverse many-to-many relationships. 122 | all_related_many_to_many_objects = [r for r in opts.related_objects if r.field.many_to_many] 123 | for relation in all_related_many_to_many_objects: 124 | accessor_name = relation.get_accessor_name() 125 | reverse_relations[accessor_name] = RelationInfo( 126 | model_field=None, 127 | related_model=relation.related_model, 128 | to_many=True, 129 | # manytomany do not have to_fields 130 | to_field=None, 131 | has_through_model=( 132 | (getattr(relation.field.remote_field, 'through', None) is not None) and 133 | not relation.field.remote_field.through._meta.auto_created 134 | ), 135 | reverse=True 136 | ) 137 | 138 | return reverse_relations 139 | 140 | 141 | def _merge_fields_and_pk(pk, fields): 142 | fields_and_pk = OrderedDict() 143 | fields_and_pk['pk'] = pk 144 | fields_and_pk[pk.name] = pk 145 | fields_and_pk.update(fields) 146 | 147 | return fields_and_pk 148 | 149 | 150 | def _merge_relationships(forward_relations, reverse_relations): 151 | return OrderedDict( 152 | list(forward_relations.items()) + 153 | list(reverse_relations.items()) 154 | ) 155 | 156 | 157 | def is_abstract_model(model): 158 | """ 159 | Given a model class, returns a boolean True if it is abstract and False if it is not. 160 | """ 161 | return hasattr(model, '_meta') and hasattr(model._meta, 'abstract') and model._meta.abstract 162 | -------------------------------------------------------------------------------- /utils/representation.py: -------------------------------------------------------------------------------- 1 | """ 2 | Helper functions for creating user-friendly representations 3 | of serializer classes and serializer fields. 4 | """ 5 | from __future__ import unicode_literals 6 | 7 | import re 8 | 9 | from django.db import models 10 | from django.utils.encoding import force_text 11 | from django.utils.functional import Promise 12 | 13 | from rest_framework.compat import unicode_repr 14 | 15 | 16 | def manager_repr(value): 17 | model = value.model 18 | opts = model._meta 19 | names_and_managers = [ 20 | (manager.name, manager) 21 | for manager 22 | in opts.managers 23 | ] 24 | for manager_name, manager_instance in names_and_managers: 25 | if manager_instance == value: 26 | return '%s.%s.all()' % (model._meta.object_name, manager_name) 27 | return repr(value) 28 | 29 | 30 | def smart_repr(value): 31 | if isinstance(value, models.Manager): 32 | return manager_repr(value) 33 | 34 | if isinstance(value, Promise) and value._delegate_text: 35 | value = force_text(value) 36 | 37 | value = unicode_repr(value) 38 | 39 | # Representations like u'help text' 40 | # should simply be presented as 'help text' 41 | if value.startswith("u'") and value.endswith("'"): 42 | return value[1:] 43 | 44 | # Representations like 45 | # 46 | # Should be presented as 47 | # 48 | value = re.sub(' at 0x[0-9A-Fa-f]{4,32}>', '>', value) 49 | 50 | return value 51 | 52 | 53 | def field_repr(field, force_many=False): 54 | kwargs = field._kwargs 55 | if force_many: 56 | kwargs = kwargs.copy() 57 | kwargs['many'] = True 58 | kwargs.pop('child', None) 59 | 60 | arg_string = ', '.join([smart_repr(val) for val in field._args]) 61 | kwarg_string = ', '.join([ 62 | '%s=%s' % (key, smart_repr(val)) 63 | for key, val in sorted(kwargs.items()) 64 | ]) 65 | if arg_string and kwarg_string: 66 | arg_string += ', ' 67 | 68 | if force_many: 69 | class_name = force_many.__class__.__name__ 70 | else: 71 | class_name = field.__class__.__name__ 72 | 73 | return "%s(%s%s)" % (class_name, arg_string, kwarg_string) 74 | 75 | 76 | def serializer_repr(serializer, indent, force_many=None): 77 | ret = field_repr(serializer, force_many) + ':' 78 | indent_str = ' ' * indent 79 | 80 | if force_many: 81 | fields = force_many.fields 82 | else: 83 | fields = serializer.fields 84 | 85 | for field_name, field in fields.items(): 86 | ret += '\n' + indent_str + field_name + ' = ' 87 | if hasattr(field, 'fields'): 88 | ret += serializer_repr(field, indent + 1) 89 | elif hasattr(field, 'child'): 90 | ret += list_repr(field, indent + 1) 91 | elif hasattr(field, 'child_relation'): 92 | ret += field_repr(field.child_relation, force_many=field.child_relation) 93 | else: 94 | ret += field_repr(field) 95 | 96 | if serializer.validators: 97 | ret += '\n' + indent_str + 'class Meta:' 98 | ret += '\n' + indent_str + ' validators = ' + smart_repr(serializer.validators) 99 | 100 | return ret 101 | 102 | 103 | def list_repr(serializer, indent): 104 | child = serializer.child 105 | if hasattr(child, 'fields'): 106 | return serializer_repr(serializer, indent, force_many=child) 107 | return field_repr(serializer) 108 | -------------------------------------------------------------------------------- /utils/serializer_helpers.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | import collections 4 | from collections import OrderedDict 5 | 6 | from django.utils.encoding import force_text 7 | 8 | from rest_framework.compat import unicode_to_repr 9 | from rest_framework.utils import json 10 | 11 | 12 | class ReturnDict(OrderedDict): 13 | """ 14 | Return object from `serializer.data` for the `Serializer` class. 15 | Includes a backlink to the serializer instance for renderers 16 | to use if they need richer field information. 17 | """ 18 | 19 | def __init__(self, *args, **kwargs): 20 | self.serializer = kwargs.pop('serializer') 21 | super(ReturnDict, self).__init__(*args, **kwargs) 22 | 23 | def copy(self): 24 | return ReturnDict(self, serializer=self.serializer) 25 | 26 | def __repr__(self): 27 | return dict.__repr__(self) 28 | 29 | def __reduce__(self): 30 | # Pickling these objects will drop the .serializer backlink, 31 | # but preserve the raw data. 32 | return (dict, (dict(self),)) 33 | 34 | 35 | class ReturnList(list): 36 | """ 37 | Return object from `serializer.data` for the `SerializerList` class. 38 | Includes a backlink to the serializer instance for renderers 39 | to use if they need richer field information. 40 | """ 41 | 42 | def __init__(self, *args, **kwargs): 43 | self.serializer = kwargs.pop('serializer') 44 | super(ReturnList, self).__init__(*args, **kwargs) 45 | 46 | def __repr__(self): 47 | return list.__repr__(self) 48 | 49 | def __reduce__(self): 50 | # Pickling these objects will drop the .serializer backlink, 51 | # but preserve the raw data. 52 | return (list, (list(self),)) 53 | 54 | 55 | class BoundField(object): 56 | """ 57 | A field object that also includes `.value` and `.error` properties. 58 | Returned when iterating over a serializer instance, 59 | providing an API similar to Django forms and form fields. 60 | """ 61 | 62 | def __init__(self, field, value, errors, prefix=''): 63 | self._field = field 64 | self._prefix = prefix 65 | self.value = value 66 | self.errors = errors 67 | self.name = prefix + self.field_name 68 | 69 | def __getattr__(self, attr_name): 70 | return getattr(self._field, attr_name) 71 | 72 | @property 73 | def _proxy_class(self): 74 | return self._field.__class__ 75 | 76 | def __repr__(self): 77 | return unicode_to_repr('<%s value=%s errors=%s>' % ( 78 | self.__class__.__name__, self.value, self.errors 79 | )) 80 | 81 | def as_form_field(self): 82 | value = '' if (self.value is None or self.value is False) else self.value 83 | return self.__class__(self._field, value, self.errors, self._prefix) 84 | 85 | 86 | class JSONBoundField(BoundField): 87 | def as_form_field(self): 88 | value = self.value 89 | # When HTML form input is used and the input is not valid 90 | # value will be a JSONString, rather than a JSON primitive. 91 | if not getattr(value, 'is_json_string', False): 92 | try: 93 | value = json.dumps(self.value, sort_keys=True, indent=4) 94 | except (TypeError, ValueError): 95 | pass 96 | return self.__class__(self._field, value, self.errors, self._prefix) 97 | 98 | 99 | class NestedBoundField(BoundField): 100 | """ 101 | This `BoundField` additionally implements __iter__ and __getitem__ 102 | in order to support nested bound fields. This class is the type of 103 | `BoundField` that is used for serializer fields. 104 | """ 105 | 106 | def __init__(self, field, value, errors, prefix=''): 107 | if value is None or value is '': 108 | value = {} 109 | super(NestedBoundField, self).__init__(field, value, errors, prefix) 110 | 111 | def __iter__(self): 112 | for field in self.fields.values(): 113 | yield self[field.field_name] 114 | 115 | def __getitem__(self, key): 116 | field = self.fields[key] 117 | value = self.value.get(key) if self.value else None 118 | error = self.errors.get(key) if isinstance(self.errors, dict) else None 119 | if hasattr(field, 'fields'): 120 | return NestedBoundField(field, value, error, prefix=self.name + '.') 121 | return BoundField(field, value, error, prefix=self.name + '.') 122 | 123 | def as_form_field(self): 124 | values = {} 125 | for key, value in self.value.items(): 126 | if isinstance(value, (list, dict)): 127 | values[key] = value 128 | else: 129 | values[key] = '' if (value is None or value is False) else force_text(value) 130 | return self.__class__(self._field, values, self.errors, self._prefix) 131 | 132 | 133 | class BindingDict(collections.MutableMapping): 134 | """ 135 | This dict-like object is used to store fields on a serializer. 136 | 137 | This ensures that whenever fields are added to the serializer we call 138 | `field.bind()` so that the `field_name` and `parent` attributes 139 | can be set correctly. 140 | """ 141 | 142 | def __init__(self, serializer): 143 | self.serializer = serializer 144 | self.fields = OrderedDict() 145 | 146 | def __setitem__(self, key, field): 147 | self.fields[key] = field 148 | field.bind(field_name=key, parent=self.serializer) 149 | 150 | def __getitem__(self, key): 151 | return self.fields[key] 152 | 153 | def __delitem__(self, key): 154 | del self.fields[key] 155 | 156 | def __iter__(self): 157 | return iter(self.fields) 158 | 159 | def __len__(self): 160 | return len(self.fields) 161 | 162 | def __repr__(self): 163 | return dict.__repr__(self.fields) 164 | -------------------------------------------------------------------------------- /utils/urls.py: -------------------------------------------------------------------------------- 1 | from django.utils.encoding import force_str 2 | from django.utils.six.moves.urllib import parse as urlparse 3 | 4 | 5 | def replace_query_param(url, key, val): 6 | """ 7 | Given a URL and a key/val pair, set or replace an item in the query 8 | parameters of the URL, and return the new URL. 9 | """ 10 | (scheme, netloc, path, query, fragment) = urlparse.urlsplit(force_str(url)) 11 | query_dict = urlparse.parse_qs(query, keep_blank_values=True) 12 | query_dict[force_str(key)] = [force_str(val)] 13 | query = urlparse.urlencode(sorted(list(query_dict.items())), doseq=True) 14 | return urlparse.urlunsplit((scheme, netloc, path, query, fragment)) 15 | 16 | 17 | def remove_query_param(url, key): 18 | """ 19 | Given a URL and a key/val pair, remove an item in the query 20 | parameters of the URL, and return the new URL. 21 | """ 22 | (scheme, netloc, path, query, fragment) = urlparse.urlsplit(force_str(url)) 23 | query_dict = urlparse.parse_qs(query, keep_blank_values=True) 24 | query_dict.pop(key, None) 25 | query = urlparse.urlencode(sorted(list(query_dict.items())), doseq=True) 26 | return urlparse.urlunsplit((scheme, netloc, path, query, fragment)) 27 | -------------------------------------------------------------------------------- /versioning.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import unicode_literals 3 | 4 | import re 5 | 6 | from django.utils.translation import ugettext_lazy as _ 7 | 8 | from rest_framework import exceptions 9 | from rest_framework.compat import unicode_http_header 10 | from rest_framework.reverse import _reverse 11 | from rest_framework.settings import api_settings 12 | from rest_framework.templatetags.rest_framework import replace_query_param 13 | from rest_framework.utils.mediatypes import _MediaType 14 | 15 | 16 | class BaseVersioning(object): 17 | default_version = api_settings.DEFAULT_VERSION 18 | allowed_versions = api_settings.ALLOWED_VERSIONS 19 | version_param = api_settings.VERSION_PARAM 20 | 21 | def determine_version(self, request, *args, **kwargs): 22 | msg = '{cls}.determine_version() must be implemented.' 23 | raise NotImplementedError(msg.format( 24 | cls=self.__class__.__name__ 25 | )) 26 | 27 | def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra): 28 | return _reverse(viewname, args, kwargs, request, format, **extra) 29 | 30 | def is_allowed_version(self, version): 31 | if not self.allowed_versions: 32 | return True 33 | return ((version is not None and version == self.default_version) or 34 | (version in self.allowed_versions)) 35 | 36 | 37 | class AcceptHeaderVersioning(BaseVersioning): 38 | """ 39 | GET /something/ HTTP/1.1 40 | Host: example.com 41 | Accept: application/json; version=1.0 42 | """ 43 | invalid_version_message = _('Invalid version in "Accept" header.') 44 | 45 | def determine_version(self, request, *args, **kwargs): 46 | media_type = _MediaType(request.accepted_media_type) 47 | version = media_type.params.get(self.version_param, self.default_version) 48 | version = unicode_http_header(version) 49 | if not self.is_allowed_version(version): 50 | raise exceptions.NotAcceptable(self.invalid_version_message) 51 | return version 52 | 53 | # We don't need to implement `reverse`, as the versioning is based 54 | # on the `Accept` header, not on the request URL. 55 | 56 | 57 | class URLPathVersioning(BaseVersioning): 58 | """ 59 | To the client this is the same style as `NamespaceVersioning`. 60 | The difference is in the backend - this implementation uses 61 | Django's URL keyword arguments to determine the version. 62 | 63 | An example URL conf for two views that accept two different versions. 64 | 65 | urlpatterns = [ 66 | url(r'^(?P[v1|v2]+)/users/$', users_list, name='users-list'), 67 | url(r'^(?P[v1|v2]+)/users/(?P[0-9]+)/$', users_detail, name='users-detail') 68 | ] 69 | 70 | GET /1.0/something/ HTTP/1.1 71 | Host: example.com 72 | Accept: application/json 73 | """ 74 | invalid_version_message = _('Invalid version in URL path.') 75 | 76 | def determine_version(self, request, *args, **kwargs): 77 | version = kwargs.get(self.version_param, self.default_version) 78 | if not self.is_allowed_version(version): 79 | raise exceptions.NotFound(self.invalid_version_message) 80 | return version 81 | 82 | def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra): 83 | if request.version is not None: 84 | kwargs = {} if (kwargs is None) else kwargs 85 | kwargs[self.version_param] = request.version 86 | 87 | return super(URLPathVersioning, self).reverse( 88 | viewname, args, kwargs, request, format, **extra 89 | ) 90 | 91 | 92 | class NamespaceVersioning(BaseVersioning): 93 | """ 94 | To the client this is the same style as `URLPathVersioning`. 95 | The difference is in the backend - this implementation uses 96 | Django's URL namespaces to determine the version. 97 | 98 | An example URL conf that is namespaced into two separate versions 99 | 100 | # users/urls.py 101 | urlpatterns = [ 102 | url(r'^/users/$', users_list, name='users-list'), 103 | url(r'^/users/(?P[0-9]+)/$', users_detail, name='users-detail') 104 | ] 105 | 106 | # urls.py 107 | urlpatterns = [ 108 | url(r'^v1/', include('users.urls', namespace='v1')), 109 | url(r'^v2/', include('users.urls', namespace='v2')) 110 | ] 111 | 112 | GET /1.0/something/ HTTP/1.1 113 | Host: example.com 114 | Accept: application/json 115 | """ 116 | invalid_version_message = _('Invalid version in URL path. Does not match any version namespace.') 117 | 118 | def determine_version(self, request, *args, **kwargs): 119 | resolver_match = getattr(request, 'resolver_match', None) 120 | if resolver_match is None or not resolver_match.namespace: 121 | return self.default_version 122 | 123 | # Allow for possibly nested namespaces. 124 | possible_versions = resolver_match.namespace.split(':') 125 | for version in possible_versions: 126 | if self.is_allowed_version(version): 127 | return version 128 | raise exceptions.NotFound(self.invalid_version_message) 129 | 130 | def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra): 131 | if request.version is not None: 132 | viewname = self.get_versioned_viewname(viewname, request) 133 | return super(NamespaceVersioning, self).reverse( 134 | viewname, args, kwargs, request, format, **extra 135 | ) 136 | 137 | def get_versioned_viewname(self, viewname, request): 138 | return request.version + ':' + viewname 139 | 140 | 141 | class HostNameVersioning(BaseVersioning): 142 | """ 143 | GET /something/ HTTP/1.1 144 | Host: v1.example.com 145 | Accept: application/json 146 | """ 147 | hostname_regex = re.compile(r'^([a-zA-Z0-9]+)\.[a-zA-Z0-9]+\.[a-zA-Z0-9]+$') 148 | invalid_version_message = _('Invalid version in hostname.') 149 | 150 | def determine_version(self, request, *args, **kwargs): 151 | hostname, separator, port = request.get_host().partition(':') 152 | match = self.hostname_regex.match(hostname) 153 | if not match: 154 | return self.default_version 155 | version = match.group(1) 156 | if not self.is_allowed_version(version): 157 | raise exceptions.NotFound(self.invalid_version_message) 158 | return version 159 | 160 | # We don't need to implement `reverse`, as the hostname will already be 161 | # preserved as part of the REST framework `reverse` implementation. 162 | 163 | 164 | class QueryParameterVersioning(BaseVersioning): 165 | """ 166 | GET /something/?version=0.1 HTTP/1.1 167 | Host: example.com 168 | Accept: application/json 169 | """ 170 | invalid_version_message = _('Invalid version in query parameter.') 171 | 172 | def determine_version(self, request, *args, **kwargs): 173 | version = request.query_params.get(self.version_param, self.default_version) 174 | print(version) 175 | if not self.is_allowed_version(version): 176 | raise exceptions.NotFound(self.invalid_version_message) 177 | return version 178 | 179 | def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra): 180 | url = super(QueryParameterVersioning, self).reverse( 181 | viewname, args, kwargs, request, format, **extra 182 | ) 183 | if request.version is not None: 184 | return replace_query_param(url, self.version_param, request.version) 185 | return url 186 | -------------------------------------------------------------------------------- /viewsets.py: -------------------------------------------------------------------------------- 1 | """ 2 | ViewSets are essentially just a type of class based view, that doesn't provide 3 | any method handlers, such as `get()`, `post()`, etc... but instead has actions, 4 | such as `list()`, `retrieve()`, `create()`, etc... 5 | 6 | Actions are only bound to methods at the point of instantiating the views. 7 | 8 | user_list = UserViewSet.as_view({'get': 'list'}) 9 | user_detail = UserViewSet.as_view({'get': 'retrieve'}) 10 | 11 | Typically, rather than instantiate views from viewsets directly, you'll 12 | register the viewset with a router and let the URL conf be determined 13 | automatically. 14 | 15 | router = DefaultRouter() 16 | router.register(r'users', UserViewSet, 'user') 17 | urlpatterns = router.urls 18 | """ 19 | from __future__ import unicode_literals 20 | 21 | from functools import update_wrapper 22 | from inspect import getmembers 23 | 24 | from django.utils.decorators import classonlymethod 25 | from django.views.decorators.csrf import csrf_exempt 26 | 27 | from rest_framework import generics, mixins, views 28 | from rest_framework.reverse import reverse 29 | 30 | 31 | def _is_extra_action(attr): 32 | return hasattr(attr, 'bind_to_methods') 33 | 34 | 35 | class ViewSetMixin(object): 36 | """ 37 | This is the magic. 38 | 39 | Overrides `.as_view()` so that it takes an `actions` keyword that performs 40 | the binding of HTTP methods to actions on the Resource. 41 | 42 | For example, to create a concrete view binding the 'GET' and 'POST' methods 43 | to the 'list' and 'create' actions... 44 | 45 | view = MyViewSet.as_view({'get': 'list', 'post': 'create'}) 46 | """ 47 | 48 | @classonlymethod 49 | def as_view(cls, actions=None, **initkwargs): 50 | """ 51 | Because of the way class based views create a closure around the 52 | instantiated view, we need to totally reimplement `.as_view`, 53 | and slightly modify the view function that is created and returned. 54 | """ 55 | # The suffix initkwarg is reserved for displaying the viewset type. 56 | # eg. 'List' or 'Instance'. 57 | cls.suffix = None 58 | 59 | # The detail initkwarg is reserved for introspecting the viewset type. 60 | cls.detail = None 61 | 62 | # Setting a basename allows a view to reverse its action urls. This 63 | # value is provided by the router through the initkwargs. 64 | cls.basename = None 65 | 66 | # actions must not be empty 67 | if not actions: 68 | raise TypeError("The `actions` argument must be provided when " 69 | "calling `.as_view()` on a ViewSet. For example " 70 | "`.as_view({'get': 'list'})`") 71 | 72 | # sanitize keyword arguments 73 | for key in initkwargs: 74 | if key in cls.http_method_names: 75 | raise TypeError("You tried to pass in the %s method name as a " 76 | "keyword argument to %s(). Don't do that." 77 | % (key, cls.__name__)) 78 | if not hasattr(cls, key): 79 | raise TypeError("%s() received an invalid keyword %r" % ( 80 | cls.__name__, key)) 81 | 82 | def view(request, *args, **kwargs): 83 | self = cls(**initkwargs) 84 | # We also store the mapping of request methods to actions, 85 | # so that we can later set the action attribute. 86 | # eg. `self.action = 'list'` on an incoming GET request. 87 | self.action_map = actions 88 | 89 | # Bind methods to actions 90 | # This is the bit that's different to a standard view 91 | for method, action in actions.items(): 92 | handler = getattr(self, action) 93 | setattr(self, method, handler) 94 | 95 | if hasattr(self, 'get') and not hasattr(self, 'head'): 96 | self.head = self.get 97 | 98 | self.request = request 99 | self.args = args 100 | self.kwargs = kwargs 101 | 102 | # And continue as usual 103 | return self.dispatch(request, *args, **kwargs) 104 | 105 | # take name and docstring from class 106 | update_wrapper(view, cls, updated=()) 107 | 108 | # and possible attributes set by decorators 109 | # like csrf_exempt from dispatch 110 | update_wrapper(view, cls.dispatch, assigned=()) 111 | 112 | # We need to set these on the view function, so that breadcrumb 113 | # generation can pick out these bits of information from a 114 | # resolved URL. 115 | view.cls = cls 116 | view.initkwargs = initkwargs 117 | view.suffix = initkwargs.get('suffix', None) 118 | view.actions = actions 119 | return csrf_exempt(view) 120 | 121 | def initialize_request(self, request, *args, **kwargs): 122 | """ 123 | Set the `.action` attribute on the view, depending on the request method. 124 | """ 125 | request = super(ViewSetMixin, self).initialize_request(request, *args, **kwargs) 126 | method = request.method.lower() 127 | if method == 'options': 128 | # This is a special case as we always provide handling for the 129 | # options method in the base `View` class. 130 | # Unlike the other explicitly defined actions, 'metadata' is implicit. 131 | self.action = 'metadata' 132 | else: 133 | self.action = self.action_map.get(method) 134 | return request 135 | 136 | def reverse_action(self, url_name, *args, **kwargs): 137 | """ 138 | Reverse the action for the given `url_name`. 139 | """ 140 | url_name = '%s-%s' % (self.basename, url_name) 141 | kwargs.setdefault('request', self.request) 142 | 143 | return reverse(url_name, *args, **kwargs) 144 | 145 | @classmethod 146 | def get_extra_actions(cls): 147 | """ 148 | Get the methods that are marked as an extra ViewSet `@action`. 149 | """ 150 | return [method for _, method in getmembers(cls, _is_extra_action)] 151 | 152 | 153 | class ViewSet(ViewSetMixin, views.APIView): 154 | """ 155 | The base ViewSet class does not provide any actions by default. 156 | """ 157 | pass 158 | 159 | 160 | class GenericViewSet(ViewSetMixin, generics.GenericAPIView): 161 | """ 162 | The GenericViewSet class does not provide any actions by default, 163 | but does include the base set of generic view behavior, such as 164 | the `get_object` and `get_queryset` methods. 165 | """ 166 | pass 167 | 168 | 169 | class ReadOnlyModelViewSet(mixins.RetrieveModelMixin, 170 | mixins.ListModelMixin, 171 | GenericViewSet): 172 | """ 173 | A viewset that provides default `list()` and `retrieve()` actions. 174 | """ 175 | pass 176 | 177 | 178 | class ModelViewSet(mixins.CreateModelMixin, 179 | mixins.RetrieveModelMixin, 180 | mixins.UpdateModelMixin, 181 | mixins.DestroyModelMixin, 182 | mixins.ListModelMixin, 183 | GenericViewSet): 184 | """ 185 | A viewset that provides default `create()`, `retrieve()`, `update()`, 186 | `partial_update()`, `destroy()` and `list()` actions. 187 | """ 188 | pass 189 | --------------------------------------------------------------------------------