├── .gitignore ├── LICENSE ├── README.md ├── django_render ├── __init__.py ├── __init__.py~ ├── annotations │ ├── __init__.py │ └── url_refactor.py ├── middleware │ ├── __init__.py │ └── multihost.py └── url_patterns_maker │ ├── __init__.py │ └── url_patterns_maker.py ├── setup.cfg ├── setup.py └── upload_pip.bash /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | bin/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # Installer logs 26 | pip-log.txt 27 | pip-delete-this-directory.txt 28 | 29 | # Unit test / coverage reports 30 | htmlcov/ 31 | .tox/ 32 | .coverage 33 | .cache 34 | nosetests.xml 35 | coverage.xml 36 | 37 | # Translations 38 | *.mo 39 | 40 | # Mr Developer 41 | .mr.developer.cfg 42 | .project 43 | .pydevproject 44 | 45 | # Rope 46 | .ropeproject 47 | 48 | # Django stuff: 49 | *.log 50 | *.pot 51 | 52 | # Sphinx documentation 53 | docs/_build/ 54 | 55 | .idea 56 | 57 | 58 | tags 59 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Django Render 2 | ============= 3 | 0. [开始](#0-开始) 4 | 1. [简介](#1-简介) 5 | 2. [安装](#2-安装) 6 | 3. [用法](#3-用法) 7 | 4. [注意](#4-注意) 8 | 5. [交流](#5-交流) 9 | 6. [联系](#6-联系) 10 | 7. [待续](#7-待续) 11 | 12 | ## 0. 开始 13 | 14 | 一个非常轻量的 Django URL 装饰器 15 | 16 | ```python 17 | from django_render.annotations import * 18 | 19 | @url(r'^/index$', method=M.POST) 20 | @post(text=str) 21 | def index(request, text): 22 | return {'hello': text} 23 | ``` 24 | 25 | ## 1. 简介 26 | 27 | 基于Python 2.7 重新定义 Django url 的用法,使用@语法可以轻松定义出 RESTful 风格的 url,也可以用于声明 HTTP GET/POST 参数。也可以用于用户认证拦截。自动识别返回类型 HttpResponse, JSON, google-protobuf 28 | 29 | ## 2. 安装 30 | 使用 pip: 31 | 32 | pip install django-render-url 33 | 34 | ## 3. 用法 35 | 36 | ### 3.1. 定义url 37 | 在urls.py中的定义: 38 | 39 | ```python 40 | from django.conf.urls import patterns, include, url 41 | 42 | urlpatterns = patterns('', 43 | url(r'^', include('mysite.views')), 44 | ) 45 | ``` 46 | 47 | 在views.py中声明: 48 | 49 | ```python 50 | @url(ur'^/hello$') 51 | def link(request): 52 | ... 53 | return True 54 | ``` 55 | 56 | 在views 是一个package 的情况下,可以使用url自动扫描: 57 | 58 | ```python 59 | from django_render.url_patterns_maker import urlpatterns_maker 60 | urlpatterns = urlpatterns_maker() 61 | ``` 62 | 上面的代码放到views/\_\_init\_\_.py 中, 就可以自动将views/下面其他的python file name 作为url 的一级目录。如果有特殊名字映射需求可以如下写法: 63 | 64 | ```python 65 | from django_render.url_patterns_maker import urlpatterns_maker 66 | urlpatterns = urlpatterns_maker(default='^', profile='^my/') 67 | ``` 68 | 69 | 等同于如下写法: 70 | ```python 71 | urlpatterns = patterns('', 72 | url(ur'^', include('chooper_api.views.default')), 73 | url(ur'^my/', include('chooper_api.views.profile')), 74 | url(ur'^friend/', include('chooper_api.views.friend')), 75 | url(ur'^feed/', include('chooper_api.views.feed')), 76 | ) 77 | 78 | ``` 79 | 80 | note that unspecified views(friend.py and feed.py here) got their file name(without extension, 81 | and surrounded by '^' and '/') as the url regex 82 | 83 | 定义 GET|POST /index: 84 | 85 | ```python 86 | @url(r'^/index$') 87 | ``` 88 | 89 | 定义 GET /index: 90 | 91 | ```python 92 | @url(r'^/index$', method=M.GET) 93 | ``` 94 | 95 | 定义 POST|PUT /name: 96 | 97 | ```python 98 | @url(r'^/name$', method=[M.POST, M.PUT]) 99 | ``` 100 | 101 | 需要分别处理的 POST /name , PUT /name: 102 | 103 | ```python 104 | @url(r'^/name$', method=M.POST) 105 | def post_name(request): 106 | ... 107 | 108 | @url(r'^/name$', method=M.PUT) 109 | def put_name(request): 110 | ... 111 | ``` 112 | 113 | RESTful Style URL: 114 | 115 | ```python 116 | @url(r'^/blog/$', method=M.GET) 117 | def get_blog_list(request): 118 | ... 119 | 120 | @url(r'^/blog/(?P\d+)$', method=M.GET) 121 | def get_specific_blog(request, id): 122 | ... 123 | 124 | @url(r'^/blog/$', method=M.POST) 125 | @post(text=str) 126 | def post_a_blog(request, text): 127 | ... 128 | 129 | @url(r'^/blog/(?P\d+)$', method=M.PUT) 130 | @post(text=str) 131 | def change_a_blog_text(request, id, text): 132 | ... 133 | ``` 134 | 135 | ### 3.2. 声明HTTP参数 136 | 137 | GET/POST 参数: 138 | 139 | ```python 140 | @get(id=int) 141 | @post(name=str) 142 | ``` 143 | 144 | 在方法上接收: 145 | 146 | ```python 147 | def hello(request, id, name): 148 | ... 149 | ``` 150 | 151 | 更灵活的使用方法, 以GET为例: 152 | 153 | ```python 154 | @get('param1', 'param2') 155 | ''' 156 | HTTP参数: param1, param2 157 | 方法实参: param1, param2 158 | 类型: str 159 | 默认值: 无 160 | ''' 161 | 162 | @get(param1=int) 163 | ''' 164 | HTTP参数: param1 165 | 方法实参: param1 166 | 类型: int 167 | 默认值: 无 168 | ''' 169 | 170 | @get(param1=(int, 0)) 171 | ''' 172 | HTTP参数: param1 173 | 方法实参: param1 174 | 类型: int 175 | 默认值: 0 176 | ''' 177 | 178 | @get(param1=('param_name', int, 0)) 179 | ''' 180 | HTTP参数: param_name 181 | 方法实参: param1 182 | 类型: int 183 | 默认值: 0 184 | ''' 185 | ``` 186 | 187 | 语义化的用法: 188 | 189 | ```python 190 | @get(param1={'name':'parameter_name', 'type':int, 'default':0}) 191 | @get(param1={'type':int, 'default':0}) 192 | @get(param1={'type':int }) 193 | ``` 194 | 195 | 参数类型除了可以转换成: 196 | 197 | ```python 198 | str, int, bool 199 | ``` 200 | 201 | 还支持array: 202 | 203 | ```python 204 | Type.int_list, Type.str_list, Type.json 205 | ``` 206 | 207 | 方法如下: 208 | 209 | ```python 210 | @get(ids=Type.int_list, names=Type.str_list, extrs=Type.json) 211 | ``` 212 | 213 | 其中 Type.int_list 和 Type.str_list, Value应构造成 ids=1,2,3 和 name=Bob,Johns,Peter, 或者 id=1&id=2&id=3 也可以 214 | 215 | 上传文件file type 216 | 217 | ```python 218 | @post(image=Type.file) 219 | ``` 220 | 221 | ### 3.3. 返回类型 222 | 223 | 如果 return HttpResponse 或其子类,则直接返回到客户端 224 | 225 | 如果希望返回Json 数据类型 226 | 227 | ```python 228 | ... 229 | return True 230 | # {'rt':true, 'message':''} 231 | 232 | ... 233 | return False 234 | # {'rt':false, 'message':''} 235 | 236 | ... 237 | return False, ErrorCode.code1 # requires enum34 # from enum.enum import Enum; class ErrorCode(Enum): pass 238 | # {'rt':false, 'message':1} 239 | 240 | ... 241 | return True, {'data': ...} 242 | # {'rt':true, 'data': ...} 243 | 244 | ... 245 | return True, 'message content' 246 | # {'rt':true, 'message':'message content'} 247 | 248 | ... 249 | return {'data':'xxx', 'num':1, 'other':[{...},...]} 250 | # {'data':'xxx', 'num':1, 'other':[{...},...]} 251 | 252 | ... 253 | return [] 254 | # [] 255 | 256 | ... 257 | return # direct return 258 | # {} 259 | 260 | ... 261 | return 'message content' 262 | # {'message':'message content'} 263 | ``` 264 | 265 | ### 3.4. 用户认证 266 | 267 | ```python 268 | # /hello?access_secret_key=The_Key_Only_You_Know 269 | @login_required(access_secret_key='The_Key_Only_You_Know', login_page='/login.html', check_auth=check_auth) 270 | ``` 271 | 272 | 全局定义参数: 273 | 274 | ```python 275 | ## in settings.py 276 | from django_render 277 | ... 278 | django_render.global_access_secret_key = 'The_Key_Only_You_Know' 279 | django_render.global_login_page = '/login.html' 280 | 281 | ## in views.py 282 | @login_required 283 | ``` 284 | 285 | 复杂的登录拦截器: 286 | 287 | ```python 288 | def read_user_interceptor(request): 289 | ... 290 | if success: 291 | return user 292 | else: 293 | return None 294 | 295 | ... 296 | 297 | @url(r'^/hello$') 298 | @login_required(read_user_interceptor=read_user_interceptor) 299 | def hello(request, user): 300 | ''' 301 | @param request: MUST BE EXIST! 302 | @param user: MUST BE EXIST! 303 | ''' 304 | return True, {'user': user.name} 305 | ``` 306 | 307 | 全局定义 read_user_interceptor: 308 | 309 | ```python 310 | #in settings.py 311 | from django_render 312 | 313 | def read_user_interceptor(request): 314 | ... 315 | if success: 316 | return user 317 | else: 318 | return None 319 | 320 | django_render.global_read_user_interceptor = read_user_interceptor 321 | 322 | ## in views.py 323 | @login_required 324 | ``` 325 | 326 | 如果是Ajax类型的请求 327 | 328 | ```python 329 | @login_required(is_ajax=True) 330 | #如果登录失败,不会跳转到 login page, 而是返回 {'rt':false, 'message':'login first'} 331 | ``` 332 | 333 | ## 4. 注意 334 | 335 | > @url() 并不是真正 django 的 url 方法,但因为是在基础上的封装,所以 django url 的其他用法 @url() 也同样支持 336 | 337 | ## 5. 交流 338 | 339 | * mail list: django-render@googlegroups.com 340 | * qq group id: 7790075 341 | 342 | ## 6. 联系 343 | 344 | * email: i@wangtai.me 345 | * twitter: wang_tai 346 | 347 | ## 7. 待续 348 | 349 | 1. 自动识别google-protobuf 350 | 2. 支持 Form, Ajax 使用 PUT/DELETE/... 351 | -------------------------------------------------------------------------------- /django_render/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Last modified: Wang Tai (i@wangtai.me) 4 | 5 | """docstring 6 | """ 7 | 8 | __revision__ = '0.1' 9 | 10 | global_read_user_interceptor = None 11 | global_access_secret_key = None 12 | global_login_page = None 13 | -------------------------------------------------------------------------------- /django_render/__init__.py~: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Last modified: Wang Tai (i@wangtai.me) 4 | 5 | """docstring 6 | """ 7 | 8 | __revision__ = '0.1' 9 | 10 | global_read_user_interceptor = None 11 | global_access_secret_key = None 12 | global_login_page = None -------------------------------------------------------------------------------- /django_render/annotations/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Last modified: Wang Tai (i@wangtai.me) 4 | 5 | """ 6 | url注解 7 | """ 8 | from .url_refactor import _Type, _RequestMethod 9 | from .url_refactor import _login_required, _get, _post, _url, _files 10 | 11 | __revision__ = '0.1' 12 | 13 | __all__ = ( 14 | 'Type', 15 | 'M', 16 | 'RequestMethod', 17 | 'login_required', 18 | 'get', 19 | 'post', 20 | 'url', 21 | 'files', 22 | "GET", 23 | ) 24 | 25 | import functools 26 | 27 | 28 | Type = _Type 29 | RequestMethod = _RequestMethod 30 | M = _RequestMethod 31 | login_required = _login_required 32 | get = _get 33 | post = _post 34 | url = _url 35 | files = _files 36 | 37 | # HTTP请求方法装饰器 38 | # methods = ("GET", "POST") 39 | # for method in methods: 40 | # setattr(__builtin__, method, functools.partial(_url, method=method)) 41 | 42 | 43 | GET = functools.partial(_url, method=M.GET) 44 | POST = functools.partial(_url, method=M.POST) 45 | PUT = functools.partial(_url, method=M.PUT) 46 | HEAD = functools.partial(_url, method=M.HEAD) 47 | TRACE = functools.partial(_url, method=M.TRACE) 48 | DELETE = functools.partial(_url, method=M.DELETE) 49 | OPTIONS = functools.partial(_url, method=M.OPTIONS) 50 | 51 | Params = _get 52 | Fields = _post 53 | -------------------------------------------------------------------------------- /django_render/annotations/url_refactor.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Last modified: Wang Tai (i@wangtai.me) 4 | 5 | """ 6 | 方法定义 7 | """ 8 | 9 | __revision__ = '0.1' 10 | 11 | import functools 12 | import json 13 | import logging 14 | import sys 15 | from copy import deepcopy 16 | from enum import Enum 17 | 18 | from django.conf.urls import url as django_url 19 | from django.http import HttpResponse, HttpResponseRedirect, Http404 20 | 21 | from django_render import global_read_user_interceptor, global_access_secret_key, global_login_page 22 | 23 | CONTENT_TYPE_JSON = 'application/json' 24 | 25 | 26 | class _Type(Enum): 27 | str_list = 0 28 | int_list = 1 29 | json = 2 30 | file = 3 31 | 32 | 33 | class _RequestMethod: 34 | def __init__(self): 35 | pass 36 | 37 | GET = 'GET' 38 | POST = 'POST' 39 | PUT = 'PUT' 40 | HEAD = 'HEAD' 41 | TRACE = 'TRACE' 42 | DELETE = 'DELETE' 43 | OPTIONS = 'OPTIONS' 44 | 45 | 46 | _M = _RequestMethod 47 | 48 | 49 | def _login_required(is_ajax=False, access_secret_key=None, read_user_interceptor=None, login_page=None, 50 | check_auth=None): 51 | def paramed_decorator(func): 52 | @functools.wraps(func) 53 | def decorated(*args, **kwargs): 54 | if is_ajax: 55 | response = HttpResponse(json.dumps({'rt': False, 'message': 'login first'}, separators=(',', ':')), 56 | content_type=CONTENT_TYPE_JSON) 57 | else: 58 | response = HttpResponseRedirect(login_page) 59 | request = args[0] 60 | if (hasattr(request, 'user') and request.user.is_authenticated()) \ 61 | or access_secret_key == request.GET.get('access_secret_key', ''): 62 | return func(*args, **kwargs) 63 | user = read_user_interceptor(request) 64 | if user is None: 65 | return response 66 | else: 67 | if check_auth is not None: 68 | if check_auth(request, user): 69 | pass 70 | else: 71 | return HttpResponse( 72 | json.dumps({'rt': False, 'message': 'Permission Denied!'}, separators=(',', ':')), 73 | content_type=CONTENT_TYPE_JSON) 74 | 75 | if sys.version > '3': 76 | co_varnames = func.__code__.co_varnames 77 | else: 78 | co_varnames = func.func_code.co_varnames 79 | if 'user' in co_varnames: 80 | kwargs.update({'user': user}) 81 | return func(*args, **kwargs) 82 | 83 | return decorated 84 | 85 | if read_user_interceptor is None: 86 | if global_read_user_interceptor is not None: 87 | read_user_interceptor = global_read_user_interceptor 88 | else: 89 | read_user_interceptor = lambda request: None 90 | 91 | if access_secret_key is None: 92 | if global_access_secret_key is not None: 93 | access_secret_key = global_access_secret_key 94 | 95 | if login_page is None: 96 | if global_login_page is not None: 97 | login_page = global_login_page 98 | else: 99 | login_page = 'login' 100 | 101 | return paramed_decorator 102 | 103 | 104 | def __param(method_name, *p_args, **p_kwargs): 105 | """ 106 | @get('param1', 'param2') 107 | @get(param1={'name':'parameter_name', 'type':int, 'default':0}) 108 | @get(param1={'type':int, 'default':0}) 109 | @get(param1={'type':int }) 110 | @get(param1=('param_name', int, 0)) 111 | @get(param1=(int, 0)) 112 | @get(param1=int) 113 | """ 114 | 115 | def paramed_decorator(func): 116 | @functools.wraps(func) 117 | def decorated(*args, **kwargs): 118 | request = args[0] 119 | req_param = deepcopy(request.GET) 120 | req_param.update(request.POST) 121 | m = {'get': request.GET, 'post': request.POST, 'param': req_param} 122 | method = m[method_name] 123 | for k, v in p_kwargs.items(): 124 | _name = None 125 | _type = None 126 | _default = None 127 | 128 | # logging.debug(v) 129 | if type(v) == str: 130 | _type = str 131 | _name = v 132 | elif type(v) == dict: 133 | if 'name' in v: 134 | _name = v['name'] 135 | if 'type' in v: 136 | _type = v['type'] 137 | if 'default' in v: 138 | _default = v['default'] 139 | elif type(v) == tuple and len(v) == 3: 140 | _name = v[0] 141 | _type = v[1] 142 | _default = v[2] 143 | elif type(v) == tuple and len(v) == 2: 144 | _type = v[0] 145 | _default = v[1] 146 | elif type(v) == type: 147 | _type = v 148 | elif v in (_Type.str_list, _Type.int_list, _Type.json, _Type.file): 149 | _type = v 150 | 151 | if _name is None: 152 | _name = k 153 | if _type is None: 154 | _type = str 155 | 156 | has_key = True 157 | try: 158 | if _type == _Type.file: 159 | if method_name != 'post': 160 | return HttpResponse( 161 | json.dumps({'rt': False, 162 | 'message': "The file parameter <{}> should in POST method".format( 163 | _name)}, separators=(',', ':')), 164 | content_type=CONTENT_TYPE_JSON) 165 | origin_v = request.FILES.get(_name, None) 166 | else: 167 | origin_v = ','.join(method.getlist(_name)).strip() 168 | if len(origin_v) == 0: 169 | has_key = False 170 | except KeyError: 171 | has_key = False 172 | if has_key: 173 | if _type == bool: 174 | origin_v = origin_v.lower() 175 | if origin_v == 'false' or origin_v == '0' or origin_v == 'off': 176 | value = False 177 | elif origin_v == 'true' or origin_v == 'on': 178 | value = True 179 | else: 180 | value = bool(origin_v) 181 | elif _type == _Type.str_list: 182 | value = [item for item in origin_v.split(',') if len(item) > 0] 183 | elif _type == _Type.int_list: 184 | value = [int(item) for item in origin_v.split(',')] 185 | elif _type == _Type.json: 186 | try: 187 | value = json.loads(origin_v) 188 | except ValueError: 189 | return HttpResponse( 190 | json.dumps({'rt': False, 'message': "No JSON object could be decoded"}, 191 | separators=(',', ':')), 192 | content_type=CONTENT_TYPE_JSON) 193 | elif _type == _Type.file: 194 | value = origin_v 195 | pass 196 | elif _type == str: 197 | value = origin_v 198 | else: 199 | value = _type(origin_v) 200 | else: 201 | if _default is not None: 202 | value = _default 203 | else: 204 | return HttpResponse( 205 | json.dumps({'rt': False, 'message': 'Please specify the parameter : ' + _name + ";"}, 206 | separators=(',', ':')), 207 | content_type=CONTENT_TYPE_JSON) 208 | kwargs.update({k: value}) 209 | 210 | for k in p_args: 211 | try: 212 | kwargs.update({k: method[k].encode('utf-8')}) 213 | except KeyError: 214 | return HttpResponse(json.dumps({'rt': False, 'message': 'Please specify the parameter : ' + k}, 215 | separators=(',', ':')), 216 | content_type=CONTENT_TYPE_JSON) 217 | return func(*args, **kwargs) 218 | 219 | return decorated 220 | 221 | return paramed_decorator 222 | 223 | 224 | def _get(*p_args, **p_kwargs): 225 | """ 226 | @get('param1', 'param2') 227 | @get(param1={'name':'parameter_name', 'type':int, 'default':0}) 228 | @get(param1={'type':int, 'default':0}) 229 | @get(param1={'type':int }) 230 | @get(param1=('param_name', int, 0)) 231 | @get(param1=(int, 0)) 232 | @get(param1=int) 233 | """ 234 | return __param('get', *p_args, **p_kwargs) 235 | 236 | 237 | def _post(*p_args, **p_kwargs): 238 | """ 239 | @post('param1', 'param2') 240 | @post(param1={'name':'parameter_name', 'type':int, 'default':0}) 241 | @post(param1={'type':int, 'default':0}) 242 | @post(param1={'type':int }) 243 | @post(param1=('param_name', int, 0)) 244 | @post(param1=(int, 0)) 245 | @post(param1=int) 246 | """ 247 | return __param('post', *p_args, **p_kwargs) 248 | 249 | 250 | def _files(*p_args, **p_kwargs): 251 | """ 252 | 253 | :param p_args: 254 | :param p_kwargs: 255 | :return: 256 | """ 257 | 258 | def paramed_decorator(func): 259 | @functools.wraps(func) 260 | def decorated(*args, **kwargs): 261 | request = args[0] 262 | for file_name in p_args: 263 | fp = request.FILES.get(file_name, None) 264 | 265 | try: 266 | kwargs.update({file_name: fp}) 267 | except ValueError: 268 | return HttpResponse( 269 | json.dumps({'rt': False, 'message': 'Please specify the parameter : ' + file_name}, 270 | separators=(',', ':')), 271 | content_type=CONTENT_TYPE_JSON) 272 | except KeyError: 273 | return HttpResponse( 274 | json.dumps({'rt': False, 'message': 'Please specify the parameter : ' + file_name}, 275 | separators=(',', ':')), 276 | content_type=CONTENT_TYPE_JSON) 277 | 278 | return func(*args, **kwargs) 279 | 280 | return decorated 281 | 282 | return paramed_decorator 283 | 284 | 285 | def _param(*p_args, **p_kwargs): 286 | return __param('param', *p_args, **p_kwargs) 287 | 288 | 289 | url_mapping = {} 290 | 291 | 292 | def url_dispatch(request, *args, **kwargs): 293 | # expect_method = kwargs.pop('expect_method', None) 294 | # logging.debug(url_mapping) 295 | url_pattern = kwargs.pop('url_pattern', None) 296 | is_json = kwargs.pop('is_json', False) 297 | method_mapping = url_mapping.get(url_pattern, None) 298 | if method_mapping is None: 299 | raise Http404 300 | view = method_mapping.get(request.method, None) 301 | # logging.debug(view) 302 | if view is not None: 303 | rt = view(request, *args, **kwargs) 304 | logging.debug(type(rt)) 305 | is_json = is_json or not issubclass(type(rt), HttpResponse) 306 | logging.debug(is_json) 307 | if is_json: 308 | return json_result(rt) 309 | return rt 310 | else: 311 | return HttpResponse(status=403, content="Request Forbidden 403") 312 | 313 | 314 | def _url(url_pattern, method=None, is_json=False, *p_args, **p_kwargs): 315 | if method is None: 316 | method = [_M.POST, _M.GET] 317 | 318 | def paramed_decorator(func): 319 | @functools.wraps(func) 320 | def decorated(self, *args, **kwargs): 321 | return func(self, *args, **kwargs) 322 | 323 | url_key = func.__module__ + url_pattern 324 | mapping = url_mapping.get(url_key, None) 325 | if mapping is None: 326 | url_mapping.update({url_key: {}}) 327 | 328 | if type(method) in (list, tuple, set): 329 | for m in method: 330 | url_mapping[url_key].update({m: decorated}) 331 | else: 332 | url_mapping[url_key].update({method: decorated}) 333 | 334 | module = sys.modules[func.__module__] 335 | if not hasattr(module, 'urlpatterns'): 336 | module.urlpatterns = [] 337 | 338 | module.urlpatterns.append( 339 | django_url(url_pattern, url_dispatch, 340 | {'url_pattern': url_key, 'is_json': is_json}, *p_args, 341 | **p_kwargs), ) 342 | return decorated 343 | 344 | return paramed_decorator 345 | 346 | 347 | def json_result(rt): 348 | response = HttpResponse(content_type=CONTENT_TYPE_JSON) 349 | if type(rt) == tuple: 350 | status = rt[0] 351 | if status: # return True, {} 352 | rt_obj = {'rt': status} 353 | rt_obj.update(rt[1]) 354 | response.content = json.dumps(rt_obj, separators=(',', ':')) 355 | return response 356 | else: # return False, 'message' 357 | if isinstance(rt[1], Enum): 358 | response.content = json.dumps({'rt': status, 'message': rt[1].value}, separators=(',', ':')) 359 | else: 360 | response.content = json.dumps({'rt': status, 'message': rt[1]}, separators=(',', ':')) 361 | return response 362 | elif type(rt) is bool: # return True / return False 363 | response.content = json.dumps({'rt': rt, 'message': ''}, separators=(',', ':')) 364 | return response 365 | elif type(rt) is dict: # return {} 366 | response.content = json.dumps(rt, separators=(',', ':')) 367 | return response 368 | elif type(rt) is list: # return [] 369 | response.content = json.dumps(rt, separators=(',', ':')) 370 | return response 371 | elif type(rt) is HttpResponse: # return {} 372 | response = rt 373 | return response 374 | elif rt is None: # direct return 375 | response.content = json.dumps({}, separators=(',', ':')) 376 | return response 377 | else: 378 | response.content = json.dumps({'message': str(rt)}, separators=(',', ':')) 379 | return response 380 | -------------------------------------------------------------------------------- /django_render/middleware/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Last modified: Wang Tai (i@wangtai.me) 4 | 5 | """docstring 6 | """ 7 | 8 | __revision__ = '0.1' 9 | 10 | -------------------------------------------------------------------------------- /django_render/middleware/multihost.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Last modified: Wang Tai (i@wangtai.me) 4 | 5 | """ 6 | ## 7 | # A simple middleware component that lets you use a single Django 8 | # instance to server multiple distinct hosts. 9 | ## 10 | """ 11 | from django.conf import settings 12 | from django.utils.cache import patch_vary_headers 13 | 14 | __revision__ = '0.1' 15 | 16 | 17 | class MultiHostMiddleware: 18 | def process_request(self, request): 19 | try: 20 | host = request.META["HTTP_HOST"] 21 | if host[-3:] == ":80": 22 | host = host[:-3] # ignore default port number, if present 23 | request.urlconf = settings.HOST_MIDDLEWARE_URLCONF_MAP[host] 24 | except KeyError: 25 | pass # use default urlconf (settings.ROOT_URLCONF) 26 | 27 | def process_response(self, request, response): 28 | if getattr(request, "urlconf", None): 29 | patch_vary_headers(response, ('Host',)) 30 | return response 31 | -------------------------------------------------------------------------------- /django_render/url_patterns_maker/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Last modified: Yang Kai (kai.yang@bugua.com) 4 | 5 | """docstring 6 | """ 7 | 8 | revision = '0.1' 9 | 10 | from .url_patterns_maker import urlpatterns_maker -------------------------------------------------------------------------------- /django_render/url_patterns_maker/url_patterns_maker.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Last modified: Yang Kai (kai.yang@bugua.com) 4 | 5 | """urlpatterns_maker 6 | This urlpatterns_maker is used to auto load url configurations loosely distributed in the ``views`` package. 7 | 8 | Usage: 9 | 1. got views like this 10 | #################################### my_app/views/default.py ##################################################### 11 | from django_render.annotations import * 12 | @url(ur'^login/auth$', method=M.POST) 13 | @post(auth_type=int, auth_id=str, auth_token=str, jpush_registration_id=str) 14 | def login_auth(request, auth_type, auth_id, auth_token, jpush_registration_id): 15 | pass 16 | return True, {'token': login_token, 'user_id': user_id} 17 | 18 | #################################### my_app/views/profile.py ##################################################### 19 | ... 20 | 21 | #################################### my_app/views/friend.py ###################################################### 22 | ... 23 | 24 | #################################### my_app/views/feed.py ######################################################## 25 | ... 26 | 27 | 2. config urlpatterns as follow 28 | #################################### my_app/views/__init__.py #################################################### 29 | from django_render.url_patterns_maker import urlpatterns_maker 30 | urlpatterns = urlpatterns_maker(default='^', profile='^my/') 31 | 32 | 3. equivalent to 33 | urlpatterns = patterns('', 34 | url(ur'^', include('chooper_api.views.default')), 35 | url(ur'^my/', include('chooper_api.views.profile')), 36 | url(ur'^friend/', include('chooper_api.views.friend')), 37 | url(ur'^feed/', include('chooper_api.views.feed')), 38 | ) 39 | 40 | note that unspecified views(friend.py and feed.py here) got their file name(without extension, 41 | and surrounded by '^' and '/') as the url regex 42 | """ 43 | 44 | import inspect 45 | import os 46 | from django.conf.urls import patterns, include, url 47 | 48 | revision = '0.1' 49 | 50 | 51 | def urlpatterns_maker(**kwargs): 52 | path_init = inspect.getouterframes(inspect.currentframe())[1][1] 53 | path_views, file_name_init = os.path.split(path_init) 54 | path_app, folder_name_views = os.path.split(path_views) 55 | app_name = os.path.split(path_app)[1] 56 | files = [f.split('.')[0] for f in os.listdir(path_views) if not f.startswith('_') and 57 | (f.endswith('.py') or os.path.isdir(os.path.join(path_views, f))) and file_name_init != f] 58 | path_list = path_init.split('/') 59 | views_index = path_list.index('views') 60 | prefix = '.'.join(path_list[views_index-1:-1]) 61 | # print(path_init) 62 | # print(file_name_init) 63 | # print(path_views) 64 | # print(path_app) 65 | # print(folder_name_views) 66 | # print(app_name) 67 | # print(files) 68 | urlpatterns = [] 69 | for file_name in files: 70 | if file_name in kwargs: 71 | urlpatterns.append( 72 | url(r'{0}'.format(kwargs[file_name]), 73 | include('{0}.{1}'.format(prefix, file_name))) 74 | ) 75 | else: 76 | urlpatterns.append( 77 | url(r'^{0}/'.format(file_name), 78 | include('{0}.{1}'.format(prefix, file_name))) 79 | ) 80 | for urlpattern in urlpatterns: 81 | urlpattern.regex # url pattern check, thanks to django 82 | return urlpatterns 83 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [egg_info] 2 | tag_build = 3 | tag_date = 0 4 | tag_svn_revision = 0 5 | 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import sys 4 | 5 | from setuptools import setup, find_packages 6 | 7 | if sys.version > '3': 8 | requires = [ 9 | 'django >= 1.8.9' 10 | ] 11 | else: 12 | requires = [ 13 | 'enum34 >= 1.1.2', 14 | 'django >= 1.8.9', 15 | ] 16 | 17 | try: 18 | long_description = open('README.md').read() 19 | except: 20 | long_description = '' 21 | 22 | setup( 23 | name='django-render-url', 24 | version='0.11', 25 | packages=find_packages(), 26 | author='WANG Tai', 27 | author_email='i@wangtai.me', 28 | url='https://github.com/wangtai/django_render', 29 | description='a very light django plugin', 30 | long_description=long_description, 31 | license='Apache2', 32 | install_requires=requires, 33 | classifiers=[ 34 | 'Development Status :: 4 - Beta', 35 | 'Environment :: Web Environment', 36 | 'Framework :: Django', 37 | 'Intended Audience :: Developers', 38 | 'Natural Language :: English', 39 | 'Operating System :: POSIX :: Linux', 40 | 'Programming Language :: Python', 41 | 'Topic :: System :: Installation/Setup' 42 | ], 43 | include_package_data=True, 44 | zip_safe=False 45 | ) 46 | -------------------------------------------------------------------------------- /upload_pip.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #sudo python setup.py sdist bdist_wininst upload 4 | sudo python setup.py sdist upload 5 | --------------------------------------------------------------------------------