├── setup.py ├── .gitignore ├── LICENSE ├── README.rst └── django_fsu ├── tests.py └── __init__.py /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | 4 | setup( 5 | name='django-fsu', 6 | version='0.1.2', 7 | packages=['django_fsu'], 8 | include_package_data=True, 9 | license='BSD License', 10 | description='Flask-Style URL Patterns for Django', 11 | url='http://github.com/afg984/django-fsu', 12 | author='afg984', 13 | author_email='afg984@gmail.com', 14 | classifiers=[ 15 | 'Development Status :: 3 - Alpha', 16 | 'Environment :: Web Environment', 17 | 'Framework :: Django', 18 | 'Intended Audience :: Developers', 19 | 'License :: OSI Approved :: BSD License', 20 | 'Operating System :: OS Independent', 21 | 'Programming Language :: Python', 22 | 'Programming Language :: Python :: 3', 23 | 'Topic :: Internet :: WWW/HTTP', 24 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 25 | ] 26 | ) 27 | -------------------------------------------------------------------------------- /.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 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | 59 | # virtualenv 60 | /venv/ 61 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, afg984 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of django-fsu nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ========== 2 | django-fsu 3 | ========== 4 | 5 | Flask-Style URL Patterns for Django 6 | 7 | ------------ 8 | Installation 9 | ------------ 10 | 11 | .. code-block:: bash 12 | 13 | pip install django-fsu 14 | 15 | ----- 16 | Usage 17 | ----- 18 | 19 | In urls.py: change your regex patterns to flask-styled paths. 20 | 21 | .. code-block:: python 22 | 23 | from django.conf.urls import include 24 | from django_fsu import url 25 | 26 | from . import views 27 | 28 | urlpatterns = [ 29 | url('login', views.login), 30 | url('user/', views.profile), 31 | url('article/', views.article), 32 | url('projects/', include('projects.urls'), 33 | ] 34 | 35 | Variable parts in the route can be specified with angular brackets (``user/``). By default a variable part in the URL accepts a string without a slash however a different format code can be specified as well by using ````. 36 | 37 | Variable parts are passed to the view function as keyword arguments. In the above example, ``views.profile`` will be passed with a keyword argument: ``username``. 38 | 39 | Currently supported formats codes are: 40 | 41 | * ``string`` (the default, accepts string without a slash) 42 | * ``int`` 43 | * ``float`` 44 | * ``uuid`` 45 | * ``path`` (accepts any string) 46 | 47 | Please note that ``int`` and ``float`` variables are still passed to the view function as a string. 48 | 49 | To see how individual functions work, 50 | see the docstrings or type ``help('django_fsu')`` in the interactive prompt. 51 | 52 | Credits to: http://flask.pocoo.org/docs/latest/api/#url-route-registrations. 53 | -------------------------------------------------------------------------------- /django_fsu/tests.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import re 3 | import django_fsu 4 | 5 | 6 | class PatternTest(unittest.TestCase): 7 | url = '/user//post/' 8 | 9 | def testQuotePattern(self): 10 | self.assertSequenceEqual( 11 | ['username', 'int:post'], 12 | django_fsu.quote.findall(self.url) 13 | ) 14 | 15 | def testQuoteSplit(self): 16 | self.assertSequenceEqual( 17 | ['/user/', 'username', '/post/', 'int:post', ''], 18 | django_fsu.quote.split(self.url) 19 | ) 20 | 21 | def testIntRegex(self): 22 | self.assertEqual( 23 | r'(?P\d+)', 24 | django_fsu.make_regex('int:post') 25 | ) 26 | 27 | def testStringRegex(self): 28 | self.assertEqual( 29 | r'(?P[^\/]+)', 30 | django_fsu.make_regex('username') 31 | ) 32 | 33 | def _match(self, pat, tar): 34 | return re.match(django_fsu.route(pat), tar) 35 | 36 | def testRegexMatchText(self): 37 | self.assertTrue(self._match('/', 'a string/')) 38 | 39 | def testRegexMatchInt(self): 40 | self.assertTrue(self._match('/', '234532/')) 41 | 42 | def testRegexMatchFloat(self): 43 | self.assertTrue(self._match('/', '234.35/')) 44 | 45 | def testRegexDoesntMatchFloatForInt(self): 46 | self.assertFalse(self._match('/', '34.332/')) 47 | 48 | def testRegexMatchIntForFloat(self): 49 | self.assertTrue(self._match('/', '2342/')) 50 | 51 | # TODO: Test Full Regex 52 | 53 | 54 | if __name__ == '__main__': 55 | unittest.main() 56 | -------------------------------------------------------------------------------- /django_fsu/__init__.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Flask-Style URL Patterns for Django. 3 | 4 | The module intends to provide a way to write URL patterns without regex. 5 | 6 | For the syntax of Flask routing URLs, see: 7 | http://flask.pocoo.org/docs/latest/api/#url-route-registrations 8 | ''' 9 | import re 10 | import itertools 11 | 12 | from django.conf.urls import url as django_url 13 | 14 | 15 | __version__ = '0.1.2' 16 | 17 | 18 | quote = re.compile(r'\<([^<>]+)\>') 19 | 20 | 21 | formatters = { 22 | 'string': r'[^\/]+', 23 | 'int': r'\d+', 24 | 'float': r'[+-]?(?:\d+\.?\d*|\d*\.\d+|[Ii][Nn][Ff]|[Nn][Aa][Nn])', 25 | 'uuid': r'[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}', # noqa 26 | 'path': r'.+', 27 | } 28 | 29 | 30 | def make_regex(spec): 31 | ''' 32 | convert a flask-style into django regex. 33 | 34 | "formatter:keyword" gives: 35 | (?Pformatter_to_regex) 36 | "keyword": 37 | this defaults formatter to 'string' 38 | ''' 39 | fstring, delim, keyword = spec.rpartition(':') 40 | formatter = formatters[fstring or 'string'] 41 | return r'(?P<{keyword}>{regex})'.format(keyword=keyword, regex=formatter) 42 | 43 | 44 | def iroute(path): 45 | for fragment, func in zip( 46 | quote.split(path), 47 | itertools.cycle([re.escape, make_regex]) 48 | ): 49 | yield func(fragment) 50 | 51 | 52 | def route(path): 53 | ''' 54 | convert a flask-style url to django's regex, 55 | without the starting '^' or ending '$'. 56 | ''' 57 | return ''.join(iroute(path)) 58 | 59 | 60 | def url(path, view, *args, **kwargs): 61 | ''' 62 | wrapper of django.conf.urls.url, 63 | but excepts flask-style url instead of regex as the first argument 64 | ''' 65 | if callable(view): 66 | func = endpoint_url 67 | elif isinstance(view, (list, tuple)): 68 | func = include_url 69 | else: 70 | raise TypeError('Invalid view %r of type %r' % (view, type(view))) 71 | return func(path, view, *args, **kwargs) 72 | 73 | 74 | def endpoint_url(path, *args, **kwargs): 75 | return django_url('^%s$' % route(path), *args, **kwargs) 76 | 77 | 78 | def include_url(path, *args, **kwargs): 79 | return django_url('^' + route(path), *args, **kwargs) 80 | 81 | 82 | endpoint_url.__doc__ = url.__doc__ + ''' 83 | assumes the view is a actual view (not a include(...))''' 84 | 85 | include_url.__doc__ = url.__doc__ + ''' 86 | assumes the view is a django.conf.urls.include(...) result''' 87 | --------------------------------------------------------------------------------