├── .gitignore ├── .travis-requirements.txt ├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── README.rst ├── docs ├── Makefile ├── _themes │ ├── LICENSE │ ├── README.rst │ ├── flask_theme_support.py │ ├── kr │ │ ├── layout.html │ │ ├── relations.html │ │ ├── static │ │ │ ├── flasky.css_t │ │ │ └── small_flask.css │ │ └── theme.conf │ └── kr_small │ │ ├── layout.html │ │ ├── static │ │ └── flasky.css_t │ │ └── theme.conf ├── conf.py ├── index.rst ├── make.bat └── tutorial.rst ├── example ├── example │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── manage.py ├── gcm ├── __init__.py ├── admin.py ├── api.py ├── locale │ └── pt_BR │ │ └── LC_MESSAGES │ │ └── django.po ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py ├── routers.py ├── serializers.py ├── settings.py ├── tests.py └── utils.py ├── setup.cfg └── setup.py /.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 | # PyCharm 60 | .idea -------------------------------------------------------------------------------- /.travis-requirements.txt: -------------------------------------------------------------------------------- 1 | coverage>=3.7.1 2 | coveralls>=0.4.2 -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.4" 5 | env: 6 | - DJANGO_VERSION=1.7 7 | - DJANGO_VERSION=1.8 8 | install: 9 | - pip install -q Django==$DJANGO_VERSION 10 | - pip install -e . 11 | - pip install -r .travis-requirements.txt 12 | script: 13 | coverage run --source=gcm example/manage.py test gcm 14 | after_success: 15 | coveralls 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Hugo Brilhante 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.rst 3 | recursive-include docs * -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ====================== 2 | Django GCM Android iOS (Deprecate due Firebase Cloud Messaging) 3 | ====================== 4 | 5 | django-gcm-android-ios is a simple Django app to send a message using GCM HTTP connection server protocol. 6 | 7 | Detailed documentation is in the "docs" directory. 8 | 9 | .. image:: https://travis-ci.org/hugobrilhante/django-gcm-android-ios.svg 10 | :target: https://travis-ci.org/hugobrilhante/django-gcm-android-ios 11 | 12 | .. image:: https://coveralls.io/repos/hugobrilhante/django-gcm-android-ios/badge.svg?branch=master&service=github 13 | :target: https://coveralls.io/github/hugobrilhante/django-gcm-android-ios?branch=master 14 | 15 | .. image:: https://readthedocs.org/projects/django-gcm-android-ios/badge/?version=latest 16 | :target: http://django-gcm-android-ios.readthedocs.org/en/latest/ 17 | :alt: Documentation Status 18 | 19 | .. image:: https://img.shields.io/pypi/status/django-gcm-android-ios.svg 20 | :target: https://pypi.python.org/pypi/django-gcm-android-ios 21 | 22 | .. image:: https://img.shields.io/pypi/dm/django-gcm-android-ios.svg 23 | :target: https://pypi.python.org/pypi/django-gcm-android-ios/1.0.0#downloads 24 | 25 | .. image:: https://img.shields.io/pypi/l/django-gcm-android-ios.svg 26 | :target: https://github.com/hugobrilhante/django-gcm-android-ios/blob/master/LICENSE 27 | 28 | .. image:: https://img.shields.io/github/release/hugobrilhante/django-gcm-android-ios.svg 29 | :target: https://github.com/hugobrilhante/django-gcm-android-ios/releases/tag/1.0.0 30 | 31 | .. image:: https://img.shields.io/pypi/pyversions/django-gcm-android-ios.svg 32 | :target: https://pypi.python.org/pypi/django-gcm-android-ios 33 | 34 | 35 | 36 | Quick start 37 | ----------- 38 | 39 | 1. Install django-gcm-android-ios:: 40 | 41 | pip install django-gcm-android-ios 42 | 43 | 2. Add "gcm" to your INSTALLED_APPS setting like this:: 44 | 45 | INSTALLED_APPS = ( 46 | ... 47 | 'gcm', 48 | ) 49 | 50 | 3. Add in setting api keys like this:: 51 | 52 | GCM_DEVICE_MODEL = "DeviceModel" # default gcm.Device 53 | GCM_IOS_APIKEY = "IOS_APIKEY" 54 | GCM_ANDROID_APIKEY = "ANDROID_APIKEY" 55 | 56 | 57 | 4. Include the gcm routers in your project urls.py like this:: 58 | 59 | from gcm.routers import router 60 | url(r'api/', include(router.urls)) 61 | 62 | 5. Run `python manage.py migrate` to create the device models 63 | 64 | 65 | 6. To register device:: 66 | 67 | curl -X POST -H "Content-Type: application/json" -H "Authorization: " 68 | -d '{ 69 | "dev_id": "Device id", 70 | "dev_type": "ANDROID or IOS", 71 | "reg_id": "Register id" 72 | }' 'http://localhost:8001/api/devices' 73 | 74 | 7. To unregister device:: 75 | 76 | curl -X DELETE -H "Content-Type: application/json" -H "Authorization: " 77 | 'http://localhost:8001/api/devices/id_device' 78 | 79 | 80 | .. image:: https://badges.gitter.im/Join%20Chat.svg 81 | :alt: Join the chat at https://gitter.im/hugobrilhante/django-gcm-android-ios 82 | :target: https://gitter.im/hugobrilhante/django-gcm-android-ios?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge 83 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-gcm-ios-android.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-gcm-ios-android.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/django-gcm-ios-android" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-gcm-ios-android" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /docs/_themes/LICENSE: -------------------------------------------------------------------------------- 1 | Modifications: 2 | 3 | Copyright (c) 2010 Kenneth Reitz. 4 | 5 | 6 | Original Project: 7 | 8 | Copyright (c) 2010 by Armin Ronacher. 9 | 10 | 11 | Some rights reserved. 12 | 13 | Redistribution and use in source and binary forms of the theme, with or 14 | without modification, are permitted provided that the following conditions 15 | are met: 16 | 17 | * Redistributions of source code must retain the above copyright 18 | notice, this list of conditions and the following disclaimer. 19 | 20 | * Redistributions in binary form must reproduce the above 21 | copyright notice, this list of conditions and the following 22 | disclaimer in the documentation and/or other materials provided 23 | with the distribution. 24 | 25 | * The names of the contributors may not be used to endorse or 26 | promote products derived from this software without specific 27 | prior written permission. 28 | 29 | We kindly ask you to only use these themes in an unmodified manner just 30 | for Flask and Flask-related products, not for unrelated projects. If you 31 | like the visual style and want to use it for your own projects, please 32 | consider making some larger changes to the themes (such as changing 33 | font faces, sizes, colors or margins). 34 | 35 | THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 36 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 37 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 38 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 39 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 40 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 41 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 42 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 43 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 44 | ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE 45 | POSSIBILITY OF SUCH DAMAGE. 46 | -------------------------------------------------------------------------------- /docs/_themes/README.rst: -------------------------------------------------------------------------------- 1 | krTheme Sphinx Style 2 | ==================== 3 | 4 | This repository contains sphinx styles Kenneth Reitz uses in most of 5 | his projects. It is a drivative of Mitsuhiko's themes for Flask and Flask related 6 | projects. To use this style in your Sphinx documentation, follow 7 | this guide: 8 | 9 | 1. put this folder as _themes into your docs folder. Alternatively 10 | you can also use git submodules to check out the contents there. 11 | 12 | 2. add this to your conf.py: :: 13 | 14 | sys.path.append(os.path.abspath('_themes')) 15 | html_theme_path = ['_themes'] 16 | html_theme = 'kr' 17 | 18 | The following themes exist: 19 | 20 | **kr** 21 | the standard flask documentation theme for large projects 22 | 23 | **kr_small** 24 | small one-page theme. Intended to be used by very small addon libraries. 25 | 26 | -------------------------------------------------------------------------------- /docs/_themes/flask_theme_support.py: -------------------------------------------------------------------------------- 1 | # flasky extensions. flasky pygments style based on tango style 2 | from pygments.style import Style 3 | from pygments.token import Keyword, Name, Comment, String, Error, \ 4 | Number, Operator, Generic, Whitespace, Punctuation, Other, Literal 5 | 6 | 7 | class FlaskyStyle(Style): 8 | background_color = "#f8f8f8" 9 | default_style = "" 10 | 11 | styles = { 12 | # No corresponding class for the following: 13 | # Text: "", # class: '' 14 | Whitespace: "underline #f8f8f8", # class: 'w' 15 | Error: "#a40000 border:#ef2929", # class: 'err' 16 | Other: "#000000", # class 'x' 17 | 18 | Comment: "italic #8f5902", # class: 'c' 19 | Comment.Preproc: "noitalic", # class: 'cp' 20 | 21 | Keyword: "bold #004461", # class: 'k' 22 | Keyword.Constant: "bold #004461", # class: 'kc' 23 | Keyword.Declaration: "bold #004461", # class: 'kd' 24 | Keyword.Namespace: "bold #004461", # class: 'kn' 25 | Keyword.Pseudo: "bold #004461", # class: 'kp' 26 | Keyword.Reserved: "bold #004461", # class: 'kr' 27 | Keyword.Type: "bold #004461", # class: 'kt' 28 | 29 | Operator: "#582800", # class: 'o' 30 | Operator.Word: "bold #004461", # class: 'ow' - like keywords 31 | 32 | Punctuation: "bold #000000", # class: 'p' 33 | 34 | # because special names such as Name.Class, Name.Function, etc. 35 | # are not recognized as such later in the parsing, we choose them 36 | # to look the same as ordinary variables. 37 | Name: "#000000", # class: 'n' 38 | Name.Attribute: "#c4a000", # class: 'na' - to be revised 39 | Name.Builtin: "#004461", # class: 'nb' 40 | Name.Builtin.Pseudo: "#3465a4", # class: 'bp' 41 | Name.Class: "#000000", # class: 'nc' - to be revised 42 | Name.Constant: "#000000", # class: 'no' - to be revised 43 | Name.Decorator: "#888", # class: 'nd' - to be revised 44 | Name.Entity: "#ce5c00", # class: 'ni' 45 | Name.Exception: "bold #cc0000", # class: 'ne' 46 | Name.Function: "#000000", # class: 'nf' 47 | Name.Property: "#000000", # class: 'py' 48 | Name.Label: "#f57900", # class: 'nl' 49 | Name.Namespace: "#000000", # class: 'nn' - to be revised 50 | Name.Other: "#000000", # class: 'nx' 51 | Name.Tag: "bold #004461", # class: 'nt' - like a keyword 52 | Name.Variable: "#000000", # class: 'nv' - to be revised 53 | Name.Variable.Class: "#000000", # class: 'vc' - to be revised 54 | Name.Variable.Global: "#000000", # class: 'vg' - to be revised 55 | Name.Variable.Instance: "#000000", # class: 'vi' - to be revised 56 | 57 | Number: "#990000", # class: 'm' 58 | 59 | Literal: "#000000", # class: 'l' 60 | Literal.Date: "#000000", # class: 'ld' 61 | 62 | String: "#4e9a06", # class: 's' 63 | String.Backtick: "#4e9a06", # class: 'sb' 64 | String.Char: "#4e9a06", # class: 'sc' 65 | String.Doc: "italic #8f5902", # class: 'sd' - like a comment 66 | String.Double: "#4e9a06", # class: 's2' 67 | String.Escape: "#4e9a06", # class: 'se' 68 | String.Heredoc: "#4e9a06", # class: 'sh' 69 | String.Interpol: "#4e9a06", # class: 'si' 70 | String.Other: "#4e9a06", # class: 'sx' 71 | String.Regex: "#4e9a06", # class: 'sr' 72 | String.Single: "#4e9a06", # class: 's1' 73 | String.Symbol: "#4e9a06", # class: 'ss' 74 | 75 | Generic: "#000000", # class: 'g' 76 | Generic.Deleted: "#a40000", # class: 'gd' 77 | Generic.Emph: "italic #000000", # class: 'ge' 78 | Generic.Error: "#ef2929", # class: 'gr' 79 | Generic.Heading: "bold #000080", # class: 'gh' 80 | Generic.Inserted: "#00A000", # class: 'gi' 81 | Generic.Output: "#888", # class: 'go' 82 | Generic.Prompt: "#745334", # class: 'gp' 83 | Generic.Strong: "bold #000000", # class: 'gs' 84 | Generic.Subheading: "bold #800080", # class: 'gu' 85 | Generic.Traceback: "bold #a40000", # class: 'gt' 86 | } 87 | -------------------------------------------------------------------------------- /docs/_themes/kr/layout.html: -------------------------------------------------------------------------------- 1 | {%- extends "basic/layout.html" %} 2 | {%- block extrahead %} 3 | {{ super() }} 4 | {% if theme_touch_icon %} 5 | 6 | {% endif %} 7 | 9 | 10 | {% endblock %} 11 | {%- block relbar2 %}{% endblock %} 12 | {%- block footer %} 13 | Fork me on GitHub 16 | 19 | {%- endblock %} 20 | -------------------------------------------------------------------------------- /docs/_themes/kr/relations.html: -------------------------------------------------------------------------------- 1 |

Related Topics

2 | 24 | -------------------------------------------------------------------------------- /docs/_themes/kr/static/flasky.css_t: -------------------------------------------------------------------------------- 1 | /* 2 | * flasky.css_t 3 | * ~~~~~~~~~~~~ 4 | * 5 | * :copyright: Copyright 2010 by Armin Ronacher. Modifications by Kenneth Reitz. 6 | * :license: Flask Design License, see LICENSE for details. 7 | */ 8 | 9 | {% set page_width = '940px' %} 10 | {% set sidebar_width = '220px' %} 11 | 12 | @import url("basic.css"); 13 | 14 | /* -- page layout ----------------------------------------------------------- */ 15 | 16 | body { 17 | font-family: 'goudy old style', 'minion pro', 'bell mt', Georgia, 'Hiragino Mincho Pro'; 18 | font-size: 17px; 19 | background-color: white; 20 | color: #000; 21 | margin: 0; 22 | padding: 0; 23 | } 24 | 25 | div.document { 26 | width: {{ page_width }}; 27 | margin: 30px auto 0 auto; 28 | } 29 | 30 | div.documentwrapper { 31 | float: left; 32 | width: 100%; 33 | } 34 | 35 | div.bodywrapper { 36 | margin: 0 0 0 {{ sidebar_width }}; 37 | } 38 | 39 | div.sphinxsidebar { 40 | width: {{ sidebar_width }}; 41 | } 42 | 43 | hr { 44 | border: 1px solid #B1B4B6; 45 | } 46 | 47 | div.body { 48 | background-color: #ffffff; 49 | color: #3E4349; 50 | padding: 0 30px 0 30px; 51 | } 52 | 53 | img.floatingflask { 54 | padding: 0 0 10px 10px; 55 | float: right; 56 | } 57 | 58 | div.footer { 59 | width: {{ page_width }}; 60 | margin: 20px auto 30px auto; 61 | font-size: 14px; 62 | color: #888; 63 | text-align: right; 64 | } 65 | 66 | div.footer a { 67 | color: #888; 68 | } 69 | 70 | div.related { 71 | display: none; 72 | } 73 | 74 | div.sphinxsidebar a { 75 | color: #444; 76 | text-decoration: none; 77 | border-bottom: 1px dotted #999; 78 | } 79 | 80 | div.sphinxsidebar a:hover { 81 | border-bottom: 1px solid #999; 82 | } 83 | 84 | div.sphinxsidebar { 85 | font-size: 14px; 86 | line-height: 1.5; 87 | } 88 | 89 | div.sphinxsidebarwrapper { 90 | padding: 18px 10px; 91 | } 92 | 93 | div.sphinxsidebarwrapper p.logo { 94 | padding: 0; 95 | margin: -10px 0 0 -20px; 96 | text-align: center; 97 | } 98 | 99 | div.sphinxsidebar h3, 100 | div.sphinxsidebar h4 { 101 | font-family: 'Garamond', 'Georgia', serif; 102 | color: #444; 103 | font-size: 24px; 104 | font-weight: normal; 105 | margin: 0 0 5px 0; 106 | padding: 0; 107 | } 108 | 109 | div.sphinxsidebar h4 { 110 | font-size: 20px; 111 | } 112 | 113 | div.sphinxsidebar h3 a { 114 | color: #444; 115 | } 116 | 117 | div.sphinxsidebar p.logo a, 118 | div.sphinxsidebar h3 a, 119 | div.sphinxsidebar p.logo a:hover, 120 | div.sphinxsidebar h3 a:hover { 121 | border: none; 122 | } 123 | 124 | div.sphinxsidebar p { 125 | color: #555; 126 | margin: 10px 0; 127 | } 128 | 129 | div.sphinxsidebar ul { 130 | margin: 10px 0; 131 | padding: 0; 132 | color: #000; 133 | } 134 | 135 | div.sphinxsidebar input { 136 | border: 1px solid #ccc; 137 | font-family: 'Georgia', serif; 138 | font-size: 1em; 139 | } 140 | 141 | /* -- body styles ----------------------------------------------------------- */ 142 | 143 | a { 144 | color: #004B6B; 145 | text-decoration: underline; 146 | } 147 | 148 | a:hover { 149 | color: #6D4100; 150 | text-decoration: underline; 151 | } 152 | 153 | div.body h1, 154 | div.body h2, 155 | div.body h3, 156 | div.body h4, 157 | div.body h5, 158 | div.body h6 { 159 | font-family: 'Garamond', 'Georgia', serif; 160 | font-weight: normal; 161 | margin: 30px 0px 10px 0px; 162 | padding: 0; 163 | } 164 | 165 | div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } 166 | div.body h2 { font-size: 180%; } 167 | div.body h3 { font-size: 150%; } 168 | div.body h4 { font-size: 130%; } 169 | div.body h5 { font-size: 100%; } 170 | div.body h6 { font-size: 100%; } 171 | 172 | a.headerlink { 173 | color: #ddd; 174 | padding: 0 4px; 175 | text-decoration: none; 176 | } 177 | 178 | a.headerlink:hover { 179 | color: #444; 180 | background: #eaeaea; 181 | } 182 | 183 | div.body p, div.body dd, div.body li { 184 | line-height: 1.4em; 185 | } 186 | 187 | div.admonition { 188 | background: #fafafa; 189 | margin: 20px -30px; 190 | padding: 10px 30px; 191 | border-top: 1px solid #ccc; 192 | border-bottom: 1px solid #ccc; 193 | } 194 | 195 | div.admonition tt.xref, div.admonition a tt { 196 | border-bottom: 1px solid #fafafa; 197 | } 198 | 199 | dd div.admonition { 200 | margin-left: -60px; 201 | padding-left: 60px; 202 | } 203 | 204 | div.admonition p.admonition-title { 205 | font-family: 'Garamond', 'Georgia', serif; 206 | font-weight: normal; 207 | font-size: 24px; 208 | margin: 0 0 10px 0; 209 | padding: 0; 210 | line-height: 1; 211 | } 212 | 213 | div.admonition p.last { 214 | margin-bottom: 0; 215 | } 216 | 217 | div.highlight { 218 | background-color: white; 219 | } 220 | 221 | dt:target, .highlight { 222 | background: #FAF3E8; 223 | } 224 | 225 | div.note { 226 | background-color: #eee; 227 | border: 1px solid #ccc; 228 | } 229 | 230 | div.seealso { 231 | background-color: #ffc; 232 | border: 1px solid #ff6; 233 | } 234 | 235 | div.topic { 236 | background-color: #eee; 237 | } 238 | 239 | p.admonition-title { 240 | display: inline; 241 | } 242 | 243 | p.admonition-title:after { 244 | content: ":"; 245 | } 246 | 247 | pre, tt { 248 | font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; 249 | font-size: 0.9em; 250 | } 251 | 252 | img.screenshot { 253 | } 254 | 255 | tt.descname, tt.descclassname { 256 | font-size: 0.95em; 257 | } 258 | 259 | tt.descname { 260 | padding-right: 0.08em; 261 | } 262 | 263 | img.screenshot { 264 | -moz-box-shadow: 2px 2px 4px #eee; 265 | -webkit-box-shadow: 2px 2px 4px #eee; 266 | box-shadow: 2px 2px 4px #eee; 267 | } 268 | 269 | table.docutils { 270 | border: 1px solid #888; 271 | -moz-box-shadow: 2px 2px 4px #eee; 272 | -webkit-box-shadow: 2px 2px 4px #eee; 273 | box-shadow: 2px 2px 4px #eee; 274 | } 275 | 276 | table.docutils td, table.docutils th { 277 | border: 1px solid #888; 278 | padding: 0.25em 0.7em; 279 | } 280 | 281 | table.field-list, table.footnote { 282 | border: none; 283 | -moz-box-shadow: none; 284 | -webkit-box-shadow: none; 285 | box-shadow: none; 286 | } 287 | 288 | table.footnote { 289 | margin: 15px 0; 290 | width: 100%; 291 | border: 1px solid #eee; 292 | background: #fdfdfd; 293 | font-size: 0.9em; 294 | } 295 | 296 | table.footnote + table.footnote { 297 | margin-top: -15px; 298 | border-top: none; 299 | } 300 | 301 | table.field-list th { 302 | padding: 0 0.8em 0 0; 303 | } 304 | 305 | table.field-list td { 306 | padding: 0; 307 | } 308 | 309 | table.footnote td.label { 310 | width: 0px; 311 | padding: 0.3em 0 0.3em 0.5em; 312 | } 313 | 314 | table.footnote td { 315 | padding: 0.3em 0.5em; 316 | } 317 | 318 | dl { 319 | margin: 0; 320 | padding: 0; 321 | } 322 | 323 | dl dd { 324 | margin-left: 30px; 325 | } 326 | 327 | blockquote { 328 | margin: 0 0 0 30px; 329 | padding: 0; 330 | } 331 | 332 | ul, ol { 333 | margin: 10px 0 10px 30px; 334 | padding: 0; 335 | } 336 | 337 | pre { 338 | background: #eee; 339 | padding: 7px 30px; 340 | margin: 15px -30px; 341 | line-height: 1.3em; 342 | } 343 | 344 | dl pre, blockquote pre, li pre { 345 | margin-left: -60px; 346 | padding-left: 60px; 347 | } 348 | 349 | dl dl pre { 350 | margin-left: -90px; 351 | padding-left: 90px; 352 | } 353 | 354 | tt { 355 | background-color: #ecf0f3; 356 | color: #222; 357 | /* padding: 1px 2px; */ 358 | } 359 | 360 | tt.xref, a tt { 361 | background-color: #FBFBFB; 362 | border-bottom: 1px solid white; 363 | } 364 | 365 | a.reference { 366 | text-decoration: none; 367 | border-bottom: 1px dotted #004B6B; 368 | } 369 | 370 | a.reference:hover { 371 | border-bottom: 1px solid #6D4100; 372 | } 373 | 374 | a.footnote-reference { 375 | text-decoration: none; 376 | font-size: 0.7em; 377 | vertical-align: top; 378 | border-bottom: 1px dotted #004B6B; 379 | } 380 | 381 | a.footnote-reference:hover { 382 | border-bottom: 1px solid #6D4100; 383 | } 384 | 385 | a:hover tt { 386 | background: #EEE; 387 | } 388 | 389 | 390 | @media screen and (max-width: 600px) { 391 | 392 | div.sphinxsidebar { 393 | display: none; 394 | } 395 | 396 | div.document { 397 | width: 100%; 398 | 399 | } 400 | 401 | div.documentwrapper { 402 | margin-left: 0; 403 | margin-top: 0; 404 | margin-right: 0; 405 | margin-bottom: 0; 406 | } 407 | 408 | div.bodywrapper { 409 | margin-top: 0; 410 | margin-right: 0; 411 | margin-bottom: 0; 412 | margin-left: 0; 413 | } 414 | 415 | ul { 416 | margin-left: 0; 417 | } 418 | 419 | .document { 420 | width: auto; 421 | } 422 | 423 | .footer { 424 | width: auto; 425 | } 426 | 427 | .bodywrapper { 428 | margin: 0; 429 | } 430 | 431 | .footer { 432 | width: auto; 433 | } 434 | 435 | .github { 436 | display: none; 437 | } 438 | 439 | } 440 | 441 | /* misc. */ 442 | 443 | .revsys-inline { 444 | display: none!important; 445 | } -------------------------------------------------------------------------------- /docs/_themes/kr/static/small_flask.css: -------------------------------------------------------------------------------- 1 | /* 2 | * small_flask.css_t 3 | * ~~~~~~~~~~~~~~~~~ 4 | * 5 | * :copyright: Copyright 2010 by Armin Ronacher. 6 | * :license: Flask Design License, see LICENSE for details. 7 | */ 8 | 9 | body { 10 | margin: 0; 11 | padding: 20px 30px; 12 | } 13 | 14 | div.documentwrapper { 15 | float: none; 16 | background: white; 17 | } 18 | 19 | div.sphinxsidebar { 20 | display: block; 21 | float: none; 22 | width: 102.5%; 23 | margin: 50px -30px -20px -30px; 24 | padding: 10px 20px; 25 | background: #333; 26 | color: white; 27 | } 28 | 29 | div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, 30 | div.sphinxsidebar h3 a { 31 | color: white; 32 | } 33 | 34 | div.sphinxsidebar a { 35 | color: #aaa; 36 | } 37 | 38 | div.sphinxsidebar p.logo { 39 | display: none; 40 | } 41 | 42 | div.document { 43 | width: 100%; 44 | margin: 0; 45 | } 46 | 47 | div.related { 48 | display: block; 49 | margin: 0; 50 | padding: 10px 0 20px 0; 51 | } 52 | 53 | div.related ul, 54 | div.related ul li { 55 | margin: 0; 56 | padding: 0; 57 | } 58 | 59 | div.footer { 60 | display: none; 61 | } 62 | 63 | div.bodywrapper { 64 | margin: 0; 65 | } 66 | 67 | div.body { 68 | min-height: 0; 69 | padding: 0; 70 | } 71 | 72 | .rtd_doc_footer { 73 | display: none; 74 | } 75 | 76 | .document { 77 | width: auto; 78 | } 79 | 80 | .footer { 81 | width: auto; 82 | } 83 | 84 | .footer { 85 | width: auto; 86 | } 87 | 88 | .github { 89 | display: none; 90 | } -------------------------------------------------------------------------------- /docs/_themes/kr/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = flasky.css 4 | pygments_style = flask_theme_support.FlaskyStyle 5 | 6 | [options] 7 | touch_icon = 8 | -------------------------------------------------------------------------------- /docs/_themes/kr_small/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "basic/layout.html" %} 2 | {% block header %} 3 | {{ super() }} 4 | {% if pagename == 'index' %} 5 |
6 | {% endif %} 7 | {% endblock %} 8 | {% block footer %} 9 | {% if pagename == 'index' %} 10 |
11 | {% endif %} 12 | {% endblock %} 13 | {# do not display relbars #} 14 | {% block relbar1 %}{% endblock %} 15 | {% block relbar2 %} 16 | {% if theme_github_fork %} 17 | Fork me on GitHub 20 | {% endif %} 21 | {% endblock %} 22 | {% block sidebar1 %}{% endblock %} 23 | {% block sidebar2 %}{% endblock %} 24 | -------------------------------------------------------------------------------- /docs/_themes/kr_small/static/flasky.css_t: -------------------------------------------------------------------------------- 1 | /* 2 | * flasky.css_t 3 | * ~~~~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- flasky theme based on nature theme. 6 | * 7 | * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | @import url("basic.css"); 13 | 14 | /* -- page layout ----------------------------------------------------------- */ 15 | 16 | body { 17 | font-family: 'Georgia', serif; 18 | font-size: 17px; 19 | color: #000; 20 | background: white; 21 | margin: 0; 22 | padding: 0; 23 | } 24 | 25 | div.documentwrapper { 26 | float: left; 27 | width: 100%; 28 | } 29 | 30 | div.bodywrapper { 31 | margin: 40px auto 0 auto; 32 | width: 700px; 33 | } 34 | 35 | hr { 36 | border: 1px solid #B1B4B6; 37 | } 38 | 39 | div.body { 40 | background-color: #ffffff; 41 | color: #3E4349; 42 | padding: 0 30px 30px 30px; 43 | } 44 | 45 | img.floatingflask { 46 | padding: 0 0 10px 10px; 47 | float: right; 48 | } 49 | 50 | div.footer { 51 | text-align: right; 52 | color: #888; 53 | padding: 10px; 54 | font-size: 14px; 55 | width: 650px; 56 | margin: 0 auto 40px auto; 57 | } 58 | 59 | div.footer a { 60 | color: #888; 61 | text-decoration: underline; 62 | } 63 | 64 | div.related { 65 | line-height: 32px; 66 | color: #888; 67 | } 68 | 69 | div.related ul { 70 | padding: 0 0 0 10px; 71 | } 72 | 73 | div.related a { 74 | color: #444; 75 | } 76 | 77 | /* -- body styles ----------------------------------------------------------- */ 78 | 79 | a { 80 | color: #004B6B; 81 | text-decoration: underline; 82 | } 83 | 84 | a:hover { 85 | color: #6D4100; 86 | text-decoration: underline; 87 | } 88 | 89 | div.body { 90 | padding-bottom: 40px; /* saved for footer */ 91 | } 92 | 93 | div.body h1, 94 | div.body h2, 95 | div.body h3, 96 | div.body h4, 97 | div.body h5, 98 | div.body h6 { 99 | font-family: 'Garamond', 'Georgia', serif; 100 | font-weight: normal; 101 | margin: 30px 0px 10px 0px; 102 | padding: 0; 103 | } 104 | 105 | {% if theme_index_logo %} 106 | div.indexwrapper h1 { 107 | text-indent: -999999px; 108 | background: url({{ theme_index_logo }}) no-repeat center center; 109 | height: {{ theme_index_logo_height }}; 110 | } 111 | {% endif %} 112 | 113 | div.body h2 { font-size: 180%; } 114 | div.body h3 { font-size: 150%; } 115 | div.body h4 { font-size: 130%; } 116 | div.body h5 { font-size: 100%; } 117 | div.body h6 { font-size: 100%; } 118 | 119 | a.headerlink { 120 | color: white; 121 | padding: 0 4px; 122 | text-decoration: none; 123 | } 124 | 125 | a.headerlink:hover { 126 | color: #444; 127 | background: #eaeaea; 128 | } 129 | 130 | div.body p, div.body dd, div.body li { 131 | line-height: 1.4em; 132 | } 133 | 134 | div.admonition { 135 | background: #fafafa; 136 | margin: 20px -30px; 137 | padding: 10px 30px; 138 | border-top: 1px solid #ccc; 139 | border-bottom: 1px solid #ccc; 140 | } 141 | 142 | div.admonition p.admonition-title { 143 | font-family: 'Garamond', 'Georgia', serif; 144 | font-weight: normal; 145 | font-size: 24px; 146 | margin: 0 0 10px 0; 147 | padding: 0; 148 | line-height: 1; 149 | } 150 | 151 | div.admonition p.last { 152 | margin-bottom: 0; 153 | } 154 | 155 | div.highlight{ 156 | background-color: white; 157 | } 158 | 159 | dt:target, .highlight { 160 | background: #FAF3E8; 161 | } 162 | 163 | div.note { 164 | background-color: #eee; 165 | border: 1px solid #ccc; 166 | } 167 | 168 | div.seealso { 169 | background-color: #ffc; 170 | border: 1px solid #ff6; 171 | } 172 | 173 | div.topic { 174 | background-color: #eee; 175 | } 176 | 177 | div.warning { 178 | background-color: #ffe4e4; 179 | border: 1px solid #f66; 180 | } 181 | 182 | p.admonition-title { 183 | display: inline; 184 | } 185 | 186 | p.admonition-title:after { 187 | content: ":"; 188 | } 189 | 190 | pre, tt { 191 | font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; 192 | font-size: 0.85em; 193 | } 194 | 195 | img.screenshot { 196 | } 197 | 198 | tt.descname, tt.descclassname { 199 | font-size: 0.95em; 200 | } 201 | 202 | tt.descname { 203 | padding-right: 0.08em; 204 | } 205 | 206 | img.screenshot { 207 | -moz-box-shadow: 2px 2px 4px #eee; 208 | -webkit-box-shadow: 2px 2px 4px #eee; 209 | box-shadow: 2px 2px 4px #eee; 210 | } 211 | 212 | table.docutils { 213 | border: 1px solid #888; 214 | -moz-box-shadow: 2px 2px 4px #eee; 215 | -webkit-box-shadow: 2px 2px 4px #eee; 216 | box-shadow: 2px 2px 4px #eee; 217 | } 218 | 219 | table.docutils td, table.docutils th { 220 | border: 1px solid #888; 221 | padding: 0.25em 0.7em; 222 | } 223 | 224 | table.field-list, table.footnote { 225 | border: none; 226 | -moz-box-shadow: none; 227 | -webkit-box-shadow: none; 228 | box-shadow: none; 229 | } 230 | 231 | table.footnote { 232 | margin: 15px 0; 233 | width: 100%; 234 | border: 1px solid #eee; 235 | } 236 | 237 | table.field-list th { 238 | padding: 0 0.8em 0 0; 239 | } 240 | 241 | table.field-list td { 242 | padding: 0; 243 | } 244 | 245 | table.footnote td { 246 | padding: 0.5em; 247 | } 248 | 249 | dl { 250 | margin: 0; 251 | padding: 0; 252 | } 253 | 254 | dl dd { 255 | margin-left: 30px; 256 | } 257 | 258 | pre { 259 | padding: 0; 260 | margin: 15px -30px; 261 | padding: 8px; 262 | line-height: 1.3em; 263 | padding: 7px 30px; 264 | background: #eee; 265 | border-radius: 2px; 266 | -moz-border-radius: 2px; 267 | -webkit-border-radius: 2px; 268 | } 269 | 270 | dl pre { 271 | margin-left: -60px; 272 | padding-left: 60px; 273 | } 274 | 275 | tt { 276 | background-color: #ecf0f3; 277 | color: #222; 278 | /* padding: 1px 2px; */ 279 | } 280 | 281 | tt.xref, a tt { 282 | background-color: #FBFBFB; 283 | } 284 | 285 | a:hover tt { 286 | background: #EEE; 287 | } 288 | -------------------------------------------------------------------------------- /docs/_themes/kr_small/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = flasky.css 4 | nosidebar = true 5 | pygments_style = flask_theme_support.FlaskyStyle 6 | 7 | [options] 8 | index_logo = '' 9 | index_logo_height = 120px 10 | github_fork = '' 11 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # django-gcm-ios-android documentation build configuration file, created by 5 | # sphinx-quickstart on Fri Jul 10 13:54:46 2015. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | import os 17 | 18 | import sys 19 | 20 | 21 | # If extensions (or modules to document with autodoc) are in another directory, 22 | # add these directories to sys.path here. If the directory is relative to the 23 | # documentation root, use os.path.abspath to make it absolute, like shown here. 24 | # sys.path.insert(0, os.path.abspath('.')) 25 | 26 | # -- General configuration ------------------------------------------------ 27 | 28 | # If your documentation needs a minimal Sphinx version, state it here. 29 | # needs_sphinx = '1.0' 30 | 31 | # Add any Sphinx extension module names here, as strings. They can be 32 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 33 | # ones. 34 | extensions = [ 35 | 'sphinx.ext.pngmath', 36 | ] 37 | 38 | # Add any paths that contain templates here, relative to this directory. 39 | templates_path = ['_templates'] 40 | 41 | # The suffix(es) of source filenames. 42 | # You can specify multiple suffix as a list of string: 43 | # source_suffix = ['.rst', '.md'] 44 | source_suffix = '.rst' 45 | 46 | # The encoding of source files. 47 | # source_encoding = 'utf-8-sig' 48 | 49 | # The master toctree document. 50 | master_doc = 'index' 51 | 52 | # General information about the project. 53 | project = 'django-gcm-android-ios' 54 | copyright = '2015, Hugo Brilhante' 55 | author = 'Hugo Brilhante' 56 | 57 | # The version info for the project you're documenting, acts as replacement for 58 | # |version| and |release|, also used in various other places throughout the 59 | # built documents. 60 | # 61 | # The short X.Y version. 62 | version = '1.0.0' 63 | # The full version, including alpha/beta/rc tags. 64 | release = '1.0.0' 65 | 66 | # The language for content autogenerated by Sphinx. Refer to documentation 67 | # for a list of supported languages. 68 | # 69 | # This is also used if you do content translation via gettext catalogs. 70 | # Usually you set "language" from the command line for these cases. 71 | language = None 72 | 73 | # There are two options for replacing |today|: either, you set today to some 74 | # non-false value, then it is used: 75 | # today = '' 76 | # Else, today_fmt is used as the format for a strftime call. 77 | # today_fmt = '%B %d, %Y' 78 | 79 | # List of patterns, relative to source directory, that match files and 80 | # directories to ignore when looking for source files. 81 | exclude_patterns = ['_build'] 82 | 83 | # The reST default role (used for this markup: `text`) to use for all 84 | # documents. 85 | # default_role = None 86 | 87 | # If true, '()' will be appended to :func: etc. cross-reference text. 88 | # add_function_parentheses = True 89 | 90 | # If true, the current module name will be prepended to all description 91 | # unit titles (such as .. function::). 92 | # add_module_names = True 93 | 94 | # If true, sectionauthor and moduleauthor directives will be shown in the 95 | # output. They are ignored by default. 96 | # show_authors = False 97 | 98 | # The name of the Pygments (syntax highlighting) style to use. 99 | pygments_style = 'flask_theme_support.FlaskyStyle' 100 | 101 | # A list of ignored prefixes for module index sorting. 102 | # modindex_common_prefix = [] 103 | 104 | # If true, keep warnings as "system message" paragraphs in the built documents. 105 | # keep_warnings = False 106 | 107 | # If true, `todo` and `todoList` produce output, else they produce nothing. 108 | todo_include_todos = False 109 | 110 | 111 | # -- Options for HTML output ---------------------------------------------- 112 | 113 | # The theme to use for HTML and HTML Help pages. See the documentation for 114 | # a list of builtin themes. 115 | # html_theme = 'alabaster' 116 | 117 | # Theme options are theme-specific and customize the look and feel of a theme 118 | # further. For a list of options available for each theme, see the 119 | # documentation. 120 | # html_theme_options = {} 121 | 122 | # Add any paths that contain custom themes here, relative to this directory. 123 | # html_theme_path = [] 124 | 125 | # The name for this set of Sphinx documents. If None, it defaults to 126 | # " v documentation". 127 | # html_title = None 128 | 129 | # A shorter title for the navigation bar. Default is the same as html_title. 130 | # html_short_title = None 131 | 132 | # The name of an image file (relative to this directory) to place at the top 133 | # of the sidebar. 134 | # html_logo = None 135 | 136 | # The name of an image file (within the static path) to use as favicon of the 137 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 138 | # pixels large. 139 | # html_favicon = None 140 | 141 | # Add any paths that contain custom static files (such as style sheets) here, 142 | # relative to this directory. They are copied after the builtin static files, 143 | # so a file named "default.css" will overwrite the builtin "default.css". 144 | html_static_path = ['_static'] 145 | 146 | # Add any extra paths that contain custom files (such as robots.txt or 147 | # .htaccess) here, relative to this directory. These files are copied 148 | # directly to the root of the documentation. 149 | # html_extra_path = [] 150 | 151 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 152 | # using the given strftime format. 153 | # html_last_updated_fmt = '%b %d, %Y' 154 | 155 | # If true, SmartyPants will be used to convert quotes and dashes to 156 | # typographically correct entities. 157 | # html_use_smartypants = True 158 | 159 | # Custom sidebar templates, maps document names to template names. 160 | # html_sidebars = {} 161 | 162 | # Additional templates that should be rendered to pages, maps page names to 163 | # template names. 164 | # html_additional_pages = {} 165 | 166 | # If false, no module index is generated. 167 | # html_domain_indices = True 168 | 169 | # If false, no index is generated. 170 | # html_use_index = True 171 | 172 | # If true, the index is split into individual pages for each letter. 173 | # html_split_index = False 174 | 175 | # If true, links to the reST sources are added to the pages. 176 | # html_show_sourcelink = True 177 | 178 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 179 | # html_show_sphinx = True 180 | 181 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 182 | # html_show_copyright = True 183 | 184 | # If true, an OpenSearch description file will be output, and all pages will 185 | # contain a tag referring to it. The value of this option must be the 186 | # base URL from which the finished HTML is served. 187 | # html_use_opensearch = '' 188 | 189 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 190 | # html_file_suffix = None 191 | 192 | # Language to be used for generating the HTML full-text search index. 193 | # Sphinx supports the following languages: 194 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' 195 | # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr' 196 | # html_search_language = 'en' 197 | 198 | # A dictionary with options for the search language support, empty by default. 199 | # Now only 'ja' uses this config value 200 | # html_search_options = {'type': 'default'} 201 | 202 | # The name of a javascript file (relative to the configuration directory) that 203 | # implements a search results scorer. If empty, the default will be used. 204 | # html_search_scorer = 'scorer.js' 205 | 206 | # Output file base name for HTML help builder. 207 | htmlhelp_basename = 'django-gcm-android-iosdoc' 208 | 209 | # -- Options for LaTeX output --------------------------------------------- 210 | 211 | latex_elements = { 212 | # The paper size ('letterpaper' or 'a4paper'). 213 | # 'papersize': 'letterpaper', 214 | 215 | # The font size ('10pt', '11pt' or '12pt'). 216 | # 'pointsize': '10pt', 217 | 218 | # Additional stuff for the LaTeX preamble. 219 | # 'preamble': '', 220 | 221 | # Latex figure (float) alignment 222 | # 'figure_align': 'htbp', 223 | } 224 | 225 | # Grouping the document tree into LaTeX files. List of tuples 226 | # (source start file, target name, title, 227 | # author, documentclass [howto, manual, or own class]). 228 | latex_documents = [ 229 | (master_doc, 'django-gcm-android-ios.tex', 'django-gcm-android-ios Documentation', 230 | 'Hugo Brilhante', 'manual'), 231 | ] 232 | 233 | # The name of an image file (relative to this directory) to place at the top of 234 | # the title page. 235 | # latex_logo = None 236 | 237 | # For "manual" documents, if this is true, then toplevel headings are parts, 238 | # not chapters. 239 | # latex_use_parts = False 240 | 241 | # If true, show page references after internal links. 242 | # latex_show_pagerefs = False 243 | 244 | # If true, show URL addresses after external links. 245 | # latex_show_urls = False 246 | 247 | # Documents to append as an appendix to all manuals. 248 | # latex_appendices = [] 249 | 250 | # If false, no module index is generated. 251 | # latex_domain_indices = True 252 | 253 | 254 | # -- Options for manual page output --------------------------------------- 255 | 256 | # One entry per manual page. List of tuples 257 | # (source start file, name, description, authors, manual section). 258 | man_pages = [ 259 | (master_doc, 'django-gcm-android-ios', 'django-gcm-android-ios Documentation', 260 | [author], 1) 261 | ] 262 | 263 | # If true, show URL addresses after external links. 264 | # man_show_urls = False 265 | 266 | 267 | # -- Options for Texinfo output ------------------------------------------- 268 | 269 | # Grouping the document tree into Texinfo files. List of tuples 270 | # (source start file, target name, title, author, 271 | # dir menu entry, description, category) 272 | texinfo_documents = [ 273 | (master_doc, 'django-gcm-android-ios', 'django-gcm-android-ios Documentation', 274 | author, 'django-gcm-android-ios', 'One line description of project.', 275 | 'Miscellaneous'), 276 | ] 277 | 278 | # Documents to append as an appendix to all manuals. 279 | # texinfo_appendices = [] 280 | 281 | # If false, no module index is generated. 282 | # texinfo_domain_indices = True 283 | 284 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 285 | # texinfo_show_urls = 'footnote' 286 | 287 | # If true, do not generate a @detailmenu in the "Top" node's menu. 288 | # texinfo_no_detailmenu = False 289 | 290 | 291 | 292 | sys.path.append(os.path.abspath('_themes')) 293 | html_theme_path = ['_themes'] 294 | html_theme = 'kr' 295 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Django gcm android ios's documentation! 2 | ======================================= 3 | 4 | django-gcm-android-ios is a simple Django app to send a message using Google Cloud Messaging HTTP connection server protocol. 5 | 6 | 7 | .. image:: https://travis-ci.org/hugobrilhante/django-gcm-android-ios.svg 8 | :target: https://travis-ci.org/hugobrilhante/django-gcm-android-ios 9 | 10 | 11 | .. image:: https://coveralls.io/repos/hugobrilhante/django-gcm-android-ios/badge.svg?branch=master&service=github 12 | :target: https://coveralls.io/github/hugobrilhante/django-gcm-android-ios?branch=master 13 | 14 | .. image:: https://img.shields.io/pypi/status/django-gcm-android-ios.svg 15 | :target: https://pypi.python.org/pypi/django-gcm-android-ios 16 | 17 | .. image:: https://img.shields.io/pypi/dm/django-gcm-android-ios.svg 18 | :target: https://pypi.python.org/pypi/django-gcm-android-ios/1.0.0#downloads 19 | 20 | .. image:: https://img.shields.io/pypi/l/django-gcm-android-ios.svg 21 | :target: https://github.com/hugobrilhante/django-gcm-android-ios/blob/master/LICENSE 22 | 23 | .. image:: https://img.shields.io/github/release/hugobrilhante/django-gcm-android-ios.svg 24 | :target: https://github.com/hugobrilhante/django-gcm-android-ios/releases/tag/1.0.0 25 | 26 | .. image:: https://img.shields.io/pypi/pyversions/django-gcm-android-ios.svg 27 | :target: https://pypi.python.org/pypi/django-gcm-android-ios 28 | 29 | 30 | 31 | 32 | Contents: 33 | 34 | .. toctree:: 35 | :maxdepth: 2 36 | 37 | tutorial 38 | 39 | 40 | 41 | ====== 42 | Author 43 | ====== 44 | 45 | - `Hugo Brilhante `_ 46 | 47 | ============= 48 | Main features 49 | ============= 50 | - Python: 2,7, 3.4. 51 | - Django: 1.7, 1.8. 52 | - API using Django Rest Framework `django-rest-framework `_ 53 | - Requests processing http/https using the library `requests `_. 54 | - Excellent coverage tests (> 80%). 55 | 56 | ===== 57 | Links 58 | ===== 59 | 60 | - `Github `_ 61 | - `Travis CI `_ 62 | - `Coveralls `_ 63 | 64 | 65 | ========== 66 | References 67 | ========== 68 | 69 | - `Google Cloud Messaging `_ 70 | 71 | ======= 72 | License 73 | ======= 74 | 75 | .. include:: ../LICENSE -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | echo. coverage to run coverage check of the documentation if enabled 41 | goto end 42 | ) 43 | 44 | if "%1" == "clean" ( 45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 46 | del /q /s %BUILDDIR%\* 47 | goto end 48 | ) 49 | 50 | 51 | REM Check if sphinx-build is available and fallback to Python version if any 52 | %SPHINXBUILD% 2> nul 53 | if errorlevel 9009 goto sphinx_python 54 | goto sphinx_ok 55 | 56 | :sphinx_python 57 | 58 | set SPHINXBUILD=python -m sphinx.__init__ 59 | %SPHINXBUILD% 2> nul 60 | if errorlevel 9009 ( 61 | echo. 62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 63 | echo.installed, then set the SPHINXBUILD environment variable to point 64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 65 | echo.may add the Sphinx directory to PATH. 66 | echo. 67 | echo.If you don't have Sphinx installed, grab it from 68 | echo.http://sphinx-doc.org/ 69 | exit /b 1 70 | ) 71 | 72 | :sphinx_ok 73 | 74 | 75 | if "%1" == "html" ( 76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 80 | goto end 81 | ) 82 | 83 | if "%1" == "dirhtml" ( 84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 88 | goto end 89 | ) 90 | 91 | if "%1" == "singlehtml" ( 92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 93 | if errorlevel 1 exit /b 1 94 | echo. 95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 96 | goto end 97 | ) 98 | 99 | if "%1" == "pickle" ( 100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 101 | if errorlevel 1 exit /b 1 102 | echo. 103 | echo.Build finished; now you can process the pickle files. 104 | goto end 105 | ) 106 | 107 | if "%1" == "json" ( 108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 109 | if errorlevel 1 exit /b 1 110 | echo. 111 | echo.Build finished; now you can process the JSON files. 112 | goto end 113 | ) 114 | 115 | if "%1" == "htmlhelp" ( 116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 117 | if errorlevel 1 exit /b 1 118 | echo. 119 | echo.Build finished; now you can run HTML Help Workshop with the ^ 120 | .hhp project file in %BUILDDIR%/htmlhelp. 121 | goto end 122 | ) 123 | 124 | if "%1" == "qthelp" ( 125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 129 | .qhcp project file in %BUILDDIR%/qthelp, like this: 130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\django-gcm-ios-android.qhcp 131 | echo.To view the help file: 132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\django-gcm-ios-android.ghc 133 | goto end 134 | ) 135 | 136 | if "%1" == "devhelp" ( 137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. 141 | goto end 142 | ) 143 | 144 | if "%1" == "epub" ( 145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 149 | goto end 150 | ) 151 | 152 | if "%1" == "latex" ( 153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 157 | goto end 158 | ) 159 | 160 | if "%1" == "latexpdf" ( 161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 162 | cd %BUILDDIR%/latex 163 | make all-pdf 164 | cd %~dp0 165 | echo. 166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdfja" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf-ja 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "text" ( 181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 182 | if errorlevel 1 exit /b 1 183 | echo. 184 | echo.Build finished. The text files are in %BUILDDIR%/text. 185 | goto end 186 | ) 187 | 188 | if "%1" == "man" ( 189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 190 | if errorlevel 1 exit /b 1 191 | echo. 192 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 193 | goto end 194 | ) 195 | 196 | if "%1" == "texinfo" ( 197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 198 | if errorlevel 1 exit /b 1 199 | echo. 200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 201 | goto end 202 | ) 203 | 204 | if "%1" == "gettext" ( 205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 206 | if errorlevel 1 exit /b 1 207 | echo. 208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 209 | goto end 210 | ) 211 | 212 | if "%1" == "changes" ( 213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 214 | if errorlevel 1 exit /b 1 215 | echo. 216 | echo.The overview file is in %BUILDDIR%/changes. 217 | goto end 218 | ) 219 | 220 | if "%1" == "linkcheck" ( 221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 222 | if errorlevel 1 exit /b 1 223 | echo. 224 | echo.Link check complete; look for any errors in the above output ^ 225 | or in %BUILDDIR%/linkcheck/output.txt. 226 | goto end 227 | ) 228 | 229 | if "%1" == "doctest" ( 230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 231 | if errorlevel 1 exit /b 1 232 | echo. 233 | echo.Testing of doctests in the sources finished, look at the ^ 234 | results in %BUILDDIR%/doctest/output.txt. 235 | goto end 236 | ) 237 | 238 | if "%1" == "coverage" ( 239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 240 | if errorlevel 1 exit /b 1 241 | echo. 242 | echo.Testing of coverage in the sources finished, look at the ^ 243 | results in %BUILDDIR%/coverage/python.txt. 244 | goto end 245 | ) 246 | 247 | if "%1" == "xml" ( 248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 249 | if errorlevel 1 exit /b 1 250 | echo. 251 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 252 | goto end 253 | ) 254 | 255 | if "%1" == "pseudoxml" ( 256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 257 | if errorlevel 1 exit /b 1 258 | echo. 259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 260 | goto end 261 | ) 262 | 263 | :end 264 | -------------------------------------------------------------------------------- /docs/tutorial.rst: -------------------------------------------------------------------------------- 1 | Tutorial 2 | ======== 3 | 4 | 5 | ============ 6 | Installation 7 | ============ 8 | 9 | Install o django-gcm-android-ios:: 10 | 11 | pip install django-gcm-android-ios 12 | 13 | ============= 14 | Configuration 15 | ============= 16 | 17 | Configure django-gcm-android-ios in your settings.py file:: 18 | 19 | INSTALLED_APPS = ( 20 | ... 21 | 'gcm', 22 | ) 23 | 24 | 25 | GCM_DEVICE_MODEL = "" # default gcm.Device 26 | GCM_IOS_APIKEY = "" 27 | GCM_ANDROID_APIKEY = "" 28 | 29 | Add django-gcm-android-ios resources to your URL router:: 30 | 31 | from gcm.routers import router 32 | urlpatterns = [ 33 | ... 34 | url(r'api/', include(router.urls)), 35 | ] 36 | 37 | You can easily test if the endpoint is working by doing the following in your terminal 38 | 39 | Register:: 40 | 41 | curl -X POST -H "Content-Type: application/json" -H "Authorization: " -d '{ 42 | "dev_id": "Device id", 43 | "dev_type": "ANDROID or IOS", 44 | "reg_id": "Register id" 45 | }' 'http://localhost:8001/api/devices' 46 | 47 | Unregister:: 48 | 49 | 50 | curl -X DELETE -H "Content-Type: application/json" -H "Authorization: " 51 | 'http://localhost:8001/api/devices/' 52 | 53 | .. _Django Rest Framework: http://www.django-rest-framework.org/api-guide/authentication/ 54 | 55 | .. note:: Authorization, see `Django Rest Framework`_ docs. 56 | ================ 57 | Sending messages 58 | ================ 59 | Using ``Django orm``:: 60 | 61 | from gcm.utils import get_device_model 62 | Device = get_device_model() 63 | 64 | device = Device.objects.get(dev_id=) 65 | 66 | device.send_message('my test message', collapse_key='something') 67 | 68 | ``collapse_key`` parameter is optional (default message). 69 | 70 | If you want to send additional arguments like ``delay_while_idle`` or other, add them as named variables:: 71 | 72 | device.send_message('my test message', delay_while_idle=True, time_to_live=5) 73 | 74 | .. _GCM Connection Server Reference: https://developers.google.com/cloud-messaging/server-ref 75 | 76 | .. note:: For more information, see `GCM Connection Server Reference`_ docs. 77 | 78 | Multicast message 79 | 80 | ``django-gcm-android-ios`` supports sending message to multiple devices at once:: 81 | 82 | from gcm.utils import get_device_model 83 | Device = get_device_model() 84 | 85 | Device.objects.all().send_messages('my message') 86 | 87 | Device.objects.filter(is_active=True).send_messages('my message', collapse_key='something') 88 | 89 | Payload 90 | 91 | ``django-gcm-android-ios`` supports sending payload:: 92 | 93 | from gcm.utils import get_device_model 94 | Device = get_device_model() 95 | 96 | device = Device.objects.get(dev_id=) 97 | 98 | device.send_message(data={ "score": "4x8", "time": "15:16.2342" }, collapse_key='something', time_to_live= 108) 99 | 100 | -------------------------------------------------------------------------------- /example/example/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugobrilhante/django-gcm-android-ios/d5abd4c79a00cd3db9021941a20fc844544a1f4d/example/example/__init__.py -------------------------------------------------------------------------------- /example/example/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for example project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.8.2. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.8/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.8/ref/settings/ 11 | """ 12 | 13 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 14 | import os 15 | 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = 'c5!6j@+#50v#uq66(d#^x1089o(+jue-3s_799ontu9@7$+y0j' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = ( 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'gcm', 41 | ) 42 | 43 | GCM_DEVICE_MODEL = 'gcm.Device' 44 | 45 | GCM_ANDROID_APIKEY = "AIzaSyDTOsEsbVUnm2sPVHrV2AuiBMsN9279czQ" 46 | 47 | GCM_IOS_APIKEY = "AIzaSyCE-T1kCt6yJDhj3VU_XBo_pD4ZT8O70lQ" 48 | 49 | MIDDLEWARE_CLASSES = ( 50 | 'django.contrib.sessions.middleware.SessionMiddleware', 51 | 'django.middleware.common.CommonMiddleware', 52 | 'django.middleware.csrf.CsrfViewMiddleware', 53 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 54 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 55 | 'django.contrib.messages.middleware.MessageMiddleware', 56 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 57 | ) 58 | 59 | ROOT_URLCONF = 'example.urls' 60 | 61 | TEMPLATES = [ 62 | { 63 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 64 | 'DIRS': [], 65 | 'APP_DIRS': True, 66 | 'OPTIONS': { 67 | 'context_processors': [ 68 | 'django.template.context_processors.debug', 69 | 'django.template.context_processors.request', 70 | 'django.contrib.auth.context_processors.auth', 71 | 'django.contrib.messages.context_processors.messages', 72 | ], 73 | }, 74 | }, 75 | ] 76 | 77 | WSGI_APPLICATION = 'example.wsgi.application' 78 | 79 | 80 | # Database 81 | # https://docs.djangoproject.com/en/1.8/ref/settings/#databases 82 | 83 | DATABASES = { 84 | 'default': { 85 | 'ENGINE': 'django.db.backends.sqlite3', 86 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 87 | } 88 | } 89 | 90 | 91 | # Internationalization 92 | # https://docs.djangoproject.com/en/1.8/topics/i18n/ 93 | 94 | LANGUAGE_CODE = 'en-us' 95 | 96 | TIME_ZONE = 'UTC' 97 | 98 | USE_I18N = True 99 | 100 | USE_L10N = True 101 | 102 | USE_TZ = True 103 | 104 | 105 | # Static files (CSS, JavaScript, Images) 106 | # https://docs.djangoproject.com/en/1.8/howto/static-files/ 107 | 108 | STATIC_URL = '/static/' 109 | 110 | TEMPLATE_DIRS = ( 111 | os.path.join(BASE_DIR, 'templates'), 112 | ) 113 | -------------------------------------------------------------------------------- /example/example/urls.py: -------------------------------------------------------------------------------- 1 | """example URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.8/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Add an import: from blog import urls as blog_urls 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include(blog_urls)) 15 | """ 16 | from django.conf.urls import include, url 17 | from django.contrib import admin 18 | 19 | from gcm.routers import router 20 | 21 | urlpatterns = [ 22 | url(r'^admin/', include(admin.site.urls)), 23 | url(r'api/', include(router.urls)), 24 | ] 25 | -------------------------------------------------------------------------------- /example/example/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for example project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /example/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | 4 | import sys 5 | 6 | if __name__ == "__main__": 7 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings") 8 | 9 | from django.core.management import execute_from_command_line 10 | 11 | execute_from_command_line(sys.argv) 12 | -------------------------------------------------------------------------------- /gcm/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = '1.0.0' 2 | -------------------------------------------------------------------------------- /gcm/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from gcm.utils import get_device_model 4 | 5 | Device = get_device_model() 6 | 7 | 8 | @admin.register(Device) 9 | class DeviceAdmin(admin.ModelAdmin): 10 | list_display = ['dev_id', 'dev_type', 'modified_date', 'is_active'] 11 | search_fields = ('dev_id', 'dev_type') 12 | list_filter = ['is_active'] 13 | date_hierarchy = 'modified_date' 14 | readonly_fields = ('dev_id', 'reg_id') 15 | -------------------------------------------------------------------------------- /gcm/api.py: -------------------------------------------------------------------------------- 1 | from rest_framework.permissions import IsAuthenticated 2 | from rest_framework.viewsets import ModelViewSet 3 | 4 | from gcm.serializers import DeviceSerializer 5 | from gcm.utils import get_device_model 6 | 7 | Device = get_device_model() 8 | 9 | 10 | class DevicesViewSet(ModelViewSet): 11 | queryset = Device.objects.all() 12 | serializer_class = DeviceSerializer 13 | permission_classes = [IsAuthenticated] 14 | http_method_names = ['post', 'delete', 'options'] 15 | -------------------------------------------------------------------------------- /gcm/locale/pt_BR/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the gcm-ios-android package. 2 | # Hugo Brilhante hugobrilhante@gmail.com, 2015. 3 | # 4 | #, fuzzy 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: 1.0.0\n" 8 | "Report-Msgid-Bugs-To: \n" 9 | "POT-Creation-Date: 2015-07-08 20:38+0000\n" 10 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 11 | "Last-Translator: Hugo Brilhante hugobrilhante@gmail.com\n" 12 | "Language-Team: LANGUAGE \n" 13 | "Language: pt_BR\n" 14 | "MIME-Version: 1.0\n" 15 | "Content-Type: text/plain; charset=UTF-8\n" 16 | "Content-Transfer-Encoding: 8bit\n" 17 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" 18 | 19 | #: gcm/models.py:39 20 | msgid "Device ID" 21 | msgstr "ID do dispositivo" 22 | 23 | #: gcm/models.py:41 24 | msgid "Device Type" 25 | msgstr "Tipo do dispositivo" 26 | 27 | #: gcm/models.py:43 28 | msgid "Registration ID" 29 | msgstr "ID de registro" 30 | 31 | #: gcm/models.py:45 32 | msgid "Creation date" 33 | msgstr "Data de criação" 34 | 35 | #: gcm/models.py:47 36 | msgid "Modified date" 37 | msgstr "Data de modificação" 38 | 39 | #: gcm/models.py:49 40 | msgid "Is active?" 41 | msgstr "Está ativo?" 42 | 43 | #: gcm/models.py:58 44 | msgid "Device" 45 | msgstr "Dispositivo" 46 | 47 | #: gcm/models.py:59 48 | msgid "Devices" 49 | msgstr "Despositivos" 50 | -------------------------------------------------------------------------------- /gcm/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ] 10 | 11 | operations = [ 12 | migrations.CreateModel( 13 | name='Device', 14 | fields=[ 15 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 16 | ('dev_id', models.CharField(verbose_name='Device ID', max_length=50, unique=True)), 17 | ('dev_type', models.CharField(blank=True, null=True, verbose_name='Device Type', max_length=255, 18 | choices=[('IOS', 'iOS'), ('ANDROID', 'Android')])), 19 | ('reg_id', models.CharField(verbose_name='Registration ID', max_length=255, unique=True)), 20 | ('creation_date', models.DateTimeField(verbose_name='Creation date', auto_now_add=True)), 21 | ('modified_date', models.DateTimeField(verbose_name='Modified date', auto_now=True)), 22 | ('is_active', models.BooleanField(verbose_name='Is active?', default=False)), 23 | ], 24 | options={ 25 | 'verbose_name_plural': 'Devices', 26 | 'ordering': ['-modified_date'], 27 | 'abstract': False, 28 | 'verbose_name': 'Device', 29 | }, 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /gcm/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugobrilhante/django-gcm-android-ios/d5abd4c79a00cd3db9021941a20fc844544a1f4d/gcm/migrations/__init__.py -------------------------------------------------------------------------------- /gcm/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.utils.translation import ugettext_lazy as _ 3 | from django.utils.encoding import python_2_unicode_compatible 4 | 5 | from gcm.utils import notification_push 6 | 7 | GCM_ERROR_MESSAGES = {'MissingRegistration': 'Check that the request contains a registration token', 8 | 'InvalidRegistration': 'Check the format of the registration token you pass to the server.', 9 | 'NotRegistered': 'The client app unregisters with GCM.', 10 | 'InvalidPackageName': 'Make sure the message was addressed to a registration token whose package' 11 | ' name matches the value passed in the request.', 12 | 'MismatchSenderId': 'A registration token is tied to a certain group of senders.', 13 | 'MessageTooBig': 'Check that the total size of the payload data included in a message does' 14 | ' not exceed GCM limits: 4096 bytes for most messages, or 2048 bytes in the case' 15 | ' of messages to topics or notification messages on iOS. This includes both' 16 | 'the keys and the values.', 17 | 'InvalidDataKey': 'Check that the payload data does not contain a key (such as from ,' 18 | ' or gcm , or any value prefixed by google ) that is used internally by GCM.', 19 | 'InvalidTtl': 'Check that the value used in time_to_live is an integer representing a' 20 | ' duration in seconds between 0 and 2,419,200 (4 weeks).', 21 | 'Unavailable': 'The server couldn\'t process the request in time.', 22 | 'InternalServerError': 'The server encountered an error while trying to process the request.', 23 | 'DeviceMessageRate': 'The rate of messages to a particular device is too high.', 24 | 'TopicsMessageRate': 'The rate of messages to subscribers to a particular topic is too high.', 25 | 'InvalidParameters': 'Check Parameters sent'} 26 | 27 | DEVICE_TYPES = (('IOS', "iOS"), ('ANDROID', "Android")) 28 | 29 | 30 | class DeviceQuerySet(models.QuerySet): 31 | def send_messages(self, message=None, **kwargs): 32 | responses = [] 33 | for device in self.all(): 34 | response = device.send_message(message, **kwargs) 35 | responses.append((device.id, response)) 36 | return responses 37 | 38 | 39 | @python_2_unicode_compatible 40 | class AbstractDevice(models.Model): 41 | dev_id = models.CharField( 42 | verbose_name=_("Device ID"), max_length=50, unique=True, ) 43 | dev_type = models.CharField( 44 | verbose_name=_("Device Type"), max_length=255, choices=DEVICE_TYPES) 45 | reg_id = models.CharField( 46 | verbose_name=_("Registration ID"), max_length=255, unique=True) 47 | creation_date = models.DateTimeField( 48 | verbose_name=_("Creation date"), auto_now_add=True) 49 | modified_date = models.DateTimeField( 50 | verbose_name=_("Modified date"), auto_now=True) 51 | is_active = models.BooleanField( 52 | verbose_name=_("Is active?"), default=True) 53 | 54 | objects = DeviceQuerySet.as_manager() 55 | 56 | def __str__(self): 57 | return self.dev_id 58 | 59 | class Meta: 60 | abstract = True 61 | verbose_name = _("Device") 62 | verbose_name_plural = _("Devices") 63 | ordering = ['-modified_date'] 64 | 65 | def send_message(self, message=None, **kwargs): 66 | response = notification_push(self.dev_type, self.reg_id, message, **kwargs) 67 | if 'success' in response: 68 | return response['success'] 69 | elif 'canonical_id' in response: 70 | self.reg_id = response['canonical_id'] 71 | self.save() 72 | return 'Message send successfully' 73 | elif 'error' in response: 74 | if response['error'] == 'NotRegistered': 75 | self.mark_inactive() 76 | return GCM_ERROR_MESSAGES[response['error']] 77 | else: 78 | return GCM_ERROR_MESSAGES.get(response['error'], response['error']) 79 | 80 | def mark_inactive(self): 81 | self.is_active = False 82 | self.save() 83 | 84 | 85 | class Device(AbstractDevice): 86 | pass 87 | -------------------------------------------------------------------------------- /gcm/routers.py: -------------------------------------------------------------------------------- 1 | from rest_framework.routers import DefaultRouter 2 | 3 | from gcm.api import DevicesViewSet 4 | 5 | router = DefaultRouter(trailing_slash=False) 6 | 7 | router.register(r'devices', DevicesViewSet) 8 | 9 | urlpatterns = router.urls 10 | -------------------------------------------------------------------------------- /gcm/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework.serializers import ModelSerializer 2 | 3 | from gcm.utils import get_device_model 4 | 5 | Device = get_device_model() 6 | 7 | 8 | class DeviceSerializer(ModelSerializer): 9 | class Meta: 10 | model = Device 11 | exclude = ('id', 'creation_date', 'modified_date', 'is_active') 12 | -------------------------------------------------------------------------------- /gcm/settings.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | GCM_DEVICE_MODEL = getattr(settings, 'GCM_DEVICE_MODEL', 'gcm.Device') 4 | 5 | GCM_ANDROID_APIKEY = getattr(settings, 'GCM_ANDROID_APIKEY', None) 6 | 7 | GCM_IOS_APIKEY = getattr(settings, 'GCM_IOS_APIKEY', None) 8 | -------------------------------------------------------------------------------- /gcm/tests.py: -------------------------------------------------------------------------------- 1 | from mock import patch 2 | 3 | from django.contrib.auth import get_user_model 4 | from django.core.urlresolvers import reverse 5 | from django.test import TestCase, override_settings 6 | from rest_framework import status 7 | from rest_framework.test import APITestCase 8 | from gcm import utils 9 | 10 | User = get_user_model() 11 | Device = utils.get_device_model() 12 | 13 | 14 | class ApiDeviceTest(APITestCase): 15 | def setUp(self): 16 | self.user = User.objects.create(username='test', email='test@test.com') 17 | self.user.set_password('test') 18 | self.user.save() 19 | self.client.login(username='test', password='test') 20 | self.device = Device.objects.create(dev_id='123-1', dev_type='ANDROID', reg_id='123-1') 21 | 22 | def test_resgister_device(self): 23 | url = reverse('device-list') 24 | data = {'dev_id': '123-2', 'dev_type': 'ANDROID', 'reg_id': '123-2'} 25 | response = self.client.post(url, data, format='json') 26 | self.assertEqual(response.status_code, status.HTTP_201_CREATED) 27 | self.assertEqual(response.data, data) 28 | 29 | def test_unresgister_device(self): 30 | kwargs = {'pk': 1} 31 | url = reverse('device-detail', kwargs=kwargs) 32 | response = self.client.delete(url) 33 | self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) 34 | self.assertEqual(response.data, None) 35 | 36 | 37 | class ModelDeviceTest(TestCase): 38 | def setUp(self): 39 | self.device = Device.objects.create(dev_id='123-1', dev_type='ANDROID', reg_id='123-1') 40 | 41 | self.not_registered = Device.objects.create(dev_id='123-2', dev_type='ANDROID', 42 | reg_id='fZBdS8D3a7U:APA91bEOMCWkMVR_IMirgiCPeFz2v7Ju_cLCaYyVYySa80s' 43 | 'fBaobYV360R2_1b0LtJQIrGGfdBbFiZvY1vM10voqPdljdYsFmKnyR8H8S4' 44 | '1bdah4LiwHgZCWy5DOP9xhMIDcIFpKugZV') 45 | self.mismatch_sender_id = Device.objects.create(dev_id='123-3', dev_type='ANDROID', 46 | reg_id='APA91bEOMCWkMVR_IMirgiCPeFz2v7Ju_cLCaYyVYySa80sfBaobYV360R2' 47 | '_1b0LtJQIrGGfdBbFiZvY1vM10voqPdljdYsFmKnyR8H8S41bdah4LiwHgZC' 48 | 'Wy5DOP9xhMIDcIFpKugZV') 49 | 50 | def test_mark_inactive(self): 51 | self.device.mark_inactive() 52 | self.assertEqual(Device.objects.filter(is_active=False).count(), 1) 53 | 54 | @override_settings(GCM_ANDROID_APIKEY='AIzaSyDTOsEsbVUnm2sPVHrV2AuiBMsN9279czQ', ) 55 | def test_send_message_error_NotRegistered_real(self): 56 | response = self.not_registered.send_message() 57 | self.assertEqual(response, 'The client app unregisters with GCM.') 58 | 59 | @override_settings(GCM_ANDROID_APIKEY='AIzaSyDTOsEsbVUnm2sPVHrV2AuiBMsN9279czQ') 60 | def test_send_message_error_MismatchSenderId_real(self): 61 | response = self.mismatch_sender_id.send_message() 62 | self.assertEqual(response, 'A registration token is tied to a certain group of senders.') 63 | 64 | @patch.object(Device, 'send_message') 65 | def test_send_message_successfully(self, mock_send_message): 66 | mock_send_message.return_value = 'Message send successfully' 67 | response = self.device.send_message() 68 | self.assertEqual(response, 'Message send successfully') 69 | 70 | @patch.object(Device, 'send_message') 71 | def test_send_message_error_MissingRegistration(self, mock_send_message): 72 | mock_send_message.return_value = 'Check that the request contains a registration token' 73 | response = self.device.send_message() 74 | self.assertEqual(response, 'Check that the request contains a registration token') 75 | 76 | @patch.object(Device, 'send_message') 77 | def test_send_message_error_InvalidRegistration(self, mock_send_message): 78 | mock_send_message.return_value = 'Check the format of the registration token you pass to the server.' 79 | response = self.device.send_message() 80 | self.assertEqual(response, 'Check the format of the registration token you pass to the server.') 81 | 82 | @patch.object(Device, 'send_message') 83 | def test_send_message_error_NotRegistered(self, mock_send_message): 84 | mock_send_message.return_value = 'The client app unregisters with GCM.' 85 | response = self.device.send_message() 86 | self.assertEqual(response, 'The client app unregisters with GCM.') 87 | 88 | @patch.object(Device, 'send_message') 89 | def test_send_message_error_InvalidPackageName(self, mock_send_message): 90 | mock_send_message.return_value = ('Make sure the message was addressed to a registration token whose package' 91 | ' name matches the value passed in the request.') 92 | response = self.device.send_message() 93 | self.assertEqual(response, 'Make sure the message was addressed to a registration token whose package' 94 | ' name matches the value passed in the request.') 95 | 96 | @patch.object(Device, 'send_message') 97 | def test_send_message_error_MismatchSenderId(self, mock_send_message): 98 | mock_send_message.return_value = 'A registration token is tied to a certain group of senders.' 99 | response = self.device.send_message() 100 | self.assertEqual(response, 'A registration token is tied to a certain group of senders.') 101 | 102 | @patch.object(Device, 'send_message') 103 | def test_send_message_error_MessageTooBig(self, mock_send_message): 104 | mock_send_message.return_value = ('Check that the total size of the payload data included in a message does' 105 | ' not exceed GCM limits: 4096 bytes for most messages, or 2048 bytes in the case' 106 | ' of messages to topics or notification messages on iOS. This includes both' 107 | 'the keys and the values.') 108 | response = self.device.send_message() 109 | self.assertEqual(response, 'Check that the total size of the payload data included in a message does' 110 | ' not exceed GCM limits: 4096 bytes for most messages, or 2048 bytes in the case' 111 | ' of messages to topics or notification messages on iOS. This includes both' 112 | 'the keys and the values.') 113 | 114 | @patch.object(Device, 'send_message') 115 | def test_send_message_error_InvalidDataKey(self, mock_send_message): 116 | mock_send_message.return_value = ('Check that the payload data does not contain a key (such as from ,' 117 | ' or gcm , or any value prefixed by google ) that is used internally by GCM.') 118 | response = self.device.send_message() 119 | self.assertEqual(response, 'Check that the payload data does not contain a key (such as from ,' 120 | ' or gcm , or any value prefixed by google ) that is used internally by GCM.') 121 | 122 | @patch.object(Device, 'send_message') 123 | def test_send_message_error_InvalidTtl(self, mock_send_message): 124 | mock_send_message.return_value = ('Check that the value used in time_to_live is an integer representing a' 125 | ' duration in seconds between 0 and 2,419,200 (4 weeks).') 126 | response = self.device.send_message() 127 | self.assertEqual(response, 'Check that the value used in time_to_live is an integer representing a' 128 | ' duration in seconds between 0 and 2,419,200 (4 weeks).') 129 | 130 | @patch.object(Device, 'send_message') 131 | def test_send_message_error_Unavailable(self, mock_send_message): 132 | mock_send_message.return_value = 'The server couldn\'t process the request in time.' 133 | response = self.device.send_message() 134 | self.assertEqual(response, 'The server couldn\'t process the request in time.') 135 | 136 | @patch.object(Device, 'send_message') 137 | def test_send_message_error_InternalServerError(self, mock_send_message): 138 | mock_send_message.return_value = 'The server encountered an error while trying to process the request.' 139 | response = self.device.send_message() 140 | self.assertEqual(response, 'The server encountered an error while trying to process the request.') 141 | 142 | @patch.object(Device, 'send_message') 143 | def test_send_message_error_DeviceMessageRate(self, mock_send_message): 144 | mock_send_message.return_value = 'The rate of messages to a particular device is too high.' 145 | response = self.device.send_message() 146 | self.assertEqual(response, 'The rate of messages to a particular device is too high.') 147 | 148 | @patch.object(Device, 'send_message') 149 | def test_send_message_error_TopicsMessageRate(self, mock_send_message): 150 | mock_send_message.return_value = 'The rate of messages to subscribers to a particular topic is too high.' 151 | response = self.device.send_message() 152 | self.assertEqual(response, 'The rate of messages to subscribers to a particular topic is too high.') 153 | 154 | @patch.object(Device, 'send_message') 155 | def test_send_message_error_InvalidParameters(self, mock_send_message): 156 | mock_send_message.return_value = 'Check Parameters sent' 157 | response = self.device.send_message() 158 | self.assertEqual(response, 'Check Parameters sent') 159 | 160 | 161 | class UtilsTest(TestCase): 162 | def test_notification_push_real(self): 163 | response = utils.notification_push('ANDROID', 164 | 'to', 165 | 'message') 166 | self.assertEqual(response, {'error': '400 Client Error: Bad Request'}) 167 | 168 | def test_get_device_model_real(self): 169 | response = utils.get_device_model() 170 | self.assertIs(response, Device) 171 | 172 | @patch('gcm.utils.notification_push') 173 | def test_notification_push_mock(self, mock_notification_push): 174 | mock_notification_push.return_value = 'Message send successfully' 175 | response = utils.notification_push('dev_type', 'to', 'message') 176 | self.assertEqual(response, 'Message send successfully') 177 | 178 | @patch('gcm.utils.get_device_model') 179 | def test_get_device_model_mock(self, mock_get_device_model): 180 | mock_get_device_model.return_value = Device 181 | response = utils.get_device_model() 182 | self.assertIs(response, Device) 183 | -------------------------------------------------------------------------------- /gcm/utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import requests 4 | from django.apps import apps 5 | from django.core.exceptions import ImproperlyConfigured 6 | from gcm import settings 7 | 8 | 9 | def notification_push(dev_type, to, message=None, **kwargs): 10 | """ 11 | Send data from your server to your users' devices. 12 | """ 13 | key = { 14 | 'ANDROID': settings.GCM_ANDROID_APIKEY, 15 | 'IOS': settings.GCM_IOS_APIKEY 16 | } 17 | 18 | if not key[dev_type]: 19 | raise ImproperlyConfigured( 20 | "You haven't set the 'GCM_{}_APIKEY' setting yet.".format(dev_type)) 21 | 22 | payload = { 23 | 'ANDROID': {'to': to, 24 | 'data': {'message': message}}, 25 | 'IOS': { 26 | 'to': to, 27 | 'notification': { 28 | 'body': message, 29 | }, 30 | } 31 | } 32 | 33 | payload[dev_type].update(**kwargs) 34 | 35 | payload = json.dumps(payload[dev_type]) 36 | 37 | headers = {'Authorization': 'key={}'.format(key[dev_type]), 'Content-Type': 'application/json'} 38 | 39 | response = requests.post(url='https://gcm-http.googleapis.com/gcm/send', 40 | data=payload, 41 | headers=headers 42 | ) 43 | 44 | if response.status_code == 200: 45 | 46 | response = response.json() 47 | 48 | if response['success']: 49 | return {'success': 'Message send successfully'} 50 | elif response['canonical_ids']: 51 | return {'canonical_id': response.get('results')[0].get('registration_id')} 52 | elif response['failure']: 53 | return {'error': response.get('results')[0].get('error')} 54 | 55 | elif 400 <= response.status_code < 500: 56 | return {'error': '%s Client Error: %s' % (response.status_code, response.reason)} 57 | 58 | elif 500 <= response.status_code < 600: 59 | return {'error': '%s Server Error: %s' % (response.status_code, response.reason)} 60 | 61 | 62 | def get_device_model(): 63 | """ 64 | Returns the Device model that is active in this project. 65 | """ 66 | try: 67 | return apps.get_model(settings.GCM_DEVICE_MODEL) 68 | except ValueError: 69 | raise ImproperlyConfigured("GCM_DEVICE_MODEL must be of the form 'app_label.model_name'") 70 | except LookupError: 71 | raise ImproperlyConfigured( 72 | "GCM_DEVICE_MODEL refers to model '%s' that has not been installed" % settings.GCM_DEVICE_MODEL 73 | ) 74 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | # This flag says that the code is written to work on both Python 2 and Python 3 | # 3. If at all possible, it is good practice to do this. If you cannot, you 4 | # will need to generate wheels for each Python version that you support. 5 | universal=1 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | import gcm 4 | 5 | version = gcm.VERSION 6 | 7 | requires = [ 8 | 'Django>=1.7', 9 | 'djangorestframework==3.1.3', 10 | 'mock==1.0.1', 11 | 'pytz==2015.4', 12 | 'requests==2.7.0', 13 | ] 14 | 15 | setup( 16 | name='django-gcm-android-ios', 17 | version=version, 18 | author='Hugo Brilhante', 19 | author_email='hugobrilhante@gmail.com', 20 | packages=find_packages(), 21 | license='MIT', 22 | description='Send a message using GCM HTTP connection server protocol', 23 | long_description=open('docs/index.rst').read(), 24 | url='https://github.com/hugobrilhante/django-gcm-android-ios', 25 | include_package_data=True, 26 | install_requires=requires, 27 | classifiers=[ 28 | 'Framework :: Django', 29 | 'Intended Audience :: Developers', 30 | 'Intended Audience :: System Administrators', 31 | 'Operating System :: OS Independent', 32 | 'Topic :: Software Development', 33 | 'Programming Language :: Python :: 2.7', 34 | 'Programming Language :: Python :: 3.4', 35 | ], 36 | ) 37 | --------------------------------------------------------------------------------