Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Introduction to develop Django plugin

011714704c4a925e542d426d4cdaa4e3?s=47 giginet
September 22, 2016

Introduction to develop Django plugin

PyCon 2016
はじめて作るDjangoプラグイン

011714704c4a925e542d426d4cdaa4e3?s=128

giginet

September 22, 2016
Tweet

More Decks by giginet

Other Decks in Technology

Transcript

  1. INTRODUCTION TO DEVELOP DJANGO PLUGIN PYCON JP 2016

  2. @GIGINET

  3. SELF INTRODUCTION ▸ Mobile / Rails Engineer @ Cookpad ▸

    Hobby Python Programmer ▸ I’m not a professional
  4. SELF INTRODUCTION ▸ Mobile / Rails Engineer @ Cookpad ▸

    Hobby Python Programmer ▸ I’m not a professional
  5. giginet/django-debug-toolbar-vcs-info

  6. giginet/django-slack-invitation

  7. giginet/django-generic-tagging

  8. lambdalisue/django-permission

  9. from django.db import models from django.contrib.auth.models import User class Article(models.Model):

    title = models.CharField('title', max_length=120) body = models.TextField('body') author = models.ForeignKey(User) collaborators = models.ManyToManyField(User) # apply AuthorPermissionLogic and CollaboratorsPermissionLogic from permission import add_permission_logic from permission.logics import AuthorPermissionLogic from permission.logics import CollaboratorsPermissionLogic add_permission_logic(Article, AuthorPermissionLogic()) add_permission_logic(Article, CollaboratorsPermissionLogic( field_name='collaborators', any_permission=False, change_permission=True, delete_permission=False, ))
  10. lambdalisue/django-inspectional- registration

  11. rosarior/awesome-django

  12. GOAL GOAL ▸ Learn how to develop Python library ▸

    especially Django plugin ▸ Develop more useful plugins ▸ Easy to use ▸ Easy to maintain
  13. INTRODUCTION

  14. TEXT WHAT'S DJANGO ▸ Web Application Framework in Python

  15. Django 1.7 2014/9/2 Django 1.8 2015/4/1 Django 1.9 2015/12/1 Django

    1.10 2016/8/1 Django 1.11 2017/4
  16. None
  17. Django 1.7 2.7, 3.2, 3.3, 3.4 Django 1.8 2.7, 3.2,

    3.3, 3.4, 3.5 Django 1.9 2.7, 3.4, 3.5 Django 1.10 2.7, 3.4, 3.5 Django 1.11 2.7, 3.4, 3.5
  18. TEXT LANGUAGE ▸ Unfortunately, many Django applications still run on

    Python 2.x ▸ ☠Write in Python 2, use 2to3 ▸ ✨Write in Python 3 with compatibility with 2
  19. HOW TO DEVELOP PLUGIN

  20. WORKFLOW WORKFLOW 1. Implement 2. Write test 3. Setup test

    environment 4. Consider compatibility 5. Release
  21. FILE HIERARCHY

  22. FILE HIERARCHY Implementation

  23. FILE HIERARCHY Testing

  24. FILE HIERARCHY Documentation

  25. FILE HIERARCHY Deploy

  26. your_plugin __init__.py models.py, views.py, templates, etc… tests __init__.py test_foobar.py ….

  27. FILE HIERARCHY Implementation

  28. your_plugin __init__.py models.py, views.py, templates, etc… tests __init__.py test_foobar.py ….

  29. HOW TO RETAIN COMPATIBILITY ▸ Use back-porting libraries ▸ __future__

    ▸ cf. enum34, mock, pathlib, etc… ▸ http://python-future.org/ standard_library_imports.html
  30. HOW TO RETAIN COMPATIBILITY ▸ six ▸ Utilities collection to

    compatible with 2 and 3 ▸ https://pythonhosted.org/six/
  31. HOW TO RETAIN COMPATIBILITY from six import python_2_unicode_compatible @python_2_unicode_compatible class

    Cat(object): name = 'Tama' def __str__(self): return self
  32. try: # Python 3 from urllib.parse import urljoin except ImportError:

    # Python 2 from urlparse import urljoin compat.py
  33. TESTING

  34. WHY TESTING ▸ Easy to check behavior ▸ Easy to

    maintain ▸ Follow dependencies(major version up of Django) ▸ Encourage contribution
  35. FILE HIERARCHY Testing

  36. import os import sys import django from django.conf import settings

    from django.test.utils import get_runner if __name__ == "__main__": os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.settings' django.setup() TestRunner = get_runner(settings) test_runner = TestRunner() failures = test_runner.run_tests([“your_plugin”]) sys.exit(bool(failures)) https://docs.djangoproject.com/en/1.10/topics/testing/advanced/ runtests.py
  37. INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'your_plugin',

    ] tests/settings.py
  38. if django.VERSION >= (1, 10): TEMPLATES = [ { 'BACKEND':

    'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'OPTIONS': { 'loaders': [ 'django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader', ], 'debug': DEBUG, }, } ] else: TEMPLATE_DEBUG = DEBUG TEMPLATE_DIRS = ( ) TEMPLATE_LOADERS = ( 'django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader', # 'django.template.loaders.eggs.Loader', )
  39. from django.test import TestCase class CalculatorTestCase(TestCase): def test_add(self): self.assertEqual(1 +

    1, 2) your_plugin/tests/test_calculator.py
  40. $ python runtests.py Creating test database for alias 'default'... .

    --------------------------------------------------- ------------------- Ran 1 test in 0.001s OK Destroying test database for alias 'default'...
  41. https://tox.readthedocs.io/en/latest/ 442742 442742 tox

  42. [tox] envlist = py33-django{17,18}, {py27,py34}-django{17,18,19,110} py35-django{18,19,110} [testenv] basepython = py27:

    python2.7 py33: python3.3 py34: python3.4 py35: python3.5 deps= django17: django>=1.7,<1.8 django18: django>=1.8,<1.9 django19: django>=1.9,<1.10 django110: django>=1.10,<1.11 tox.ini
  43. $ tox py33-django17: commands succeeded py33-django18: commands succeeded py27-django17: commands

    succeeded py27-django18: commands succeeded py27-django19: commands succeeded py27-django110: commands succeeded py34-django17: commands succeeded py34-django18: commands succeeded py34-django19: commands succeeded py34-django110: commands succeeded py35-django18: commands succeeded py35-django19: commands succeeded py35-django110: commands succeeded congratulations :)
  44. $ tox -e py35-django110

  45. USE DJANGO TESTING UTILITIES ▸ There are useful testing utilities

    in Django ▸ django.test.utils ▸ https://docs.djangoproject.com/en/1.10/_modules/ django/test/utils/
  46. from django.test import TestCase, override_settings from django.conf import settings class

    APIClientTest(TestCase): @override_settings(API_TOKEN='token') def test_override_settings(self): self.assetEqual(settings['API_TOKEN'], 'token')
  47. import time from django.test.utils import freeze_time def test_freeze_time(self): frozen_time =

    time.time() with freeze_time(frozen_time): self.assertEqual(time.time(), frozen_time)
  48. from unittest import TestCase from .compat import Mock, patch class

    UserClientTest(TestCase): def test_request(self): response = Mock() response.json.side_effect = lambda: {'name': 'giginet'} response.status_code = 200 client = APIClient() with patch('requests.get', return_value=response) as get: user = client.users(name='giginet') get.assert_called_with('https://example.com/api/users', params={'name': 'giginet'})
  49. try: from unittest.mock import patch, Mock except ImportError: from mock

    import patch, Mock
  50. from django.db import models class TestArticle(models.Model): title = models.CharField('Title', max_length=255)

    class Meta: app_label = 'your_plugin' your_plugin/tests/models.py
  51. INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'your_plugin',

    'your_plugin.tests' ] tests/settings.py
  52. class UserFactory(factory.Factory): class Meta: model = models.User first_name = 'John'

    last_name = 'Doe' email = factory.Sequence(lambda n: 'person{0}@example.com'.format(n)) >>> UserFactory().email 'person0@example.com' >>> UserFactory().email 'person1@example.com' FactoryBoy/factory_boy
  53. CONTINUOUS INTEGRATION

  54. None
  55. sudo: false language: python python: - 2.7 - 3.3 -

    3.4 - 3.5 install: - pip install tox tox-travis - pip install coverage coveralls script: - tox after_success: - coverage report - coveralls .travis.yml
  56. ryanhiebert/tox-travis

  57. sudo: false language: python python: - 2.7 - 3.3 -

    3.4 - 3.5 install: - pip install tox tox-travis - pip install coverage coveralls script: - tox after_success: - coverage report - coveralls .travis.yml
  58. coveralls.io

  59. None
  60. sudo: false language: python python: - 2.7 - 3.3 -

    3.4 - 3.5 install: - pip install tox tox-travis - pip install coverage coveralls script: - tox after_success: - coverage report - coveralls .travis.yml
  61. USEFUL TOOLS ▸ requires.io ▸ scrutinizer ▸ landscape.io

  62. requires.io

  63. scrutinizer

  64. landscape.io

  65. DOCUMENTATION

  66. None
  67. https://docs.readthedocs.io/en/latest/getting_started.html $ pip install sphinx sphinx- autobuild $ mkdir docs

    $ cd docs $ sphinx-quickstart $ make html
  68. https://pythonhosted.org/an_example_pypi_project/sphinx.html def public_fn_with_sphinxy_docstring(name, state=None): """This function does something. :param name:

    The name to use. :type name: str. :param state: Current state to be in. :type state: bool. :returns: int -- the return code. :raises: AttributeError, KeyError """ return 0
  69. None
  70. https://pythonhosted.org/an_example_pypi_project/sphinx.html def public_fn_with_googley_docstring(name, state=None): """This function does something. Args: name

    (str): The name to use. Kwargs: state (bool): Current state to be in. Returns: int. The return code:: 0 -- Success! 1 -- No good. 2 -- Try again. Raises: AttributeError, KeyError A really great idea. A way you might use me is >>> print public_fn_with_googley_docstring(name='foo', state=None)
  71. readthedocs

  72. DEPLOY

  73. TEXT ▸ MANIFEST.in ▸ LICENSE.rst ▸ README.rst ▸ setup.py

  74. include README.rst include LICENSE.rst include requirements.txt include requirements-test.txt MANIFEST.IN

  75. README.rst

  76. shields.io

  77. from setuptools import setup, find_packages setup( name=NAME, version=VERSION, description='A Django

    Debug Toolbar panel to show VCS info', long_description=read('README.rst'), classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Framework :: Django', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Software Development :: Libraries', 'Topic :: Software Development :: Libraries :: Application Frameworks', 'Topic :: Software Development :: Libraries :: Python Modules', ], keywords='django django-debug-toolbar git vcs info revision hash panel', author='giginet', author_email='giginet.net@gmail.com', url='https://github.com/giginet/%s' % NAME, download_url='https://github.com/giginet/%s/tarball/master' % NAME, license='MIT', packages=find_packages(), include_package_data=True, package_data={ '': ['README.rst', 'requirements.txt', 'requirements-test.txt'], }, zip_safe=True, install_requires=readlist('requirements.txt'), test_suite='runtests.run_tests', tests_require=readlist('requirements-test.txt'), )
  78. $ python setup.py register $ python setup.py sdist upload

  79. None
  80. None
  81. CONCLUSION ▸ How to develop Django plugin ▸ Workflow ▸

    Testing ▸ Testing is IMPORTANT ▸ Documentation ▸ Deploying
  82. ANY QUESTIONS?