├── .gitignore ├── Makefile ├── _plugins ├── __init__.py └── writers │ ├── __init__.py │ └── htmlid.py ├── _static ├── breadcrumb_background.png ├── default.css ├── documentation.png ├── header_sm_mid.png ├── scrn1.png ├── scrn2.png ├── searchfield_leftcap.png ├── searchfield_repeat.png ├── searchfield_rightcap.png ├── title_background.png ├── toc.js ├── triangle_closed.png ├── triangle_left.png └── triangle_open.png ├── _templates └── layout.html ├── app_construction ├── index.rst ├── version_reporting.py ├── version_reporting.rst ├── version_reporting_appskel.py ├── version_reporting_django.py ├── version_reporting_sphinx.py └── views_as_a_package.rst ├── conf.py ├── configuration ├── autodiscovery.rst ├── autodiscovery1.py ├── configure_app.rst ├── configure_app1.py ├── configure_app2.py ├── configure_app3.py ├── configure_app4.py └── index.rst ├── decorator_apps ├── add_fieldsets_to_model_admin.rst ├── change_the_admin_widget.rst ├── index.rst ├── introduction.rst ├── lazy_field_insertion.rst └── lazy_manager_insertion.rst ├── development_philosophy.rst ├── example_pattern.rst ├── index.rst ├── make.bat ├── models ├── abstract_model_mixins.rst ├── automatically_filling_user.rst ├── automatically_filling_user_admin.py ├── automatically_filling_user_admin2.py ├── automatically_filling_user_form.py ├── automatically_filling_user_model.py ├── common_options.rst ├── common_options_1.py ├── common_options_2.py ├── configurable_relations.rst ├── flexible_storage_uploaded_files.rst └── index.rst ├── templates ├── easily_overridable_templates.rst ├── extend_one_template.rst ├── import_your_template_blocks.rst └── index.rst ├── urls ├── index.rst └── urls_that_live_under_any_prefix.rst └── what_are_pluggable_apps.rst /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | dev.db 3 | local_settings.py 4 | media/ugc 5 | docs/_build/ 6 | src/ 7 | pip-log.txt 8 | media/js/*.r*.js 9 | media/css/*.r*.css 10 | *DS_Store 11 | *.egg-info 12 | _build 13 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = -a 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | DESTDIR = .. 10 | 11 | # Internal variables. 12 | PAPEROPT_a4 = -D latex_paper_size=a4 13 | PAPEROPT_letter = -D latex_paper_size=letter 14 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 15 | 16 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest 17 | 18 | help: 19 | @echo "Please use \`make ' where is one of" 20 | @echo " html to make standalone HTML files" 21 | @echo " dirhtml to make HTML files named index.html in directories" 22 | @echo " singlehtml to make a single large HTML file" 23 | @echo " pickle to make pickle files" 24 | @echo " json to make JSON files" 25 | @echo " htmlhelp to make HTML files and a HTML help project" 26 | @echo " qthelp to make HTML files and a qthelp project" 27 | @echo " devhelp to make HTML files and a Devhelp project" 28 | @echo " epub to make an epub" 29 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 30 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 31 | @echo " text to make text files" 32 | @echo " man to make manual pages" 33 | @echo " changes to make an overview of all changed/added/deprecated items" 34 | @echo " linkcheck to check all external links for integrity" 35 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 36 | 37 | clean: 38 | -rm -rf $(BUILDDIR)/* 39 | 40 | html: 41 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 42 | @echo 43 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 44 | 45 | dirhtml: 46 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 47 | @echo 48 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 49 | 50 | singlehtml: 51 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 52 | @echo 53 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 54 | 55 | pickle: 56 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 57 | @echo 58 | @echo "Build finished; now you can process the pickle files." 59 | 60 | json: 61 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 62 | @echo 63 | @echo "Build finished; now you can process the JSON files." 64 | 65 | htmlhelp: 66 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 67 | @echo 68 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 69 | ".hhp project file in $(BUILDDIR)/htmlhelp." 70 | 71 | qthelp: 72 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 73 | @echo 74 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 75 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 76 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/sample.qhcp" 77 | @echo "To view the help file:" 78 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/sample.qhc" 79 | 80 | devhelp: 81 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 82 | @echo 83 | @echo "Build finished." 84 | @echo "To view the help file:" 85 | @echo "# mkdir -p $$HOME/.local/share/devhelp/sample" 86 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/sample" 87 | @echo "# devhelp" 88 | 89 | epub: 90 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 91 | @echo 92 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 93 | 94 | latex: 95 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 96 | @echo 97 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 98 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 99 | "(use \`make latexpdf' here to do that automatically)." 100 | 101 | latexpdf: 102 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 103 | @echo "Running LaTeX files through pdflatex..." 104 | make -C $(BUILDDIR)/latex all-pdf 105 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 106 | 107 | text: 108 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 109 | @echo 110 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 111 | 112 | man: 113 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 114 | @echo 115 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 116 | 117 | changes: 118 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 119 | @echo 120 | @echo "The overview file is in $(BUILDDIR)/changes." 121 | 122 | linkcheck: 123 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 124 | @echo 125 | @echo "Link check complete; look for any errors in the above output " \ 126 | "or in $(BUILDDIR)/linkcheck/output.txt." 127 | 128 | doctest: 129 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 130 | @echo "Testing of doctests in the sources finished, look at the " \ 131 | "results in $(BUILDDIR)/doctest/output.txt." 132 | -------------------------------------------------------------------------------- /_plugins/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coordt/djangopatterns/f17382ec04dba98149d991b1858e1116c7955b07/_plugins/__init__.py -------------------------------------------------------------------------------- /_plugins/writers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coordt/djangopatterns/f17382ec04dba98149d991b1858e1116c7955b07/_plugins/writers/__init__.py -------------------------------------------------------------------------------- /_plugins/writers/htmlid.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | from sphinx.writers.html import HTMLWriter, HTMLTranslator, \ 3 | SmartyPantsHTMLTranslator 4 | 5 | 6 | class HTMLIdTranslator(SmartyPantsHTMLTranslator): 7 | """An HTML translator that includes incremented ids and anchors""" 8 | id_counter = 0 9 | # def __init__(self, builder, *args, **kwargs): 10 | # super(HTMLIdTranslator, self).__init__(builder, *args, **kwargs) 11 | # self.id_counter = 0 12 | 13 | def visit_paragraph(self, node): 14 | self.id_counter += 1 15 | hashname = u"%s%s%s" % (self.builder.current_docname, self.id_counter, node.astext()) 16 | id_name = hashlib.sha1(hashname.encode('utf8')).hexdigest() 17 | node['ids'].append(id_name) 18 | 19 | if self.should_be_compact_paragraph(node): 20 | self.context.append('') 21 | else: 22 | #self.body.append('' % id_name) 23 | self.body.append(self.starttag(node, 'p', '', CLASS='cn')) 24 | self.context.append('

\n') 25 | 26 | def visit_block_quote(self, node): 27 | self.id_counter += 1 28 | node['ids'].append(str(self.id_counter)) 29 | 30 | self.body.append(self.starttag(node, 'blockquote', CLASS='cn') + '
') 31 | 32 | ### 33 | # The li items usually have p tags in them. Don't need to have nested cn's 34 | 35 | # def visit_definition_list(self, node): 36 | # self.id_counter += 1 37 | # node['ids'].append(str(self.id_counter)) 38 | # 39 | # self.body.append(self.starttag(node, 'dl', CLASS='docutils cn')) 40 | # 41 | # def visit_list_item(self, node): 42 | # self.id_counter += 1 43 | # node['ids'].append(str(self.id_counter)) 44 | # 45 | # self.body.append(self.starttag(node, 'li', '', CLASS='cn')) 46 | # if len(node): 47 | # node[0]['classes'].append('first') 48 | 49 | -------------------------------------------------------------------------------- /_static/breadcrumb_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coordt/djangopatterns/f17382ec04dba98149d991b1858e1116c7955b07/_static/breadcrumb_background.png -------------------------------------------------------------------------------- /_static/default.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Sphinx stylesheet -- basic theme 3 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | */ 5 | h3 { 6 | color:#000000; 7 | font-size: 17px; 8 | margin-bottom:0.5em; 9 | margin-top:2em; 10 | } 11 | /* -- main layout ----------------------------------------------------------- */ 12 | 13 | div.clearer { 14 | clear: both; 15 | } 16 | 17 | /* -- header ---------------------------------------------------------------- */ 18 | 19 | #header #title { 20 | background:#29334F url(title_background.png) repeat-x scroll 0 0; 21 | border-bottom:1px solid #B6B6B6; 22 | height:25px; 23 | overflow:hidden; 24 | } 25 | #headerButtons { 26 | position: absolute; 27 | list-style: none outside; 28 | top: 26px; 29 | left: 0px; 30 | right: 0px; 31 | margin: 0px; 32 | padding: 0px; 33 | border-top: 1px solid #2B334F; 34 | border-bottom: 1px solid #EDEDED; 35 | height: 20px; 36 | font-size: 8pt; 37 | overflow: hidden; 38 | background-color: #D8D8D8; 39 | } 40 | 41 | #headerButtons li { 42 | background-repeat:no-repeat; 43 | display:inline; 44 | margin-top:0; 45 | padding:0; 46 | } 47 | 48 | .headerButton { 49 | display: inline; 50 | height:20px; 51 | } 52 | 53 | .headerButton a { 54 | text-decoration: none; 55 | float: right; 56 | height: 20px; 57 | padding: 4px 15px; 58 | border-left: 1px solid #ACACAC; 59 | font-family:'Lucida Grande',Geneva,Helvetica,Arial,sans-serif; 60 | color: black; 61 | } 62 | .headerButton a:hover { 63 | color: white; 64 | background-color: #787878; 65 | 66 | } 67 | 68 | li#toc_button { 69 | text-align:left; 70 | } 71 | 72 | li#toc_button .headerButton a { 73 | width:198px; 74 | padding-top: 4px; 75 | font-family:'Lucida Grande',Geneva,Helvetica,Arial,sans-serif; 76 | color: black; 77 | float: left; 78 | padding-left:15px; 79 | border-right:1px solid #ACACAC; 80 | background:transparent url(triangle_open.png) no-repeat scroll 4px 6px; 81 | } 82 | 83 | li#toc_button .headerButton a:hover { 84 | background-color: #787878; 85 | color: white; 86 | } 87 | 88 | li#page_buttons { 89 | position:absolute; 90 | right:0; 91 | } 92 | 93 | #breadcrumbs { 94 | color: black; 95 | background-image:url(breadcrumb_background.png); 96 | border-top:1px solid #2B334F; 97 | bottom:0; 98 | font-size:10px; 99 | height:15px; 100 | left:0; 101 | overflow:hidden; 102 | padding:3px 10px 0; 103 | position:absolute; 104 | right:0; 105 | white-space:nowrap; 106 | z-index:901; 107 | } 108 | #breadcrumbs a { 109 | color: black; 110 | text-decoration: none; 111 | } 112 | #breadcrumbs a:hover { 113 | text-decoration: underline; 114 | } 115 | #breadcrumbs img { 116 | padding-left: 3px; 117 | } 118 | /* -- sidebar --------------------------------------------------------------- */ 119 | #sphinxsidebar { 120 | position: absolute; 121 | top: 84px; 122 | bottom: 19px; 123 | left: 0px; 124 | width: 229px; 125 | background-color: #E4EBF7; 126 | border-right: 1px solid #ACACAC; 127 | border-top: 1px solid #2B334F; 128 | overflow-x: hidden; 129 | overflow-y: auto; 130 | padding: 0px 0px 0px 0px; 131 | font-size:11px; 132 | } 133 | 134 | div.sphinxsidebarwrapper { 135 | padding: 10px 5px 0 10px; 136 | } 137 | 138 | #sphinxsidebar li { 139 | margin: 0px; 140 | padding: 0px; 141 | font-weight: normal; 142 | margin: 0px 0px 7px 0px; 143 | overflow: hidden; 144 | text-overflow: ellipsis; 145 | font-size: 11px; 146 | } 147 | 148 | #sphinxsidebar ul { 149 | list-style: none; 150 | margin: 0px 0px 0px 0px; 151 | padding: 0px 5px 0px 5px; 152 | } 153 | 154 | #sphinxsidebar ul ul, 155 | #sphinxsidebar ul.want-points { 156 | list-style: square; 157 | } 158 | 159 | #sphinxsidebar ul ul { 160 | margin-top: 0; 161 | margin-bottom: 0; 162 | } 163 | 164 | #sphinxsidebar form { 165 | margin-top: 10px; 166 | } 167 | 168 | #sphinxsidebar input { 169 | border: 1px solid #787878; 170 | font-family: sans-serif; 171 | font-size: 1em; 172 | } 173 | 174 | img { 175 | border: 0; 176 | } 177 | 178 | #sphinxsidebar li.toctree-l1 a { 179 | font-weight: bold; 180 | color: #000; 181 | text-decoration: none; 182 | } 183 | 184 | #sphinxsidebar li.toctree-l2 a { 185 | font-weight: bold; 186 | color: #4f4f4f; 187 | text-decoration: none; 188 | } 189 | 190 | /* -- search page ----------------------------------------------------------- */ 191 | 192 | ul.search { 193 | margin: 10px 0 0 20px; 194 | padding: 0; 195 | } 196 | 197 | ul.search li { 198 | padding: 5px 0 5px 20px; 199 | background-image: url(file.png); 200 | background-repeat: no-repeat; 201 | background-position: 0 7px; 202 | } 203 | 204 | ul.search li a { 205 | font-weight: bold; 206 | } 207 | 208 | ul.search li div.context { 209 | color: #888; 210 | margin: 2px 0 0 30px; 211 | text-align: left; 212 | } 213 | 214 | ul.keywordmatches li.goodmatch a { 215 | font-weight: bold; 216 | } 217 | #sphinxsidebar input.prettysearch {border:none;} 218 | input.searchbutton { 219 | float: right; 220 | } 221 | .search-wrapper {width: 100%; height: 25px;} 222 | .search-wrapper input.prettysearch { border: none; width:200px; height: 16px; background: url(searchfield_repeat.png) center top repeat-x; border: 0px; margin: 0; padding: 3px 0 0 0; font: 11px "Lucida Grande", "Lucida Sans Unicode", Arial, sans-serif; } 223 | .search-wrapper input.prettysearch { width: 184px; margin-left: 20px; *margin-top:-1px; *margin-right:-2px; *margin-left:10px; } 224 | .search-wrapper .search-left { display: block; position: absolute; width: 20px; height: 19px; background: url(searchfield_leftcap.png) left top no-repeat; } 225 | .search-wrapper .search-right { display: block; position: relative; left: 204px; top: -19px; width: 10px; height: 19px; background: url(searchfield_rightcap.png) right top no-repeat; } 226 | 227 | /* -- index page ------------------------------------------------------------ */ 228 | 229 | table.contentstable { 230 | width: 90%; 231 | } 232 | 233 | table.contentstable p.biglink { 234 | line-height: 150%; 235 | } 236 | 237 | a.biglink { 238 | font-size: 1.3em; 239 | } 240 | 241 | span.linkdescr { 242 | font-style: italic; 243 | padding-top: 5px; 244 | font-size: 90%; 245 | } 246 | 247 | /* -- general index --------------------------------------------------------- */ 248 | 249 | table.indextable td { 250 | text-align: left; 251 | vertical-align: top; 252 | } 253 | 254 | table.indextable dl, table.indextable dd { 255 | margin-top: 0; 256 | margin-bottom: 0; 257 | } 258 | 259 | table.indextable tr.pcap { 260 | height: 10px; 261 | } 262 | 263 | table.indextable tr.cap { 264 | margin-top: 10px; 265 | background-color: #f2f2f2; 266 | } 267 | 268 | img.toggler { 269 | margin-right: 3px; 270 | margin-top: 3px; 271 | cursor: pointer; 272 | } 273 | 274 | /* -- general body styles --------------------------------------------------- */ 275 | .document { 276 | border-top:1px solid #2B334F; 277 | overflow:auto; 278 | padding-left:2em; 279 | padding-right:2em; 280 | position:absolute; 281 | z-index:1; 282 | top:84px; 283 | bottom:19px; 284 | right:0; 285 | left:230px; 286 | } 287 | 288 | a.headerlink { 289 | visibility: hidden; 290 | } 291 | 292 | h1:hover > a.headerlink, 293 | h2:hover > a.headerlink, 294 | h3:hover > a.headerlink, 295 | h4:hover > a.headerlink, 296 | h5:hover > a.headerlink, 297 | h6:hover > a.headerlink, 298 | dt:hover > a.headerlink { 299 | visibility: visible; 300 | } 301 | 302 | div.body p.caption { 303 | text-align: inherit; 304 | } 305 | 306 | div.body td { 307 | text-align: left; 308 | } 309 | 310 | .field-list ul { 311 | padding-left: 1em; 312 | } 313 | 314 | .first { 315 | margin-top: 0 !important; 316 | } 317 | 318 | p.rubric { 319 | margin-top: 30px; 320 | font-weight: bold; 321 | } 322 | 323 | /* -- sidebars -------------------------------------------------------------- */ 324 | 325 | /*div.sidebar { 326 | margin: 0 0 0.5em 1em; 327 | border: 1px solid #ddb; 328 | padding: 7px 7px 0 7px; 329 | background-color: #ffe; 330 | width: 40%; 331 | float: right; 332 | } 333 | 334 | p.sidebar-title { 335 | font-weight: bold; 336 | } 337 | */ 338 | /* -- topics ---------------------------------------------------------------- */ 339 | 340 | div.topic { 341 | border: 1px solid #ccc; 342 | padding: 7px 7px 0 7px; 343 | margin: 10px 0 10px 0; 344 | } 345 | 346 | p.topic-title { 347 | font-size: 1.1em; 348 | font-weight: bold; 349 | margin-top: 10px; 350 | } 351 | 352 | /* -- admonitions ----------------------------------------------------------- */ 353 | .admonition { 354 | border: 1px solid #a1a5a9; 355 | background-color: #f7f7f7; 356 | margin: 20px; 357 | padding: 0px 8px 7px 9px; 358 | text-align: left; 359 | } 360 | .warning { 361 | background-color:#E8E8E8; 362 | border:1px solid #111111; 363 | margin:30px; 364 | } 365 | .admonition p { 366 | font: 12px 'Lucida Grande', Geneva, Helvetica, Arial, sans-serif; 367 | margin-top: 7px; 368 | margin-bottom: 0px; 369 | } 370 | 371 | div.admonition dt { 372 | font-weight: bold; 373 | } 374 | 375 | div.admonition dl { 376 | margin-bottom: 0; 377 | } 378 | 379 | p.admonition-title { 380 | margin: 0px 10px 5px 0px; 381 | font-weight: bold; 382 | padding-top: 3px; 383 | } 384 | 385 | div.body p.centered { 386 | text-align: center; 387 | margin-top: 25px; 388 | } 389 | 390 | /* -- tables ---------------------------------------------------------------- */ 391 | 392 | table.docutils { 393 | border-collapse: collapse; 394 | border-top: 1px solid #919699; 395 | border-left: 1px solid #919699; 396 | border-right: 1px solid #919699; 397 | font-size:12px; 398 | padding:8px; 399 | text-align:left; 400 | vertical-align:top; 401 | } 402 | 403 | table.docutils td, table.docutils th { 404 | padding: 8px; 405 | font-size: 12px; 406 | text-align: left; 407 | vertical-align: top; 408 | border-bottom: 1px solid #919699; 409 | } 410 | 411 | table.docutils th { 412 | font-weight: bold; 413 | } 414 | /* This alternates colors in up to six table rows (light blue for odd, white for even)*/ 415 | .docutils tr { 416 | background: #F0F5F9; 417 | } 418 | 419 | .docutils tr + tr { 420 | background: #FFFFFF; 421 | } 422 | 423 | .docutils tr + tr + tr { 424 | background: #F0F5F9; 425 | } 426 | 427 | .docutils tr + tr + tr + tr { 428 | background: #FFFFFF; 429 | } 430 | 431 | .docutils tr + tr + tr +tr + tr { 432 | background: #F0F5F9; 433 | } 434 | 435 | .docutils tr + tr + tr + tr + tr + tr { 436 | background: #FFFFFF; 437 | } 438 | 439 | .docutils tr + tr + tr + tr + tr + tr + tr { 440 | background: #F0F5F9; 441 | } 442 | 443 | table.footnote td, table.footnote th { 444 | border: 0 !important; 445 | } 446 | 447 | th { 448 | text-align: left; 449 | padding-right: 5px; 450 | } 451 | 452 | /* -- other body styles ----------------------------------------------------- */ 453 | 454 | dl { 455 | margin-bottom: 15px; 456 | } 457 | 458 | dd p { 459 | margin-top: 0px; 460 | font-size: 12px; 461 | } 462 | 463 | dd ul, dd table { 464 | margin-bottom: 10px; 465 | } 466 | 467 | dd { 468 | margin-top: 3px; 469 | margin-bottom: 10px; 470 | margin-left: 30px; 471 | font-size: 12px; 472 | } 473 | 474 | dt:target, .highlight { 475 | background-color: #fbe54e; 476 | } 477 | 478 | dl.glossary dt { 479 | font-weight: bold; 480 | font-size: 0.8em; 481 | } 482 | 483 | dl.glossary dd { 484 | font-size:12px; 485 | } 486 | .field-list ul { 487 | vertical-align: top; 488 | margin: 0; 489 | padding-bottom: 0; 490 | list-style: none inside; 491 | } 492 | 493 | .field-list ul li { 494 | margin-top: 0; 495 | } 496 | 497 | .field-list p { 498 | margin: 0; 499 | } 500 | 501 | .refcount { 502 | color: #060; 503 | } 504 | 505 | .optional { 506 | font-size: 1.3em; 507 | } 508 | 509 | .versionmodified { 510 | font-style: italic; 511 | } 512 | 513 | .system-message { 514 | background-color: #fda; 515 | padding: 5px; 516 | border: 3px solid red; 517 | } 518 | 519 | .footnote:target { 520 | background-color: #ffa 521 | } 522 | 523 | /* -- code displays --------------------------------------------------------- */ 524 | 525 | pre { 526 | overflow: auto; 527 | background-color:#F1F5F9; 528 | border:1px solid #C9D1D7; 529 | border-spacing:0; 530 | font-family:"Bitstream Vera Sans Mono",Monaco,"Lucida Console",Courier,Consolas,monospace; 531 | font-size:11px; 532 | padding: 10px; 533 | } 534 | 535 | td.linenos pre { 536 | padding: 5px 0px; 537 | border: 0; 538 | background-color: transparent; 539 | color: #aaa; 540 | } 541 | 542 | table.highlighttable { 543 | margin-left: 0.5em; 544 | } 545 | 546 | table.highlighttable tr { 547 | background-color: transparent; 548 | } 549 | 550 | table.highlighttable td { 551 | padding: 0 0.5em 0 0.5em; 552 | } 553 | 554 | tt { 555 | font-family:"Bitstream Vera Sans Mono",Monaco,"Lucida Console",Courier,Consolas,monospace; 556 | 557 | } 558 | 559 | tt.descname { 560 | background-color: transparent; 561 | font-weight: bold; 562 | font-size: 1em; 563 | } 564 | 565 | tt.descclassname { 566 | background-color: transparent; 567 | } 568 | 569 | tt.xref, a tt { 570 | background-color: transparent; 571 | font-weight: bold; 572 | } 573 | 574 | h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { 575 | background-color: transparent; 576 | } 577 | 578 | /* -- math display ---------------------------------------------------------- */ 579 | 580 | img.math { 581 | vertical-align: middle; 582 | } 583 | 584 | div.body div.math p { 585 | text-align: center; 586 | } 587 | 588 | span.eqno { 589 | float: right; 590 | } 591 | 592 | /* -- printout stylesheet --------------------------------------------------- */ 593 | 594 | @media print { 595 | div.document, 596 | div.documentwrapper, 597 | div.bodywrapper { 598 | margin: 0; 599 | width: 100%; 600 | } 601 | 602 | div.sphinxsidebar, 603 | div.related, 604 | div.footer, 605 | #top-link { 606 | display: none; 607 | } 608 | } 609 | 610 | body { 611 | font-family:'Lucida Grande',Geneva,Helvetica,Arial,sans-serif; 612 | } 613 | 614 | dl.class dt { 615 | padding: 3px; 616 | /* border-top: 2px solid #999;*/ 617 | } 618 | 619 | em.property { 620 | font-style: normal; 621 | } 622 | 623 | dl.class dd p { 624 | margin-top: 6px; 625 | } 626 | 627 | dl.class dd dl.exception dt { 628 | padding: 3px; 629 | background-color: #FFD6D6; 630 | border-top: none; 631 | } 632 | 633 | dl.class dd dl.method dt { 634 | padding: 3px; 635 | background-color: #e9e9e9; 636 | border-top: none; 637 | 638 | } 639 | 640 | dl.function dt { 641 | padding: 3px; 642 | border-top: 2px solid #999; 643 | } 644 | 645 | ul { 646 | list-style-image:none; 647 | list-style-position:outside; 648 | list-style-type:square; 649 | margin:0 0 0 30px; 650 | padding:0 0 12px 6px; 651 | } 652 | #docstitle { 653 | height: 36px; 654 | background-image: url(header_sm_mid.png); 655 | left: 0; 656 | top: 0; 657 | position: absolute; 658 | width: 100%; 659 | } 660 | #docstitle p { 661 | padding:7px 0 0 45px; 662 | margin: 0; 663 | color: white; 664 | text-shadow:0 1px 0 #787878; 665 | background: transparent url(documentation.png) no-repeat scroll 10px 3px; 666 | height: 36px; 667 | font-size: 15px; 668 | } 669 | #header { 670 | height:45px; 671 | left:0; 672 | position:absolute; 673 | right:0; 674 | top:36px; 675 | z-index:900; 676 | } 677 | 678 | #header h1 { 679 | font-size:10pt; 680 | margin:0; 681 | padding:5px 0 0 10px; 682 | text-shadow:0 1px 0 #D5D5D5; 683 | white-space:nowrap; 684 | } 685 | 686 | h1 { 687 | -x-system-font:none; 688 | color:#000000; 689 | font-family:'Lucida Grande',Geneva,Helvetica,Arial,sans-serif; 690 | font-size:30px; 691 | font-size-adjust:none; 692 | font-stretch:normal; 693 | font-style:normal; 694 | font-variant:normal; 695 | font-weight:bold; 696 | line-height:normal; 697 | margin-bottom:25px; 698 | margin-top:1em; 699 | } 700 | 701 | .footer { 702 | border-top:1px solid #DDDDDD; 703 | clear:both; 704 | padding-top:9px; 705 | width:100%; 706 | font-size:10px; 707 | } 708 | 709 | p { 710 | -x-system-font:none; 711 | font-family:'Lucida Grande',Geneva,Helvetica,Arial,sans-serif; 712 | font-size:12px; 713 | font-size-adjust:none; 714 | font-stretch:normal; 715 | font-style:normal; 716 | font-variant:normal; 717 | font-weight:normal; 718 | line-height:normal; 719 | margin-bottom:10px; 720 | margin-top:0; 721 | } 722 | 723 | h2 { 724 | border-bottom:1px solid #919699; 725 | color:#000000; 726 | font-size:24px; 727 | margin-top:2.5em; 728 | padding-bottom:2px; 729 | } 730 | 731 | a:link:hover { 732 | color:#093D92; 733 | text-decoration:underline; 734 | } 735 | 736 | a:link { 737 | color:#093D92; 738 | text-decoration:none; 739 | } 740 | 741 | 742 | ol { 743 | list-style-position:outside; 744 | list-style-type:decimal; 745 | margin:0 0 0 30px; 746 | padding:0 0 12px 6px; 747 | } 748 | li { 749 | margin-top:7px; 750 | font-family:'Lucida Grande',Geneva,Helvetica,Arial,sans-serif; 751 | font-size:12px; 752 | font-size-adjust:none; 753 | font-stretch:normal; 754 | font-style:normal; 755 | font-variant:normal; 756 | font-weight:normal; 757 | line-height:normal; 758 | } 759 | li > p { 760 | display:inline; 761 | } 762 | li p { 763 | margin-top:8px; 764 | } -------------------------------------------------------------------------------- /_static/documentation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coordt/djangopatterns/f17382ec04dba98149d991b1858e1116c7955b07/_static/documentation.png -------------------------------------------------------------------------------- /_static/header_sm_mid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coordt/djangopatterns/f17382ec04dba98149d991b1858e1116c7955b07/_static/header_sm_mid.png -------------------------------------------------------------------------------- /_static/scrn1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coordt/djangopatterns/f17382ec04dba98149d991b1858e1116c7955b07/_static/scrn1.png -------------------------------------------------------------------------------- /_static/scrn2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coordt/djangopatterns/f17382ec04dba98149d991b1858e1116c7955b07/_static/scrn2.png -------------------------------------------------------------------------------- /_static/searchfield_leftcap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coordt/djangopatterns/f17382ec04dba98149d991b1858e1116c7955b07/_static/searchfield_leftcap.png -------------------------------------------------------------------------------- /_static/searchfield_repeat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coordt/djangopatterns/f17382ec04dba98149d991b1858e1116c7955b07/_static/searchfield_repeat.png -------------------------------------------------------------------------------- /_static/searchfield_rightcap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coordt/djangopatterns/f17382ec04dba98149d991b1858e1116c7955b07/_static/searchfield_rightcap.png -------------------------------------------------------------------------------- /_static/title_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coordt/djangopatterns/f17382ec04dba98149d991b1858e1116c7955b07/_static/title_background.png -------------------------------------------------------------------------------- /_static/toc.js: -------------------------------------------------------------------------------- 1 | var TOC = { 2 | load: function () { 3 | $('#toc_button').click(TOC.toggle); 4 | }, 5 | 6 | toggle: function () { 7 | if ($('#sphinxsidebar').toggle().is(':hidden')) { 8 | $('div.document').css('left', "0px"); 9 | $('toc_button').removeClass("open"); 10 | } else { 11 | $('div.document').css('left', "230px"); 12 | $('#toc_button').addClass("open"); 13 | } 14 | return $('#sphinxsidebar'); 15 | } 16 | }; 17 | 18 | $(document).ready(function () { 19 | TOC.load(); 20 | }); -------------------------------------------------------------------------------- /_static/triangle_closed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coordt/djangopatterns/f17382ec04dba98149d991b1858e1116c7955b07/_static/triangle_closed.png -------------------------------------------------------------------------------- /_static/triangle_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coordt/djangopatterns/f17382ec04dba98149d991b1858e1116c7955b07/_static/triangle_left.png -------------------------------------------------------------------------------- /_static/triangle_open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coordt/djangopatterns/f17382ec04dba98149d991b1858e1116c7955b07/_static/triangle_open.png -------------------------------------------------------------------------------- /_templates/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "basic/layout.html" %} 2 | {%- block doctype -%} 3 | 5 | {%- endblock %} 6 | {%- set reldelim1 = reldelim1 is not defined and ' »' or reldelim1 %} 7 | {%- set reldelim2 = reldelim2 is not defined and ' |' or reldelim2 %} 8 | {%- block linktags %} 9 | {%- if hasdoc('about') %} 10 | 11 | {%- endif %} 12 | {%- if hasdoc('genindex') %} 13 | 14 | {%- endif %} 15 | {%- if hasdoc('search') %} 16 | 17 | {%- endif %} 18 | {%- if hasdoc('copyright') %} 19 | 20 | {%- endif %} 21 | 22 | {%- if parents %} 23 | 24 | {%- endif %} 25 | {%- if next %} 26 | 27 | {%- endif %} 28 | {%- if prev %} 29 | 30 | {%- endif %} 31 | {%- endblock %} 32 | {%- block extrahead %} {% endblock %} 33 | {%- block header %}{% endblock %} 34 | {%- block relbar1 %} 35 |
36 |

{{docstitle}}

37 |
38 | 49 | {% endblock %} 50 | 51 | {%- block sidebar1 %} 52 | {%- if not embedded %}{% if not theme_nosidebar|tobool %} 53 |
54 |
55 | {%- block sidebarlogo %} 56 | {%- if logo %} 57 | 60 | {%- endif %} 61 | {%- endblock %} 62 | {%- block sidebartoc %} 63 | 64 | {{ toctree() }} 65 | {%- endblock %} 66 | {%- block sidebarrel %} 67 | {%- endblock %} 68 | {%- block sidebarsourcelink %} 69 | {%- if show_source and has_source and sourcename %} 70 |

{{ _('This Page') }}

71 | 75 | {%- endif %} 76 | {%- endblock %} 77 | {%- if customsidebar %} 78 | {% include customsidebar %} 79 | {%- endif %} 80 | {%- block sidebarsearch %} 81 | {%- if pagename != "search" %} 82 | 98 | 99 | {%- endif %} 100 | {%- endblock %} 101 |
102 |
103 | {%- endif %}{% endif %} 104 | 105 | {% endblock %} 106 | {%- block document %} 107 |
108 | {%- if not embedded %}{% if not theme_nosidebar|tobool %} 109 |
110 | {%- endif %}{% endif %} 111 |
112 | {% block body %} {% endblock %} 113 |
114 | {%- if not embedded %}{% if not theme_nosidebar|tobool %} 115 |
116 | {%- endif %}{% endif %} 117 |
118 | 135 | {%- endblock %} 136 | {%- block sidebar2 %}{% endblock %} 137 | {%- block relbar2 %}{% endblock %} 138 | {%- block footer %} 139 | 146 | 147 | {%- endblock %} 148 | -------------------------------------------------------------------------------- /app_construction/index.rst: -------------------------------------------------------------------------------- 1 | ======================== 2 | Application Construction 3 | ======================== 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | :glob: 8 | 9 | version_reporting 10 | views_as_a_package 11 | -------------------------------------------------------------------------------- /app_construction/version_reporting.py: -------------------------------------------------------------------------------- 1 | __version_info__ = { 2 | 'major': 0, 3 | 'minor': 1, 4 | 'micro': 0, 5 | 'releaselevel': 'alpha', 6 | 'serial': 1 7 | } 8 | 9 | def get_version(short=False): 10 | assert __version_info__['releaselevel'] in ('alpha', 'beta', 'final') 11 | vers = ["%(major)i.%(minor)i" % __version_info__, ] 12 | if __version_info__['micro']: 13 | vers.append(".%(micro)i" % __version_info__) 14 | if __version_info__['releaselevel'] != 'final' and not short: 15 | vers.append('%s%i' % (__version_info__['releaselevel'][0], __version_info__['serial'])) 16 | return ''.join(vers) 17 | 18 | __version__ = get_version() 19 | -------------------------------------------------------------------------------- /app_construction/version_reporting.rst: -------------------------------------------------------------------------------- 1 | :Author: Corey Oordt 2 | 3 | ================= 4 | Version Reporting 5 | ================= 6 | 7 | **Contributors:** Corey Oordt, Stefan Foulis 8 | 9 | .. contents:: 10 | :local: 11 | 12 | 13 | What problem does this pattern solve? 14 | ===================================== 15 | 16 | It provides a flexible method of recording and reporting your application's version. 17 | 18 | When to use it 19 | ============== 20 | 21 | You should use it with any application or project that has specific releases. 22 | 23 | Why should I use it? 24 | ==================== 25 | 26 | 1. It is easy to see which version is currently installed somewhere. 27 | 28 | 2. It is easy to import the version into other places, like documentation or packaging. 29 | 30 | 3. It is easy for others to test the version of your code to better handle backwards-compatibility. 31 | 32 | 33 | Implementation 34 | ============== 35 | 36 | `PEP 386 `_ defines the standard way to specify versions within the Python community. The most common scenario is the ``Major.Minor.Micro`` with a possible ``alpha/beta/release candidate`` suffix. 37 | 38 | Examples:: 39 | 40 | 1.0 41 | 0.6.1 42 | 2.1.1b1 43 | 0.3rc2 44 | 45 | When recording your version number you should: 46 | 47 | * Put it within the code, so it's accessible after the package is installed 48 | 49 | * Easily retrieve all the individual parts of the version 50 | 51 | * Record the individual version parts as integers (where appropriate) for easy comparison 52 | 53 | * Have a properly formatted string version available 54 | 55 | Putting the version information in your application's ``__init__.py`` is a great, out-of-the-way place. 56 | 57 | Here is an example that conforms to PEP 386: 58 | 59 | .. rst-class:: caption 60 | 61 | **coolapp/__index__.py** 62 | 63 | .. literalinclude:: version_reporting.py 64 | :linenos: 65 | 66 | This sets up a ``__version_info__`` dictionary to hold the version fields, a ``get_version()`` function to format the ``__version_info__`` into a string, and ``__version__``\ , which is the formatted string version. It is similar to Django's method: 67 | 68 | .. rst-class:: caption 69 | 70 | **django/__init__.py** 71 | 72 | .. literalinclude:: version_reporting_django.py 73 | :linenos: 74 | 75 | 76 | How to use it 77 | ============= 78 | 79 | Inside your setup.py file 80 | ------------------------- 81 | 82 | The ``setup.py`` file needs a version for your application and you can import it directly from your application, ass seen in this example taken from `django-app-skeleton `_\ 's ``setup.py`` file: 83 | 84 | .. rst-class:: caption 85 | 86 | **django-app-skeleton/skel/setup.py** 87 | 88 | .. literalinclude:: version_reporting_appskel.py 89 | :linenos: 90 | 91 | 92 | Inside your Sphinx documentation's conf.py 93 | ------------------------------------------ 94 | 95 | Sphinx also likes to have the version of your application in the formatted documentation. Since the ``conf.py`` configuration file is just Python, you can import your version. 96 | 97 | .. rst-class:: caption 98 | 99 | **coolapp/docs/conf.py** 100 | 101 | .. literalinclude:: version_reporting_sphinx.py 102 | :linenos: 103 | -------------------------------------------------------------------------------- /app_construction/version_reporting_appskel.py: -------------------------------------------------------------------------------- 1 | # ... other stuff above 2 | 3 | setup( 4 | name = "$$$$APP_NAME$$$$", 5 | version = __import__('$$$$PKG_NAME$$$$').get_version().replace(' ', '-'), 6 | url = '$$$$URL$$$$', 7 | author = '$$$$AUTHOR$$$$', 8 | author_email = '$$$$AUTHOR_EMAIL$$$$', 9 | description = DESC, 10 | long_description = get_readme(), 11 | packages = find_packages(), 12 | include_package_data = True, 13 | install_requires = read_file('requirements.txt'), 14 | classifiers = [ 15 | 'License :: OSI Approved :: Apache Software License', 16 | 'Framework :: Django', 17 | ], 18 | ) 19 | -------------------------------------------------------------------------------- /app_construction/version_reporting_django.py: -------------------------------------------------------------------------------- 1 | VERSION = (1, 4, 0, 'alpha', 0) 2 | 3 | def get_version(): 4 | version = '%s.%s' % (VERSION[0], VERSION[1]) 5 | if VERSION[2]: 6 | version = '%s.%s' % (version, VERSION[2]) 7 | if VERSION[3:] == ('alpha', 0): 8 | version = '%s pre-alpha' % version 9 | else: 10 | if VERSION[3] != 'final': 11 | version = '%s %s %s' % (version, VERSION[3], VERSION[4]) 12 | from django.utils.version import get_svn_revision 13 | svn_rev = get_svn_revision() 14 | if svn_rev != u'SVN-unknown': 15 | version = "%s %s" % (version, svn_rev) 16 | return version 17 | -------------------------------------------------------------------------------- /app_construction/version_reporting_sphinx.py: -------------------------------------------------------------------------------- 1 | sys.path.append(os.path.abspath('..')) 2 | os.environ['DJANGO_SETTINGS_MODULE'] = 'example.settings' 3 | 4 | import coolapp 5 | 6 | # The version info for the project you're documenting, acts as replacement for 7 | # |version| and |release|, also used in various other places throughout the 8 | # built documents. 9 | # 10 | # The short X.Y version. 11 | version = coolapp.get_version(short=True) 12 | # The full version, including alpha/beta/rc tags. 13 | release = coolapp.get_version() -------------------------------------------------------------------------------- /app_construction/views_as_a_package.rst: -------------------------------------------------------------------------------- 1 | :Author: Corey Oordt 2 | 3 | ================== 4 | Views as a package 5 | ================== 6 | 7 | **Contributors:** Corey Oordt 8 | 9 | .. contents:: 10 | :local: 11 | 12 | What problem does this pattern solve? 13 | ===================================== 14 | 15 | Code in ``views.py`` has become unmanageable. 16 | 17 | When to use it 18 | ============== 19 | 20 | You want to refactor your views into several files. 21 | 22 | Why should I use it? 23 | ==================== 24 | 25 | This pattern allows for refactoring the view code into several files without effecting the import process in other files. In other words ``from coolapp.views import foo`` still works. 26 | 27 | Implementation 28 | ============== 29 | 30 | .. note:: 31 | 32 | When refactoring your code into multiple files, look deeper and see if there are better ways to accomplish the tasks, such as using generic views. 33 | 34 | Python ``import``\ s briefly 35 | ---------------------------- 36 | 37 | This pattern takes advantage of the way that Python handles importing items into the local namespace. The statement ``from foo import views`` will work with code organized as: 38 | 39 | .. rst-class:: caption 40 | 41 | **Example 1** 42 | 43 | :: 44 | 45 | foo 46 | ├── __init__.py 47 | └── views.py 48 | 49 | as well as: 50 | 51 | .. rst-class:: caption 52 | 53 | **Example 2** 54 | 55 | :: 56 | 57 | foo 58 | ├── __init__.py 59 | └── views 60 | └── __init__.py 61 | 62 | In the case of Example 2, the contents of ``foo/views/__init__.py`` is executed. The ``__init__.py`` file is going to be important in the switch from a `module `_ (one file) to a `package `_ (directory with ``__init__.py``\ ). 63 | 64 | First rename ``views.py`` to something like ``old_views.py`` to prevent name confusion. Second create the ``views`` directory and add an ``__init__.py`` file. Then refactor the ``old_views.py`` into two or more files. See Example 3. 65 | 66 | .. rst-class:: caption 67 | 68 | **Example 3** 69 | 70 | :: 71 | 72 | foo 73 | ├── __init__.py 74 | ├── old_views.py 75 | └── views 76 | ├── __init__.py 77 | ├── bar.py 78 | └── baz.py 79 | 80 | .. note:: 81 | 82 | When refactoring your views, you will probably need to change imports from other modules in your app, such as models. The statement ``from models import Foo`` will no longer work since the ``models.py`` file is not in the same directory. 83 | 84 | Instead, you will need to use a full path import: ``from foo.models import Foo``\ . 85 | 86 | Now, to make imports such as ``from views import bar_detail_view`` work, we need to add a couple of lines to ``views/__init__.py`` 87 | 88 | .. rst-class:: caption 89 | 90 | **views/__init__.py** 91 | 92 | .. code-block:: python 93 | :linenos: 94 | 95 | from bar import * 96 | from baz import * 97 | 98 | These statements import all the contents of ``views.bar`` and ``views.baz`` into ``views``\ . You can limit what is imported with ``*`` defining a list named ``__all__`` (see `Importing * from a Package `_\ ) within the module. 99 | 100 | ``__all__`` it is taken to be the list of names that should be imported when ``from module import *`` is encountered. Django uses this often, such as in ``django.conf.urls.defaults``\ . 101 | 102 | .. attention:: 103 | 104 | It is up to you to maintain the ``__all__`` list as you update the file. 105 | 106 | 107 | Sources 108 | ======= 109 | 110 | http://stackoverflow.com/questions/2675722/django-breaking-up-views 111 | 112 | 113 | -------------------------------------------------------------------------------- /conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # app documentation build configuration file, created by 4 | # sphinx-quickstart on Wed Oct 21 13:18:22 2009. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | sys.path.append(os.path.abspath('..')) 20 | sys.path.append(os.path.abspath('./_plugins')) 21 | 22 | os.environ['DJANGO_SETTINGS_MODULE'] = 'example.settings' 23 | 24 | # -- General configuration ----------------------------------------------------- 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be extensions 27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 28 | extensions = [] 29 | 30 | # Add any paths that contain templates here, relative to this directory. 31 | templates_path = ['_templates'] 32 | 33 | # The suffix of source filenames. 34 | source_suffix = '.rst' 35 | 36 | # The encoding of source files. 37 | #source_encoding = 'utf-8' 38 | 39 | # The master toctree document. 40 | master_doc = 'index' 41 | 42 | # General information about the project. 43 | project = u'Django Patterns' 44 | copyright = u'2011, Corey Oordt' 45 | 46 | # The version info for the project you're documenting, acts as replacement for 47 | # |version| and |release|, also used in various other places throughout the 48 | # built documents. 49 | # 50 | # The short X.Y version. 51 | version = '1.0' 52 | # The full version, including alpha/beta/rc tags. 53 | release = '1.0' 54 | 55 | # The language for content autogenerated by Sphinx. Refer to documentation 56 | # for a list of supported languages. 57 | #language = None 58 | 59 | # There are two options for replacing |today|: either, you set today to some 60 | # non-false value, then it is used: 61 | #today = '' 62 | # Else, today_fmt is used as the format for a strftime call. 63 | #today_fmt = '%B %d, %Y' 64 | 65 | # List of documents that shouldn't be included in the build. 66 | #unused_docs = [] 67 | 68 | # List of directories, relative to source directory, that shouldn't be searched 69 | # for source files. 70 | exclude_trees = ['_build'] 71 | 72 | # The reST default role (used for this markup: `text`) to use for all documents. 73 | #default_role = None 74 | 75 | # If true, '()' will be appended to :func: etc. cross-reference text. 76 | #add_function_parentheses = True 77 | 78 | # If true, the current module name will be prepended to all description 79 | # unit titles (such as .. function::). 80 | #add_module_names = True 81 | 82 | # If true, sectionauthor and moduleauthor directives will be shown in the 83 | # output. They are ignored by default. 84 | #show_authors = False 85 | 86 | # The name of the Pygments (syntax highlighting) style to use. 87 | pygments_style = 'sphinx' 88 | 89 | # A list of ignored prefixes for module index sorting. 90 | #modindex_common_prefix = [] 91 | 92 | 93 | # -- Options for HTML output --------------------------------------------------- 94 | 95 | # The theme to use for HTML and HTML Help pages. Major themes that come with 96 | # Sphinx are currently 'default' and 'sphinxdoc'. 97 | html_theme = 'default' 98 | 99 | # Theme options are theme-specific and customize the look and feel of a theme 100 | # further. For a list of options available for each theme, see the 101 | # documentation. 102 | #html_theme_options = {} 103 | 104 | # Add any paths that contain custom themes here, relative to this directory. 105 | #html_theme_path = [] 106 | 107 | #html_translator_class = 'writers.htmlid.HTMLIdTranslator' 108 | 109 | # The name for this set of Sphinx documents. If None, it defaults to 110 | # " v documentation". 111 | html_title = 'Django Patterns' 112 | 113 | # A shorter title for the navigation bar. Default is the same as html_title. 114 | #html_short_title = None 115 | 116 | # The name of an image file (relative to this directory) to place at the top 117 | # of the sidebar. 118 | #html_logo = None 119 | 120 | # The name of an image file (within the static path) to use as favicon of the 121 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 122 | # pixels large. 123 | #html_favicon = None 124 | 125 | # Add any paths that contain custom static files (such as style sheets) here, 126 | # relative to this directory. They are copied after the builtin static files, 127 | # so a file named "default.css" will overwrite the builtin "default.css". 128 | html_static_path = ['_static'] 129 | 130 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 131 | # using the given strftime format. 132 | #html_last_updated_fmt = '%b %d, %Y' 133 | 134 | # If true, SmartyPants will be used to convert quotes and dashes to 135 | # typographically correct entities. 136 | #html_use_smartypants = True 137 | 138 | # Custom sidebar templates, maps document names to template names. 139 | #html_sidebars = {} 140 | 141 | # Additional templates that should be rendered to pages, maps page names to 142 | # template names. 143 | #html_additional_pages = {} 144 | 145 | # If false, no module index is generated. 146 | #html_use_modindex = True 147 | 148 | # If false, no index is generated. 149 | #html_use_index = True 150 | 151 | # If true, the index is split into individual pages for each letter. 152 | #html_split_index = False 153 | 154 | # If true, links to the reST sources are added to the pages. 155 | html_show_sourcelink = False 156 | 157 | # If true, an OpenSearch description file will be output, and all pages will 158 | # contain a tag referring to it. The value of this option must be the 159 | # base URL from which the finished HTML is served. 160 | #html_use_opensearch = '' 161 | 162 | # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). 163 | #html_file_suffix = '' 164 | 165 | html_compact_lists = True 166 | 167 | # Output file base name for HTML help builder. 168 | htmlhelp_basename = 'djangopatternsdoc' 169 | 170 | 171 | # -- Options for LaTeX output -------------------------------------------------- 172 | 173 | # The paper size ('letter' or 'a4'). 174 | #latex_paper_size = 'letter' 175 | 176 | # The font size ('10pt', '11pt' or '12pt'). 177 | #latex_font_size = '10pt' 178 | 179 | # Grouping the document tree into LaTeX files. List of tuples 180 | # (source start file, target name, title, author, documentclass [howto/manual]). 181 | latex_documents = [ 182 | ('index', 'djangopatterns.tex', u'Django Patterns', 183 | u'Corey Oordt', 'manual'), 184 | ] 185 | 186 | # The name of an image file (relative to this directory) to place at the top of 187 | # the title page. 188 | #latex_logo = None 189 | 190 | # For "manual" documents, if this is true, then toplevel headings are parts, 191 | # not chapters. 192 | #latex_use_parts = False 193 | 194 | # Additional stuff for the LaTeX preamble. 195 | #latex_preamble = '' 196 | 197 | # Documents to append as an appendix to all manuals. 198 | #latex_appendices = [] 199 | 200 | # If false, no module index is generated. 201 | #latex_use_modindex = True 202 | -------------------------------------------------------------------------------- /configuration/autodiscovery.rst: -------------------------------------------------------------------------------- 1 | ============= 2 | Autodiscovery 3 | ============= 4 | 5 | .. warning:: 6 | This is just a stub document. It will be fleshed out more. 7 | 8 | What problem does this pattern solve? 9 | ===================================== 10 | 11 | An app provides a service that requires complex configuration or customization by other apps to properly use it. 12 | 13 | When to use it 14 | ============== 15 | 16 | Why should I use it? 17 | ==================== 18 | 19 | Implementation 20 | ============== 21 | 22 | For each app, we need to look for an specific module inside that app's package. We can't use os.path here -- recall that modules may be imported different ways (think zip files) -- so we need to get the app's __path__ and look for the module on that path. 23 | 24 | Step 1: find out the app's ``__path__`` Import errors here will (and should) bubble up, but a missing __path__ (which is legal, but weird) fails silently -- apps that do weird things with __path__ might need to roll their own index registration. 25 | 26 | Step 2: use imp.find_module to find the app's search_indexes.py. For some reason imp.find_module raises ImportError if the app can't be found but doesn't actually try to import the module. So skip this app if its search_indexes.py doesn't exist 27 | 28 | Step 3: import the app's search_index file. If this has errors we want them to bubble up. 29 | 30 | .. rst-class:: caption 31 | 32 | **Django Snippet 2404: Generic Autodiscovery** 33 | 34 | .. literalinclude:: autodiscovery1.py 35 | :linenos: 36 | 37 | 38 | How to use it 39 | ============= 40 | 41 | Sources 42 | ======= 43 | 44 | Useful Links 45 | ============ 46 | 47 | * `Generic Autodiscovery `_ 48 | * `Looking at registration patterns in Django `_ 49 | * `django-config-wizard autodiscover.py `_ 50 | 51 | Where is it used? 52 | ================= 53 | 54 | -------------------------------------------------------------------------------- /configuration/autodiscovery1.py: -------------------------------------------------------------------------------- 1 | def generic_autodiscover(module_name): 2 | """ 3 | Usage: 4 | generic_autodiscover('commands') <-- find all commands.py and load 'em 5 | """ 6 | import imp 7 | from django.conf import settings 8 | 9 | for app in settings.INSTALLED_APPS: 10 | try: 11 | import_module(app) 12 | app_path = sys.modules[app].__path__ 13 | except AttributeError: 14 | continue 15 | try: 16 | imp.find_module(module_name, app_path) 17 | except ImportError: 18 | continue 19 | import_module('%s.%s' % (app, module_name)) 20 | app_path = sys.modules['%s.%s' % (app, module_name)] 21 | -------------------------------------------------------------------------------- /configuration/configure_app.rst: -------------------------------------------------------------------------------- 1 | :Author: Corey Oordt 2 | 3 | ========================= 4 | Configurable Applications 5 | ========================= 6 | 7 | **Contributors:** Corey Oordt 8 | 9 | .. contents:: 10 | :local: 11 | 12 | What problem does this pattern solve? 13 | ===================================== 14 | 15 | You want to allow configuration of your app without having to modify its code. You may also want to provide reasonable defaults that users can override. 16 | 17 | When to use it 18 | ============== 19 | 20 | Use this whenever project- or implementation-specific information is required at runtime or there are obvious choices or options for the application. 21 | 22 | Good examples: 23 | 24 | * API key 25 | * Debugging flags 26 | * Location(s) to look for files 27 | * Which features should be used (feature flags) 28 | 29 | Implementation 30 | ============== 31 | 32 | Create a ``settings.py`` file in your application 33 | 34 | :: 35 | 36 | coolapp 37 | ├── __init__.py 38 | ├── admin.py 39 | ├── models.py 40 | ├── settings.py 41 | ├── tests.py 42 | └── views.py 43 | 44 | Basic Pattern with one setting 45 | ------------------------------ 46 | 47 | Inside the ``settings.py`` file, you will import Django's settings and use ``getattr()`` to retrieve the value, or use a default value. There are several parts to this: 48 | 49 | * **Internal Name:** The name you will use within your application 50 | * **Namespaced Name:** The name used in a project's ``settings.py``\ , with a prefix to avoid collisions. 51 | * **Default Value:** The value for this setting if the namespaced name is not in the project's ``settings.py``\ . 52 | 53 | .. rst-class:: caption 54 | 55 | **coolapp/settings.py** 56 | 57 | .. literalinclude:: configure_app1.py 58 | :linenos: 59 | 60 | Here, ``COOL_WORD`` is the *internal name,* ``COOLAPP_COOL_WORD`` is the *namespaced name,* and ``'cool'`` is the *default value.* 61 | 62 | Requiring a value for a setting 63 | ------------------------------- 64 | 65 | For something like an API key, you will want to draw attention if it's empty. You will do this by raising an ``ImproperlyConfigured`` exception. 66 | 67 | .. rst-class:: caption 68 | 69 | **coolapp/settings.py** 70 | 71 | .. literalinclude:: configure_app2.py 72 | :linenos: 73 | 74 | Many settings for your application 75 | ---------------------------------- 76 | 77 | Django has internally began using dictionaries for groups of settings, such as ``DATABASES``\ . Django debug toolbar, for example, uses one dictionary to store all its configurations. 78 | 79 | .. rst-class:: caption 80 | 81 | **debug_toolbar/toolbar/loader.py** 82 | 83 | .. literalinclude:: configure_app3.py 84 | :linenos: 85 | 86 | It creates a standard set of configurations in line 13, and then uses the dictionaries ``update()`` method in line 18 to add or override current key/values. 87 | 88 | 89 | Settings with nested dictionaries 90 | --------------------------------- 91 | 92 | If your settings dictionary has a dictionary as a value, you need to take a slightly different approach. ``dict.update()`` will completely overwrite the nested dictionaries, not merge them. To make things trickier, ``dict.update()`` doesn't return a value, so 93 | 94 | .. code-block:: python 95 | :linenos: 96 | 97 | DEFAULT_SETTINGS.update(getattr(settings, 'FOOBAR_SETTINGS', {})) 98 | DEFAULT_SETTINGS['FOO'] = DEFAULT_FOO.update(DEFAULT_SETTINGS.get('FOO', {})) 99 | 100 | leaves ``DEFAULT_SETTINGS['FOO']`` with a value of ``None``\ . So lets try something else. 101 | 102 | .. rst-class:: caption 103 | 104 | **supertagging/settings.py** 105 | 106 | .. literalinclude:: configure_app4.py 107 | :linenos: 108 | 109 | 110 | In this example taken from django-supertagging, line 8 shows the default values for ``SUPERTAGGING_SETTINGS['MARKUP']``\ . Line 16 retrieves the ``SUPERTAGGING_SETTINGS`` dictionary into a temporary variable using ``getattr``\ . 111 | 112 | Line 17 merges the ``DEFAULT_SETTINGS`` dictionary with the dictionary retrieved in line 16 into a new copy. By converting each dictionary into a list of tuple-pairs with the ``items()`` method, it can combine them using the ``+`` operator. When this list is converted back into a dictionary, it uses the last found key-value pair. 113 | 114 | Lines 18-20 merge the defaults for ``MARKUP`` with whatever the user has specified. 115 | 116 | Turning the keys into attributes 117 | -------------------------------- 118 | Having one dictionary for all your applications settings is all well and good, but requires more typing. Instead of typing: 119 | 120 | .. code-block:: python 121 | :linenos: 122 | 123 | from settings import USER_SETTINGS 124 | if USER_SETTINGS['ENABLED']: 125 | # do something 126 | pass 127 | 128 | it would be nice to type: 129 | 130 | .. code-block:: python 131 | :linenos: 132 | 133 | from settings import ENABLED 134 | if ENABLED: 135 | # do something 136 | pass 137 | 138 | What we want to do is convert the first set of keys into variables. Python has a built-in function called `globals() `_ that returns a dictionary of the symbol table of the current module. 139 | 140 | If you printed the value of ``globals()`` in an empty Python script, you would see something like: 141 | 142 | .. code-block:: python 143 | 144 | >>> print globals() 145 | {'__builtins__': , '__name__': '__main__', '__doc__': None, '__package__': None} 146 | 147 | Since ``globals()`` returns a dictionary, you can use its ``update()`` method to alter its contents in place. 148 | 149 | .. code-block:: python 150 | 151 | >>> d = {'foo': 1, 'bar': 2} 152 | >>> globals().update(d) 153 | >>> print globals() 154 | {'bar': 2, 'd': {'foo': 1, 'bar': 2}, '__builtins__': , '__name__': '__main__', 'foo': 1, '__doc__': None, '__package__': None} 155 | 156 | .. warning:: 157 | 158 | While the effect of this is localized to this module, you must be careful with the names of the dictionary keys. If there is a naming conflict, your dictionary wins. That is probably not what you want. 159 | 160 | Using the suppertagging example above, adding: 161 | 162 | .. code-block:: python 163 | :linenos: 164 | 165 | globals().update(USER_SETTINGS) 166 | 167 | to the bottom of the ``supertagging/settings.py`` file, gives you access to all the top-level keys of ``USER_SETTINGS`` as attributes of ``settings.py`` 168 | 169 | 170 | How to use it 171 | ============= 172 | 173 | Access to your settings is a simple import. As shown in the previous section, you can import the entire dictionary of settings or, if you added them to the settings module, you can import them individually. 174 | -------------------------------------------------------------------------------- /configuration/configure_app1.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | COOL_WORD = getattr(settings, 'COOLAPP_COOL_WORD', 'cool') 4 | -------------------------------------------------------------------------------- /configuration/configure_app2.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.core.exceptions import ImproperlyConfigured 3 | 4 | API_KEY = getattr(settings, 'COOLAPP_API_KEY', None) 5 | 6 | if API_KEY is None: 7 | raise ImproperlyConfigured("You haven't set 'COOLAPP_API_KEY'.") 8 | -------------------------------------------------------------------------------- /configuration/configure_app3.py: -------------------------------------------------------------------------------- 1 | """ 2 | The main DebugToolbar class that loads and renders the Toolbar. 3 | """ 4 | from django.conf import settings 5 | from django.template.loader import render_to_string 6 | 7 | class DebugToolbar(object): 8 | 9 | def __init__(self, request): 10 | self.request = request 11 | self.panels = [] 12 | base_url = self.request.META.get('SCRIPT_NAME', '') 13 | self.config = { 14 | 'INTERCEPT_REDIRECTS': True, 15 | 'MEDIA_URL': u'%s/__debug__/m/' % base_url 16 | } 17 | # Check if settings has a DEBUG_TOOLBAR_CONFIG and updated config 18 | self.config.update(getattr(settings, 'DEBUG_TOOLBAR_CONFIG', {})) 19 | 20 | # ... more code below -------------------------------------------------------------------------------- /configuration/configure_app4.py: -------------------------------------------------------------------------------- 1 | DEFAULT_SETTINGS = { 2 | 'ENABLED': False, 3 | 'DEBUG': False, 4 | 5 | # ... other settings 6 | } 7 | 8 | DEFAULT_MARKUP_SETTINGS = { 9 | 'ENABLED': False, 10 | 'FIELD_SUFFIX': "tagged", 11 | 'EXCLUDE': [], 12 | 'CONTENT_CACHE_TIMEOUT': 3600, 13 | 'MIN_RELEVANCE': 0, 14 | } 15 | 16 | temp_settings = getattr(settings, 'SUPERTAGGING_SETTINGS', {}) 17 | USER_SETTINGS = dict(DEFAULT_SETTINGS.items() + temp_settings.items()) 18 | USER_SETTINGS['MARKUP'] = dict( 19 | DEFAULT_MARKUP_SETTINGS.items() + USER_SETTINGS.get('MARKUP', {}).items() 20 | ) -------------------------------------------------------------------------------- /configuration/index.rst: -------------------------------------------------------------------------------- 1 | ====================== 2 | Configuration Patterns 3 | ====================== 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | :glob: 8 | 9 | autodiscovery 10 | configure_app -------------------------------------------------------------------------------- /decorator_apps/add_fieldsets_to_model_admin.rst: -------------------------------------------------------------------------------- 1 | =================================== 2 | Adding fieldsets to a model's admin 3 | =================================== 4 | 5 | .. warning:: 6 | This is just a stub document. It will be fleshed out more. If you wish to comment on it, please e-mail coreyoordt at gmail. 7 | 8 | .. code-block:: python 9 | 10 | for model, modeladmin in admin.site._registry.items(): 11 | if model in model_registry.values() and modeladmin.fieldsets: 12 | fieldsets = getattr(modeladmin, 'fieldsets', ()) 13 | fields = [cat.split('.')[2] for cat in registry if registry[cat] == model] 14 | # check each field to see if already defined 15 | for cat in fields: 16 | for k,v in fieldsets: 17 | if cat in v['fields']: 18 | fields.remove(cat) 19 | # if there are any fields left, add them under the categories fieldset 20 | if len(fields) > 0: 21 | print fields 22 | admin.site.unregister(model) 23 | admin.site.register(model, type('newadmin', (modeladmin.__class__,), { 24 | 'fieldsets': fieldsets + (('Categories', { 25 | 'fields': fields 26 | }),) 27 | })) 28 | -------------------------------------------------------------------------------- /decorator_apps/change_the_admin_widget.rst: -------------------------------------------------------------------------------- 1 | ================================== 2 | Change the admin widget of a field 3 | ================================== 4 | 5 | .. warning:: 6 | This is just a stub document. It will be fleshed out more. If you wish to comment on it, please e-mail coreyoordt at gmail. 7 | 8 | Django TinyMCE allows you to add TinyMCE functionality to your app if you make certain modifications to your app. This is great if it is your code. However, it doesn’t work so well, if it is someone else’s code. Justin forked Django-TinyMCE to provide this lazy customization. 9 | 10 | The configuration is simple: the app.model name is the key, and then value is a list of fields to have TinyMCE on in the admin. 11 | 12 | :: 13 | 14 | TINYMCE_ADMIN_FIELDS = { 15 | 'app1.model1': ('body',), 16 | 'app1.model2': ('blog_text', 'blog_teaser') 17 | } 18 | 19 | 20 | There are several steps to this process. 21 | 22 | The first is creating a REGISTRY variable to hold the Model and field specifications in our settings.py 23 | 24 | :: 25 | 26 | from django.db.models import get_model 27 | import django.conf import settings 28 | 29 | REGISTRY = {} 30 | ADMIN_FIELDS = getattr(settings, 'TINYMCE_ADMIN_FIELDS', {}) 31 | 32 | for model_name, field in ADMIN_FIELDS.items(): 33 | if isinstance(model_name, basestring): 34 | model = get_model(*model_name.split('.')) 35 | if model in registry: 36 | return 37 | REGISTRY[model] = field 38 | 39 | 40 | Next in out admin.py, we declare a Model admin class, with one new attribute: editor_fields. We are also going to override a standard model admin method: 41 | 42 | formfield for dbfield. This is the method that given a database field will return the form field to render. 43 | 44 | our overridden method checks to see if this field is in our list of editor_fields, and if so, returns a version using the TinyMCE widget. 45 | 46 | if the field is not in our list, we punt it back to the super class. 47 | 48 | ``admin.py`` 49 | 50 | :: 51 | 52 | # Define a new ModelAdmin subclass 53 | 54 | class TinyMCEAdmin(admin.ModelAdmin): 55 | editor_fields = () 56 | 57 | def formfield_for_dbfield(self, db_field, **kwargs): 58 | if db_field.name in self.editor_fields: 59 | return db_field.formfield(widget=TinyMCE()) 60 | return super(TinyMCEAdmin, self).formfield_for_dbfield( 61 | db_field, **kwargs) 62 | 63 | 64 | œœ 65 | Finally, we put the two pieces together. At the bottom of admin.py we loop through the admin’s current admin registry. 66 | 67 | Check if the current iteration is in our registry 68 | 69 | if it is, we unregister that model’s current admin 70 | 71 | and then re-register the model with a dynamically-created class called newadmin 72 | 73 | that is a subclass of our previously declared admin and the model’s current admin 74 | 75 | and we set that new class’s editor-fields attribute to the fields in our registry 76 | 77 | ``admin.py`` 78 | 79 | :: 80 | 81 | for model, modeladmin in admin.site._registry.items(): 82 | if model in REGISTRY: 83 | admin.site.unregister(model) 84 | admin.site.register( 85 | model, 86 | type('newadmin', 87 | (TinyMCEAdmin, modeladmin.__class__), 88 | {'editor_fields': REGISTRY[model],} 89 | ) 90 | ) 91 | -------------------------------------------------------------------------------- /decorator_apps/index.rst: -------------------------------------------------------------------------------- 1 | ====================== 2 | Decorator App Patterns 3 | ====================== 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | :glob: 8 | 9 | introduction 10 | add_fieldsets_to_model_admin 11 | change_the_admin_widget 12 | lazy_field_insertion 13 | lazy_manager_insertion 14 | -------------------------------------------------------------------------------- /decorator_apps/introduction.rst: -------------------------------------------------------------------------------- 1 | ====================================== 2 | Introduction to decorator applications 3 | ====================================== 4 | 5 | **Contributors:** Corey Oordt 6 | 7 | .. contents:: 8 | :local: 9 | 10 | What is a decorator app? 11 | ======================== 12 | 13 | A decorator app is an application that adds functionality to another application without the application's awareness. It is different than a Python decorator, rather it follows the idea from 14 | `Design Patterns: Elements of Reusable Object-Oriented Software `_\ : 15 | 16 | **Intent:** Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality. 17 | 18 | **Also Known As:** Wrapper 19 | 20 | **Motivation:** Sometimes we want to add responsibilities to individual objects, not to an entire class. A graphical user interface toolkit, for example, should let you add properties like borders or behaviors like scrolling to any user interface component. 21 | 22 | One way to add responsibilities is with inheritance. Inheriting a border from another class puts a border around every subclass instance. This is inflexible, however, because the choice of border is made statically. A client can't control how and when to decorate the component with a border. 23 | 24 | It is different than a true decorator in that Django has no way to wrap models or applications. Instead of wrapping the model, and because we're using a dynamic language like Python, we will inject independent code into the model at runtime. 25 | 26 | Benefits 27 | ======== 28 | 29 | Abstraction of metadata 30 | ----------------------- 31 | 32 | When developing an application to manage data, images for example, you include data and metadata. The data is the image, or path to that image. Any other information is metadata. 33 | 34 | For this image application, how much metadata do you include? Some metadata may seem straightforward enough to include: name, width, height, resolution and format come to mind. What about less common things such as author, usage license, categories, and tags? And some of that metadata might be shared across other data applications. A video application might also include usage license, categories and tags. Should each application store their metadata separately? 35 | 36 | You can design data applications that store minimal amounts of metadata (metadata that is easily extracted from the image, for example) and leave other metadata to specialized decorator applications. 37 | 38 | Metadata aggregation 39 | -------------------- 40 | 41 | It is likely that you would want to manage taxonomy metadata like categories or tags the same way throughout a project. It's rather cumbersome if every third-party application allows for a different system for handling it. A decorator application can provide a single way to manage that metadata and aggregate it throughout a project. It is easy then to query all objects, across all applications, that are tagged *foo*\ , or are categorized as *bar*\ . 42 | 43 | Metadata customization 44 | ---------------------- 45 | 46 | ":ref:`no-two-projects-are-alike`\ " I always say, and that includes how they want to handle metadata. A checkbox stating you have reproduction rights might work in one project while another requires a much more specific licensing description. A decorator app for licensing allows the image application to ignore that bit of metadata in its code. When both apps are included in a project, however, the same image application can show different licensing options, depending on the project configuration. 47 | 48 | Alternative data handling 49 | ------------------------- 50 | 51 | Decorators aren't just good for metadata; they can also alter how the data is managed. Take an application to handle blog entries, for example. The primary data is the text of the entry. A good question for the application is "How does the user enter text into that field?" Most apps force a decision on the user, such as a markup language such as Textile, reStructuredText, or Markdown or a WYSIWYG editor like TinyMCE. 52 | 53 | If no two projects are alike, might that also include text formatting? In one project, the users might want a WYSIWYG editor, while others prefer a specific markup language. A decorator app can manage that for the data app, especially if the data app includes some hooks to make it easier. 54 | 55 | 56 | Warnings 57 | ======== 58 | 59 | Django doesn't have any native way to add functionality to other applications. Therefore accomplishing this task requires modifying class definitions at runtime. Depending on the amount and type of modification, there could be unforeseen consequences. 60 | 61 | All the patterns discussed are used in production and heavily tested. Each pattern does significant error checking to make sure any inserted code doesn't clobber existing functionality. 62 | -------------------------------------------------------------------------------- /decorator_apps/lazy_field_insertion.rst: -------------------------------------------------------------------------------- 1 | ==================== 2 | Lazy Field Insertion 3 | ==================== 4 | 5 | .. warning:: 6 | This is just a stub document. It will be fleshed out more. If you wish to comment on it, please e-mail coreyoordt at gmail. 7 | 8 | 9 | The idea is allow developers to decide which models will have categories in the project’s settings.py, using a dictionary with the model as the key and the field or fields as the value. 10 | 11 | 12 | 13 | .. code-block:: python 14 | 15 | 'FK_REGISTRY': { 16 | 'flatpages.flatpage': 'category', 17 | 'simpletext.simpletext': ( 18 | 'primary_category', 19 | {'name': 'secondary_category', 'related_name': 'simpletext_sec_cat'}, 20 | ), 21 | }, 22 | 'M2M_REGISTRY': { 23 | 'simpletext.simpletext': {'name': 'categories', 'related_name': 'm2mcats'}, 24 | 'flatpages.flatpage': ( 25 | {'name': 'other_categories', 'related_name': 'other_cats'}, 26 | {'name': 'more_categories', 'related_name': 'more_cats'}, 27 | ), 28 | }, 29 | 30 | 31 | at the bottom of the category app's __init__.py, you can read the configuration from settings. 32 | 33 | loop through them 34 | 35 | Do a bit of error checking 36 | 37 | Load the model class 38 | 39 | Loop through the given fields 40 | 41 | We make sure that the field doesn't already exist by attempting to get it 42 | 43 | Finally we add the field to the model by instantiating the field and calling its contribute_to_class method. 44 | 45 | .. code-block:: python 46 | 47 | import fields 48 | 49 | from django.db.models import FieldDoesNotExist 50 | 51 | class AlreadyRegistered(Exception): 52 | """ 53 | An attempt was made to register a model more than once. 54 | """ 55 | pass 56 | 57 | # The field registry keeps track of the individual fields created. 58 | # {'app.model.field': Field(**extra_params)} 59 | # Useful for doing a schema migration 60 | field_registry = {} 61 | 62 | # The model registry keeps track of which models have one or more fields 63 | # registered. 64 | # {'app': [model1, model2]} 65 | # Useful for admin alteration 66 | model_registry = {} 67 | 68 | def register_m2m(model, field_name='categories', extra_params={}): 69 | return _register(model, field_name, extra_params, fields.CategoryM2MField) 70 | 71 | def register_fk(model, field_name='category', extra_params={}): 72 | return _register(model, field_name, extra_params, fields.CategoryFKField) 73 | 74 | def _register(model, field_name, extra_params={}, field=fields.CategoryFKField): 75 | app_label = model._meta.app_label 76 | registry_name = ".".join((app_label, model.__name__, field_name)).lower() 77 | 78 | if registry_name in field_registry: 79 | return #raise AlreadyRegistered 80 | opts = model._meta 81 | try: 82 | opts.get_field(field_name) 83 | except FieldDoesNotExist: 84 | if app_label not in model_registry: 85 | model_registry[app_label] = [] 86 | if model not in model_registry[app_label]: 87 | model_registry[app_label].append(model) 88 | field_registry[registry_name] = field(**extra_params) 89 | field_registry[registry_name].contribute_to_class(model, field_name) 90 | 91 | from categories import settings 92 | from django.core.exceptions import ImproperlyConfigured 93 | from django.db.models import get_model 94 | 95 | for key, value in settings.FK_REGISTRY.items(): 96 | model = get_model(*key.split('.')) 97 | if model is None: 98 | raise ImproperlyConfigured('%s is not a model' % key) 99 | if isinstance(value, (tuple, list)): 100 | for item in value: 101 | if isinstance(item, basestring): 102 | register_fk(model, item) 103 | elif isinstance(item, dict): 104 | field_name = item.pop('name') 105 | register_fk(model, field_name, extra_params=item) 106 | else: 107 | raise ImproperlyConfigured("CATEGORY_SETTINGS['FK_REGISTRY'] doesn't recognize the value of %s" % key) 108 | elif isinstance(value, basestring): 109 | register_fk(model, value) 110 | elif isinstance(item, dict): 111 | field_name = item.pop('name') 112 | register_fk(model, field_name, extra_params=item) 113 | else: 114 | raise ImproperlyConfigured("CATEGORY_SETTINGS['FK_REGISTRY'] doesn't recognize the value of %s" % key) 115 | for key, value in settings.M2M_REGISTRY.items(): 116 | model = get_model(*key.split('.')) 117 | if model is None: 118 | raise ImproperlyConfigured('%s is not a model' % key) 119 | if isinstance(value, (tuple, list)): 120 | for item in value: 121 | if isinstance(item, basestring): 122 | register_m2m(model, item) 123 | elif isinstance(item, dict): 124 | field_name = item.pop('name') 125 | register_m2m(model, field_name, extra_params=item) 126 | else: 127 | raise ImproperlyConfigured("CATEGORY_SETTINGS['M2M_REGISTRY'] doesn't recognize the value of %s: %s" % (key, item)) 128 | elif isinstance(value, basestring): 129 | register_m2m(model, value) 130 | elif isinstance(value, dict): 131 | field_name = value.pop('name') 132 | register_m2m(model, field_name, extra_params=value) 133 | else: 134 | raise ImproperlyConfigured("CATEGORY_SETTINGS['M2M_REGISTRY'] doesn't recognize the value of %s" % key) 135 | -------------------------------------------------------------------------------- /decorator_apps/lazy_manager_insertion.rst: -------------------------------------------------------------------------------- 1 | ====================== 2 | Lazy Manager Insertion 3 | ====================== 4 | 5 | .. warning:: 6 | This is just a stub document. It will be fleshed out more. If you wish to comment on it, please e-mail coreyoordt at gmail. 7 | 8 | Loosely based on Django-mptt. It is very similar to how we handled inserting a field. 9 | :: 10 | 11 | COOLAPP_MODELS = { 12 | 'app1.Model': 'cool_manager', 13 | 'app2.Model': 'cool_manager', 14 | } 15 | 16 | 17 | At the bottom of this app’s models.py, you can read the configuration from settings. 18 | 19 | loop through them and Do a bit of error checking 20 | 21 | Load the model class 22 | 23 | Loop through the given fields 24 | 25 | We make sure that the model doesn’t have an attribute by the same name, we add the field to the model by instantiating the manager and calling its contribute_to_class method. 26 | 27 | 28 | :: 29 | 30 | from django.db.models import get_model 31 | import django.conf import settings 32 | from coolapp.managers import CustomManager 33 | 34 | MODELS = getattr(settings, 'COOLAPP_MODELS', {}) 35 | 36 | for model_name, mgr_name in MODELS.items(): 37 | if not isinstance(model_name, basestring): 38 | continue 39 | 40 | model = get_model(*model_name.split('.')) 41 | 42 | if not getattr(model, mgr_name, False): 43 | manager = CustomManager() 44 | manager.contribute_to_class(model, mgr_name) -------------------------------------------------------------------------------- /development_philosophy.rst: -------------------------------------------------------------------------------- 1 | ========================================= 2 | My Development Assumptions and Principles 3 | ========================================= 4 | 5 | by Corey Oordt 6 | 7 | Since I started developing in Django in 2006, I've been lucky enough to meet and work with many talented people with a variety of experiences. Gradually, typically through failure or dire need, we developed a methodology and approach to development of projects. 8 | 9 | The approach was internalized; each of us *knew* how it worked so it was never directly expressed. With a recent move to a new job, I was struck by the differences and needed to express the ideas in terms of **assumptions** and **principles**. 10 | 11 | *Assumptions* are the preconceptions you and your team hold when approaching a project. These assumptions aren't necessarily bad. As long as you are aware of them, and regularly check to make sure they are valid, they will be helpful. 12 | 13 | *Principles* are the guides to behavior. They are not hard and fast rules that you must never break, merely guides to success that should be understood and deviated from with full knowledge of why it makes sense in the situation. 14 | 15 | Key Design Assumptions 16 | ====================== 17 | 18 | .. _no-two-projects-are-alike: 19 | 20 | No two projects are alike. 21 | ************************** 22 | 23 | Each project will share some things with others, but not all projects will share the same things, or in the same way. You will need to listen to the needs of the end users and the people running the project. 24 | 25 | Most projects will fail 26 | *********************** 27 | 28 | It should fail quickly, with as little effort as possible to determine its imminent doom. This is not a pessimism, but innovation. The easier it is to try something to see if it works, the more you will try and the more that will work. 29 | 30 | People have their own way of doing things. 31 | ****************************************** 32 | 33 | I'm an opinionated bastard. As an opinionated bastard, it really torques me when others make me do things in ways I disagree with. When there are several ways to get the same result, let others get there by any of those means. 34 | 35 | Another way to look at this is to manage *outcomes,* not practices or methods. 36 | 37 | Two or more project's requirements may conflict. 38 | ************************************************ 39 | 40 | The conflicts only matter if the projects must share something or are co-dependent. 41 | 42 | 43 | Things change. 44 | ************** 45 | 46 | Life moves very fast on the internet. Projects must adapt quickly or fail miserably. 47 | 48 | 49 | Key Design Principles 50 | ===================== 51 | 52 | Design the user's experience first 53 | ********************************** 54 | 55 | Many developers create solutions that are functional, but not usable. When you start with the user's experience, you at least get more usable solutions, if not better ones. 56 | 57 | Break down the tools to their simplest bits. 58 | ******************************************** 59 | 60 | It is easier to find new uses for simple tools than complex tools. I am constantly surprised how people have found new ways to use code that was meant for something else. 61 | 62 | Similar ideas are: 63 | 64 | * Separate functionality whenever possible. 65 | 66 | * Be flexible in implementation. 67 | 68 | * Couple loosely. 69 | 70 | Code is like a making a baby: only do it if you are willing to support it for the rest of your life. 71 | **************************************************************************************************** 72 | 73 | I've learned the hard way that code you wrote years ago can come back to haunt you. I've received calls from people I wrote code for years before asking for help due to an unforeseen bug. (To me the unforeseen bug was that they were still using the software.) Unless you are willing to be a jerk, you got to give them a hand. 74 | 75 | This leads to two practices: use external code libraries whenever possible to reduce the amount of code for which you are directly responsible, and write your code in a way that you won't be too horrified to see it in three years. 76 | 77 | A similar practice is when making a utility to enhance functionality, don't assume that the implementer will "own the code". 78 | 79 | External dependencies should be declared and few 80 | ************************************************ 81 | 82 | I see dependencies like farts on an elevator: the fewer the better (and please confess). 83 | 84 | If you want people to do something, make it incredibly easy to do. 85 | ****************************************************************** 86 | 87 | And don't forget its sibling: *If you want people to do something a specific way, make it easier to do it that way than any other.* 88 | 89 | Even small barriers will limit how often "best practices" are followed. People put off things if: 90 | 91 | * They have to do prep work to accomplish the task 92 | 93 | * They aren't sure how to accomplish the task 94 | 95 | * They don't understand why the task is important 96 | 97 | 98 | -------------------------------------------------------------------------------- /example_pattern.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | ============ 4 | Pattern Name 5 | ============ 6 | 7 | What problem does this pattern solve? 8 | ===================================== 9 | 10 | When to use it 11 | ============== 12 | 13 | Why should I use it? 14 | ==================== 15 | 16 | Implementation 17 | ============== 18 | 19 | How to use it 20 | ============= 21 | 22 | Sources 23 | ======= 24 | 25 | Useful Links 26 | ============ 27 | 28 | Where is it used? 29 | ================= 30 | 31 | -------------------------------------------------------------------------------- /index.rst: -------------------------------------------------------------------------------- 1 | ====================== 2 | Django Coding Patterns 3 | ====================== 4 | 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | :glob: 9 | 10 | what_are_pluggable_apps 11 | development_philosophy 12 | app_construction/index 13 | configuration/index 14 | decorator_apps/index 15 | models/index 16 | templates/index 17 | urls/index 18 | 19 | 20 | -------------------------------------------------------------------------------- /make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | set SPHINXBUILD=sphinx-build 6 | set BUILDDIR=_build 7 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 8 | if NOT "%PAPER%" == "" ( 9 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 10 | ) 11 | 12 | if "%1" == "" goto help 13 | 14 | if "%1" == "help" ( 15 | :help 16 | echo.Please use `make ^` where ^ is one of 17 | echo. html to make standalone HTML files 18 | echo. dirhtml to make HTML files named index.html in directories 19 | echo. pickle to make pickle files 20 | echo. json to make JSON files 21 | echo. htmlhelp to make HTML files and a HTML help project 22 | echo. qthelp to make HTML files and a qthelp project 23 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 24 | echo. changes to make an overview over all changed/added/deprecated items 25 | echo. linkcheck to check all external links for integrity 26 | echo. doctest to run all doctests embedded in the documentation if enabled 27 | goto end 28 | ) 29 | 30 | if "%1" == "clean" ( 31 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 32 | del /q /s %BUILDDIR%\* 33 | goto end 34 | ) 35 | 36 | if "%1" == "html" ( 37 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 38 | echo. 39 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 40 | goto end 41 | ) 42 | 43 | if "%1" == "dirhtml" ( 44 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 45 | echo. 46 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 47 | goto end 48 | ) 49 | 50 | if "%1" == "pickle" ( 51 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 52 | echo. 53 | echo.Build finished; now you can process the pickle files. 54 | goto end 55 | ) 56 | 57 | if "%1" == "json" ( 58 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 59 | echo. 60 | echo.Build finished; now you can process the JSON files. 61 | goto end 62 | ) 63 | 64 | if "%1" == "htmlhelp" ( 65 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 66 | echo. 67 | echo.Build finished; now you can run HTML Help Workshop with the ^ 68 | .hhp project file in %BUILDDIR%/htmlhelp. 69 | goto end 70 | ) 71 | 72 | if "%1" == "qthelp" ( 73 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 74 | echo. 75 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 76 | .qhcp project file in %BUILDDIR%/qthelp, like this: 77 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\app.qhcp 78 | echo.To view the help file: 79 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\app.ghc 80 | goto end 81 | ) 82 | 83 | if "%1" == "latex" ( 84 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 85 | echo. 86 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 87 | goto end 88 | ) 89 | 90 | if "%1" == "changes" ( 91 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 92 | echo. 93 | echo.The overview file is in %BUILDDIR%/changes. 94 | goto end 95 | ) 96 | 97 | if "%1" == "linkcheck" ( 98 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 99 | echo. 100 | echo.Link check complete; look for any errors in the above output ^ 101 | or in %BUILDDIR%/linkcheck/output.txt. 102 | goto end 103 | ) 104 | 105 | if "%1" == "doctest" ( 106 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 107 | echo. 108 | echo.Testing of doctests in the sources finished, look at the ^ 109 | results in %BUILDDIR%/doctest/output.txt. 110 | goto end 111 | ) 112 | 113 | :end 114 | -------------------------------------------------------------------------------- /models/abstract_model_mixins.rst: -------------------------------------------------------------------------------- 1 | ===================== 2 | Abstract Model Mixins 3 | ===================== 4 | 5 | .. warning:: 6 | This is just a stub document. It will be fleshed out more. If you wish to comment on it, please e-mail coreyoordt at gmail. 7 | 8 | What problem does this pattern solve? 9 | ===================================== 10 | 11 | It creates a tool kit to build complex models. 12 | 13 | When to use it 14 | ============== 15 | 16 | For models that you will commonly build in projects but have many different potential features, and you want your models to only contain the features necessary. Blogs are a good example, where where are many potential options to include within a blog, but you don't need all of them all the time. 17 | 18 | Why should I use it? 19 | ==================== 20 | 21 | This allows developers to fix bugs once, in the tool kit. Installing the new tool kit version will fix those bugs in each model created from it. 22 | 23 | Where is it used? 24 | ================= 25 | 26 | I first saw this in `GLAMkit `_\ 's `blogtools `_ package. It is also used in Django 1.3's `class-based views `_\ . 27 | 28 | Implementation 29 | ============== 30 | 31 | Models 32 | ------ 33 | 34 | First isolate all the potential or optional features from core features. The core features and each isolated set of optional features will make up individual abstract Django models. 35 | 36 | GLAMkit isolated the one base model (``EntryBase``\ ), and four optional features: add a *featured* flag, add a *status* field, add a tag field, and allow for the body and excerpt content to convert to HTML. 37 | 38 | ``EntryBase`` includes seven fields--author, title, pub_date, slug, enable_comments, excerpt, and body--as well as ``__unicode__()``\ , ``get_absolute_url()`` and ``excerpt_or_body()`` functions. The ``Meta`` class has ``abstract=True`` so that Django never tries to represent this model in a database. It must be subclassed. 39 | 40 | ``FeaturableEntryMixin`` is an abstract class that merely defines an ``is_featured`` field. 41 | 42 | ``StatusableEntryMixin`` is an abstract class that defines ``LIVE_STATUS``\ , ``DRAFT_STATUS``\ , and ``HIDDEN_STATUS`` values and choices. It defines a ``status`` field with those choices. 43 | 44 | ``TaggableEntryMixin`` is an abstract class that is only available if Django-Tagging is installed, as it uses the ``TagField`` for the ``tags`` field it defines. 45 | 46 | ``HTMLFormattableEntryMixin`` is a much more complex abstract class. It is only available if the ``template_utils`` package is available. It defines two text fields, ``excerpt_html`` and ``body_html``\ . It also overrides the ``save()`` method so it can convert the contents of ``exceprt`` and ``body`` into HTML for ``excerpt_html`` and ``body_html``\ , respectively. Finally it re-defines the ``excerpt_or_body()`` method to return the ``excerpt_html`` or ``body_html`` value. 47 | 48 | Admin 49 | ----- 50 | 51 | It is difficult to provide a really good ``ModelAdmin`` class when you aren't sure what fields or features are included in the final model. GLAMkit provides a ``EntryAdminBase`` which is subclassed from ``object`` (not ``ModelAdmin``\ ). Providing other admin mixins would make sense if there were admin-specific features to provide, such as adding a WYSIWYG editor, autocomplete lookups or special filtering. 52 | 53 | URLs 54 | ---- 55 | 56 | 57 | Views 58 | ----- 59 | 60 | 61 | Syndication Feeds 62 | ----------------- 63 | 64 | 65 | 66 | 67 | How to use it 68 | ============= 69 | 70 | Sources 71 | ======= 72 | 73 | Useful Links 74 | ============ 75 | 76 | 77 | GLAMKit (Gallery, Library, Archive, Museum) an Australian group, tackles this situation with a set of abstract classes that provide very basic features. 78 | 79 | You create your model by subclassing the classes that provide the functionality you need. 80 | 81 | And you don’t have to stop there. You can add your own fields as well. 82 | 83 | 84 | .. code-block:: python 85 | 86 | class PRBlog(EntryBase, 87 | StatusableEntryMixin): 88 | 89 | subhead = models.CharField() 90 | pdf = models.FileField() 91 | 92 | 93 | -------------------------------------------------------------------------------- /models/automatically_filling_user.rst: -------------------------------------------------------------------------------- 1 | ============================================ 2 | Automatically Filling in a User in the Admin 3 | ============================================ 4 | 5 | What problem does this pattern solve? 6 | ===================================== 7 | 8 | * Easily keep track of the last user who modified a record 9 | * Automatically set the "author" of a record 10 | 11 | Why should I use it? 12 | ==================== 13 | 14 | It is a safe way to save the user time and still keep track of the information. 15 | 16 | Implementation 17 | ============== 18 | 19 | The important parts of the implementation are: 20 | 21 | 1. The :py:class:`ForeignKey` to :py:class:`User` field needs ``blank=True`` 22 | 2. Create a :py:class:`ModelForm` that assigns a fake :py:class:`User` to the field. 23 | 3. Override the :py:func:`ModelAdmin.save_model()` function to assign the ``request.user`` to the field. 24 | 25 | 26 | For discussion we'll use this model with two relations to the :py:class:`User` model: 27 | 28 | .. rst-class:: caption 29 | 30 | **coolblog/models.py** 31 | 32 | .. literalinclude:: automatically_filling_user_model.py 33 | :linenos: 34 | 35 | This :py:class:`Entry` model has two fields that we want to fill automatically: ``author`` and ``last_modified_by``\ . Notice both fields have ``blank=True``\ . This is important so we can get past some initial Django validation. 36 | 37 | Faking validation 38 | ----------------- 39 | 40 | Whenever you save a model, Django attempts to validate it. Validation will fail without special validation tomfoolery. The first stage of validation fakery was setting ``blank=True`` on the fields. The next stage involves setting a temporary value for each field. 41 | 42 | .. rst-class:: caption 43 | 44 | **coolblog/forms.py** 45 | 46 | .. literalinclude:: automatically_filling_user_form.py 47 | :linenos: 48 | 49 | 50 | The lower-level Django model validation actually checks if the value of a related field is an instance of the correct class. Since a form's validation happens before any attempt to save the model, we create a new :py:class:`ModelForm`, called ``EntryForm``\ . The :py:func:`clean_author()` and :py:func:`clean_last_modified_by()` methods check for an empty value and assigns it an unsaved and empty instance of :py:class:`User`\ , and Django is happy. 51 | 52 | 53 | Saving the model 54 | ---------------- 55 | 56 | In the model's :py:class:`ModelAdmin`\ , we make a few adjustments. 57 | 58 | .. rst-class:: caption 59 | 60 | **coolblog/admin.py** 61 | 62 | .. literalinclude:: automatically_filling_user_admin.py 63 | :linenos: 64 | 65 | First, we set the ``form`` attribute to the ``EntryForm`` we just created. 66 | 67 | Then, since we don't want the author to worry about selecting themselves in the ``author`` field, we left it out of the ``fieldsets``\ . We left in the ``last_modified_by`` field for reference and made it a read-only field. 68 | 69 | The final magic comes in the overridden :py:func:`save_model()` method on line 17. We check to see if the ``author`` attribute actually has an ``id``\ . If it doesn't, it must be the empty instance we set in ``EntryForm``\ , so we assign the ``author`` field to the current user. Since we always want to assign or re-assign the user modifying this record, the ``last_modified_by`` field is set every time. 70 | 71 | 72 | Sources 73 | ======= 74 | 75 | `ModelAdmin.save_model documentation `_ 76 | 77 | `Audit Fields `_ 78 | 79 | `Users and the admin `_ 80 | -------------------------------------------------------------------------------- /models/automatically_filling_user_admin.py: -------------------------------------------------------------------------------- 1 | class EntryAdmin(admin.ModelAdmin): 2 | form = EntryForm 3 | 4 | list_display = ('title', 'pub_date', 'author') 5 | prepopulated_fields = { 'slug': ['title'] } 6 | readonly_fields = ('last_modified', 'last_modified_by',) 7 | fieldsets = (( 8 | None, { 9 | 'fields': ('title', 'body', 'pub_date') 10 | }), ( 11 | 'Other Information', { 12 | 'fields': ('last_modified', 'last_modified_by', 'slug'), 13 | 'classes': ('collapse',) 14 | }) 15 | ) 16 | 17 | def save_model(self, request, obj, form, change): 18 | if not obj.author.id: 19 | obj.author = request.user 20 | obj.last_modified_by = request.user 21 | obj.save() -------------------------------------------------------------------------------- /models/automatically_filling_user_admin2.py: -------------------------------------------------------------------------------- 1 | class EntryAdmin(admin.ModelAdmin): 2 | form = EntryForm 3 | 4 | list_display = ('title', 'pub_date', 'author') 5 | prepopulated_fields = { 'slug': ['title'] } 6 | readonly_fields = ('last_modified', 'last_modified_by',) 7 | fieldsets = (( 8 | None, { 9 | 'fields': ('title', 'body', 'pub_date') 10 | }), ( 11 | 'Other Information', { 12 | 'fields': ('author', 'last_modified', 'last_modified_by', 'slug'), 13 | 'classes': ('collapse',) 14 | }) 15 | ) 16 | 17 | def save_model(self, request, obj, form, change): 18 | if not obj.author.id: 19 | obj.author = request.user 20 | obj.last_modified_by = request.user 21 | obj.save() -------------------------------------------------------------------------------- /models/automatically_filling_user_form.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | 3 | class EntryForm(forms.ModelForm): 4 | class Meta: 5 | model = Entry 6 | 7 | def clean_author(self): 8 | if not self.cleaned_data['author']: 9 | return User() 10 | return self.cleaned_data['author'] 11 | 12 | def clean_last_modified_by(self): 13 | if not self.cleaned_data['last_modified_by']: 14 | return User() 15 | return self.cleaned_data['last_modified_by'] -------------------------------------------------------------------------------- /models/automatically_filling_user_model.py: -------------------------------------------------------------------------------- 1 | class Entry(models.Model): 2 | title = models.CharField(max_length=250) 3 | slug = models.SlugField() 4 | pub_date = models.DateField(default=datetime.datetime.today) 5 | author = models.ForeignKey( 6 | User, 7 | related_name='entries', 8 | blank=True) 9 | body = models.TextField() 10 | last_modified = models.DateTimeField(auto_now=True) 11 | last_modified_by = models.ForeignKey( 12 | User, 13 | related_name='entry_modifiers', 14 | blank=True) -------------------------------------------------------------------------------- /models/common_options.rst: -------------------------------------------------------------------------------- 1 | ========================================== 2 | Configurable Options for Common Situations 3 | ========================================== 4 | .. warning:: 5 | This is just a stub document. It will be fleshed out more. If you wish to comment on it, please e-mail coreyoordt at gmail. 6 | 7 | A few, well-known of variations 8 | (e.g. Use django.contrib.sites?) 9 | 10 | **models.py** 11 | .. literalinclude:: common_options_1.py 12 | :linenos: 13 | 14 | 15 | Another example: 16 | 17 | 18 | **models.py** 19 | .. literalinclude:: common_options_2.py 20 | :linenos: 21 | 22 | 23 | You can provide for optional field settings. 24 | 25 | Import the setting from your own apps settings 26 | 27 | Based on that setting, you can optionally import classes. And in your model definition... 28 | 29 | Optionally declare fields. The only drawback of this depends on the type of field. Changing your mind after the initial table creation might require you to either manually add the field or drop the table and syncdb again. 30 | 31 | Link to way to do migration with south if field is added. -------------------------------------------------------------------------------- /models/common_options_1.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from coolapp.settings import MULTIPLE_SITES, SINGLE_SITE 3 | 4 | if MULTIPLE_SITES or SINGLE_SITE: 5 | from django.contrib.sites.models import Site 6 | 7 | class Entry(models.Model): 8 | title = models.CharField(max_length=100) 9 | # Other stuff 10 | 11 | if MULTIPLE_SITES: 12 | sites = models.ManyToManyField(Site) 13 | if SINGLE_SITE: 14 | sites = models.ForeignKey(Site) 15 | -------------------------------------------------------------------------------- /models/common_options_2.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from myapp.settings import USE_TAGGING 3 | 4 | if USE_TAGGING: 5 | from tagging.fields import TagField 6 | 7 | class Entry(models.Model): 8 | title = models.CharField(max_length=100) 9 | # Other stuff 10 | 11 | if USE_TAGGING: 12 | tags = TagField() 13 | -------------------------------------------------------------------------------- /models/configurable_relations.rst: -------------------------------------------------------------------------------- 1 | ========================= 2 | Configurable Foreign Keys 3 | ========================= 4 | 5 | .. warning:: 6 | This is just a stub document. It will be fleshed out more. If you wish to comment on it, please e-mail coreyoordt at gmail. 7 | 8 | We had a staff model that we wanted to related it to in some projects, but not all. So in the application settings we use a django function called get_model. This allows you to specify the model in an app-dot-model format in the project settings and then dynamically import it 9 | 10 | .. code-block:: python 11 | 12 | from django.conf import settings 13 | from django.db.models import get_model 14 | 15 | model_string = getattr(settings, 'VIEWPOINT_AUTHOR_MODEL', 'auth.User') 16 | AUTHOR_MODEL = get_model(*model_string.split('.')) 17 | 18 | Now we simply import the AUTHOR_MODEL setting, which is a django model. And use it as the parameter for the ForeignKey field. 19 | 20 | .. code-block:: python 21 | 22 | from viewpoint.settings import AUTHOR_MODEL 23 | 24 | class Entry(models.Model): 25 | title = models.CharField(max_length=100) 26 | author = models.ForeignKey(AUTHOR_MODEL) 27 | ... -------------------------------------------------------------------------------- /models/flexible_storage_uploaded_files.rst: -------------------------------------------------------------------------------- 1 | ================================== 2 | Flexible storage of uploaded files 3 | ================================== 4 | 5 | .. warning:: 6 | This is just a stub document. It will be fleshed out more. If you wish to comment on it, please e-mail coreyoordt at gmail. 7 | 8 | :: 9 | 10 | from django.conf import settings 11 | from django.core.files.storage import get_storage_class 12 | 13 | DEFAULT_STORAGE = get_storage_class( 14 | getattr(settings, "MMEDIA_DEFAULT_STORAGE", settings.DEFAULT_FILE_STORAGE) 15 | ) 16 | 17 | 18 | :: 19 | 20 | from massmedia.settings import IMAGE_UPLOAD_TO, DEFAULT_STORAGE 21 | 22 | class Image(models.Model): 23 | file = models.FileField( 24 | upload_to = IMAGE_UPLOAD_TO, 25 | blank = True, 26 | null = True, 27 | storage=DEFAULT_STORAGE()) -------------------------------------------------------------------------------- /models/index.rst: -------------------------------------------------------------------------------- 1 | ============== 2 | Model Patterns 3 | ============== 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | :glob: 8 | 9 | abstract_model_mixins 10 | automatically_filling_user 11 | common_options 12 | configurable_relations 13 | flexible_storage_uploaded_files -------------------------------------------------------------------------------- /templates/easily_overridable_templates.rst: -------------------------------------------------------------------------------- 1 | ============================ 2 | Easily overridable templates 3 | ============================ 4 | 5 | .. warning:: 6 | This is just a stub document. It will be fleshed out more. If you wish to comment on it, please e-mail coreyoordt at gmail. 7 | 8 | Templates are an important part of your pluggable app. They demonstrate how your app works. The more complex your app, the more important templates are. Following a few practices can make your templates very useful. We have two goals: 1. Get demonstrable functionality in as short a time as possible, and 2. modify the fewest templates to do so. 9 | 10 | Instead of putting your templates loose in your templates directory where they can conflict with other apps templates, *** put them in a directory within templates, named after your app to name space them. *** Then you reference them as the relative path from templates, in this case: coolapp/base.html 11 | 12 | :: 13 | 14 | coolapp 15 | +-templates 16 | +-coolapp 17 | +-base.html 18 | 19 | -------------------------------------------------------------------------------- /templates/extend_one_template.rst: -------------------------------------------------------------------------------- 1 | =================== 2 | Extend one template 3 | =================== 4 | 5 | .. warning:: 6 | This is just a stub document. It will be fleshed out more. If you wish to comment on it, please e-mail coreyoordt at gmail. 7 | 8 | If all your templates extend a template that you assume exists, such as ``base.html`` 9 | 10 | You have to change each template when your project uses ``site_base.html`` instead. 11 | 12 | If instead all your templates extend a known, base template in your name space and it extends the mythical ``base.html`` 13 | 14 | when the inevitable happens and the base template name changes changing one template makes all the others work. 15 | 16 | If your coolapp/base.html defines all the blocks that you use, it is also trivial to change them to match the project’s template, just by enclosing your blocks in the appropriate base template blocks 17 | 18 | 19 | ``coolapp/base.html`` 20 | 21 | :: 22 | 23 | {% extends "site_base.html" %} 24 | 25 | {% block extra_head %} 26 | {% block head %} 27 | {% endblock %} 28 | {% endblock %} 29 | 30 | {% block content %} 31 | {% block body %} 32 | {% endblock %} 33 | {% endblock %} 34 | 35 | -------------------------------------------------------------------------------- /templates/import_your_template_blocks.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | Import your template blocks 3 | =========================== 4 | 5 | .. warning:: 6 | This is just a stub document. It will be fleshed out more. If you wish to comment on it, please e-mail coreyoordt at gmail. 7 | 8 | If each template only focuses on one template block and imports other blocks, such as extra header information, you can selectively choose which template to modify: extra_head.html to add this functionality once for all templates, or detail.html to change the content. 9 | 10 | 11 | :: 12 | 13 | {% extends "coolapp/base.html" %} 14 | 15 | {% block extra_head %} 16 | {{ block.super }} 17 | {% import "coolapp/extra_head.html" %} 18 | {% endblock %} 19 | 20 | {% block content %} 21 | {# Important content stuff here #} 22 | 23 | 24 | 25 | {% endblock %} -------------------------------------------------------------------------------- /templates/index.rst: -------------------------------------------------------------------------------- 1 | ================= 2 | Template Patterns 3 | ================= 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | :glob: 8 | 9 | easily_overridable_templates 10 | extend_one_template 11 | import_your_template_blocks -------------------------------------------------------------------------------- /urls/index.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | URL patterns 3 | ============ 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | :glob: 8 | 9 | urls_that_live_under_any_prefix -------------------------------------------------------------------------------- /urls/urls_that_live_under_any_prefix.rst: -------------------------------------------------------------------------------- 1 | =============================== 2 | URLs that live under any prefix 3 | =============================== 4 | 5 | .. warning:: 6 | This is just a stub document. It will be fleshed out more. If you wish to comment on it, please e-mail coreyoordt at gmail. 7 | 8 | It’s bad practice to hard code url paths or assume certain paths, for example that my blog app is always going to be under the path /blogs/ 9 | 10 | Django provides an easy way to abstractly reference a url and its view. All you have to do ... 11 | 12 | Is add a url function in front of the pattern and add a name parameter. This allows you to ... 13 | 14 | :: 15 | 16 | from django.conf.urls.defaults import * 17 | 18 | urlpatterns = patterns('', 19 | url(r'^$', 'coolapp_app.views.index', name='coolapp_index'), 20 | ) 21 | 22 | 23 | :: 24 | 25 |

Go to the Index

26 | 27 | 28 | Retrieve the url using the url template tag or use the reverse function within your code. 29 | 30 | 31 | :: 32 | 33 | from django.core.urlresolvers import reverse 34 | 35 | :: 36 | 37 | def myview(request): 38 | return HttpResponseRedirect(reverse('coolapp_index', args=[])) -------------------------------------------------------------------------------- /what_are_pluggable_apps.rst: -------------------------------------------------------------------------------- 1 | ======================== 2 | What are pluggable apps? 3 | ======================== 4 | 5 | .. warning:: 6 | This is just a stub document. It will be fleshed out more. If you wish to comment on it, please e-mail coreyoordt at gmail. 7 | 8 | Confusion between the "web app" the user sees and the Django apps that power it. 9 | 10 | Pluggable apps are: 11 | 12 | * Focused: focused use cases and include nothing that isn’t required. "Write programs that do one thing and do it well." — Doug McIlroy (inventor of UNIX pipes)" 13 | 14 | * Self-contained: Everything someone needs to get the app working is declared or included. 15 | 16 | * Easily adaptable: A focused application can inevitably find new uses, if it doesn’t take too much for granted or make too many assumptions. 17 | 18 | * Easily installed: Pluggable applications are installed and not modified. Applications are wired together and configured in the project. The only “Apps” in your project codebase are apps that are so specific to the project that they can’t be used elsewhere. 19 | 20 | --------------------------------------------------------------------------------