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

Introduction to develop Django plugin

giginet
September 22, 2016

Introduction to develop Django plugin

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

giginet

September 22, 2016
Tweet

More Decks by giginet

Other Decks in Technology

Transcript

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

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

    Hobby Python Programmer ▸ I’m not a professional
  3. 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, ))
  4. GOAL GOAL ▸ Learn how to develop Python library ▸

    especially Django plugin ▸ Develop more useful plugins ▸ Easy to use ▸ Easy to maintain
  5. 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
  6. 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
  7. WORKFLOW WORKFLOW 1. Implement 2. Write test 3. Setup test

    environment 4. Consider compatibility 5. Release
  8. HOW TO RETAIN COMPATIBILITY ▸ Use back-porting libraries ▸ __future__

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

    compatible with 2 and 3 ▸ https://pythonhosted.org/six/
  10. try: # Python 3 from urllib.parse import urljoin except ImportError:

    # Python 2 from urlparse import urljoin compat.py
  11. WHY TESTING ▸ Easy to check behavior ▸ Easy to

    maintain ▸ Follow dependencies(major version up of Django) ▸ Encourage contribution
  12. 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
  13. 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', )
  14. $ python runtests.py Creating test database for alias 'default'... .

    --------------------------------------------------- ------------------- Ran 1 test in 0.001s OK Destroying test database for alias 'default'...
  15. [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
  16. $ 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 :)
  17. 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/
  18. 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')
  19. 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)
  20. 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'})
  21. 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 '[email protected]' >>> UserFactory().email '[email protected]' FactoryBoy/factory_boy
  22. 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
  23. 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
  24. 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
  25. 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
  26. 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)
  27. 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='[email protected]', 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'), )
  28. CONCLUSION ▸ How to develop Django plugin ▸ Workflow ▸

    Testing ▸ Testing is IMPORTANT ▸ Documentation ▸ Deploying