├── .gitignore ├── .gitmodules ├── AUTHORS ├── CONTRIB ├── LICENSE ├── Makefile ├── README.md ├── logo.png ├── source ├── conf.py ├── contents.rst ├── forms.rst ├── index.rst ├── misc.rst ├── models.rst ├── templates.rst ├── urls.rst ├── views.rst └── workflow.rst └── themes └── sphinxdoc2 ├── layout.html ├── static ├── contents.png ├── navigation.png └── sphinxdoc.css └── theme.conf /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.pyc 3 | build/* 4 | *~ 5 | 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "themes/agiliq"] 2 | path = themes/agiliq 3 | url = git://github.com/agiliq/Fusion_Sphinx 4 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Akshar Raaj 2 | Ben Rousch 3 | Dmitry Zhuravlev-Nevsky 4 | Igor Sobreira 5 | Javed Khan 6 | Kris Kumler 7 | Rafael Luís de Souza Garbin 8 | saikiran 9 | Shabda Raaj 10 | Sungho.Ryu 11 | Thejaswi Puthraya 12 | -------------------------------------------------------------------------------- /CONTRIB: -------------------------------------------------------------------------------- 1 | Please send your comments/contributions/critique in any of the formats. 2 | 1. Fork it on github and send patches for inclusion. 3 | 2. Mail me at shabda@agiliq.com. 4 | 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This work is dual licensed under 2 | `Creative Commons Attribution-Share Alike 3.0 Unported License`_ 3 | and 4 | `GNU Free Documentation License`_ 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /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 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 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 " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/ABCDInc.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ABCDInc.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/ABCDInc" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/ABCDInc" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Agiliq: Django Design Patterns](https://github.com/agiliq/django-design-patterns/raw/master/logo.png) 2 | 3 | Django design patterns is a book about commonly occurring patterns in Django. Not 4 | patterns in [GOF](http://c2.com/cgi/wiki?GangOfFour) sense, but patterns in the sense, we work this way, and it works 5 | for us. For us it is a ~pattern of work~ sense. 6 | 7 | The latest sources are always available from 8 | http://github.com/agiliq/django-design-pattern 9 | and latest html from http://agiliq.com/books/djangodesignpatterns/ 10 | 11 | To export the book into a format of your choice on Ubuntu Linux: 12 | 1. sudo apt-get install git texlive-full python-sphinx 13 | 2. git clone https://github.com/agiliq/django-design-patterns.git 14 | 3. cd django-design-patterns 15 | 4. make # Should show you options for latexpdf, epub, html etc 16 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agiliq/django-design-patterns/34ecd924d03e04961d7471cdcd48e113f27f10f3/logo.png -------------------------------------------------------------------------------- /source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Django design patterns documentation build configuration file, created by 4 | # sphinx-quickstart on Thu Jun 18 21:08:53 2009. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # The contents of this file are pickled, so don't put values in the namespace 9 | # that aren't pickleable (module imports are okay, they're removed automatically). 10 | # 11 | # All configuration values have a default value; values that are commented out 12 | # serve to show the default value. 13 | 14 | import sys, os 15 | 16 | # If your extensions are in another directory, add it here. If the directory 17 | # is relative to the documentation root, use os.path.abspath to make it 18 | # absolute, like shown here. 19 | #sys.path.append(os.path.abspath('some/directory')) 20 | 21 | # General configuration 22 | # --------------------- 23 | 24 | # Add any Sphinx extension module names here, as strings. They can be extensions 25 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 26 | extensions = [] 27 | 28 | # Add any paths that contain templates here, relative to this directory. 29 | templates_path = ['../themes', '_templates'] 30 | 31 | # The suffix of source filenames. 32 | source_suffix = '.rst' 33 | 34 | # The master toctree document. 35 | master_doc = 'index' 36 | 37 | # General substitutions. 38 | project = u'Django Design Patterns' 39 | copyright = u'2011, Agiliq' 40 | 41 | # The default replacements for |version| and |release|, also used in various 42 | # other places throughout the built documents. 43 | # 44 | # The short X.Y version. 45 | version = '0.2' 46 | # The full version, including alpha/beta/rc tags. 47 | release = '0.2' 48 | 49 | # There are two options for replacing |today|: either, you set today to some 50 | # non-false value, then it is used: 51 | #today = '' 52 | # Else, today_fmt is used as the format for a strftime call. 53 | today_fmt = '%B %d, %Y' 54 | 55 | # List of documents that shouldn't be included in the build. 56 | #unused_docs = [] 57 | 58 | # List of directories, relative to source directories, that shouldn't be searched 59 | # for source files. 60 | exclude_trees = [] 61 | 62 | # The reST default role (used for this markup: `text`) to use for all documents. 63 | #default_role = None 64 | 65 | # If true, '()' will be appended to :func: etc. cross-reference text. 66 | #add_function_parentheses = True 67 | 68 | # If true, the current module name will be prepended to all description 69 | # unit titles (such as .. function::). 70 | #add_module_names = True 71 | 72 | # If true, sectionauthor and moduleauthor directives will be shown in the 73 | # output. They are ignored by default. 74 | #show_authors = False 75 | 76 | # The name of the Pygments (syntax highlighting) style to use. 77 | pygments_style = 'sphinx' 78 | 79 | 80 | # Options for HTML output 81 | # ----------------------- 82 | 83 | html_theme = "agiliq" 84 | html_theme_path = ["../themes/", "../../themes"] 85 | 86 | # The name for this set of Sphinx documents. If None, it defaults to 87 | # " v documentation". 88 | html_title = "Django Design Patterns" 89 | 90 | # A shorter title for the navigation bar. Default is the same as html_title. 91 | #html_short_title = None 92 | 93 | # The name of an image file (relative to this directory) to place at the top 94 | # of the sidebar. 95 | html_logo = '../../themes/agiliq/static/logo.png' 96 | 97 | # The name of an image file (within the static path) to use as favicon of the 98 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 99 | # pixels large. 100 | #html_favicon = None 101 | 102 | # Add any paths that contain custom static files (such as style sheets) here, 103 | # relative to this directory. They are copied after the builtin static files, 104 | # so a file named "default.css" will overwrite the builtin "default.css". 105 | html_static_path = ['../../themes/agiliq/static/'] 106 | css_files = ['_static/sphinxdoc.css', ] 107 | 108 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 109 | # using the given strftime format. 110 | html_last_updated_fmt = '%b %d, %Y' 111 | 112 | 113 | 114 | # If true, SmartyPants will be used to convert quotes and dashes to 115 | # typographically correct entities. 116 | #html_use_smartypants = True 117 | 118 | # Custom sidebar templates, maps document names to template names. 119 | #html_sidebars = {} 120 | 121 | # Additional templates that should be rendered to pages, maps page names to 122 | # template names. 123 | #html_additional_pages = {} 124 | 125 | # If false, no module index is generated. 126 | #html_use_modindex = True 127 | 128 | # If false, no index is generated. 129 | #html_use_index = True 130 | 131 | # If true, the index is split into individual pages for each letter. 132 | #html_split_index = False 133 | 134 | # If true, the reST sources are included in the HTML build as _sources/. 135 | #html_copy_source = True 136 | 137 | # If true, an OpenSearch description file will be output, and all pages will 138 | # contain a tag referring to it. The value of this option must be the 139 | # base URL from which the finished HTML is served. 140 | html_use_opensearch = 'http://agiliq/books/djangodesignpatterns/' 141 | 142 | # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). 143 | #html_file_suffix = '' 144 | 145 | # Output file base name for HTML help builder. 146 | htmlhelp_basename = 'Django Design Patterns' 147 | 148 | 149 | # Options for LaTeX output 150 | # ------------------------ 151 | 152 | # The paper size ('letter' or 'a4'). 153 | #latex_paper_size = 'letter' 154 | 155 | # The font size ('10pt', '11pt' or '12pt'). 156 | #latex_font_size = '10pt' 157 | 158 | # Grouping the document tree into LaTeX files. List of tuples 159 | # (source start file, target name, title, author, document class [howto/manual]). 160 | latex_documents = [ 161 | ('index', 'Djangodesignpatterns.tex', u'Django design patterns Documentation', 162 | u'Agiliq and Contributors', 'manual'), 163 | ] 164 | 165 | # The name of an image file (relative to this directory) to place at the top of 166 | # the title page. 167 | #latex_logo = None 168 | 169 | # For "manual" documents, if this is true, then toplevel headings are parts, 170 | # not chapters. 171 | #latex_use_parts = False 172 | 173 | # Additional stuff for the LaTeX preamble. 174 | #latex_preamble = '' 175 | 176 | # Documents to append as an appendix to all manuals. 177 | #latex_appendices = [] 178 | 179 | # If false, no module index is generated. 180 | #latex_use_modindex = True 181 | 182 | 183 | -------------------------------------------------------------------------------- /source/contents.rst: -------------------------------------------------------------------------------- 1 | ================================================== 2 | Django Design Patterns 3 | ================================================== 4 | 5 | Prerequisites 6 | ------------------ 7 | 8 | 1. You know Python. If you do not, try `Dive into Python `_ 9 | 2. You know Django. You work with Django 2.0+. If you do not please read `Django Docs `_ 10 | 11 | Getting the docs 12 | ---------------------- 13 | The documents are generated using `Sphinx `_. The code 14 | is available on `Github `_. 15 | The generated Html is available at `Django Design Patterns `_ 16 | 17 | To generate the docs, do `make html` within the top level folder of repo. 18 | 19 | License 20 | ------------- 21 | This work is dual licensed under 22 | `Creative Commons Attribution-Share Alike 3.0 Unported License `_ 23 | and 24 | `GNU Free Documentation License `_ 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /source/forms.rst: -------------------------------------------------------------------------------- 1 | ================= 2 | Forms 3 | ================= 4 | 5 | Prefer ModelForm to Form 6 | -------------------------- 7 | ModelForm already know the correct UI widgets for your underlying Models. In 8 | most of the cases ModelForm would suffice instead of Forms. 9 | 10 | Some common scenarios 11 | 12 | Hiding some fields from ModelForm which are needed for a DB save. 13 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 14 | 15 | Eg, you want to create a profile for the logged in user.:: 16 | 17 | #in models.py 18 | class Profile(models.Model): 19 | user = models.OneToOneField(User) 20 | company = models.CharField(max_length=50) 21 | 22 | #in forms.py 23 | class ProfileForm(forms.ModelForm): 24 | class Meta: 25 | model = Profile 26 | fields = ['company',] 27 | 28 | #In views.py: 29 | form = ProfileForm(request.POST) 30 | profile = form.save(commit = False) 31 | profile.user = request.user 32 | profile.save() 33 | 34 | Or:: 35 | 36 | class ProfileForm(forms.ModelForm): 37 | 38 | class Meta: 39 | model = Profile 40 | fields =['company',] 41 | 42 | def __init__(self, user, *args, **kwargs) 43 | self.user = user 44 | super(ProfileForm, self).__init__(*args, **kwargs) 45 | 46 | def save(self, *args, **kwargs): 47 | self.instance.user = self.user 48 | super(ProfileForm, self).save(*args, **kwargs) 49 | 50 | 51 | Customizing widgets in ModelForm fields 52 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 53 | 54 | Sometimes you just need to override the widget of a field that's already on 55 | your ModelForm. Instead of duplicating the field definition (with `help_text`, 56 | `required`, `max_length`, etc). You can do this:: 57 | 58 | from django.contrib.admin.widgets import AdminFileWidget 59 | 60 | class ProfileForm(forms.ModelForm): 61 | class Meta: 62 | model = Profile 63 | fields = ['picture', 'company'] 64 | 65 | def __init__(self, *args, **kwargs): 66 | super(ProfileForm, self).__init__(*args, **kwargs) 67 | # note that self.fields is available just after calling super's __init__ 68 | self.fields['picture'].widget = AdminFileWidget() 69 | 70 | 71 | Saving multiple Objects in one form 72 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 73 | 74 | As:: 75 | 76 | class ProfileForm(forms.ModelForm): 77 | class Meta: 78 | model = Profile 79 | fields = ['company',] 80 | 81 | class UserForm(forms.ModelForm): 82 | class Meta: 83 | model = User 84 | fields = [...] 85 | 86 | #in views.py 87 | userform = UserForm(request.POST) 88 | profileform = ProfileForm(request.POST) 89 | if userform.is_valid() and profileform.is_valid(): 90 | #Only if both are valid together 91 | user = userform.save() 92 | profile = profileform.save(commit = False) 93 | profile.user = user 94 | profile.save() 95 | 96 | {# In templates #} 97 |
98 | {{ userform }} 99 | {{ profileform }} 100 | 101 |
102 | 103 | 104 | 105 | 106 | Forms should know how to save themselves. 107 | --------------------------------------------- 108 | 109 | If your forms is a `forms.ModelForm`, it already knows how to save its data. If you 110 | write a forms.Form, it should have a `.save()`. This keeps things symmetrical with 111 | `ModelForms`, and allows you to do:: 112 | 113 | #in views.py 114 | def view_func(request): 115 | if request.method == 'POST': 116 | form = FormClass(request.POST) 117 | if form.is_valid(): 118 | obj = form.save() 119 | ... 120 | ... 121 | 122 | Instead of:: 123 | 124 | if form.is_valid(): 125 | #handle the saving in DB inside of views. 126 | 127 | The `.save()` should return a Model Object 128 | 129 | 130 | The form should know what to do with it's data 131 | ------------------------------------------------ 132 | 133 | If you're building a contact form, or something like this, the goal of your form is 134 | to send an email. So this logic should stay in the form:: 135 | 136 | class ContactForm(forms.Form): 137 | subject = forms.CharField(...) 138 | message = forms.TextField(...) 139 | email = forms.EmailField(...) 140 | ... 141 | 142 | def save(self): 143 | mail_admins(self.cleaned_data['subject'], self.cleaned_data['message']) 144 | 145 | I've used `save()`, and not `send()`, even when i'm not really saving anything. 146 | This is just a convention, people prefer to use `save()` to keep the same interface to 147 | ModelForms. But it doesn't really matter, call it whatever you want. 148 | -------------------------------------------------------------------------------- /source/index.rst: -------------------------------------------------------------------------------- 1 | .. Django design patterns documentation master file, created by sphinx-quickstart on Thu Jun 18 21:08:53 2009. 2 | You can adapt this file completely to your liking, but it should at least 3 | contain the root `toctree` directive. 4 | 5 | ================= 6 | Index 7 | ================= 8 | 9 | This is a collection of patterns which we have found occurring commonly with 10 | Django. All of these either make collaboration easier, coding simpler or code 11 | more maintainable. None of them are design patterns in the sense of GoF design 12 | patterns. We call them design patterns as none other seem closer or more 13 | convenient. 14 | 15 | These are guidelines, which need to be overridden (very commonly, in some cases). 16 | Use your judgement when using them. As PEP8 says, "Foolish consistency is the 17 | hobgoblin of small minds." 18 | 19 | Contents: 20 | 21 | Chapters 22 | =========== 23 | 24 | .. toctree:: 25 | :maxdepth: 2 26 | 27 | 28 | 29 | contents 30 | urls 31 | models 32 | views 33 | forms 34 | templates 35 | workflow 36 | misc 37 | 38 | Indices and tables 39 | ================== 40 | 41 | * :ref:`genindex` 42 | * :ref:`modindex` 43 | * :ref:`search` -------------------------------------------------------------------------------- /source/misc.rst: -------------------------------------------------------------------------------- 1 | ================= 2 | Misc 3 | ================= 4 | 5 | 6 | settings.py and localsettings.py 7 | ------------------------------------ 8 | The settings for your project which are a machine specific should be refactored 9 | out of settings.py into localsettings.py. In your settings.py, you should do:: 10 | 11 | try: 12 | from localsettings import * 13 | except ImportError: 14 | print 'localsettings could not be imported' 15 | pass #Or raise 16 | 17 | This should be at the end of settings.py, so that localsetting.py override 18 | settings in settings.py 19 | 20 | This file should not be checked in your repository. 21 | 22 | 23 | Use relative path in settings.py 24 | -------------------------------------- 25 | Instead of writing:: 26 | 27 | TEMPLATE_DIRS = '/home/user/project/templates' 28 | 29 | Do:: 30 | 31 | #settings.py 32 | import os 33 | 34 | CURRENT_DIR = os.path.dirname(__file__) 35 | TEMPLATE_DIRS = os.path.join(CURRENT_DIR, 'template') 36 | 37 | 38 | Apps should provide default values for settings they are trying to read. 39 | --------------------------------------------------------------------------- 40 | As far as possible, apps should have defaults for settings they are trying to 41 | read. Instead of:: 42 | 43 | DEFAULT_SORT_UP = settings.DEFAULT_SORT_UP 44 | 45 | Use:: 46 | 47 | DEFAULT_SORT_UP = getattr(settings, 'DEFAULT_SORT_UP' , '↑') 48 | 49 | 50 | 51 | Use templatetag when the output does not depend on the request 52 | ------------------------------------------------------------------- 53 | In the sidebar, you want to show the 5 latest comments. You do not need 54 | the request to output this. Make it a templatetag. 55 | 56 | 57 | Import as if your apps are on your project path 58 | ---------------------------------------------------- 59 | Instead of doing `from project.app.models import ModelClass` do `from app.models 60 | import ModelClass`. This makes you apps reusable as they are not tied to a project. 61 | 62 | Naming things 63 | ----------------- 64 | 65 | Model class names should be singular, not plural.:: 66 | 67 | class Post(models.Model): 68 | ... 69 | 70 | and not:: 71 | 72 | class Posts(models.Model): 73 | ... 74 | 75 | Foreign key should use the name of the referenced class.:: 76 | 77 | class Post(models.Model): 78 | user = models.ForeignKey(User) 79 | 80 | Querysets should be plural, instances should be singular.:: 81 | 82 | posts = Post.objects.all() 83 | posts = Post.objects.filter(...) 84 | 85 | post = Post.object.get(pk = 5) 86 | post = Post.object.latest() 87 | 88 | Using pdb remotely 89 | ------------------------ 90 | Sometimes you will hit bugs which show up on server but not on your local 91 | system. To handle these, you need to debug on the server. Doing `manage.py 92 | runserver` only allows local connections. To allow remote connections, use:: 93 | 94 | python manage.py runserver 0.0.0.0:8000 95 | 96 | So that your `pdb.set_trace()` which are on remote servers are hit when you access 97 | them from your local system. 98 | 99 | 100 | Do not use primary keys in urls 101 | ----------------------------------- 102 | If you use PK in urls you are giving away sensitive information, for example, 103 | the number of entries in your table. It also makes it trivial to guess other urls. 104 | 105 | Use slugs in urls. This has the advantage of being both user and SEO 106 | friendly. 107 | 108 | If slugs do not make sense, instead use a CRC algorithm.:: 109 | 110 | class Customer(models.Model): 111 | name = models.CharField(max_length = 100) 112 | 113 | def get_absolute_url(self): 114 | import zlib 115 | #Use permalink in real case 116 | return '/customer/%s/' % zlib.crc32(self.pk) 117 | 118 | 119 | Code defensively in middleware and context processors. 120 | ----------------------------------------------------------- 121 | 122 | Your middleware and context processors are going to be run for **all** requests. 123 | Have you handled all cases? 124 | 125 | def process_request(request): 126 | if user.is_authenticated(): 127 | profile = request.user.get_profile() 128 | # Hah, I create profiles during 129 | # registration so this is safe. 130 | ... 131 | 132 | 133 | Or it is? What about users created via `manage.py createsuperuser`? With the 134 | above middleware, the default user can not access even the admin site. 135 | 136 | Hence handle all scenarios in middleware and context processors. This is one place 137 | where `try: .. except: ..` (bare except) blocks are acceptable. You do not want one 138 | middleware bringing down the entire site. 139 | 140 | 141 | Move long running tasks to a message queue. 142 | ------------------------------------------------ 143 | If you have long running requests they should be handled in a message queue, and not in the request thread. For example, using a lot of API calls, will make your pages crawl. Instead move the API processing to a message queue such as `celery `_. 144 | 145 | -------------------------------------------------------------------------------- /source/models.rst: -------------------------------------------------------------------------------- 1 | ================= 2 | Models 3 | ================= 4 | 5 | Multiple managers 6 | -------------------- 7 | A Model class can have multiple managers, depending upon your needs. Suppose you 8 | do not want to display any object on your site which is unapproved(is_approved = 9 | False in your Model).:: 10 | 11 | class ModelClassApprovedOnlyManager(models.Manager): 12 | def get_query_set(*args, **kwargs): 13 | return super(ModelClassApprovedOnlyManager, self).get_query_set(*args, **kwargs).filter(is_approved = True) 14 | 15 | class ModelClass(models.Model): 16 | ... 17 | is_approved = models.BooleanField(default = False) 18 | 19 | objects = models.Manager() 20 | approved_objects = ModelClassApprovedOnlyManager() 21 | 22 | If you use multiple managers, the first manager should be the default manager. This is as the first 23 | manager is accessible as `ModelClass._default_manager`, which is used by admin to get all objects. 24 | 25 | Custom Manager Methods 26 | ---------------------- 27 | Imagine you have a query like this:: 28 | 29 | Event.objects.filter(is_published=True).filter(start_date__gte=datetime.datetime.now()).order_by('start_date') 30 | 31 | you probably will need to filter by status and created date again, to avoid duplicating 32 | code you could add custom methods to your default manager:: 33 | 34 | class EventQuerySet(models.query.QuerySet): 35 | def published(self): 36 | return self.filter(is_published=True) 37 | 38 | def upcoming(self): 39 | return self.filter(start_date__gte=datetime.datetime.now()) 40 | 41 | class EventManager(models.Manager): 42 | def get_query_set(self): 43 | return EventQuerySet(self.model, using=self._db) # note the `using` parameter, new in 1.2 44 | 45 | def published(self): 46 | return self.get_query_set().published() 47 | 48 | def upcoming(self): 49 | return self.get_query_set().upcoming() 50 | 51 | class Event(models.Model): 52 | is_published = models.BooleanField(default=False) 53 | start_date = models.DateTimeField() 54 | ... 55 | 56 | objects = EventManager() # override the default manager 57 | 58 | 59 | This way you keep your logic in your model. 60 | Why do you need a custom QuerySet? To be able to chain method calls. Now that query could be:: 61 | 62 | Event.objects.published().upcoming().order_by('start_date') 63 | 64 | Hierarchical Relationships 65 | ---------------------------- 66 | You may want to model hierarchical relationships. The simplest way to do this is:: 67 | 68 | class ModelClass(models.Model): 69 | ... 70 | parent = models.ForeignKey('ModelClass') 71 | 72 | This is called adjacency list model, and is very inefficient for large trees. If your 73 | trees are very shallow you can use this. Otherwise you want to use a more 74 | efficient but complex modeling called MPTT. Fortunately, you can just use django-mptt. 75 | 76 | Singleton classes 77 | ------------------- 78 | Sometimes you want to make sure that only one Object of a Model can be created. 79 | 80 | Logging 81 | ----------- 82 | To make sure, when an object is create/edited/deleted, there is a log. 83 | 84 | Audit Trail and rollback 85 | ---------------------------- 86 | When an object is modified or deleted, to be able to go back to the previous 87 | version. 88 | 89 | Define an __unicode___ 90 | -------------------------- 91 | Until you define an `__unicode__` for your ModelClass, in Admin and at various 92 | other places you will get an `` where the object needs to be 93 | displayed. Define a meaningful `__unicode__` for you ModelClass, to get 94 | meaningful display. Once you define `__unicode__`, you do not need to define 95 | `__str__`. 96 | 97 | Define a get_absolute_url() 98 | ----------------------------- 99 | `get_absolute_url` is used at various places by Django. (In Admin for "view on 100 | site" option, and in feeds framework). 101 | 102 | Use reverse() for calculating get_absolute_url 103 | --------------------------------------------------- 104 | You want only one canonical representation of your urls. This should be in urls.py 105 | 106 | The `permalink` decorator is `no longer recommended `_ for use. 107 | 108 | If you write a class like:: 109 | 110 | class Customer(models.Model) 111 | ... 112 | 113 | def get_absolute_url(self): 114 | return /customer/%s/ % self.slug 115 | 116 | You have this representation at two places. You instead want to do:: 117 | 118 | class Customer(models.Model) 119 | ... 120 | 121 | def get_absolute_url(self): 122 | return reverse('customers.detail', args=[self.slug]) 123 | 124 | AuditFields 125 | ---------------- 126 | 127 | You want to keep track of when an object was created and updated. Create 128 | two DateTimeFields with `auto_now` and `auto_now_add`.:: 129 | 130 | class ItemSold(models.Model): 131 | name = models.CharField(max_length = 100) 132 | value = models.PositiveIntegerField() 133 | ... 134 | #Audit field 135 | created_on = models.DateTimeField(auto_now_add = True) 136 | updated_on = models.DateTimeField(auto_now = True) 137 | 138 | Now you want, created_by and updated_by. This is possible using the 139 | `threadlocals `_ 140 | technique, but since we `do not want `_ 141 | to do that, we will need to pass user to the methods.:: 142 | 143 | class ItemSoldManager(models.Manager): 144 | def create_item_sold(self, user, ...): 145 | 146 | 147 | class ItemSold(models.Model): 148 | name = models.CharField(max_length = 100) 149 | value = models.PositiveIntegerField() 150 | ... 151 | #Audit field 152 | created_on = models.DateTimeField(auto_now_add = True) 153 | updated_on = models.DateTimeField(auto_now = True) 154 | created_by = models.ForeignKey(User, ...) 155 | updated_by = models.ForeignKey(User, ...) 156 | 157 | def set_name(self, user, value): 158 | self.created_by = user 159 | self.name = value 160 | self.save() 161 | 162 | ... 163 | 164 | objects = ItemSoldManager() 165 | 166 | Working with denormalised fields 167 | ----------------------------------- 168 | 169 | Working with child tables. 170 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 171 | 172 | You want to keep track of number of employees of a department.:: 173 | 174 | class Department(models.Model): 175 | name = models.CharField(max_length = 100) 176 | employee_count = models.PositiveIntegerField(default = 0) 177 | 178 | 179 | class Employee(models.Model): 180 | department = models.ForeignKey(Department) 181 | 182 | One way to do so would be to override, `save` and `delete`.:: 183 | 184 | class Employee(models.Model): 185 | ... 186 | 187 | def save(self, *args, **kwargs): 188 | if not self.id: 189 | #this is a create, not an update 190 | self.department.employee_count += 1 191 | self.department.save() 192 | super(Employee, self).save(*args, **kwargs) 193 | 194 | def delete(self): 195 | self.department.employee_count -= 1 196 | self.department.save() 197 | super(Employee, self).delete() 198 | 199 | Other option would be to attach listeners for `post_save` and `post_delete`.:: 200 | 201 | from django.db.models import signals 202 | 203 | def increment_employee_count(sender, instance, created, raw, **kwargs): 204 | if created: 205 | instance.department.employee_count += 1 206 | instance.department.save() 207 | 208 | def decrement_employee_count(sender, instance, **kwargs): 209 | instance.department.employee_count -= 1 210 | instance.department.save() 211 | 212 | signals.post_save.connect(increment_employee_count, sender=Employee) 213 | signals.post_delete.connect(decrement_employee_count, sender=Employee) 214 | 215 | 216 | Abstract custom queries in Manager methods. 217 | ---------------------------------------------- 218 | 219 | If you have some complex Sql query, not easily representable via Django ORM, 220 | you can write custom Sql. These should be abstracted as Manager methods. 221 | -------------------------------------------------------------------------------- /source/templates.rst: -------------------------------------------------------------------------------- 1 | ================= 2 | Templates 3 | ================= 4 | 5 | Projects and apps. 6 | -------------------- 7 | There should be one base.html at the project level, and one `base.html` at each of 8 | the app levels. The app level `base.html` should extend the project level 9 | `base.html`.:: 10 | 11 | {# Eg Project base.html #} 12 | 13 | 14 | 15 | {% block title %}My Super project{% endblock %} 16 | ... 17 | 18 | {# app base.html #} 19 | 20 | {% extends 'base.html' %} 21 | 22 | {% block title %}{{ block.super }} - My duper app {% endblock %} 23 | ... 24 | 25 | 26 | {# login.html #} 27 | 28 | {% extends 'auth/base.html' %} 29 | {% block title %}{{ block.super }} - Login {% endblock %} 30 | ... 31 | 32 | 33 | Location of templates 34 | ---------------------------- 35 | 36 | The templates for an app should be available as `appname/template.html`. So the 37 | templates should be physically located at either 38 | 39 | 1. project/templates/app/template.html 40 | 2. project/app/templates/app/template.html 41 | 42 | This allows two apps to have the same templates names. 43 | 44 | Handling iterables which maybe empty 45 | ----------------------------------------- 46 | 47 | In your view you do:: 48 | 49 | posts = BlogPosts.objects.all() 50 | ... 51 | payload = {'posts': posts} 52 | return render_to_response('blog/posts.html', payload, ...) 53 | 54 | Now `posts` may be empty, so in template we do,:: 55 | 56 |
    57 | {% for post in posts %} 58 |
  • {{ post.title }}
  • 59 | {% empty %} 60 |
  • Sorry, no posts yet!
  • 61 | {% endfor %} 62 |
      63 | 64 | Please, note about `empty` clause using. If `posts` is empty or could not be found, the `empty` clause will be displayed. 65 | -------------------------------------------------------------------------------- /source/urls.rst: -------------------------------------------------------------------------------- 1 | ================= 2 | Urls 3 | ================= 4 | 5 | Projects and apps 6 | -------------------- 7 | 8 | There should be one `urls.py` at the project level, and one `urls.py` at each app 9 | level. The project level `urls.py` should include each of the `urls.py` under a 10 | prefix.:: 11 | 12 | #project urls.py 13 | 14 | urlpatterns = patterns( 15 | '', 16 | (r'^', include('mainpages.urls')), 17 | (r'^admin/', include(admin.site.urls)), 18 | (r'^captcha/', include('yacaptcha.urls')), 19 | ..... 20 | ) 21 | 22 | #app urls.py 23 | urlpatterns = patterns( 24 | 'app.views', 25 | url(r'^$', 'index'), 26 | url(r'^what/$', 'what_view') 27 | ..... 28 | ) 29 | 30 | Naming urls 31 | --------------- 32 | 33 | Urlpatterns should be named. [#ref1]_ This is done as:: 34 | 35 | url(r'^$', 'index', name='main_index'), 36 | 37 | This enables calling `{% url urlpatternname %}` much easier. 38 | 39 | The pattern name should be of the form `appname_viewname`. If the same view is 40 | used in multiple urlpatterns, the name should be of form `appname_viewname_use`, 41 | as in `search_advanced_product` and `search_advanced_content`.:: 42 | 43 | #urls.py for app search 44 | urlpatterns = patterns( 45 | 'search.views' 46 | url(r'^advanced_product_search/$', 'advanced', name='search_advanced_product'), 47 | url(r'^advanced_content_search/$', 'advanced', name='search_advanced_content'), 48 | ... 49 | ) 50 | 51 | Here the same view `advanced` is used at two different urls and has two different names. 52 | 53 | References 54 | ---------------- 55 | 56 | .. [#ref1] http://github.com/agiliq/django-blogango/blob/9525dfa621ca54219eed0c0e9c1624de89948045/blogango/urls.py#L23 57 | -------------------------------------------------------------------------------- /source/views.rst: -------------------------------------------------------------------------------- 1 | ================= 2 | Views 3 | ================= 4 | 5 | Generic views 6 | ------------------ 7 | Use generic views where possible. 8 | 9 | Generic views are just functions 10 | ------------------------------------ 11 | This means you can use them instead of calling say, `render_to_response`. For 12 | example, suppose you want to show a list of objects, so you would like to use 13 | `django.views.generic.object_list`. However, you also want to allow comments to 14 | be posted on these objects, which this generic view does not allow. [#ref1]_ :: 15 | 16 | def object_list_comment(request): 17 | if request.method == 'POST': 18 | form = CommentForm(request.POST) 19 | if form.is_valid(): 20 | obj = form.save() 21 | ... 22 | #redirect 23 | #Handle get or invalid form Post 24 | queryset = ModelClass.object.filter(...) 25 | payload = {'form':form} 26 | return object_list(request, queryset, extra_context = payload) 27 | 28 | 29 | Handle GET and POST in same view function 30 | ---------------------------------------------- 31 | 32 | This keeps things grouped logically together. [#ref2]_ Eg.:: 33 | 34 | def foo(request): 35 | form = FormClass() 36 | if request.method == 'POST': 37 | #Handle POST and form saving etc. 38 | #Redirect etc 39 | #Any more GET handling 40 | payload = {'form': form, ...} 41 | return render_to_response(...) 42 | 43 | 44 | Querysets are chainable and lazy 45 | ----------------------------------- 46 | This means that your view can keep on creating querysets and they would be 47 | evaluated only when used. Suppose you have an advanced search view which 48 | can take multiple criteria all of which are optional.:: 49 | 50 | def advanced_search(request, criteria1=None, criteria2=None, criteria3=None): 51 | queryset = ModelClass.objects.all() 52 | if criteria1: 53 | queryset = queryset.filter(critera1=critera1) 54 | if criteria2: 55 | queryset = queryset.filter(critera2=critera2) 56 | if criteria3: 57 | queryset = queryset.filter(critera3=critera3) 58 | return objects_list(request, queryset=queryset) 59 | 60 | References 61 | ---------------- 62 | 63 | .. [#ref1] http://github.com/mightylemon/mightylemon/blob/ff916fec3099d0edab5ba7b07f4cf838ba6fec7b/apps/events/views.py 64 | .. [#ref2] http://github.com/agiliq/django-blogango/blob/9525dfa621ca54219eed0c0e9c1624de89948045/blogango/views.py#L65 -------------------------------------------------------------------------------- /source/workflow.rst: -------------------------------------------------------------------------------- 1 | ================= 2 | Workflow 3 | ================= 4 | 5 | Use a source control system 6 | ------------------------------- 7 | Either of Git and Mercurial are good choices. 8 | 9 | Create a requirements.txt 10 | ---------------------------------- 11 | Your project should have a requirements.txt file. A `pip install -r requirements.txt` 12 | should get all the third party apps which are not part of your source control system. 13 | 14 | pin your requirements.txt 15 | ------------------------------------ 16 | For predictable and deterministic 17 | 18 | Use virtualenv and pip (sandbox) 19 | ---------------------------------- 20 | Your various projects might require different versions of third party libraries. Use `virtualenv `_ to keep 21 | separate environments and use `pip `_ to manage dependencies. 22 | 23 | If you use virtualenv a long time, you can try `virtualenvwrapper ` 24 | 25 | Use pep8.py to check compliance with Python coding guidelines. 26 | ---------------------------------------------------------------- 27 | Your code would be using conforming to pep8, which are the standard coding guidelines. `Pep8.py `_ can check your code for deviations. 28 | 29 | 30 | Use one code analyzer for static analysis. 31 | ---------------------------------------------------------------- 32 | * `Pyflakes `_ can find out some common mistakes. 33 | * `Pylint `_ code analyzer which looks for programming errors, helps enforcing a coding standard and sniffs for some code smells. 34 | 35 | Run it after each deploy. 36 | 37 | 38 | Use a bug tracking tool. 39 | ---------------------------- 40 | Use one of 41 | 42 | * Trello 43 | * Jira 44 | * Asana 45 | * Basecamp 46 | * Trac 47 | * Assembla 48 | * Unfuddle 49 | 50 | Use south for schema migration 51 | --------------------------------- 52 | Django doesn't come with a built in tool for schema migration. `South `_ is the best tool for managing schema migrations and is well supported. 53 | 54 | Create various entries in your /etc/hosts mapped to localhost 55 | ------------------------------------------------------------------ 56 | While development you probably want multiple users logged in to the site 57 | simultaneously. For example, while developing, I have one user logged in the 58 | admin, one normal 59 | user using the site. If both try to access the site from localhost, one will be 60 | logged out when other logs in. 61 | 62 | If you have multiple entries mapped to localhost in /etc/hosts, you can use 63 | multiple users simultaneously logged in. 64 | 65 | Do not commit the generated files 66 | ----------------------------------- 67 | Django does not have a lot of auto generated files. However as you work with 68 | other Django apps, you may come across auto generated files. These should not be 69 | checked in the the Django repository. 70 | For example, for this book, we commit the source files and folder, but not the 71 | auto-generated build folders. 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /themes/sphinxdoc2/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "basic/layout.html" %} 2 | 3 | {# put the sidebar before the body #} 4 | {% block sidebar1 %} 5 | 6 | {{ sidebar() }} 7 | 8 | {% endblock %} 9 | {% block sidebar2 %}{% endblock %} 10 | 11 | {% block footer %} 12 | 13 |
      blog comments powered by Disqus 14 |