├── .gitignore ├── LICENSE ├── code ├── readme.md ├── requirements.txt └── tango_with_django_project │ ├── manage.py │ ├── media │ └── cat.jpg │ ├── populate_rango.py │ ├── rango │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── bing_search.py │ ├── forms.py │ ├── models.py │ ├── templatetags │ │ ├── __init__.py │ │ └── rango_template_tags.py │ ├── tests.py │ ├── urls.py │ ├── views.py │ ├── views_ajax.py │ └── webhose_search.py │ ├── static │ ├── images │ │ ├── favicon.ico │ │ └── rango.jpg │ └── js │ │ ├── rango-ajax.js │ │ └── rango-jquery.js │ ├── tango_with_django_project │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py │ └── templates │ ├── rango │ ├── about.html │ ├── add_category-pre-chapter12.html │ ├── add_category.html │ ├── add_page.html │ ├── base.html │ ├── base_bootstrap.html │ ├── category.html │ ├── cats.html │ ├── index-pre-chapter12.html │ ├── index-pre-chapter8.html │ ├── index.html │ ├── list_profiles.html │ ├── page_list.html │ ├── profile.html │ ├── profile_registration.html │ ├── search.html │ └── temp.html │ └── registration │ ├── login.html │ ├── logout.html │ ├── registration_complete.html │ └── registration_form.html ├── manuscript ├── Book-all.txt ├── Book.txt ├── Sample.txt ├── Subset.txt ├── appendix-intro.md ├── chapter-css.md ├── chapter-deployment.md ├── chapter-git.md ├── chapter-quick-guide.md ├── chapter-summary.md ├── chapter-system-setup.md ├── chapter-testing.md ├── chapter-unix.md ├── chapter-virtual-env.md ├── chapter-work-in-progress.md ├── chapter1.md ├── chapter10-cookies.md ├── chapter11-redux.md ├── chapter12-bootstrap.md ├── chapter13-template-tags.md ├── chapter14-bing.md ├── chapter14-webhose.md ├── chapter15-rango-exercises.md ├── chapter16-rango-hints.md ├── chapter17-jquery.md ├── chapter18-ajax.md ├── chapter2.md ├── chapter3.md ├── chapter4-templates-static-media.md ├── chapter5-database-setup.md ├── chapter6-models-templates-views.md ├── chapter7-forms.md ├── chapter8-templates.md ├── chapter9-user-authenication.md ├── images │ ├── ch-deploy-hello-world.png │ ├── ch-deploy-pa-interface.png │ ├── ch1-rango-cat-page.png │ ├── ch1-rango-index.png │ ├── ch10-bbcnews.png │ ├── ch10-cookie-visits.png │ ├── ch10-sessionid.png │ ├── ch10-test-cookie.png │ ├── ch12-about-bootstrap.png │ ├── ch12-about-nostyling.png │ ├── ch12-styled-index.png │ ├── ch12-styled-login.png │ ├── ch12-styled-register.png │ ├── ch14-bing-account.png │ ├── ch14-bing-python-search.png │ ├── ch14-bing-search-api.png │ ├── ch14-webhose-dashboard.png │ ├── ch14-webhose-query.png │ ├── ch3-about-page.png │ ├── ch3-django-powered-page.png │ ├── ch3-hey-there.png │ ├── ch3-url-chain.png │ ├── ch4-first-template.png │ ├── ch4-rango-bold-context.png │ ├── ch4-rango-picture.png │ ├── ch4-rango-site-with-alt-text.png │ ├── ch4-rango-site-with-pic.png │ ├── ch5-admin-completed.png │ ├── ch5-admin-first.png │ ├── ch5-admin-populated.png │ ├── ch5-admin-second.png │ ├── ch6-exercises.png │ ├── ch6-rango-categories-index.png │ ├── ch6-rango-links.png │ ├── ch7-add-cat.png │ ├── ch9-rango-login-message.png │ ├── ch9-rango-register-form.png │ ├── css-box-model.png │ ├── css-cascading.png │ ├── css-class.png │ ├── css-colours.png │ ├── css-ex1.png │ ├── css-ex10.png │ ├── css-ex11.png │ ├── css-ex12.png │ ├── css-ex13.png │ ├── css-ex14.png │ ├── css-ex15.png │ ├── css-ex16.png │ ├── css-ex17.png │ ├── css-ex18.png │ ├── css-ex19.png │ ├── css-ex2.png │ ├── css-ex20.png │ ├── css-ex3.png │ ├── css-ex4.png │ ├── css-ex5.png │ ├── css-ex6.png │ ├── css-ex7.png │ ├── css-ex8.png │ ├── css-ex9.png │ ├── css-id.png │ ├── css-nesting-blocks.png │ ├── css-render.png │ ├── exercises-categories.png │ ├── exercises-main.png │ ├── exercises-profile.png │ ├── exercises-results.png │ ├── exercises-suggestion.png │ ├── git-sequence.png │ ├── rango-erd.png │ ├── rango-ntier-architecture.png │ └── title_page.png ├── link_checker.py ├── out.txt ├── sort_acknowledgements.py └── todo.txt └── official_django_tutorial.pdf /.gitignore: -------------------------------------------------------------------------------- 1 | *.pdf 2 | *.pyc 3 | *.DS_Store 4 | db.sqlite3 5 | *.key 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /code/readme.md: -------------------------------------------------------------------------------- 1 | This directory will contain the code for rango. 2 | -------------------------------------------------------------------------------- /code/requirements.txt: -------------------------------------------------------------------------------- 1 | -f /usr/share/pip-wheels 2 | Django==1.9.6 3 | django-bootstrap-toolkit==2.15.0 4 | django-registration-redux==1.4 5 | Pillow==3.3.0 6 | -------------------------------------------------------------------------------- /code/tango_with_django_project/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tango_with_django_project.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /code/tango_with_django_project/media/cat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/code/tango_with_django_project/media/cat.jpg -------------------------------------------------------------------------------- /code/tango_with_django_project/populate_rango.py: -------------------------------------------------------------------------------- 1 | import os 2 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tango_with_django_project.settings') 3 | 4 | import django 5 | django.setup() 6 | 7 | from rango.models import Category, Page 8 | 9 | def populate(): 10 | 11 | python_pages = [ 12 | {"title": "Official Python Tutorial", "url":"http://docs.python.org/2/tutorial/", "views": 32}, 13 | {"title":"How to Think like a Computer Scientist", "url":"http://www.greenteapress.com/thinkpython/", "views": 16}, 14 | {"title":"Learn Python in 10 Minutes", "url":"http://www.korokithakis.net/tutorials/python/", "views": 8}, ] 15 | 16 | django_pages = [ 17 | {"title":"Official Django Tutorial", 18 | "url":"https://docs.djangoproject.com/en/1.9/intro/tutorial01/", "views": 32}, 19 | {"title":"Django Rocks", 20 | "url":"http://www.djangorocks.com/" , "views": 16}, 21 | { "title":"How to Tango with Django", 22 | "url":"http://www.tangowithdjango.com/", "views": 8 } ] 23 | 24 | other_pages = [ 25 | { "title":"Bottle", "url":"http://bottlepy.org/docs/dev/", "views": 32}, 26 | { "title":"Flask", "url":"http://flask.pocoo.org", "views": 16} ] 27 | 28 | cats = {"Python": {"pages": python_pages, "views": 128, "likes": 64}, 29 | "Django": {"pages": django_pages, "views": 64, "likes": 32}, 30 | "Other Frameworks": {"pages": other_pages, "views": 32, "likes": 16}, 31 | "Pascal": {"pages": [], "views": 32, "likes": 16}, 32 | "Perl": {"pages": [], "views": 32, "likes": 16}, 33 | "Php": {"pages": [], "views": 32, "likes": 16}, 34 | "Prolog": {"pages": [], "views": 32, "likes": 16}, 35 | "Programming": {"pages": [], "views": 32, "likes": 16}, 36 | 37 | } 38 | 39 | # if you want to add more catergories or pages, add them to the dictionaries above 40 | 41 | # The code below goes through the cats dictionary, then adds each category, 42 | # and then adds all the associated pages for that category 43 | # if you are using Python 2.x then use cats.iteritems() see 44 | # http://docs.quantifiedcode.com/python-anti-patterns/readability/not_using_items_to_iterate_over_a_dictionary.html 45 | # for more information about using items() and how to iterate over a dictionary properly 46 | 47 | # Using the .items returns the key and the value. In this case the key is "Python", "Django" or "Other Frameworks" and the value (cat_data) is the corresponding dictionary in cats. 48 | for cat, cat_data in cats.items(): 49 | # c = add_cat(cat) 50 | # Updated the population script to pass through the specific values for views and likes 51 | c = add_cat(cat, cat_data["views"], cat_data["likes"]) 52 | for p in cat_data["pages"]: 53 | add_page(c, p["title"], p["url"], p["views"]) 54 | 55 | # Print out what we have added to the user. 56 | for c in Category.objects.all(): 57 | for p in Page.objects.filter(category=c): 58 | print("- {0} - {1}".format(str(c), str(p))) 59 | 60 | def add_page(cat, title, url, views=0): 61 | p = Page.objects.get_or_create(category=cat, title=title)[0] 62 | p.url=url 63 | p.views=views 64 | # we need to save the changes we made!! 65 | p.save() 66 | return p 67 | 68 | def add_cat(name, views=0, likes=0): 69 | c = Category.objects.get_or_create(name=name)[0] 70 | c.views=views 71 | c.likes=likes 72 | c.save() 73 | return c 74 | 75 | # Start execution here! 76 | if __name__ == '__main__': 77 | print("Starting Rango population script...") 78 | populate() -------------------------------------------------------------------------------- /code/tango_with_django_project/rango/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/code/tango_with_django_project/rango/__init__.py -------------------------------------------------------------------------------- /code/tango_with_django_project/rango/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from rango.models import Category, Page 3 | from rango.models import UserProfile 4 | 5 | class PageAdmin(admin.ModelAdmin): 6 | list_display = ('title', 'category', 'url') 7 | 8 | 9 | class CategoryAdmin(admin.ModelAdmin): 10 | prepopulated_fields = {'slug':('name',)} 11 | 12 | 13 | admin.site.register(Category, CategoryAdmin) 14 | admin.site.register(Page, PageAdmin) 15 | admin.site.register(UserProfile) 16 | -------------------------------------------------------------------------------- /code/tango_with_django_project/rango/apps.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.apps import AppConfig 4 | 5 | 6 | class RangoConfig(AppConfig): 7 | name = 'rango' 8 | -------------------------------------------------------------------------------- /code/tango_with_django_project/rango/bing_search.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | # Add your Microsoft Account Key to a file called bing.key 4 | 5 | def read_bing_key(): 6 | """ 7 | reads the BING API key from a file called 'bing.key' 8 | returns: a string which is either None, i.e. no key found, or with a key 9 | remember to put bing.key in your .gitignore file to avoid committing it to the repo. 10 | """ 11 | 12 | # See Python Anti-Patterns - it is an awesome resource to improve your python code 13 | # Here we using "with" when opening documents 14 | # http://docs.quantifiedcode.com/python-anti-patterns/maintainability/not_using_with_to_open_files.html 15 | 16 | bing_api_key = None 17 | try: 18 | with open('bing.key','r') as f: 19 | bing_api_key = f.readline() 20 | except: 21 | raise IOError('bing.key file not found') 22 | 23 | return bing_api_key 24 | 25 | 26 | def run_query(search_terms): 27 | 28 | bing_api_key = read_bing_key() 29 | if not bing_api_key: 30 | raise KeyError('Bing Key Not Found') 31 | 32 | # Specify the base url and the service (Bing Search API 2.0) 33 | root_url = 'https://api.datamarket.azure.com/Bing/Search/' 34 | service = 'Web' 35 | 36 | # Specify how many results we wish to be returned per page. 37 | # Offset specifies where in the results list to start from. 38 | # With results_per_page = 10 and offset = 11, this would start from page 2. 39 | results_per_page = 10 40 | offset = 0 41 | 42 | # Wrap quotes around our query terms as required by the Bing API. 43 | # The query we will then use is stored within variable query. 44 | query = "'{0}'".format(search_terms) 45 | 46 | # Turn the query into an HTML encoded string. 47 | # We use urllib for this - differences exist between Python 2 and 3. 48 | # The try/except blocks are used to determine which function call works. 49 | # Replace this try/except block with the relevant import and query assignment. 50 | try: 51 | from urllib import parse # Python 3 import. 52 | query = parse.quote(query) 53 | except ImportError: # If the import above fails, you are running Python 2.7.x. 54 | from urllib import quote 55 | query = quote(query) 56 | 57 | # Construct the latter part of our request's URL. 58 | # Sets the format of the response to JSON and sets other properties. 59 | search_url = "{0}{1}?$format=json&$top={2}&$skip={3}&Query={4}".format( 60 | root_url, 61 | service, 62 | results_per_page, 63 | offset, 64 | query) 65 | 66 | # Setup authentication with the Bing servers. 67 | # The username MUST be a blank string, and put in your API key! 68 | username = '' 69 | 70 | #headers = {'Authorization' : 'Basic {0}'.format( b64encode(bing_api_key) )} 71 | # Create a 'password manager' which handles authentication for us. 72 | 73 | try: 74 | from urllib import request # Python 3 import. 75 | password_mgr = request.HTTPPasswordMgrWithDefaultRealm() 76 | except ImportError: # Running Python 2.7.x - import urllib2 instead. 77 | import urllib2 78 | password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm() 79 | 80 | password_mgr.add_password(None, search_url, username, bing_api_key) 81 | 82 | # Create our results list which we'll populate. 83 | results = [] 84 | 85 | try: 86 | # Prepare for connecting to Bing's servers. 87 | try: # Python 3.5 and 3.6 88 | handler = request.HTTPBasicAuthHandler(password_mgr) 89 | opener = request.build_opener(handler) 90 | request.install_opener(opener) 91 | except UnboundLocalError: # Python 2.7.x 92 | handler = urllib2.HTTPBasicAuthHandler(password_mgr) 93 | opener = urllib2.build_opener(handler) 94 | urllib2.install_opener(opener) 95 | 96 | # Connect to the server and read the response generated. 97 | try: # Python 3.5 or 3.6 98 | response = request.urlopen(search_url).read() 99 | response = response.decode('utf-8') 100 | except UnboundLocalError: # Python 2.7.x 101 | response = urllib2.urlopen(search_url).read() 102 | 103 | # Convert the string response to a Python dictionary object. 104 | json_response = json.loads(response) 105 | 106 | # Loop through each page returned, populating out results list. 107 | for result in json_response['d']['results']: 108 | results.append({ 109 | 'title': result['Title'], 110 | 'link': result['Url'], 111 | 'summary': result['Description']}) 112 | except: 113 | print("Error when querying the Bing API") 114 | 115 | # Return the list of results to the calling function. 116 | return results 117 | 118 | 119 | def main(): 120 | print("Enter a query ") 121 | query = raw_input() 122 | results = run_query(query) 123 | for result in results: 124 | print(result['title']) 125 | print('-'*len(result['title'])) 126 | print(result['summary']) 127 | print(result['link']) 128 | print() 129 | 130 | 131 | if __name__ == '__main__': 132 | main() -------------------------------------------------------------------------------- /code/tango_with_django_project/rango/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from rango.models import Page, Category, UserProfile 3 | 4 | class CategoryForm(forms.ModelForm): 5 | name = forms.CharField(max_length=128, help_text="Please enter the category name.") 6 | views = forms.IntegerField(widget=forms.HiddenInput(), initial=0) 7 | likes = forms.IntegerField(widget=forms.HiddenInput(), initial=0) 8 | slug = forms.CharField(widget=forms.HiddenInput(), required=False) 9 | 10 | # An inline class to provide additional information on the form. 11 | class Meta: 12 | # Provide an association between the ModelForm and a model 13 | model = Category 14 | fields = ('name',) 15 | 16 | class PageForm(forms.ModelForm): 17 | title = forms.CharField(max_length=128, help_text="Please enter the title of the page.") 18 | url = forms.URLField(max_length=200, help_text="Please enter the URL of the page.") 19 | views = forms.IntegerField(widget=forms.HiddenInput(), initial=0) 20 | 21 | def clean(self): 22 | cleaned_data = self.cleaned_data 23 | url = cleaned_data.get('url') 24 | 25 | # If url is not empty and doesn't start with 'http://', prepend 'http://'. 26 | if url and not url.startswith('http://'): 27 | url = 'http://' + url 28 | cleaned_data['url'] = url 29 | 30 | return cleaned_data 31 | 32 | class Meta: 33 | # Provide an association between the ModelForm and a model 34 | model = Page 35 | 36 | # What fields do we want to include in our form? 37 | # This way we don't need every field in the model present. 38 | # Some fields may allow NULL values, so we may not want to include them... 39 | # Here, we are hiding the foreign key. 40 | # we can either exclude the category field from the form, 41 | exclude = ('category',) 42 | #or specify the fields to include (i.e. not include the category field) 43 | #fields = ('title', 'url', 'views') 44 | 45 | class UserProfileForm(forms.ModelForm): 46 | website = forms.URLField(required=False) 47 | picture = forms.ImageField(required=False) 48 | 49 | class Meta: 50 | model = UserProfile 51 | exclude = ('user',) -------------------------------------------------------------------------------- /code/tango_with_django_project/rango/models.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.db import models 4 | from django.template.defaultfilters import slugify 5 | from django.contrib.auth.models import User 6 | 7 | class Category(models.Model): 8 | name = models.CharField(max_length=128, unique=True) 9 | views = models.IntegerField(default=0) 10 | likes = models.IntegerField(default=0) 11 | slug = models.SlugField(unique=True) 12 | 13 | def save(self, *args, **kwargs): 14 | self.slug = slugify(self.name) 15 | super(Category, self).save(*args, **kwargs) 16 | 17 | class Meta: 18 | verbose_name_plural = 'categories' 19 | 20 | def __str__(self): 21 | return self.name 22 | 23 | class Page(models.Model): 24 | category = models.ForeignKey(Category) 25 | title = models.CharField(max_length=128) 26 | url = models.URLField() 27 | views = models.IntegerField(default=0) 28 | 29 | def __str__(self): 30 | return self.title 31 | 32 | 33 | class UserProfile(models.Model): 34 | # This line is required. Links UserProfile to a User model instance. 35 | user = models.OneToOneField(User) 36 | # The additional attributes we wish to include. 37 | website = models.URLField(blank=True) 38 | picture = models.ImageField(upload_to='profile_images', blank=True) 39 | 40 | # Override the __unicode__() method to return out something meaningful! 41 | def __str__(self): 42 | return self.user.username -------------------------------------------------------------------------------- /code/tango_with_django_project/rango/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/code/tango_with_django_project/rango/templatetags/__init__.py -------------------------------------------------------------------------------- /code/tango_with_django_project/rango/templatetags/rango_template_tags.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from rango.models import Category 3 | 4 | register = template.Library() 5 | 6 | 7 | @register.inclusion_tag('rango/cats.html') 8 | def get_category_list(cat=None): 9 | return {'cats': Category.objects.all(), 10 | 'act_cat': cat} -------------------------------------------------------------------------------- /code/tango_with_django_project/rango/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from django.core.urlresolvers import reverse 3 | from django.contrib.staticfiles import finders 4 | 5 | # Thanks to Enzo Roiz https://github.com/enzoroiz who made these tests during an internship with us 6 | 7 | class GeneralTests(TestCase): 8 | def test_serving_static_files(self): 9 | # If using static media properly result is not NONE once it finds rango.jpg 10 | result = finders.find('images/rango.jpg') 11 | self.assertIsNotNone(result) 12 | 13 | 14 | class IndexPageTests(TestCase): 15 | 16 | def test_index_contains_hello_message(self): 17 | # Check if there is the message 'Rango Says' 18 | # Chapter 4 19 | response = self.client.get(reverse('index')) 20 | self.assertIn(b'Rango says', response.content) 21 | 22 | def test_index_using_template(self): 23 | # Check the template used to render index page 24 | # Chapter 4 25 | response = self.client.get(reverse('index')) 26 | self.assertTemplateUsed(response, 'rango/index.html') 27 | 28 | def test_rango_picture_displayed(self): 29 | # Check if is there an image called 'rango.jpg' on the index page 30 | # Chapter 4 31 | response = self.client.get(reverse('index')) 32 | self.assertIn(b'img src="/static/images/rango.jpg', response.content) 33 | 34 | def test_index_has_title(self): 35 | # Check to make sure that the title tag has been used 36 | # And that the template contains the HTML from Chapter 4 37 | response = self.client.get(reverse('index')) 38 | self.assertIn(b'', response.content) 39 | self.assertIn(b'', response.content) 40 | 41 | 42 | class AboutPageTests(TestCase): 43 | 44 | def test_about_contains_create_message(self): 45 | # Check if in the about page is there - and contains the specified message 46 | # Exercise from Chapter 4 47 | response = self.client.get(reverse('about')) 48 | self.assertIn(b'This tutorial has been put together by', response.content) 49 | 50 | 51 | def test_about_contain_image(self): 52 | # Check if is there an image on the about page 53 | # Chapter 4 54 | response = self.client.get(reverse('about')) 55 | self.assertIn(b'img src="/media/', response.content) 56 | 57 | def test_about_using_template(self): 58 | # Check the template used to render index page 59 | # Exercise from Chapter 4 60 | response = self.client.get(reverse('about')) 61 | 62 | self.assertTemplateUsed(response, 'rango/about.html') 63 | 64 | 65 | 66 | class ModelTests(TestCase): 67 | 68 | def setUp(self): 69 | try: 70 | from populate_rango import populate 71 | populate() 72 | except ImportError: 73 | print('The module populate_rango does not exist') 74 | except NameError: 75 | print('The function populate() does not exist or is not correct') 76 | except: 77 | print('Something went wrong in the populate() function :-(') 78 | 79 | 80 | def get_category(self, name): 81 | 82 | from rango.models import Category 83 | try: 84 | cat = Category.objects.get(name=name) 85 | except Category.DoesNotExist: 86 | cat = None 87 | return cat 88 | 89 | def test_python_cat_added(self): 90 | cat = self.get_category('Python') 91 | self.assertIsNotNone(cat) 92 | 93 | def test_python_cat_with_views(self): 94 | cat = self.get_category('Python') 95 | self.assertEquals(cat.views, 128) 96 | 97 | def test_python_cat_with_likes(self): 98 | cat = self.get_category('Python') 99 | self.assertEquals(cat.likes, 64) 100 | 101 | 102 | class Chapter4ViewTests(TestCase): 103 | def test_index_contains_hello_message(self): 104 | # Check if there is the message 'hello world!' 105 | response = self.client.get(reverse('index')) 106 | self.assertIn('Rango says', response.content) 107 | 108 | def test_does_index_contain_img(self): 109 | # Check if the index page contains an img 110 | response = self.client.get(reverse('index')) 111 | self.assertIn('img', response.content) 112 | 113 | def test_about_using_template(self): 114 | # Check the template used to render index page 115 | # Exercise from Chapter 4 116 | response = self.client.get(reverse('about')) 117 | 118 | self.assertTemplateUsed(response, 'rango/about.html') 119 | 120 | def test_does_about_contain_img(self): 121 | # Check if in the about page contains an image 122 | response = self.client.get(reverse('about')) 123 | self.assertIn('img', response.content) 124 | 125 | def test_about_contains_create_message(self): 126 | # Check if in the about page contains the message from the exercise 127 | response = self.client.get(reverse('about')) 128 | self.assertIn('This tutorial has been put together by', response.content) 129 | 130 | 131 | class Chapter5ViewTests(TestCase): 132 | 133 | def setUp(self): 134 | try: 135 | from populate_rango import populate 136 | populate() 137 | except ImportError: 138 | print('The module populate_rango does not exist') 139 | except NameError: 140 | print('The function populate() does not exist or is not correct') 141 | except: 142 | print('Something went wrong in the populate() function :-(') 143 | 144 | 145 | def get_category(self, name): 146 | 147 | from rango.models import Category 148 | try: 149 | cat = Category.objects.get(name=name) 150 | except Category.DoesNotExist: 151 | cat = None 152 | return cat 153 | 154 | def test_python_cat_added(self): 155 | cat = self.get_category('Python') 156 | self.assertIsNotNone(cat) 157 | 158 | def test_python_cat_with_views(self): 159 | cat = self.get_category('Python') 160 | 161 | self.assertEquals(cat.views, 128) 162 | 163 | def test_python_cat_with_likes(self): 164 | cat = self.get_category('Python') 165 | self.assertEquals(cat.likes, 64) 166 | 167 | def test_view_has_title(self): 168 | response = self.client.get(reverse('index')) 169 | 170 | #Check title used correctly 171 | self.assertIn('', response.content) 172 | self.assertIn('', response.content) 173 | 174 | # Need to add tests to: 175 | # check admin interface - is it configured and set up 176 | 177 | def test_admin_interface_page_view(self): 178 | from admin import PageAdmin 179 | self.assertIn('category', PageAdmin.list_display) 180 | self.assertIn('url', PageAdmin.list_display) 181 | 182 | 183 | class Chapter6ViewTests(TestCase): 184 | 185 | def setUp(self): 186 | try: 187 | from populate_rango import populate 188 | populate() 189 | except ImportError: 190 | print('The module populate_rango does not exist') 191 | except NameError: 192 | print('The function populate() does not exist or is not correct') 193 | except: 194 | print('Something went wrong in the populate() function :-(') 195 | 196 | 197 | # are categories displayed on index page? 198 | 199 | # does the category model have a slug field? 200 | 201 | 202 | # test the slug field works.. 203 | def test_does_slug_field_work(self): 204 | from rango.models import Category 205 | cat = Category(name='how do i create a slug in django') 206 | cat.save() 207 | self.assertEqual(cat.slug,'how-do-i-create-a-slug-in-django') 208 | 209 | # test category view does the page exist? 210 | 211 | 212 | # test whether you can navigate from index to a category page 213 | 214 | 215 | # test does index page contain top five pages? 216 | 217 | # test does index page contain the words "most liked" and "most viewed" 218 | 219 | # test does category page contain a link back to index page? 220 | 221 | 222 | class Chapter7ViewTests(TestCase): 223 | 224 | def setUp(self): 225 | try: 226 | from forms import PageForm 227 | from forms import CategoryForm 228 | 229 | except ImportError: 230 | print('The module forms does not exist') 231 | except NameError: 232 | print('The class PageForm does not exist or is not correct') 233 | except: 234 | print('Something else went wrong :-(') 235 | 236 | pass 237 | # test is there a PageForm in rango.forms 238 | 239 | # test is there a CategoryForm in rango.forms 240 | 241 | # test is there an add page page? 242 | 243 | # test is there an category page? 244 | 245 | 246 | # test if index contains link to add category page 247 | #Add a New Category
248 | 249 | 250 | # test if the add_page.html template exists. -------------------------------------------------------------------------------- /code/tango_with_django_project/rango/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | from rango import views, views_ajax 3 | 4 | urlpatterns = [ 5 | url(r'^$', views.index, name='index'), 6 | url(r'about/$', views.about, name='about'), 7 | url(r'^add_category/$', views.add_category, name='add_category'), 8 | url(r'^category/(?P[\w\-]+)/$', views.show_category, name='show_category'), 9 | url(r'^category/(?P[\w\-]+)/add_page/$', views.add_page, name='add_page'), 10 | url(r'search/$', views.search, name='search'), 11 | url(r'goto/$', views.track_url, name='goto'), 12 | url(r'like/$', views_ajax.like_category, name='like_category'), 13 | url(r'^suggest/$', views_ajax.suggest_category, name='suggest_category'), 14 | url(r'^add/$', views_ajax.auto_add_page, name='auto_add_page'), 15 | url(r'^register_profile/$', views.register_profile, name='register_profile'), 16 | url(r'^profile/(?P[\w\-]+)/$', views.profile, name='profile'), 17 | url(r'^profiles/$', views.list_profiles, name='list_profiles'), 18 | ] 19 | -------------------------------------------------------------------------------- /code/tango_with_django_project/rango/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from django.shortcuts import redirect 3 | from django.core.urlresolvers import reverse 4 | from django.http import HttpResponse 5 | from rango.models import Category, Page, UserProfile 6 | from rango.forms import CategoryForm, PageForm, UserProfileForm 7 | from datetime import datetime 8 | from rango.webhose_search import run_query 9 | from registration.backends.simple.views import RegistrationView 10 | from django.contrib.auth.decorators import login_required 11 | from django.contrib.auth.models import User 12 | from django.contrib.auth.forms import PasswordChangeForm 13 | from django.contrib.auth import authenticate, login 14 | 15 | # Create your views here. 16 | def get_server_side_cookie(request, cookie, default_val=None): 17 | val = request.session.get(cookie) 18 | if not val: 19 | val = default_val 20 | return val 21 | 22 | def visitor_cookie_handler(request): 23 | # Get the number of visits to the site. 24 | # We use the COOKIES.get() function to obtain the visits cookie. 25 | # If the cookie exists, the value returned is casted to an integer. 26 | # If the cookie doesn't exist, then the default value of 1 is used. 27 | visits = int(get_server_side_cookie(request, 'visits', '1')) 28 | 29 | last_visit_cookie = get_server_side_cookie(request, 'last_visit', str(datetime.now()) ) 30 | 31 | last_visit_time = datetime.strptime(last_visit_cookie[:-7], "%Y-%m-%d %H:%M:%S") 32 | #last_visit_time = datetime.now() 33 | # If it's been more than a day since the last visit... 34 | if (datetime.now() - last_visit_time).seconds > 0: 35 | visits = visits + 1 36 | #update the last visit cookie now that we have updated the count 37 | request.session['last_visit'] = str(datetime.now()) 38 | else: 39 | visits = 1 40 | # set the last visit cookie 41 | request.session['last_visit'] = last_visit_cookie 42 | # update/set the visits cookie 43 | request.session['visits'] = visits 44 | 45 | 46 | 47 | 48 | def index(request): 49 | #context_dict = {'boldmessage': "Crunchie, creamy, cookie, candy, cupcake!"} 50 | 51 | request.session.set_test_cookie() 52 | 53 | category_list = Category.objects.order_by('-likes')[:5] 54 | 55 | page_list = Page.objects.order_by('-views')[:5] 56 | 57 | context_dict = {'categories': category_list, 'pages': page_list} 58 | 59 | visitor_cookie_handler(request) 60 | 61 | context_dict['visits'] = request.session['visits'] 62 | 63 | print(request.session['visits']) 64 | 65 | response = render(request, 'rango/index.html', context=context_dict) 66 | 67 | return response 68 | 69 | 70 | def about(request): 71 | if request.session.test_cookie_worked(): 72 | print("TEST COOKIE WORKED!") 73 | request.session.delete_test_cookie() 74 | # To complete the exercise in chapter 4, we need to remove the following line 75 | # return HttpResponse("Rango says here is the about page. View index page") 76 | 77 | # and replace it with a pointer to ther about.html template using the render method 78 | return render(request, 'rango/about.html',{}) 79 | 80 | def show_category(request, category_name_slug): 81 | # Create a context dictionary which we can pass 82 | # to the template rendering engine. 83 | context_dict = {} 84 | 85 | try: 86 | # Can we find a category name slug with the given name? 87 | # If we can't, the .get() method raises a DoesNotExist exception. 88 | # So the .get() method returns one model instance or raises an exception. 89 | category = Category.objects.get(slug=category_name_slug) 90 | # Retrieve all of the associated pages. 91 | # Note that filter() returns a list of page objects or an empty list 92 | pages = Page.objects.filter(category=category) 93 | 94 | # Adds our results list to the template context under name pages. 95 | context_dict['pages'] = pages 96 | # We also add the category object from 97 | # the database to the context dictionary. 98 | # We'll use this in the template to verify that the category exists. 99 | context_dict['category'] = category 100 | 101 | # We get here if we didn't find the specified category. 102 | # Don't do anything - 103 | # the template will display the "no category" message for us. 104 | except Category.DoesNotExist: 105 | context_dict['category'] = None 106 | context_dict['pages'] = None 107 | 108 | 109 | # create a default query based on the category name 110 | # to be shown in the search box 111 | context_dict['query'] = category.name 112 | 113 | result_list = [] 114 | if request.method == 'POST': 115 | query = request.POST['query'].strip() 116 | if query: 117 | # Run our Webhose function to get the results list! 118 | result_list = run_query(query) 119 | context_dict['query'] = query 120 | context_dict['result_list'] = result_list 121 | 122 | 123 | # Go render the response and return it to the client. 124 | return render(request, 'rango/category.html', context_dict) 125 | 126 | 127 | 128 | 129 | def add_category(request): 130 | form = CategoryForm() 131 | # A HTTP POST? 132 | if request.method == 'POST': 133 | form = CategoryForm(request.POST) 134 | # Have we been provided with a valid form? 135 | if form.is_valid(): 136 | # Save the new category to the database. 137 | category = form.save(commit=True) 138 | print(category, category.slug) 139 | # Now that the category is saved 140 | # We could give a confirmation message 141 | # But instead since the most recent catergory added is on the index page 142 | # Then we can direct the user back to the index page. 143 | return index(request) 144 | else: 145 | # The supplied form contained errors - just print them to the terminal. 146 | print(form.errors) 147 | # Will handle the bad form (or form details), new form or no form supplied cases. 148 | # Render the form with error messages (if any). 149 | return render(request, 'rango/add_category.html', {'form': form}) 150 | 151 | 152 | def add_page(request, category_name_slug): 153 | try: 154 | category = Category.objects.get(slug=category_name_slug) 155 | except Category.DoesNotExist: 156 | category = None 157 | 158 | form = PageForm() 159 | if request.method == 'POST': 160 | form = PageForm(request.POST) 161 | if form.is_valid(): 162 | if category: 163 | page = form.save(commit=False) 164 | page.category = category 165 | page.views = 0 166 | page.save() 167 | # probably better to use a redirect here. 168 | return show_category(request, category_name_slug) 169 | else: 170 | print(form.errors) 171 | 172 | context_dict = {'form':form, 'category': category} 173 | 174 | return render(request, 'rango/add_page.html', context_dict) 175 | 176 | 177 | def search(request): 178 | result_list = [] 179 | if request.method == 'POST': 180 | query = request.POST['query'].strip() 181 | if query: 182 | # Run our Webhose function to get the results list! 183 | result_list = run_query(query) 184 | return render(request, 'rango/search.html', {'result_list': result_list}) 185 | 186 | 187 | def register(request): 188 | registered = False 189 | if request.method == 'POST': 190 | user_form = UserForm(data=request.POST) 191 | profile_form = UserProfileForm(data=request.POST) 192 | 193 | if user_form.is_valid() and profile_form.is_valid(): 194 | user = user_form.save() 195 | user.set_password(user.password) 196 | user.save() 197 | 198 | profile = profile_form.save(commit=False) 199 | profile.user = user 200 | if 'picture' in request.FILES: 201 | profile.picture = request.FILES['picture'] 202 | profile.save() 203 | registered = True 204 | else: 205 | print(user_form.errors, profile_form.errors) 206 | else: 207 | ## ON the PDF of tangowithdjango19,the e.g is like that: 208 | # else: 209 | # print(user_form.errors, profile_form.errors) 210 | # else: 211 | # user_form = UserForm() 212 | # profile_form = UserProfileForm() 213 | 214 | user_form = UserForm() 215 | profile_form = UserProfileForm() 216 | 217 | return render(request, 218 | 'rango/register.html', 219 | {'user_form': user_form, 220 | 'profile_form': profile_form, 221 | 'registered': registered 222 | }) 223 | 224 | def track_url(request): 225 | page_id = None 226 | if request.method == 'GET': 227 | if 'page_id' in request.GET: 228 | page_id = request.GET['page_id'] 229 | if page_id: 230 | try: 231 | page = Page.objects.get(id=page_id) 232 | page.views = page.views + 1 233 | page.save() 234 | return redirect(page.url) 235 | except: 236 | return HttpResponse("Page id {0} not found".format(page_id)) 237 | print("No page_id in get string") 238 | return redirect(reverse('index')) 239 | 240 | @login_required 241 | def register_profile(request): 242 | form = UserProfileForm() 243 | if request.method == 'POST': 244 | form = UserProfileForm(request.POST, request.FILES) 245 | if form.is_valid(): 246 | user_profile = form.save(commit=False) 247 | user_profile.user = request.user 248 | user_profile.save() 249 | 250 | return redirect('index') 251 | else: 252 | print(form.errors) 253 | 254 | context_dict = {'form':form} 255 | 256 | return render(request, 'rango/profile_registration.html', context_dict) 257 | 258 | class RangoRegistrationView(RegistrationView): 259 | def get_success_url(self, user): 260 | return reverse('register_profile') 261 | 262 | @login_required 263 | def profile(request, username): 264 | try: 265 | user = User.objects.get(username=username) 266 | except User.DoesNotExist: 267 | return redirect('index') 268 | 269 | userprofile = UserProfile.objects.get_or_create(user=user)[0] 270 | form = UserProfileForm({'website': userprofile.website, 'picture': userprofile.picture}) 271 | 272 | if request.method == 'POST': 273 | form = UserProfileForm(request.POST, request.FILES, instance=userprofile) 274 | if form.is_valid(): 275 | form.save(commit=True) 276 | return redirect('profile', user.username) 277 | else: 278 | print(form.errors) 279 | 280 | return render(request, 'rango/profile.html', {'userprofile': userprofile, 'selecteduser': user, 'form': form}) 281 | 282 | @login_required 283 | def list_profiles(request): 284 | # user_list = User.objects.all() 285 | userprofile_list = UserProfile.objects.all() 286 | return render(request, 'rango/list_profiles.html', { 'userprofile_list' : userprofile_list}) -------------------------------------------------------------------------------- /code/tango_with_django_project/rango/views_ajax.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from django.shortcuts import redirect 3 | from django.core.urlresolvers import reverse 4 | from django.http import HttpResponse 5 | from rango.models import Category, Page 6 | from rango.forms import CategoryForm, PageForm 7 | from datetime import datetime 8 | from rango.webhose_search import run_query 9 | 10 | from django.contrib.auth.decorators import login_required 11 | 12 | @login_required 13 | def like_category(request): 14 | cat_id = None 15 | if request.method == 'GET': 16 | cat_id = request.GET['category_id'] 17 | likes = 0 18 | if cat_id: 19 | cat = Category.objects.get(id=int(cat_id)) 20 | if cat: 21 | likes = cat.likes + 1 22 | cat.likes = likes 23 | cat.save() 24 | return HttpResponse(likes) 25 | 26 | 27 | 28 | 29 | def get_category_list(max_results=0, starts_with=''): 30 | cat_list = [] 31 | if starts_with: 32 | cat_list = Category.objects.filter(name__istartswith=starts_with) 33 | 34 | if max_results > 0: 35 | if len(cat_list) > max_results: 36 | cat_list = cat_list[:max_results] 37 | return cat_list 38 | 39 | 40 | def suggest_category(request): 41 | cat_list = [] 42 | starts_with = '' 43 | 44 | if request.method == 'GET': 45 | starts_with = request.GET['suggestion'] 46 | cat_list = get_category_list(8, starts_with) 47 | 48 | return render(request, 'rango/cats.html', {'cats': cat_list }) 49 | 50 | 51 | @login_required 52 | def auto_add_page(request): 53 | cat_id = None 54 | url = None 55 | title = None 56 | context_dict = {} 57 | if request.method == 'GET': 58 | cat_id = request.GET['category_id'] 59 | url = request.GET['url'] 60 | title = request.GET['title'] 61 | if cat_id: 62 | category = Category.objects.get(id=int(cat_id)) 63 | p = Page.objects.get_or_create(category=category, title=title, url=url) 64 | pages = Page.objects.filter(category=category).order_by('-views') 65 | # Adds our results list to the template context under name pages. 66 | context_dict['pages'] = pages 67 | 68 | return render(request, 'rango/page_list.html', context_dict) -------------------------------------------------------------------------------- /code/tango_with_django_project/rango/webhose_search.py: -------------------------------------------------------------------------------- 1 | import json 2 | import urllib 3 | import urllib2 4 | 5 | def read_webhose_key(): 6 | """ 7 | Reads the Webhose API key from a file called 'search.key'. 8 | Returns either None (no key found), or a string representing the key. 9 | Remember: put search.key in your .gitignore file to avoid committing it! 10 | """ 11 | # See Python Anti-Patterns - it's an awesome resource! 12 | # Here we are using "with" when opening files. 13 | # http://docs.quantifiedcode.com/python-anti-patterns/maintainability/ 14 | webhose_api_key = None 15 | 16 | try: 17 | with open('search.key', 'r') as f: 18 | webhose_api_key = f.readline().strip() 19 | except: 20 | raise IOError('search.key file not found') 21 | 22 | return webhose_api_key 23 | 24 | def run_query(search_terms, size=10): 25 | """ 26 | Given a string containing search terms (query), and a number of results to return (default of 10), 27 | returns a list of results from the Webhose API, with each result consisting of a title, link and summary. 28 | """ 29 | webhose_api_key = read_webhose_key() 30 | 31 | if not webhose_api_key: 32 | raise KeyError('Webhose key not found') 33 | 34 | 35 | # What's the base URL for the Webhose API? 36 | root_url = 'http://webhose.io/search' 37 | 38 | # Format the query string - escape special characters. 39 | query_string = urllib.quote(search_terms) 40 | 41 | # Use string formatting to construct the complete API URL. 42 | search_url = '{root_url}?token={key}&format=json&q={query}&sort=relevancy&size={size}'.format( 43 | root_url=root_url, 44 | key=webhose_api_key, 45 | query=query_string, 46 | size=size) 47 | 48 | results = [] 49 | 50 | try: 51 | # Connect to the Webhose API, and convert the response to a Python dictionary. 52 | print(search_url) 53 | response = urllib2.urlopen(search_url).read() 54 | print(response) 55 | json_response = json.loads(response) 56 | 57 | # Loop through the posts, appendng each to the results list as a dictionary. 58 | for post in json_response['posts']: 59 | print(post['title']) 60 | results.append({'title': post['title'], 61 | 'link': post['url'], 62 | 'summary': post['text'][:200]}) 63 | except Exception as e: 64 | print(e) 65 | print("Error when querying the Webhose API") 66 | 67 | # Return the list of results to the calling function. 68 | return results 69 | 70 | #### 71 | #### The code below is the Python 3 version. 72 | #### 73 | 74 | # import json 75 | # import urllib.parse # Py3 76 | # import urllib.request # Py3 77 | # 78 | # def read_webhose_key(): 79 | # """ 80 | # Reads the Webhose API key from a file called 'search.key'. 81 | # Returns either None (no key found), or a string representing the key. 82 | # Remember: put search.key in your .gitignore file to avoid committing it! 83 | # """ 84 | # # See Python Anti-Patterns - it's an awesome resource! 85 | # # Here we are using "with" when opening files. 86 | # # http://docs.quantifiedcode.com/python-anti-patterns/maintainability/ 87 | # webhose_api_key = None 88 | # 89 | # try: 90 | # with open('search.key', 'r') as f: 91 | # webhose_api_key = f.readline().strip() 92 | # except: 93 | # raise IOError('search.key file not found') 94 | # 95 | # return webhose_api_key 96 | # 97 | # def run_query(search_terms, size=10): 98 | # """ 99 | # Given a string containing search terms (query), and a number of results to return (default of 10), 100 | # returns a list of results from the Webhose API, with each result consisting of a title, link and summary. 101 | # """ 102 | # webhose_api_key = read_webhose_key() 103 | # 104 | # if not webhose_api_key: 105 | # raise KeyError('Webhose key not found') 106 | # 107 | # # What's the base URL for the Webhose API? 108 | # root_url = 'http://webhose.io/search' 109 | # 110 | # # Format the query string - escape special characters. 111 | # query_string = urllib.parse.quote(search_terms) # Py3 112 | # 113 | # # Use string formatting to construct the complete API URL. 114 | # search_url = '{root_url}?token={key}&format=json&q={query}&sort=relevancy&size={size}'.format( 115 | # root_url=root_url, 116 | # key=webhose_api_key, 117 | # query=query_string, 118 | # size=size) 119 | # 120 | # results = [] 121 | # 122 | # try: 123 | # # Connect to the Webhose API, and convert the response to a Python dictionary. 124 | # response = urllib.request.urlopen(search_url).read().decode('utf-8') #Py3 (library, decode) 125 | # json_response = json.loads(response) 126 | # 127 | # # Loop through the posts, appendng each to the results list as a dictionary. 128 | # for post in json_response['posts']: 129 | # results.append({'title': post['title'], 130 | # 'link': post['url'], 131 | # 'summary': post['text'][:200]}) 132 | # except: 133 | # print("Error when querying the Webhose API") 134 | # 135 | # # Return the list of results to the calling function. 136 | # return results -------------------------------------------------------------------------------- /code/tango_with_django_project/static/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/code/tango_with_django_project/static/images/favicon.ico -------------------------------------------------------------------------------- /code/tango_with_django_project/static/images/rango.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/code/tango_with_django_project/static/images/rango.jpg -------------------------------------------------------------------------------- /code/tango_with_django_project/static/js/rango-ajax.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | // JQuery code to be added in here. 3 | $('#likes').click(function(){ 4 | var catid; 5 | catid = $(this).attr("data-catid"); 6 | $.get('/rango/like_category/', {category_id: catid}, function(data){ 7 | $('#like_count').html(data); 8 | $('#likes').hide(); 9 | }); 10 | }); 11 | 12 | 13 | 14 | 15 | $('#suggestion').keyup(function(){ 16 | var query; 17 | query = $(this).val(); 18 | $.get('/rango/suggest/', {suggestion: query}, function(data){ 19 | $('#cats').html(data); 20 | }); 21 | }); 22 | 23 | $('.rango-add').click(function(){ 24 | var catid = $(this).attr("data-catid"); 25 | var url = $(this).attr("data-url"); 26 | var title = $(this).attr("data-title"); 27 | var me = $(this) 28 | $.get('/rango/add/', {category_id: catid, url: url, title: title}, function(data){ 29 | $('#pages').html(data); 30 | me.hide(); 31 | }); 32 | }); 33 | 34 | }); 35 | -------------------------------------------------------------------------------- /code/tango_with_django_project/static/js/rango-jquery.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | // JQuery code to be added in here. 3 | 4 | $("#about-btn").click( function(event) { 5 | alert("You clicked the button using JQuery!"); 6 | }); 7 | 8 | 9 | $("p").hover( function() { 10 | $(this).css('color', 'red'); 11 | }, 12 | function() { 13 | $(this).css('color', 'blue'); 14 | }); 15 | 16 | 17 | $("#about-btn").click( function(event) { 18 | msgstr = $("#msg").html() 19 | msgstr = msgstr + "ooo" 20 | $("#msg").html(msgstr) 21 | }); 22 | 23 | 24 | 25 | 26 | 27 | }); -------------------------------------------------------------------------------- /code/tango_with_django_project/tango_with_django_project/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/code/tango_with_django_project/tango_with_django_project/__init__.py -------------------------------------------------------------------------------- /code/tango_with_django_project/tango_with_django_project/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for tango_with_django_project project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.9.5. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.9/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.9/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | TEMPLATE_DIR = os.path.join(BASE_DIR, 'templates') 18 | STATIC_DIR = os.path.join(BASE_DIR, 'static') 19 | MEDIA_DIR = os.path.join(BASE_DIR, 'media') 20 | 21 | 22 | # Quick-start development settings - unsuitable for production 23 | # See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/ 24 | 25 | # SECURITY WARNING: keep the secret key used in production secret! 26 | SECRET_KEY = '_6t8%4fh3aa%9p5$562y)922e(@2q(%&pjd61f$8&jj0-q)+qy' 27 | 28 | # SECURITY WARNING: don't run with debug turned on in production! 29 | DEBUG = True 30 | 31 | ALLOWED_HOSTS = [] 32 | 33 | 34 | # Application definition 35 | 36 | INSTALLED_APPS = [ 37 | 'django.contrib.admin', 38 | 'django.contrib.auth', 39 | 'django.contrib.contenttypes', 40 | 'django.contrib.sessions', 41 | 'django.contrib.messages', 42 | 'django.contrib.staticfiles', 43 | 'rango', 44 | 'registration', 45 | ] 46 | 47 | MIDDLEWARE_CLASSES = [ 48 | 'django.middleware.security.SecurityMiddleware', 49 | 'django.contrib.sessions.middleware.SessionMiddleware', 50 | 'django.middleware.common.CommonMiddleware', 51 | 'django.middleware.csrf.CsrfViewMiddleware', 52 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 53 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 54 | 'django.contrib.messages.middleware.MessageMiddleware', 55 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 56 | ] 57 | 58 | ROOT_URLCONF = 'tango_with_django_project.urls' 59 | 60 | TEMPLATES = [ 61 | { 62 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 63 | 'DIRS': [TEMPLATE_DIR], 64 | 'APP_DIRS': True, 65 | 'OPTIONS': { 66 | 'context_processors': [ 67 | 'django.template.context_processors.debug', 68 | 'django.template.context_processors.request', 69 | 'django.contrib.auth.context_processors.auth', 70 | 'django.contrib.messages.context_processors.messages', 71 | 'django.template.context_processors.media' 72 | ], 73 | }, 74 | }, 75 | ] 76 | 77 | WSGI_APPLICATION = 'tango_with_django_project.wsgi.application' 78 | 79 | 80 | # Database 81 | # https://docs.djangoproject.com/en/1.9/ref/settings/#databases 82 | 83 | DATABASES = { 84 | 'default': { 85 | 'ENGINE': 'django.db.backends.sqlite3', 86 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 87 | } 88 | } 89 | 90 | # Password hashing functions 91 | # https://docs.djangoproject.com/en/1.9/topics/auth/passwords/#how-django-stores-passwords 92 | PASSWORD_HASHERS = [ 93 | 'django.contrib.auth.hashers.PBKDF2PasswordHasher', 94 | 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher', 95 | ] 96 | 97 | 98 | # Password validation 99 | # https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators 100 | 101 | AUTH_PASSWORD_VALIDATORS = [ 102 | { 103 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 104 | }, 105 | { 106 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 107 | 'OPTIONS': { 'min_length': 6, }, 108 | }, 109 | { 110 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 111 | }, 112 | { 113 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 114 | }, 115 | ] 116 | 117 | 118 | # Internationalization 119 | # https://docs.djangoproject.com/en/1.9/topics/i18n/ 120 | 121 | LANGUAGE_CODE = 'en-us' 122 | 123 | TIME_ZONE = 'UTC' 124 | 125 | USE_I18N = True 126 | 127 | USE_L10N = True 128 | 129 | USE_TZ = True 130 | 131 | 132 | # Static files (CSS, JavaScript, Images) 133 | # https://docs.djangoproject.com/en/1.9/howto/static-files/ 134 | 135 | STATICFILES_DIRS = [STATIC_DIR] 136 | 137 | STATIC_URL = '/static/' 138 | 139 | REGISTRATION_OPEN = True 140 | ACCOUNT_ACTIVATION_DAYS = 7 141 | REGISTRATION_AUTO_LOGIN = True 142 | LOGIN_REDIRECT_URL = '/rango/' 143 | LOGIN_URL = '/accounts/login/' 144 | 145 | MEDIA_ROOT = MEDIA_DIR 146 | MEDIA_URL = '/media/' -------------------------------------------------------------------------------- /code/tango_with_django_project/tango_with_django_project/urls.py: -------------------------------------------------------------------------------- 1 | """tango_with_django_project URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.9/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.conf.urls import url, include 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 | """ 16 | from django.conf.urls import url, include 17 | from django.contrib import admin 18 | from rango import views 19 | from django.conf import settings 20 | from django.conf.urls.static import static 21 | 22 | 23 | urlpatterns = [ 24 | url(r'^$', views.index, name='index'), 25 | url(r'^rango/', include('rango.urls')), 26 | url(r'^admin/', admin.site.urls), 27 | url(r'^accounts/register/$', views.RangoRegistrationView.as_view(), name='registration_register'), 28 | url(r'^accounts/', include('registration.backends.simple.urls')), 29 | ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) -------------------------------------------------------------------------------- /code/tango_with_django_project/tango_with_django_project/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for tango_with_django_project project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.9/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tango_with_django_project.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /code/tango_with_django_project/templates/rango/about.html: -------------------------------------------------------------------------------- 1 | {% extends 'rango/base.html' %} 2 | {% load staticfiles %} 3 | {% block title_block %} 4 | About 5 | {% endblock %} 6 | 7 | {% block body_block %} 8 |
9 |

About Page

10 | This tutorial has been put together by: leifos and maxwelld90 11 |
12 | Picture of Rango 13 |

14 | Picture of Elgin the Cat from Rango 15 |

16 | The image of the cat was taken from Deviant Art by LizzyChrome. 17 |

18 |
19 | 20 | 21 |

This text is for a Jquery Example - I'll turn red when you hover over me.

22 |
Hello - I'm here for a Jquery Example too
23 | {% endblock %} 24 | 25 | -------------------------------------------------------------------------------- /code/tango_with_django_project/templates/rango/add_category-pre-chapter12.html: -------------------------------------------------------------------------------- 1 | {% extends 'rango/base.html' %} 2 | {% load staticfiles %} 3 | {% block title_block %} 4 | Add Category 5 | {% endblock %} 6 | 7 | {% block body_block %} 8 |
9 |

Add a Category

10 |
11 |
12 |
13 | {% csrf_token %} 14 | {% for hidden in form.hidden_fields %} 15 | {{ hidden }} 16 | {% endfor %} 17 | 18 | {% for field in form.visible_fields %} 19 | {{ field.errors }} 20 | {{ field.help_text }} 21 | {{ field }} 22 | {% endfor %} 23 | 24 | 25 |
26 |
27 | 28 | {% endblock %} 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | Rango - Add Category 39 | 40 | 41 | 42 |

Add a Category

43 | 44 |
45 | 46 | {% csrf_token %} 47 | {% for hidden in form.hidden_fields %} 48 | {{ hidden }} 49 | {% endfor %} 50 | 51 | {% for field in form.visible_fields %} 52 | {{ field.errors }} 53 | {{ field.help_text }} 54 | {{ field }} 55 | {% endfor %} 56 | 57 | 58 |
59 | 60 | 61 | 62 | 63 | --> -------------------------------------------------------------------------------- /code/tango_with_django_project/templates/rango/add_category.html: -------------------------------------------------------------------------------- 1 | {% extends 'rango/base.html' %} 2 | {% load staticfiles %} 3 | {% block title_block %} 4 | Add Category 5 | {% endblock %} 6 | 7 | {% block body_block %} 8 |
9 |

Add a Category

10 |
11 | {% csrf_token %} 12 | {% for hidden in form.hidden_fields %} 13 | {{ hidden }} 14 | {% endfor %} 15 | 16 | {% for field in form.visible_fields %} 17 | {{ field.errors }} 18 | {{ field.help_text }} 19 | {{ field }} 20 | {% endfor %} 21 | 22 | 23 |
24 |
25 | 26 | {% endblock %} -------------------------------------------------------------------------------- /code/tango_with_django_project/templates/rango/add_page.html: -------------------------------------------------------------------------------- 1 | {% extends 'rango/base.html' %} 2 | {% load staticfiles %} 3 | {% block title_block %} 4 | Add Page 5 | {% endblock %} 6 | 7 | {% block body_block %} 8 |
9 | {% if category %} 10 | 11 |

Add a Page to {{category.name}}

12 | 13 |
14 | 15 | {% csrf_token %} 16 | {% for hidden in form.hidden_fields %} 17 | {{ hidden }} 18 | {% endfor %} 19 | 20 | {% for field in form.visible_fields %} 21 | {{ field.errors }} 22 | {{ field.help_text }} 23 | {{ field }} 24 | {% endfor %} 25 | 26 | 27 |
28 | 29 | {% else %} 30 | The specified category does not exist! 31 | {% endif%} 32 | 33 | 34 | 35 |
36 | 37 | {% endblock %} 38 | 39 | -------------------------------------------------------------------------------- /code/tango_with_django_project/templates/rango/base.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends 'rango/base_bootstrap.html' %} 3 | 4 | 5 | 6 | 7 | 8 | {% load staticfiles %} 9 | {% load rango_template_tags %} 10 | 11 | 12 | 13 | Rango - 14 | {% block title_block_ %} 15 | How to Tango with Django! 16 | {% endblock %} 17 | 18 | 19 | 20 | 21 |
22 | {% block sidebar_block %} 23 | {% get_category_list category %} 24 | {% endblock %} 25 |
26 | 27 |
28 | {% block body_block %} 29 | {% endblock %} 30 |
31 |
32 |
33 | 38 |
39 | 40 | 41 | 42 | --> 43 | -------------------------------------------------------------------------------- /code/tango_with_django_project/templates/rango/base_bootstrap.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% load staticfiles %} 4 | {% load rango_template_tags %} 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Rango - {% block title %}How to Tango with Django!{% endblock %} 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 63 | 64 |
65 |
66 | 82 |
83 | {% block body_block %}{% endblock %} 84 | 85 | 86 |
87 |
88 |
89 | 90 | 92 | 93 | 94 | 97 | 98 | 99 | 100 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /code/tango_with_django_project/templates/rango/category.html: -------------------------------------------------------------------------------- 1 | {% extends 'rango/base.html' %} 2 | {% load staticfiles %} 3 | {% block title_block %} 4 | {{ category.name }} 5 | {% endblock %} 6 | 7 | {% block body_block %} 8 |
9 | {% if category %} 10 | 11 |

{{ category.name }}

12 | 13 |
14 | {{ category.likes }} people like this category 15 | {% if user.is_authenticated %} 16 | 19 | {% endif %} 20 |
21 | 22 |
23 | {% if pages %} 24 |
    25 | {% for page in pages %} 26 |
  • {{ page.title }}{{page.views}}
  • 27 | {% endfor %} 28 |
29 | {% else %} 30 | No pages currently in category. 31 | {% endif %} 32 | 33 | 34 | {% else %} 35 | The specified category does not exist! 36 | {% endif %} 37 |
38 | 39 | Add a Page 40 |
41 | 42 |
43 | 44 |
45 |
47 | {% csrf_token %} 48 |
49 | 51 |
52 | 54 |
55 |
56 | {% if result_list %} 57 |

Results

58 | 59 |
60 | {% for result in result_list %} 61 |
62 |

63 | {{ result.title }} 64 |

65 |

{{ result.summary }}

66 | {% if user.is_authenticated %} 67 | 70 | {% endif %} 71 |
72 | {% endfor %} 73 |
74 | {% endif %} 75 |
76 | 77 |
78 | 79 | 80 | 81 | {% endblock %} 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /code/tango_with_django_project/templates/rango/cats.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /code/tango_with_django_project/templates/rango/index-pre-chapter12.html: -------------------------------------------------------------------------------- 1 | {% extends 'rango/base.html' %} 2 | {% load staticfiles %} 3 | {% block title_block %} 4 | Index 5 | {% endblock %} 6 | {% block body_block %} 7 |
8 |

Rango says...

9 | hey there partner! 10 |
11 |
12 |
13 |

Most Liked Categories

14 |
15 | {% if categories %} 16 | 21 | {% else %} 22 | There are no categories present. 23 | {% endif %} 24 |
25 | 26 |
27 |
28 |

Most Viewed Pages

29 |
30 | {% if pages %} 31 | 36 | {% else %} 37 | There are no categories present. 38 | {% endif %} 39 |
40 | 41 | Picture of Rango 42 | 43 | {% endblock %} -------------------------------------------------------------------------------- /code/tango_with_django_project/templates/rango/index-pre-chapter8.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% load staticfiles %} 4 | 5 | 6 | 7 | 8 | Rango 9 | 10 | 11 | 12 |

Rango says...

13 |
14 | hey there partner! 15 |
16 |
17 |
18 |

Most Liked Categories

19 |
20 | {% if categories %} 21 | 26 | {% else %} 27 | There are no categories present. 28 | {% endif %} 29 |
30 | 31 |
32 | 33 |
34 |

Most Viewed Pages

35 |
36 | {% if pages %} 37 | 42 | {% else %} 43 | There are no categories present. 44 | {% endif %} 45 |
46 | 47 | 48 |
49 | Add a New Category
50 | About Rango
51 | Picture of Rango 52 |
53 | 54 | 55 | -------------------------------------------------------------------------------- /code/tango_with_django_project/templates/rango/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'rango/base.html' %} 2 | {% load staticfiles %} 3 | {% block title_block %} 4 | Index 5 | {% endblock %} 6 | 7 | {% block body_block %} 8 | 9 |
10 |

Rango says...

11 | {% if user.is_authenticated %} 12 |

hey there {{ user.username }}

13 | {% else %} 14 |

hey there partner!

15 | {% endif %} 16 | 17 |
18 | 19 |
20 |
21 |

Most Liked Categories

22 |

23 | {% if categories %} 24 |

    25 | {% for category in categories %} 26 |
  • {{ category.name }} {{category.likes}}
  • 27 | {% endfor %} 28 |
29 | {% else %} 30 | There are no categories present. 31 | {% endif %} 32 | 33 |

34 | 35 |
36 |
37 |

Most Viewed Pages

38 |

39 | {% if pages %} 40 |

    41 | {% for page in pages %} 42 |
  • {{ page.title }} {{page.views}}
  • 43 | {% endfor %} 44 |
45 | {% else %} 46 | There are no categories present. 47 | {% endif %} 48 |

49 |
50 |
51 | 52 | Picture of Rango 53 | 54 | {% endblock %} 55 | 56 | 57 | -------------------------------------------------------------------------------- /code/tango_with_django_project/templates/rango/list_profiles.html: -------------------------------------------------------------------------------- 1 | {% extends 'rango/base_bootstrap.html' %} 2 | 3 | {% load staticfiles %} 4 | 5 | {% block title %}User Profiles{% endblock %} 6 | 7 | {% block body_block %} 8 |

User Profiles

9 | 10 |
11 | {% if userprofile_list %} 12 |
13 | 14 |
15 |
16 | {% for listuser in userprofile_list %} 17 |
18 | {% if listuser.picture %} 19 | 20 | {% else %} 21 | 22 | {% endif %} 23 | 24 |

25 | {{ listuser.user.username }} 26 |

27 | 28 | 29 | 30 |
31 | {% endfor %} 32 |
33 |
34 |
35 | {% else %} 36 |

There are no users for the site.

37 | {% endif %} 38 |
39 | {% endblock %} -------------------------------------------------------------------------------- /code/tango_with_django_project/templates/rango/page_list.html: -------------------------------------------------------------------------------- 1 | {% if pages %} 2 | 7 | {% else %} 8 | No pages currently in category. 9 | {% endif %} 10 | -------------------------------------------------------------------------------- /code/tango_with_django_project/templates/rango/profile.html: -------------------------------------------------------------------------------- 1 | {% extends 'rango/base_bootstrap.html' %} 2 | 3 | {% load staticfiles %} 4 | 5 | {% block title %}{{ user.username }} Profile{% endblock %} 6 | 7 | {% block body_block %} 8 | 11 | {% if userprofile.picture %} 12 | {{user.username}} 13 | {% else %} 14 | {{user.username}} 15 | {% endif %} 16 |

17 |
18 | {% if selecteduser.username == user.username %} 19 |
20 | {% csrf_token %} 21 | {{ form.as_p }} 22 | 23 |
24 | {% else %} 25 |

Email: {{selecteduser.email}}

26 |

Website: {{userprofile.website}}

27 | {% endif %} 28 |
29 | {% endblock %} -------------------------------------------------------------------------------- /code/tango_with_django_project/templates/rango/profile_registration.html: -------------------------------------------------------------------------------- 1 | {% extends "rango/base.html" %} 2 | 3 | 4 | {% block title_block %} 5 | Registration - Step 2 6 | {% endblock %} 7 | 8 | {% block body_block %} 9 |

Registration - Step 2

10 |
11 | {% csrf_token %} 12 | {{ form.as_p }} 13 | 14 |
15 | {% endblock %} -------------------------------------------------------------------------------- /code/tango_with_django_project/templates/rango/search.html: -------------------------------------------------------------------------------- 1 | {% extends 'rango/base.html' %} 2 | {% load staticfiles %} 3 | {% block title %} Search {% endblock %} 4 | {% block body_block %} 5 |
6 |

Search with Rango

7 |
8 |
10 | {% csrf_token %} 11 |
12 | 14 |
15 | 17 |
18 |
19 | {% if result_list %} 20 |

Results

21 | 22 |
23 | {% for result in result_list %} 24 |
25 |

26 | {{ result.title }} 27 |

28 |

{{ result.summary }}

29 |
30 | {% endfor %} 31 |
32 | {% endif %} 33 |
34 |
35 | {% endblock %} 36 | -------------------------------------------------------------------------------- /code/tango_with_django_project/templates/rango/temp.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Dashboard Template for Bootstrap 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 43 | 44 |
45 |
46 | 66 |
67 |

Dashboard

68 | 69 |
70 |
71 | Generic placeholder thumbnail 72 |

Label

73 | Something else 74 |
75 |
76 | Generic placeholder thumbnail 77 |

Label

78 | Something else 79 |
80 |
81 | Generic placeholder thumbnail 82 |

Label

83 | Something else 84 |
85 |
86 | Generic placeholder thumbnail 87 |

Label

88 | Something else 89 |
90 |
91 | 92 |

Section title

93 |
94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 |
#HeaderHeaderHeaderHeader
1,001Loremipsumdolorsit
1,002ametconsecteturadipiscingelit
1,003IntegernecodioPraesent
1,003liberoSedcursusante
1,004dapibusdiamSednisi
1,005Nullaquissemat
1,006nibhelementumimperdietDuis
1,007sagittisipsumPraesentmauris
1,008Fuscenectellussed
1,009auguesemperportaMauris
1,010massaVestibulumlaciniaarcu
1,011egetnullaClassaptent
1,012tacitisociosquadlitora
1,013torquentperconubianostra
1,014perinceptoshimenaeosCurabitur
1,015sodalesligulainlibero
219 |
220 |
221 |
222 |
223 | 224 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | {% load staticfiles %} 240 | {% load rango_template_tags %} 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | Rango - {% block title %}How to Tango with Django!{% endblock %} 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 288 | 289 |
290 |
291 | 297 |
298 | {% block body_block %}{% endblock %} 299 | 300 | 302 |
303 |
304 |
305 | 306 | 307 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | -------------------------------------------------------------------------------- /code/tango_with_django_project/templates/registration/login.html: -------------------------------------------------------------------------------- 1 | {% extends "rango/base.html" %} 2 | 3 | {% block title_block %} 4 | Login 5 | {% endblock %} 6 | 7 | {% block body_block %} 8 | 9 | 10 | 11 | 12 | 13 |
14 |

Login

15 |
16 | 17 | 27 | 28 |

Not a member? Register!

29 | {% endblock %} -------------------------------------------------------------------------------- /code/tango_with_django_project/templates/registration/logout.html: -------------------------------------------------------------------------------- 1 | {% extends "rango/base.html" %} 2 | 3 | {% block title_block %} 4 | Logout 5 | {% endblock %} 6 | 7 | {% block body_block %} 8 |

Logged Out

9 |

You are now logged out.

10 | {% endblock %} -------------------------------------------------------------------------------- /code/tango_with_django_project/templates/registration/registration_complete.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends "rango/base.html" %} 3 | 4 | {% block title_block %} 5 | Registration Complete 6 | {% endblock %} 7 | 8 | {% block body_block %} 9 |

Registration Complete

10 |

You are now registered

11 | {% endblock %} -------------------------------------------------------------------------------- /code/tango_with_django_project/templates/registration/registration_form.html: -------------------------------------------------------------------------------- 1 | {% extends "rango/base.html" %} 2 | {% block body_block %} 3 | 4 | 5 | 6 |
7 | {% csrf_token %} 8 |
9 |

10 | 11 | Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only. 12 |

13 |

14 | 15 |

16 |

17 | 18 |

19 |

20 | 21 | Enter the same password as before, for verification. 22 |

23 | 24 |
25 | 26 |
27 | {% endblock %} -------------------------------------------------------------------------------- /manuscript/Book-all.txt: -------------------------------------------------------------------------------- 1 | chapter1.md 2 | chapter2.md 3 | chapter3.md 4 | chapter4-templates-static-media.md 5 | chapter5-database-setup.md 6 | chapter6-models-templates-views.md 7 | chapter7-forms.md 8 | chapter8-templates.md 9 | 10 | chapter9-user-authenication.md 11 | chapter10-cookies.md 12 | chapter11-redux.md 13 | chapter12-bootstrap.md 14 | chapter14-webhose.md 15 | 16 | chapter-work-in-progress.md 17 | chapter15-rango-exercises.md 18 | chapter16-rango-hints.md 19 | chapter17-jquery.md 20 | chapter18-ajax.md 21 | 22 | chapter-testing.md 23 | chapter-deployment.md 24 | chapter-summary.md 25 | 26 | chapter-system-setup.md 27 | chapter-unix.md 28 | chapter-virtual-env.md 29 | chapter-git.md 30 | -------------------------------------------------------------------------------- /manuscript/Book.txt: -------------------------------------------------------------------------------- 1 | chapter1.md 2 | chapter2.md 3 | chapter3.md 4 | chapter4-templates-static-media.md 5 | chapter5-database-setup.md 6 | chapter6-models-templates-views.md 7 | chapter7-forms.md 8 | chapter8-templates.md 9 | 10 | chapter9-user-authenication.md 11 | chapter10-cookies.md 12 | chapter11-redux.md 13 | 14 | chapter12-bootstrap.md 15 | chapter14-webhose.md 16 | 17 | chapter15-rango-exercises.md 18 | chapter16-rango-hints.md 19 | chapter17-jquery.md 20 | chapter18-ajax.md 21 | 22 | chapter-testing.md 23 | chapter-deployment.md 24 | chapter-summary.md 25 | 26 | {backmatter} 27 | 28 | appendix-intro.md 29 | chapter-system-setup.md 30 | chapter-unix.md 31 | chapter-git.md 32 | chapter-css.md -------------------------------------------------------------------------------- /manuscript/Sample.txt: -------------------------------------------------------------------------------- 1 | chapter1.md 2 | chapter2.md 3 | chapter3.md 4 | chapter4-templates-static-media.md 5 | chapter5-database-setup.md 6 | chapter6-models-templates-views.md 7 | chapter7-forms.md 8 | chapter-summary.md 9 | chapter-system-setup.md 10 | chapter-unix.md 11 | chapter-virtual-env.md 12 | chapter-git.md 13 | -------------------------------------------------------------------------------- /manuscript/Subset.txt: -------------------------------------------------------------------------------- 1 | chapter1.md 2 | chapter2.md 3 | chapter3.md 4 | chapter4-templates-static-media.md 5 | chapter5-database-setup.md 6 | chapter6-models-templates-views.md 7 | chapter7-forms.md 8 | chapter8-templates.md 9 | chapter-testing.md 10 | chapter-deployment.md 11 | chapter-summary.md 12 | chapter-system-setup.md 13 | chapter-unix.md 14 | chapter-virtual-env.md 15 | chapter-git.md -------------------------------------------------------------------------------- /manuscript/appendix-intro.md: -------------------------------------------------------------------------------- 1 | # Appendices -------------------------------------------------------------------------------- /manuscript/chapter-quick-guide.md: -------------------------------------------------------------------------------- 1 | # Cheat Sheet - Quick Command Reference Guide 2 | 3 | A quick guide to common Django Commands, Functions and Methods. 4 | 5 | ## Start the Project/Application 6 | 7 | ### Create the Project 8 | To setup and create a project called ``: 9 | 10 | $ django-admin.py startproject 11 | 12 | ###Create an Application 13 | 14 | $ python manage.py startapp 15 | 16 | Remember to add the application name `` to `INSTALLED_APPS` in `settings.py`. 17 | 18 | 19 | ## Run Server 20 | To kick off the server with port ``: 21 | 22 | $ python manage.py runserver 23 | 24 | if `` is not specified, defaults to 8000. 25 | 26 | 27 | ## Database Commands 28 | 29 | 30 | ### Setup database 31 | The simplest database setup is to use an SQL Lite 3 database (which is a native database). In `settings.py`, set the `DATABASES` dictionary up as follows: 32 | 33 | 34 | ``` 35 | DATABASES = { 36 | 'default': { 37 | 'ENGINE': 'django.db.backends.sqlite3', 38 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 39 | } 40 | } 41 | ``` 42 | Ensure that `BASE_DIR` is defined and `os` is imported, i.e: 43 | 44 | ``` 45 | import os 46 | BASE_DIR = os.getcwd() 47 | ``` 48 | 49 | ### Perform Migrations 50 | To perform the migrations: 51 | 52 | $ python manage.py migrate 53 | 54 | Note: you need to run migrate before running the server for the first time for Django to setup a series of tables. 55 | 56 | ### Create a superuser 57 | Create a superuser, if you have not done so already 58 | 59 | $ python manage.py createsuperuser 60 | 61 | ### Update Database 62 | When you changes your models you will need to undertake the migration process. First make the migrations: 63 | 64 | $ python manage.py makemigrations 65 | 66 | Then apply the migrations: 67 | 68 | $ python manage.py migrate 69 | 70 | 71 | Sometime the migrations wont go smoothly because of an unresolvable conflict. If this happens, you can delete the database and all the migrations in the directory `/migrations`, and then repeat the process above. This will destroy all your data! 72 | 73 | 74 | ## Setup Paths 75 | Set up your `BASE_DIR`, in `settings.py`: 76 | 77 | ``` 78 | import os 79 | BASE_DIR = os.getcwd() 80 | ``` 81 | 82 | ### Templates Path 83 | 84 | In `settings.py` add: 85 | ``` 86 | TEMPLATE_PATH = os.path.join(BASE_DIR, 'templates') 87 | TEMPLATE_DIRS = (TEMPLATE_PATH,) 88 | ``` 89 | For each ``, create a directory called `` in this templates directory. Then refer to `/' in your code i.e. when calling `render()`. 90 | 91 | Create a Simple View 92 | -------------------- 93 | 94 | In `/views.py`: 95 | 96 | ``` 97 | from django.http import HttpResponse 98 | 99 | def index(request): 100 | return HttpResponse("Rango says hey there world!") 101 | 102 | ``` 103 | 104 | In `/urls.py`: 105 | 106 | ``` 107 | from django.conf.urls import patterns, url 108 | from import views 109 | 110 | urlpatterns = patterns('', 111 | url(r'^$', views.index, name='index')) 112 | 113 | ``` 114 | 115 | Remember to include a pointer to your `` urls in `/urls.py` add: 116 | 117 | ``` 118 | url(r'^rango/', include('rango.urls')), 119 | ``` 120 | 121 | 122 | 123 | 124 | ### Set up Static Path 125 | In `settings.py` add/include: 126 | 127 | ``` 128 | STATIC_PATH = os.path.join(BASE_DIR,'static') 129 | STATIC_URL = '/static/' 130 | ``` 131 | 132 | You may find this is already defined as such. 133 | 134 | ``` 135 | STATICFILES_DIRS = ( 136 | STATIC_PATH, 137 | ) 138 | ``` 139 | 140 | In `/urls.py` add: 141 | 142 | ``` 143 | 144 | from django.conf import settings # New Import 145 | from django.conf.urls.static import static # New Import 146 | 147 | 148 | if not settings.DEBUG: 149 | urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)``` 150 | 151 | 152 | 153 | To include staticfiles in the templates, in the template include: 154 | 155 | ``` 156 | {% load staticfiles %} 157 | ``` 158 | 159 | Reference static media with the static tag, i.e: 160 | 161 | 162 | ``` 163 | Picture of Rango 164 | ``` 165 | 166 | 167 | ###Set up the Media Server (for debugging) 168 | 169 | In `/urls.py` add: 170 | 171 | ``` 172 | if settings.DEBUG: 173 | urlpatterns += patterns( 174 | 'django.views.static', 175 | (r'^media/(?P.*)', 176 | 'serve', 177 | {'document_root': settings.MEDIA_ROOT}), ) 178 | ``` 179 | 180 | 181 | And in `settings.py` add: 182 | ``` 183 | MEDIA_URL = '/media/' 184 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media') 185 | ``` 186 | 187 | 188 | 189 | ##Models 190 | 191 | ### Create the Model 192 | 193 | In `/models.py`, create a model: 194 | 195 | ``` 196 | class (models.Model): 197 | = models.() 198 | = models.() 199 | 200 | 201 | def __unicode__(self): 202 | # need to return a unicode string 203 | return self. 204 | 205 | ``` 206 | 207 | where common ``s include `CharField`, `IntegerField`, `BooleanField`, see more at: [Django Models Field Reference](https://docs.djangoproject.com/en/1.7/ref/models/fields/) 208 | 209 | 210 | ### Register your model in the admin interface 211 | 212 | In `/admin.py` add in the code to register the interface: 213 | 214 | ``` 215 | from django.contrib import admin 216 | from rango.models import 217 | 218 | admin.site.register(model_name) 219 | ``` 220 | 221 | ###Create a population script 222 | 223 | The basic template for the population script is: 224 | 225 | ``` 226 | import os 227 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', '.settings') 228 | 229 | import django 230 | django.setup() 231 | 232 | from .models import 233 | 234 | def populate(): 235 | 236 | .objects.get_or_create('values') 237 | .... 238 | 239 | if __name__ == '__main__': 240 | print("Starting population script...") 241 | populate() 242 | 243 | ``` 244 | 245 | Save the script into the base directory and to run the population script: 246 | 247 | python populate.py 248 | 249 | Note that if you use `.objects.get_or_create()` it will return a list of records matching that values provided. 250 | 251 | Let's say you have a Person model with name and age (default=0). So you add a Person, with 252 | 253 | Person.objects.get_or_create(name='Jim', age='20') 254 | 255 | If you want to update Jim's age, and change the script so that it is: 256 | 257 | Person.objects.get_or_create(name='Jim', age='21') 258 | 259 | Then django will create a new record, and so you will have two records: (Jim, 20) and (Jim, 21) in the database. 260 | 261 | Now if you asked for: 262 | 263 | jims = Person.objects.get_or_create(name='Jim') 264 | 265 | you would have a list of records that match the name 'Jim', in this case the list would contain [ (Jim, 20), (Jim, 21) ]. If the model was empty, then a new Jim record would be created and it would return [ (Jim, 0) ] 266 | 267 | Let's say that the name property is unique and that the model contains (Jim, 20), when the script is updated with: 268 | 269 | Person.objects.get_or_create(name='Jim', age='21') 270 | 271 | and then executed, a unique constraint error will occur. This is because Django tries to create a new Jim record, because it can not find a jim who is 21 in the model. To avoid such problems, we need to first find the record by the unique fields first, i.e: 272 | 273 | jim = Person.objects.get_or_create(name='Jim')[0] 274 | 275 | Then update jim's record, note we have to select the first (and only) Jim from the list. 276 | 277 | jim.age = 21 278 | jim.save() 279 | 280 | Note we have to change the attribute and then save it! 281 | 282 | ##Create a Data-Driven Page 283 | 284 | You will need to: 285 | 286 | * create the view function to handle the request 287 | * create the template to present/house the data 288 | * create the url mapping that points to the view 289 | 290 | 291 | ### View 292 | 293 | In `views.py`, in the ``select the data that you want to present, for example: 294 | 295 | 296 | ``` 297 | def (request): 298 | n = 10 #max number of objects to take from the model 299 | data_to_show = .objects.order_by('-')[:n] 300 | context_dict = {'': data_to_show} 301 | return render (request, '/template_name.html', context_dict) 302 | 303 | ``` 304 | 305 | In this view, up to ten items from are selected, and then passed to the template. 306 | 307 | ### Template 308 | 309 | In template, `/template_name.html`, access the data: 310 | 311 | ``` 312 | {% for record in %} 313 |

{{ field. }}

314 | {% endfor %} 315 | 316 | ``` 317 | ### URL Mapping 318 | Finally you need to link your view to a URL. For the example you would add the following to `urls.py`: 319 | 320 | 321 | url(r’^datadriven/$’, view., name=‘datadriven’) 322 | 323 | 324 | 325 | ## URL Patterns 326 | 327 | Some special characters that can be put in URL patterns: 328 | 329 | * `^` starts with 330 | * `d` alphanumeric 331 | * `|` optional, i.e. drinks/mocha|expresso 332 | * `$` no trailing characters 333 | 334 | Below are a number of example URLs, URL patterns along with the matching view function headers and template references: 335 | 336 | ### URL with a 4 digit number 337 | Examples: 338 | 339 | * /year/2002/ 340 | * /year/2042/ 341 | 342 | Url Mapping: 343 | 344 | url(r’^year/(?P\d{4})/$’, view.year_view, name=‘year’) 345 | 346 | View: 347 | 348 | def year_view(request, year): 349 | 350 | Template: 351 | 352 | {% url ‘year’ 1945 %} 353 | {% url ‘year’ {{item.year}} %} 354 | 355 | where `item` is an object with an attribute year. 356 | 357 | 358 | ### URL with a four digit and two digit number 359 | 360 | Examples: 361 | 362 | * /yearmonth/2002/12/ 363 | * /yearmonth/2042/03/ 364 | 365 | 366 | Url Mapping: 367 | 368 | url(r’^year/(?P\d{4})/(?P\d{2})/$’, view.year_month_view) 369 | 370 | View: 371 | 372 | def year_month_view(request, year, month): 373 | 374 | Template: 375 | 376 | tba 377 | 378 | 379 | ### URL with a number of any length 380 | 381 | Examples: 382 | 383 | * /number/38382992/ 384 | * /number/3838/ 385 | * /number/123/ 386 | 387 | Url Mapping: 388 | 389 | url(r’^year/(?P\d+)/$’, view.number_view) 390 | 391 | View: 392 | 393 | def number_view(request, number): 394 | 395 | # URL with slug lines 396 | 397 | Examples: 398 | * /slug/monkey-brains-eaten/ 399 | * /slug/temple-destroyed/ 400 | * /slug/arc-stolen-from-temple/ 401 | 402 | Urls Mapping: 403 | 404 | url(r’^slug/(?P[\w\-]+)’, view.slug_view, name=‘slug’) 405 | 406 | The string must contain alphanumeric (`\w`) characters and/or dashes(`\-`) 407 | and there can be any number of these (i.e. `+`) 408 | 409 | views: 410 | 411 | def slug_view(request, page_slug) 412 | 413 | Templates: 414 | 415 | {% url ‘slug’ monkey-brains-eaten %} 416 | {% url ‘slug’ item.slug %} 417 | 418 | where item is an object with an attribute slug. 419 | 420 | 421 | 422 | 423 | 424 | 425 | -------------------------------------------------------------------------------- /manuscript/chapter-summary.md: -------------------------------------------------------------------------------- 1 | #Final Thoughts 2 | In this book, we have gone through the process of web development from specification to deployment. Along the way we have shown how to use the Django framework to construct the models, views and templates associated with a Web application. We have also demonstrated how toolkits and services like Bootstrap, JQuery and PythonAnywhere. can be integrated within an application. However, the road doesn't stop here. While, as we have only painted the broad brush strokes of a web application - as you have probably noticed there are lots of improvements that could be made to Rango - and these finer details often take a lot more time to complete as you polish the application. By developing your application on a firm base and good setup you will be able to construct up to 80% of your site very rapidly and get a working demo online. 3 | 4 | In future versions of this book we intend to provide some more details on various aspects of the framework, along with covering the basics of some of the other fundamental technologies associated with web development. If you have any suggestions or comments about how to improve the book please get in touch. 5 | 6 | Please report any bugs, problems, etc., or submit change requests [via GitHub](https://github.com/leifos/tango_with_django_19/). Thank you! 7 | 8 | ## Acknowledgements 9 | This book was written to help teach web application development to computing science students. In writing the book and the tutorial, we have had to rely upon the awesome Django community and the Django Documentation for the answers and solutions. This book is really the combination of that knowledge pieced together in the context of building Rango. 10 | 11 | We would also like to thank all the people who have helped to improve this resource by sending us comments, suggestions, Git issues and pull requests. If you've sent in changes over the years, please do remind us if you are not on the list! 12 | 13 | %% Keep the BEGIN and END for the sorting script to know where the list starts and ends. 14 | %% BEGIN ACKNOWLEDGEMENTS LIST 15 | 16 | **Adam Kikowski,** 17 | [Adam Mertz](https://github.com/Amertz08), 18 | **Alessio Oxilia,** 19 | Ally Weir, 20 | **[bernieyangmh](https://github.com/bernieyangmh),** 21 | [Breakerfall](https://github.com/breakerfall), 22 | **[Brian](https://github.com/flycal6),** 23 | [Burak K.](https://github.com/McMutton), 24 | **Burak Karaboga,** 25 | [Can Ibanoglu](https://github.com/canibanoglu), 26 | **Charlotte,** 27 | [Claus Conrad](https://github.com/cconrad), 28 | **[Codenius](https://twitter.com/Codenius),** 29 | [cspollar](https://github.com/cspollar), 30 | **Dan C,** 31 | [Darius](https://github.com/dariushazimi), 32 | **David Manlove,** 33 | [David Skillman](https://github.com/reggaedit), 34 | **[Deep Sukhwani](https://github.com/ProProgrammer)** 35 | [Devin Fitzsimons](https://github.com/aisflat439), 36 | **[Dhiraj Thakur](https://github.com/dhirajt),** 37 | Duncan Drizy, 38 | **[Gerardo A-C](https://github.com/gerac83),** 39 | [Giles T.](https://github.com/gpjt), 40 | **[Grigoriy M](https://github.com/GriMel),** 41 | James Yeo, 42 | **[Jan Felix Trettow](https://twitter.com/JanFelixTrettow),** 43 | Joe Maskell, 44 | **[Jonathan Sundqvist](https://github.com/jonathan-s),** 45 | Karen Little, 46 | **[Kartik Singhal](https://github.com/k4rtik),** 47 | [koviusesGitHub](https://github.com/koviusesGitHub), 48 | **[Krace Kumar](https://github.com/kracekumar),** 49 | [ma-152478](https://github.com/ma-152478), 50 | **Manoel Maria,** 51 | [Martin de G.](https://github.com/martindegroot), 52 | **[Matevz P.](https://github.com/matonsjojc),** 53 | [mHulb](https://github.com/mHulb), 54 | **[Michael Herman](https://github.com/mjhea0),** 55 | [Michael Ho Chum](https://github.com/michaelchum), 56 | **[Mickey P.](https://github.com/mickeypash),** 57 | Mike Gleen, 58 | **[nCrazed](https://github.com/nCrazed),** 59 | Nitin Tulswani, 60 | **[nolan-m](https://github.com/nolan-m),** 61 | Oleg Belausov, 62 | **[pawonfire](https://github.com/pawonfire),** 63 | [pdehaye](https://github.com/pdehaye), 64 | **[Peter Mash](https://github.com/PeterMash),** 65 | [Pierre-Yves Mathieu](https://github.com/pywebdesign), 66 | **Praestgias,** 67 | [pzkpfwVI](https://github.com/pzkpfwVI), 68 | **[Ramdog](https://github.com/ramdog),** 69 | [Rezha Julio](https://github.com/kimiamania), 70 | **[rnevius](https://github.com/rnevius),** 71 | Sadegh Kh, 72 | **[Saex](https://github.com/SaeX),** 73 | [Saurabh Tandon](https://twitter.com/saurabhtand), 74 | **Serede Sixty Six,** 75 | Svante Kvarnstrom, 76 | **Tanmay Kansara,** 77 | Thomas Murphy, 78 | **[Thomas Whyyou](https://twitter.com/thomaswhyyou),** 79 | William Vincent, and 80 | **[Zhou](https://github.com/AugustLONG).** 81 | 82 | 83 | %% END ACKNOWLEDGEMENTS LIST 84 | 85 | Thank you all very much! 86 | -------------------------------------------------------------------------------- /manuscript/chapter-testing.md: -------------------------------------------------------------------------------- 1 | # Automated Testing {#chapter-testing} 2 | It is good practice to get into the habit of writing and developing tests. A lot of software engineering is about writing and developing tests and test suites in order to ensure the software is robust. Of course, most of the time, we are too busy trying to build things to bother about making sure that they work. Or too arrogant to believe it would fail. 3 | 4 | According to the [Django Tutorial](https://docs.djangoproject.com/en/1.9/intro/tutorial05/), there are numerous reasons why you should include tests. 5 | 6 | - Test will save you time: a change in a complex system can cause failures in unpredictable places. 7 | - Tests don't just identify problems, they prevent them: tests show where the code is not meeting expectations. 8 | - Test make your code more attractive: *"Code without tests is broken by design"* - Jacob Kaplan-Moss, one of Django's original developers. 9 | - Tests help teams work together: they make sure your team doesn't inadvertently break your code. 10 | 11 | According to the [Python Guide](http://docs.python-guide.org/en/latest/writing/tests/), there are a number of general rules you should try to follow when writing tests. Below are some main rules. 12 | 13 | - Tests should focus on one small bit of functionality 14 | - Tests should have a clear purpose 15 | - Tests should be independent. 16 | - Run your tests, before you code, and before your commit and push 17 | your code. 18 | - Even better create a hook that tests code on push. 19 | - Use long and descriptive names for tests. 20 | 21 | I> ###Testing in Django 22 | I> Currently this chapter provides the very basics of testing and follows a similar format to the [Django Tutorial](https://docs.djangoproject.com/en/1.9/intro/tutorial05/), with some additional notes. We hope to expand this further in the future. 23 | 24 | ## Running Tests 25 | With Django is a suite of functionality to test apps built. You can test your Rango app by issuing the following command: 26 | 27 | {lang="text",linenos=off} 28 | $ python manage.py test rango 29 | 30 | Creating test database for alias 'default'... 31 | 32 | ---------------------------------------------------------------------- 33 | Ran 0 tests in 0.000s 34 | 35 | OK 36 | Destroying test database for alias 'default'... 37 | 38 | This will run through the tests associated with the Rango app. At the moment, nothing much happens. That is because you may have noticed the file `rango/tests.py` only contains an import statement. Every time you create an application, Django automatically creates such a file to encourage you to write tests. 39 | 40 | From this output, you might also notice that a database called `default` is referred to. When you run tests, a temporary database is constructed, which your tests can populate, and perform operations on. This way your testing is performed independently of your live database. 41 | 42 | ### Testing the models in Rango 43 | Let's create a test. In the `Category` model, we want to ensure that views are either zero or positive, because the number of views, let's say, can never be less than zero. To create a test for this we can put the following code into `rango/tests.py`: 44 | 45 | {lang="python", linenos=off} 46 | from django.test import TestCase 47 | from rango.models import Category 48 | 49 | class CategoryMethodTests(TestCase): 50 | def test_ensure_views_are_positive(self): 51 | """ 52 | ensure_views_are_positive should results True for categories 53 | where views are zero or positive 54 | """ 55 | cat = Category(name='test',views=-1, likes=0) 56 | cat.save() 57 | self.assertEqual((cat.views >= 0), True) 58 | 59 | The first thing you should notice, if you have not written tests before, is that we have to inherit from `TestCase`. The naming over the method in the class also follows a convention, all tests start with `test_` and they also contain some type of assertion, which is the test. Here we are checking if the values are equal, with the `assertEqual` method, but other types of assertions are also possible. See the [Python 2 Documentation on unit tests]() or the [Python 3 Documentation on unit tests](https://docs.python.org/3/library/unittest.html) for other commands (i.e. `assertItemsEqual`, `assertListEqual`, `assertDictEqual`, etc). Django's testing machinery is derived from Python's but also provides a number of other asserts and specific test cases. 60 | 61 | Now let's run the test: 62 | 63 | {lang="text",linenos=off} 64 | $ python manage.py test rango 65 | 66 | Creating test database for alias 'default'... 67 | F 68 | ====================================================================== 69 | FAIL: test_ensure_views_are_positive (rango.tests.CategoryMethodTests) 70 | ---------------------------------------------------------------------- 71 | Traceback (most recent call last): 72 | File "/Users/leif/Code/tango_with_django_project_19/rango/tests.py", 73 | line 12, in test_ensure_views_are_positive 74 | self.assertEqual((cat.views>=0), True) 75 | AssertionError: False != True 76 | 77 | ---------------------------------------------------------------------- 78 | Ran 1 test in 0.001s 79 | 80 | FAILED (failures=1) 81 | 82 | As we can see this test fails. This is because the model does not check whether the value is less than zero or not. Since we really want to ensure that the values are non-zero, we will need to update the model to ensure that this requirement is fulfilled. Do this now by adding some code to the `save()` method of the `Category` model. The code should check the value of `views` attribute, and update it accordingly. A simple conditional check of `self.views` where it is less than zero should suffice here. 83 | 84 | Once you have updated your model, you can now re-run the test, and see if your code now passes it. If not, try again. 85 | 86 | Let's try adding another test that ensures an appropriate slug line is created, i.e. one with dashes, and in lowercase. Add the following code to `rango/tests.py`: 87 | 88 | {lang="python", linenos=off} 89 | def test_slug_line_creation(self): 90 | """ 91 | slug_line_creation checks to make sure that when we add 92 | a category an appropriate slug line is created 93 | i.e. "Random Category String" -> "random-category-string" 94 | """ 95 | cat = cat('Random Category String') 96 | cat.save() 97 | self.assertEqual(cat.slug, 'random-category-string') 98 | 99 | Does your code still work? 100 | 101 | ### Testing Views 102 | So far we have written tests that focus on ensuring the integrity of the data housed in the models. Django also provides testing mechanisms to test views. It does this with a mock client, that internally makes a calls a Django view via the URL. In the test you have access to the response (including the HTML) and the context dictionary. 103 | 104 | Let's create a test that checks that when the index page loads, it displays the message that `There are no categories present`, when the `Category` model is empty. 105 | 106 | {lang="python",linenos=off} 107 | from django.core.urlresolvers import reverse 108 | 109 | class IndexViewTests(TestCase): 110 | 111 | def test_index_view_with_no_categories(self): 112 | """ 113 | If no questions exist, an appropriate message should be displayed. 114 | """ 115 | response = self.client.get(reverse('index')) 116 | self.assertEqual(response.status_code, 200) 117 | self.assertContains(response, "There are no categories present.") 118 | self.assertQuerysetEqual(response.context['categories'], []) 119 | 120 | First of all, the Django `TestCase` has access to a `client` object, which can make requests. Here, it uses the helper function `reverse` to look up the URL of the `index` page. Then it tries to get that page, where the `response` is stored. The test then checks a number of things: whether the page loaded OK, whether the response HTML contains the phrase `"There are no categories present."`, and whether the context dictionary contains an empty categories list. Recall that when you run tests, a new database is created, which by default is not populated. 121 | 122 | Let's now check the resulting view when categories are present. First add a helper method. 123 | 124 | {lang="python",linenos=off} 125 | from rango.models import Category 126 | 127 | def add_cat(name, views, likes): 128 | c = Category.objects.get_or_create(name=name)[0] 129 | c.views = views 130 | c.likes = likes 131 | c.save() 132 | return c 133 | 134 | Then add another method to the `class IndexViewTests(TestCase)`: 135 | 136 | {lang="python",linenos=off} 137 | def test_index_view_with_categories(self): 138 | """ 139 | Check to make sure that the index has categories displayed 140 | """ 141 | 142 | add_cat('test',1,1) 143 | add_cat('temp',1,1) 144 | add_cat('tmp',1,1) 145 | add_cat('tmp test temp',1,1) 146 | 147 | response = self.client.get(reverse('index')) 148 | self.assertEqual(response.status_code, 200) 149 | self.assertContains(response, "tmp test temp") 150 | 151 | num_cats =len(response.context['categories']) 152 | self.assertEqual(num_cats , 4) 153 | 154 | In this test, we populate the database with four categories, and then check that the loaded page contains the text `tmp test temp` and if the number of categories is equal to 4. Note that this makes three checks, but is only considered to be one test. 155 | 156 | ### Testing the Rendered Page 157 | It is also possible to perform tests that load up the application and programmatically interact with the DOM elements on the HTML pages by using either Django's test client and/or Selenium, which is are "in-browser" frameworks to test the way the HTML is rendered in a browser. 158 | 159 | ## Coverage Testing 160 | Code coverage measures how much of your code base has been tested, and how much of your code has been put through its paces via tests. You can install a package called `coverage` via with `pip install coverage` that automatically analyses how much code coverage you have. Once you have `coverage` installed, run the following command: 161 | 162 | {lang="text",linenos=off} 163 | $ coverage run --source='.' manage.py test rango 164 | 165 | This will run through all the tests and collect the coverage data for the Rango application. To see the coverage report you need to then type: 166 | 167 | {lang="text",linenos=off} 168 | $ coverage report 169 | 170 | Name Stmts Miss Cover 171 | -------------------------------------------------------------- 172 | manage 6 0 100% 173 | populate 33 33 0% 174 | rango/__init__ 0 0 100% 175 | rango/admin 7 0 100% 176 | rango/forms 35 35 0% 177 | rango/migrations/0001_initial 5 0 100% 178 | rango/migrations/0002_auto_20141015_1024 5 0 100% 179 | rango/migrations/0003_category_slug 5 0 100% 180 | rango/migrations/0004_auto_20141015_1046 5 0 100% 181 | rango/migrations/0005_userprofile 6 0 100% 182 | rango/migrations/__init__ 0 0 100% 183 | rango/models 28 3 89% 184 | rango/tests 12 0 100% 185 | rango/urls 12 12 0% 186 | rango/views 110 110 0% 187 | tango_with_django_project/__init__ 0 0 100% 188 | tango_with_django_project/settings 28 0 100% 189 | tango_with_django_project/urls 9 9 0% 190 | tango_with_django_project/wsgi 4 4 0% 191 | -------------------------------------------------------------- 192 | TOTAL 310 206 34% 193 | 194 | We can see from the above report that critical parts of the code have not been tested, i.e. `rango/views`. The `coverage` package [has many more features](http://nedbatchelder.com/code/coverage/) that you can explore to make your tests even more comprehensive! 195 | 196 | X> ###Exercises 197 | X> 198 | X> Lets say that we want to extend the `Page` to include two additional fields, `last_visit` and 199 | X> `first_visit` that will be of type `timedate`. 200 | X> 201 | X> - Update the model to include these two fields. 202 | X> - Update the add page functionality, and the goto functionality. 203 | X> - Add in a test to ensure the last visit or first visit is not in the future. 204 | X> - Add in a test to ensure that the last visit equal to or after the first visit. 205 | X> - Run through [Part Five of the official Django Tutorial](https://docs.djangoproject.com/en/1.9/intro/tutorial05/) to learn more about testing. 206 | X> - Check out the [tutorial on test driven development by Harry Percival](http://www.tdd-django-tutorial.com). 207 | -------------------------------------------------------------------------------- /manuscript/chapter-virtual-env.md: -------------------------------------------------------------------------------- 1 | #Virtual Environments {#chapter-virtual-environments} 2 | Virtual environments allow multiple installations of Python and their relevant 3 | packages to exist in harmony. This is the generally accepted approach to 4 | configuring a Python setup nowadays. 5 | 6 | They are pretty easy to setup. Assuming you have `pip` installed, you can install the following packages: 7 | 8 | {lang="text",linenos=off} 9 | $ pip install virtualenv 10 | $ pip install virtualenvwrapper 11 | 12 | 13 | The first package provides you with the infrastructure to create a 14 | virtual environment. See [a non-magical introduction to Pip and 15 | Virtualenv for Python 16 | Beginners](http://dabapps.com/blog/introduction-to-pip-and-virtualenv-python/) 17 | by Jamie Matthews for details about using `virtualenv`. However, using 18 | just *virtualenv* alone is rather complex. The second package provides a 19 | wrapper to the functionality in the `virtualenv` package and makes life a 20 | lot easier. The wrapper 21 | provides a series of extensions by [Doug 22 | Hellman](http://doughellmann.com/) to the original `virtualenv` tool, 23 | making it easier for us to create, delete and use virtual environments. 24 | 25 | If you are using a linux/unix based OS, then to use the wrapper you need 26 | to call the following shell script from your command line: 27 | 28 | {lang="text",linenos=off} 29 | $ source virtualenvwrapper.sh 30 | 31 | It is a good idea to add this to your bash/profile script. You therefore don't 32 | have to run it each and every time you want to use a virtual environment. However, if you are using windows, then install the 33 | [virtualenvwrapper-win](https://pypi.python.org/pypi/virtualenvwrapper-win) 34 | package: 35 | 36 | {lang="text",linenos=off} 37 | $ pip install virtualenvwrapper-win 38 | 39 | Now you should be all set to create a virtual environment: 40 | 41 | {lang="text",linenos=off} 42 | $ mkvirtualenv rango 43 | 44 | You can list the virtual environments created with `lsvirtualenv`, and 45 | you can activate a virtual environment as follows: 46 | 47 | {lang="text",linenos=off} 48 | $ workon rango 49 | (rango)$ 50 | 51 | Your prompt with change and the current virtual environment will be 52 | displayed, i.e. rango. Now within this environment you will be able to 53 | install all the packages you like, without interfering with your 54 | standard or other environments. Try `pip list` to see you don't have 55 | `Django` or `Pillow` installed in your virtual environment. You can now 56 | install them with `pip` so that they exist in your virtual environment. 57 | 58 | In our [chapter on deployment](#chapter-deploy), we will go through a similar process when deploying your application to [PythonAnywhere](https://www.pythonanywhere.com/?affiliate_id=000116e3). 59 | 60 | -------------------------------------------------------------------------------- /manuscript/chapter-work-in-progress.md: -------------------------------------------------------------------------------- 1 | #Work In Progress 2 | 3 | 4 | We are part way through updating the book to Django 1.9 - the rest will be coming soon! 5 | 6 | Please give us your feedback on the book and if you have any changes that you think would improve the book, you can submit a Git request to our GitHub account at https://github.com/leifos/tango_with_django_19. 7 | 8 | W> ### Warning 9 | W> You can attempt the content following this page, but it may be incomplete or require polishing. -------------------------------------------------------------------------------- /manuscript/chapter11-redux.md: -------------------------------------------------------------------------------- 1 | # User Authentication with `Django-Registration-Redux` {#chapter-redux} 2 | In the [previous chapter](#chapter-user), we added in login and registration functionality by manually coding up the URLs, views and templates. However, such functionality is common to many web application so developers have created numerous add-on applications that can be included in your Django project to reduce the amount of code required to provide login, registration, one-step and two-step authentication, password chaning, password recovery, etc. In this chapter, we will be using the package `django-registration-redux` to provide these facilities. 3 | 4 | This will mean we will need to re-factor our code to remove the login and registration functionality we previously created, and then setup and configure our project to include the `django-registration-redux` application. This chapter also will provide you with some experience of using external applications and show you how easily they can be plugged into your Django project. 5 | 6 | ## Setting up Django Registration Redux 7 | To start we need to first install `django-registration-redux` version 1.4 into your environment using `pip`. 8 | 9 | {lang="text",linenos=off} 10 | pip install -U django-registration-redux==1.4 11 | 12 | 13 | Now that it is installed, we need to tell Django that we will be using this application. Open up the `settings.py` file, and update the `INSTALLED_APPS` list: 14 | 15 | {lang="python",linenos=off} 16 | INSTALLED_APPS = [ 17 | 'django.contrib.admin', 18 | 'django.contrib.auth', 19 | 'django.contrib.contenttypes', 20 | 'django.contrib.sessions', 21 | 'django.contrib.messages', 22 | 'django.contrib.staticfiles', 23 | 'rango', 24 | 'registration' # add in the registration package 25 | ] 26 | 27 | 28 | While you are in the `settings.py` file you can also add the following variables that are part of the registrations package's configuration (these settings should be pretty self explanatory): 29 | 30 | {lang="python",linenos=off} 31 | # If True, users can register 32 | REGISTRATION_OPEN = True 33 | # One-week activation window; you may, of course, use a different value. 34 | ACCOUNT_ACTIVATION_DAYS = 7 35 | # If True, the user will be automatically logged in. 36 | REGISTRATION_AUTO_LOGIN = True 37 | # The page you want users to arrive at after they successfully log in 38 | LOGIN_REDIRECT_URL = '/rango/' 39 | # The page users are directed to if they are not logged in, 40 | # and are trying to access pages requiring authentication 41 | LOGIN_URL = '/accounts/login/' 42 | 43 | In `tango_with_django_project/urls.py`, you can now update the `urlpatterns` so that it includes a reference to the registration package: 44 | 45 | {lang="python",linenos=off} 46 | url(r'^accounts/', include('registration.backends.simple.urls')), 47 | 48 | The `django-registration-redux` package provides a number of different registration backends, depending on your needs. For example you may want a two-step process, where user is sent a confirmation email, and a verification link. Here we will be using the simple one-step registration process, where a user sets up their account by entering in a username, email, and password, and is automatically logged in. 49 | 50 | ## Functionality and URL mapping 51 | The Django Registration Redux package provides the machinery for numerous functions. In the `registration.backend.simple.urls`, it provides the following mappings: 52 | 53 | - registration -\> `/accounts/register/` 54 | - registration complete -\> `/accounts/register/complete/` 55 | - login -\> `/accounts/login/` 56 | - logout -\> `/accounts/logout/` 57 | - password change -\> `/password/change/` 58 | - password reset -\> `/password/reset/` 59 | 60 | while in the `registration.backends.default.urls` it also provides the functions for activating the account in a two stage process: 61 | 62 | - activation complete (used in the two-step registration) -\> `activate/complete/` 63 | - activate (used if the account action fails) -\> `activate//` 64 | - activation email (notifies the user an activation email has been sent out) 65 | 66 | > - activation email body (a text file, that contains the activation email text) 67 | > - activation email subject (a text file, that contains the subject line of the activation email) 68 | 69 | Now the catch. While Django Registration Redux provides all this functionality, it does not provide the templates because these tend to be application specific. So we need to create the templates associated with each view. 70 | 71 | ## Setting up the Templates 72 | In the [Django Registration Redux Quick Start Guide](https://django-registration-redux.readthedocs.org/en/latest/quickstart.html), 73 | it provides an overview of what templates are required, but it is not immediately clear what goes within each template. Rather than try and work it out from the code, we can take a look at a set of [templates written by Anders Hofstee](https://github.com/macdhuibh/django-registration-templates) to quickly get the gist of what we need to code up. 74 | 75 | First, create a new directory in the `templates` directory, called `registration`. This is where we will house all the pages associated with the Django Registration Redux application, as it will look in this directory for the templates it requires. 76 | 77 | ### Login Template {#section-redux-templates-login} 78 | In `templates/registration` create the file, `login.html` with the following code: 79 | 80 | {lang="html",linenos=off} 81 | {% extends "rango/base.html" %} 82 | {% block body_block %} 83 |

Login

84 |
85 | {% csrf_token %} 86 | {{ form.as_p }} 87 | 88 | 89 |
90 |

91 | Not a member? 92 | Register 93 |

94 | {% endblock %} 95 | 96 | Notice that whenever a URL is referenced, the `url` template tag is used to reference it. If you visit, `http://127.0.0.1:8000/accounts/` then you will see the list of URL mappings, and the names associated with each URL (assuming that `DEBUG=True` in `settings.py`). 97 | 98 | ### Registration Template 99 | In `templates/registration` create the file, `registration_form.html` with the following code: 100 | 101 | {lang="html",linenos=off} 102 | {% extends "rango/base.html" %} 103 | {% block body_block %} 104 |

Register Here

105 |
106 | {% csrf_token %} 107 | {{ form.as_p }} 108 | 109 |
110 | {% endblock %} 111 | 112 | ### Registration Complete Template 113 | In `templates/registration` create the file, `registration_complete.html` with the following code: 114 | 115 | {lang="html",linenos=off} 116 | {% extends "rango/base.html" %} 117 | {% block body_block %} 118 |

Registration Complete

119 |

You are now registered

120 | {% endblock %} 121 | 122 | ### Logout Template 123 | In `templates/registration` create the file, `logout.html` with the following code: 124 | 125 | {lang="html",linenos=off} 126 | {% extends "rango/base.html" %} 127 | {% block body_block %} 128 |

Logged Out

129 |

You are now logged out.

130 | {% endblock %} 131 | 132 | ### Try out the Registration Process 133 | Run the server and visit: Note how the registration form contains two fields for password - so that it can be checked. Try registering, but enter different passwords. 134 | 135 | While this works, not everything is hooked up. 136 | 137 | ### Refactoring your project 138 | Now you will need to update the `base.html` so that the new registration URLs and views are used. 139 | 140 | - Update register to point to ``. 141 | - Update login to point to ``. 142 | - Update logout to point to ``. 143 | - In `settings.py`, update `LOGIN_URL` to be `'/accounts/login/'`. 144 | 145 | Notice that for the logout, we have included a `?next=/rango/`. This is so when the user logs out, it will redirect them to the index page of Rango. If we exclude it, then they will be directed to the log out page (but that would not be very nice). 146 | 147 | Next, decommission the `register`, `login`, `logout` functionality from the `rango` application, i.e. remove the URLs, views, and templates (or comment them out). 148 | 149 | ### Modifying the Registration Flow {#section-redux-templates-flow} 150 | At the moment, when users register, it takes them to the registration complete page. This feels a bit clunky; so instead, we can take them to the main index page. Overriding the `RegistrationView provided by registration.backends.simple.views` can do this. Update the `tango_with_django_project/urls.py` by importing `RegistrationView`, add in the following registration class. 151 | 152 | {lang="python",linenos=off} 153 | from registration.backends.simple.views import RegistrationView 154 | 155 | # Create a new class that redirects the user to the index page, 156 | #if successful at logging 157 | class MyRegistrationView(RegistrationView): 158 | def get_success_url(self, user): 159 | return '/rango/' 160 | 161 | Then update the `urlpatterns` list in your Django project's `urls.py` module by adding the following line before the pattern for `accounts`. Note that this is *not* the `urls.py` module within the `rango` directory! 162 | 163 | {lang="python",linenos=off} 164 | url(r'^accounts/register/$', 165 | MyRegistrationView.as_view(), 166 | name='registration_register'), 167 | 168 | This will allow for `accounts/register` to be matched before any other `accounts/` URL. This allows us to redirect `accounts/register` to our customised registration view. 169 | 170 | X> ###Exercise and Hints 171 | X> - Provide users with password change functionality. 172 | X> - Hint: see [Anders Hofstee's Templates](https://github.com/macdhuibh/django-registration-templates/tree/master/registration) to get started. 173 | X> - Hint: the URL to change passwords is `accounts/password/change/` and the URL to denote the password has been changed is: `accounts/password/change/done/` 174 | -------------------------------------------------------------------------------- /manuscript/chapter13-template-tags.md: -------------------------------------------------------------------------------- 1 | #Template Tags 2 | 3 | 4 | ##Providing Categories on Every Page 5 | 6 | It would be nice to show the different categories that users can browse 7 | through in the sidebar on each page. Given what we have learnt so far we 8 | could do the following: 9 | 10 | - In the `base.html` template we could add some code to display an 11 | item list of categories, if the category list has been passed 12 | through. 13 | - Then in each view, we could access the Category object, get all the 14 | categories, and return that in the context dictionary. 15 | 16 | However, this is a pretty nasty solution. It requires a lot of cutting 17 | and pasting of code. Also, we will run into problems, when we want to 18 | show the categories on pages serviced by the django-registration-redux 19 | package. So we need a different approach, by using `templatetags` that 20 | are included in the template that request the data required. 21 | 22 | ###Using Template Tags 23 | 24 | Create a directory `rango/templatetags`, and create two files, one 25 | called `__init__.py`, which will be empty, and another called, 26 | `rango_extras.py`, where you can add the following code: 27 | 28 | {lang="python",linenos=off} 29 | from django import template 30 | from rango.models import Category 31 | 32 | register = template.Library() 33 | 34 | @register.inclusion_tag('rango/cats.html') 35 | def get_category_list(): 36 | return {'cats': Category.objects.all()} 37 | 38 | As you can see we have made a method called, `get_category_list()` which 39 | returns the list of categories, and that is assocaited with a template 40 | called `rango/cats.html`. Now create a template called 41 | ''rango/cats.html`in the templates directory with the following code: 42 | 43 | {lang="html",linenos=off} 44 | 53 | 54 | Now in your`base.html`you can access the template tag by first loading it up with`{% 55 | load rango\_extras %}`and then slotting it into the page with`{% 56 | get\_category\_list 57 | %}`, i.e.: 58 | 59 | {lang="html",linenos=off} 60 | 65 | 66 | 67 | I> Restart Server to Register Tags 68 | I> 69 | I> You will need to restart your server every time you modify the template tags. Otherwise they will not be registered, and you will be really confused why your code is not working. 70 | 71 | ###Parameterised Template Tags 72 | 73 | Now lets extend this so that when we visit a category page, it highlights which category we are in. To do this we need to paramterise the templatetag. So update the method in`rango\_extras.py`to be: 74 | 75 | {lang="python",linenos=off} 76 | def get_category_list(cat=None): 77 | return {'cats': Category.objects.all(), 'act_cat': cat} 78 | 79 | 80 | This lets us pass through the category we are on. We can now update the`base.html`to pass through the category, if it exists. 81 | 82 | {lang="html",linenos=off} 83 | 84 | 89 | 90 | Now update the`cats.html` template: 91 | 92 | {lang="html",linenos=off} 93 | {% for c in cats %} 94 | {% if c == act_cat %} 95 |
  • 96 | {% else %} 97 |
  • 98 | {% endif %} 99 | {{ c.name }}
  • 100 | {% endfor %} 101 | 102 | 103 | Here we check to see if the category being displayed is the same as the category being passed through (i.e.`act\_cat`), if so, we assign the`active`class to it from Bootstrap (http://getbootstrap.com/components/#nav). Restart the development web server, and now visit the pages. We have passed through the`category`variable. When you view a category page, the template has access to the`category`variable, and so provides a value to the template tag`get\_category\_list()`. This is then used in the`cats.html\`\` 104 | template to select which category to highlight as active. 105 | -------------------------------------------------------------------------------- /manuscript/chapter15-rango-exercises.md: -------------------------------------------------------------------------------- 1 | #Making Rango Tango! Exercises {#chapter-ex} 2 | So far we have been adding in different pieces of functionality to Rango. We've been building up the application in this manner to get you familiar with the Django Framework, and to learn about how to construct the various parts of an application. However, at the moment, Rango is not very cohesive or interactive. In this chapter, we challenge you to improve the application and its user experience by bringing together some of the functionality that we have already implemented along with some other features. 3 | 4 | To make Rango more coherent, integrated and interactive, it would be nice to add the following functionality. 5 | 6 | - Track the clickthroughs of Categories and Pages, i.e.: 7 | - count the number of times a category is viewed 8 | - count the number of times a page is viewed via Rango, and 9 | - collect likes for categories (see [Django and Ajax Chapter](#chapter-ajax)). 10 | - Integrate the browsing and searching within categories, i.e.: 11 | - instead of having a disconnected search page, let users search for pages on each specific category page, and 12 | - let users filter the set of categories shown in the side bar (see [Django and Ajax Chapter](#chapter-ajax)). 13 | - Provide services for Registered Users, i.e.: 14 | - Assuming you have switched the `django-registration-redux`, we need to setup the registration form to collect the additional information (i.e. website, profile picture) 15 | - let users view their profile 16 | - let users edit their profile, and 17 | - let users see the list of users and their profiles. 18 | 19 | I> ### Note 20 | I> We won't be working through all of these tasks right now. Some will be taken care of in the [Django and Ajax Chapter]({#chapter-ajax}), while others will be left to you to complete as additional exercises. 21 | 22 | Before we start to add this additional functionality we will make a todo list to plan our workflow for each task. Breaking tasks down into sub-tasks will greatly simplify the implementation so that we are attacking each one with a clear plan. In this chapter, we will provide you with the workflow for a number of the above tasks. From what you have learnt so far, you should be able to fill in the gaps and implement most of it on your own (except those requiring AJAX). In the following chapter, we have included hints, tips and code snippets elaborating on how to implement these features. Of course, if you get really stuck, you can always check out our implementation on GitHub. 23 | 24 | ## Track Page Clickthroughs 25 | Currently, Rango provides a direct link to external pages. This is not very good if you want to track the number of times each page is clicked and viewed. To count the number of times a page is viewed via Rango you will need to perform the following steps. 26 | 27 | - Create a new view called `track_url()`, and map it to URL `/rango/goto/` and name it `'name=goto'`. 28 | - The `track_url()` view will examine the HTTP `GET` request parameters and pull out the `page_id`. The HTTP `GET` requests will look something like `/rango/goto/?page_id=1`. 29 | - In the view, select/get the `page` with `page_id` and then increment the associated `views` field, and `save()` it. 30 | - Have the view redirect the user to the specified URL using Django's `redirect` method. Remember to include the import, `from django.shortcuts import redirect` 31 | - If no parameters are in the HTTP `GET` request for `page_id`, or the parameters do not return a `Page` object, redirect the user to Rango's homepage. Use the `reverse` method from `django.core.urlresolvers` to get the URL string and then redirect. If you are using Django 1.10, then you can import the `reverse` method from `django.shortcuts`. 32 | - See [Django Shortcut Functions](https://docs.djangoproject.com/en/1.9/topics/http/shortcuts/) for more on `redirect` and `reverse`. 33 | - Update the `category.html` so that it uses `/rango/goto/?page_id=XXX`. 34 | - Remember to use the `url` template tag instead of using the direct URL i.e. 35 | 36 | {lang="python",linenos=off} 37 | 38 | 39 | 40 | I> ### `GET` Parameters Hint 41 | I> 42 | I> If you're unsure of how to retrieve the `page_id` *querystring* from the HTTP `GET` request, the following code sample should help you. 43 | I> 44 | I> {lang="python",linenos=off} 45 | I> page_id = None 46 | I> if request.method == 'GET': 47 | I> if 'page_id' in request.GET: 48 | I> page_id = request.GET['page_id'] 49 | I> 50 | I> Always check the request method is of type `GET` first, then you can access the dictionary `request.GET` which contains values passed as part of the request. If `page_id` exists within the dictionary, you can pull the required value out with `request.GET['page_id']`. 51 | I> 52 | I> You could also do this without using a *querystring*, but through the URL instead, i.e. `/rango/goto//`. In which case you would need to create a `urlpattern` that pulls out the `page_id`, i.e. `r'goto/(?P\d+)/$'`. 53 | 54 | 55 | ## Searching Within a Category Page 56 | Rango aims to provide users with a helpful directory of useful web pages. At the moment, the search functionality is essentially independent of the categories. It would be nicer to have search integrated within the categories. We will assume that a user will first browse through the category of interest. If they can't find a relevant page, they can then search. If they find a page that is relevant, then they can add it to the category. Let's focus on the first problem, of putting search on the category page. To do this, perform the following steps: 57 | 58 | - Remove the generic *Search* link from the menu bar, i.e. we are decommissioning the global search functionality. 59 | - Take the search form and results template markup from `search.html` and place it into `category.html`. 60 | - Update the search form so that action refers back to the category page, i.e.: 61 | 62 | {lang="html",linenos=off} 63 |
    65 | 66 | - Update the category view to handle a HTTP `POST` request. The view must then include any search results in the context dictionary for the template to render. 67 | - Also, lets make it so that only authenticated users can search. So to restrict access within the `category.html` template use: 68 | 69 | {lang="python",linenos=off} 70 | {% if user.authenticated %} 71 | 72 | {% endif %} 73 | 74 | ## Create and View Profiles 75 | If you have swapped over to the `django-registration-redux` package, then you'll have to collect the `UserProfile` data. To do this, instead of redirecting the user to the Rango index page, you will need to redirect them to a new form, to collect the user's profile picture and URL details. To add the UserProfile registration functionality, you need to: 76 | 77 | - create a `profile_registration.html` which will display the `UserProfileForm`; 78 | - create a `UserProfileForm` `ModelForm` class to handle the new form; 79 | - create a `register_profile()` view to capture the profile details; 80 | - map the view to a URL, i.e. `rango/register_profile/`; and 81 | - in the `MyRegistrationView`, update the `get_success_url()` to point to `rango/add_profile/`. 82 | 83 | Another useful feature is to let users inspect and edit their own profile. Undertake the following steps to add this functionality. 84 | 85 | - First, create a template called `profile.html`. In this template, add in the fields associated with the user profile and the user (i.e. username, email, website and picture). 86 | - Create a view called `profile()`. This view will obtain the data required to render the user profile template. 87 | - Map the URL `/rango/profile/` to your new `profile()` view. 88 | - In the base template add a link called *Profile* into the menu bar, preferably with other user-related links. This should only be available to users who are logged in (i.e. `{% if user.is_authenticated %}`). 89 | 90 | To let users browse through user profiles, you can also create a users page that lists all the users. If you click on a user page, then you can see their profile. However, you must make sure that a user is only able to edit their profile! 91 | 92 | T> ### Referencing Uploaded Content in Templates 93 | T> 94 | T> If you have successfully completed all of the [Templates and Media chapter](#section-templates-upload), your Django setup should be ready to deal with the uploading and serving of user media files. You should be able to reference the `MEDIA_URL` URL (defined in `settings.py`) in your templates through use of the `{{ MEDIA_URL }}` tag, provided by the [media template context processor](https://docs.djangoproject.com/en/1.9/ref/templates/api/#django-template-context-processors-media), e.g. ``. 95 | 96 | In the next chapter, we provide a series of hints and tips to help you complete the aforementioned features. -------------------------------------------------------------------------------- /manuscript/chapter17-jquery.md: -------------------------------------------------------------------------------- 1 | #JQuery and Django 2 | JQuery rocks! JQuery is a library written in JavaScript that lets you access the power of JavaScript without the pain. This is because a few lines of JQuery often encapsulates hundreds of lines of JavaScript. Also, JQuery provides a suite of functionality that is mainly focused on manipulating HTML elements. In this chapter, we will describe: 3 | 4 | - how to incorporate JQuery within your Django app; 5 | - explain how to interpret JQuery code; and 6 | - and provide a number of small examples. 7 | 8 | ## Including JQuery in Your Django Project/App 9 | In your *base* template include a reference to: 10 | 11 | {lang="html",linenos=off} 12 | {% load staticfiles %} 13 | 15 | 16 | or if you have downloaded and saved a copy to your static directory, then you can reference it as follows: 17 | 18 | {lang="html",linenos=off} 19 | {% load staticfiles %} 20 | 21 | 22 | 23 | Make sure you have your static files set up (see [Chapter Templates and Static Media](#chapter-templates-static)) 24 | 25 | In the `static` directory, create a *js* directory and place the JQuery JavaScript file (`jquery.js`) here along with an file called `rango-jquery.js`. This script will house our JavaScript code. In `rango-jquery.js`, add the following JavaScript: 26 | 27 | {lang="javascript",linenos=off} 28 | $(document).ready(function() { 29 | // JQuery code to be added in here. 30 | }); 31 | 32 | This piece of JavaScript utilises JQuery. It first selects the document object (with `$(document)`), and then makes a call to `ready()`. Once the document is ready (i.e. the complete page is loaded), the anonymous function denoted by `function() { }` will be executed. It is pretty typical, if not standard, to wait until the document has been finished loading before running the JQuery functions. Otherwise, the code may begin executing before all the HTML elements have been downloaded. See the [JQuery Documentation on Ready](http://api.jquery.com/ready/) for more details. 33 | 34 | I> ### Select and Act Pattern 35 | I> JQuery requires you to think in a more *functional* programming style, as opposed to the typical JavaScript style which is often written in a more *procedural* programming style. For all the JQuery commands, they follow a similar pattern: **Select and Act**. Select an element, and then perform some action on/with the element. 36 | 37 | ### Example Popup Box on Click 38 | In this example, we want to show you the difference between doing the same functionality in standard JavaScript versus JQuery. In your `about.html` template, add the following piece of code: 39 | 40 | {lang="html",linenos=off} 41 | 45 | 46 | As you can see, we are assigning the function `alert()` to the `onClick` handler of the button. Load up the `about` page, and try it out. Now lets do it using JQuery, by first adding another button: 47 | 48 | {lang="html",linenos=off} 49 | 51 |

    This is a example

    52 |

    This is another example

    53 | 54 | Notice that there is no JavaScript code associated with the button currently. We will be doing that with the following code added to `rango-jquery.js`: 55 | 56 | {lang="javascript",linenos=off} 57 | $(document).ready( function() { 58 | $("#about-btn").click( function(event) { 59 | alert("You clicked the button using JQuery!"); 60 | }); 61 | }); 62 | 63 | Reload the page, and try it out. Hopefully, you will see that both buttons pop up an alert. 64 | 65 | The JQuery/JavaScript code here first selects the document object, and when it is ready, it executes the functions within its body, i.e. `$("#about-btn").click()`. This code selects the element in the page with an `id` equal to `about-btn`, and then programatically assigns to the `click` event the `alert()` function. 66 | 67 | At first, you might think that JQuery is rather cumbersome, as it requires us to include a lot more code to do the same thing. This may be true for a simple function like `alert()`. For more complex functions, it is much cleaner as the JQuery/JavaScript code is maintained in a separate file. This is because we assign the event handler at runtime rather than statically within the code. We achieve separation of concerns between the JQuery/JavaScript code and the HTML markup. 68 | 69 | T> ###Keep Them Separated 70 | T> [Separation of Concerns](https://en.wikipedia.org/wiki/Separation_of_concerns) is a design principle that is good to keep in mind. In terms of web apps, the HTML is responsible for the page content; CSS is used to style the presentation of the content, while JavaScript is responsible for how the user can interact with the content, and manipulating the content and style. 71 | T> 72 | T> By keeping them separated, you will have cleaner code and you will reduce maintenance woes in the future. 73 | T> 74 | T> Put another way, *never mix, never worry!* 75 | 76 | ### Selectors 77 | There are different ways to select elements in JQuery. The above example shows how the `#` selector can be used to find elements with a particular `id` in your HTML document. To find classes, you can use the `.` selector, as shown in the example below. 78 | 79 | {lang="javascript",linenos=off} 80 | $(".ouch").click( function(event) { 81 | alert("You clicked me! ouch!"); 82 | }); 83 | 84 | Then all elements in the document that have the `class="ouch"` would be selected, and assigned to its on click handler, the `alert()` function. Note that all the elements would be assigned the same function. 85 | 86 | HTML tags can also be selected by referring to the tag in the selector: 87 | 88 | {lang="javascript",linenos=off} 89 | $("p").hover( function() { 90 | $(this).css('color', 'red'); 91 | }, 92 | function() { 93 | $(this).css('color', 'blue'); 94 | }); 95 | 96 | Add this JavaScript to your `rango-jquery.js`, and then in the `about.html` template, add a paragraph, `

    This text is for a JQuery Example

    `. Try it out, go to the about page and hover over the text. 97 | 98 | Here, we are selecting all the `p` HTML elements, and on hover we are associated two functions, one for on hover, and the other for hover off. You can see that we are using another selector called, `this`, which selects the element in question, and then sets its colour to red or blue respectively. Note that the JQuery `hover()` function takes [two functions](http://api.jquery.com/hover/), and the JQuery [`click()`](http://api.jquery.com/click/) function requires the event to be passed through. 99 | 100 | Try adding the above code your `rango-jquery.js` file, making sure it is within the `$(document).ready()` function. What happens if you change the `$(this)` to `$(p)`? 101 | 102 | Hovering is an example of a mouse move event. For descriptions on other such events, see the [JQuery API documentation](http://api.jquery.com/category/events/mouse-events/). 103 | 104 | ## DOM Manipulation Example 105 | In the above example, we used the `hover` function to assign an event handler to the on hover event, and then used the `css` function to change the colour of the element. The `css` function is one example of DOM manipulation, however, the standard JQuery library provides many other ways in which to manipulate the DOM. For example, we can add classes to elements, with the `addClass` function: 106 | 107 | {lang="javascript",linenos=off} 108 | $("#about-btn").addClass('btn btn-primary') 109 | 110 | This will select the element with `id` `#about-btn`, and assign the classes `btn` and `btn-primary` to it. By adding these Bootstrap classes, the button will now appear in the Bootstrap style (assuming you are using the Bootstrap toolkit). 111 | 112 | It is also possible to access the inner HTML of a particular element. For example, lets put a `div` in the `about.html` template: 113 | 114 | {lang="html",linenos=off} 115 |
    Hello - I'm here for a JQuery Example too
    116 | 117 | Then add the following JQuery to `rango-jquery.js`: 118 | 119 | {lang="javascript",linenos=off} 120 | $("#about-btn").click( function(event) { 121 | msgstr = $("#msg").html() 122 | msgstr = msgstr + "ooo" 123 | $("#msg").html(msgstr) 124 | }); 125 | 126 | When the element with `id` `#about-btn` is clicked, we first get the HTML inside the element with `id` `msg` and append `"o"` to it. We then change the HTML inside the element by calling the `html()` function again, but this time passing through string `msgstr` to replace the HTML inside that element. 127 | 128 | In this chapter, we have provided a very rudimentary guide to using JQuery and how you can incorporate it within your Django app. From here, you should be able to understand how JQuery operates and experiment with the different functions and libraries provided by JQuery and JQuery developers. In the next chapter, we will be using JQuery to help provide AJAX functionality within Rango. -------------------------------------------------------------------------------- /manuscript/chapter2.md: -------------------------------------------------------------------------------- 1 | # Getting Ready to Tango 2 | Before we get down to coding, it's really important that we get our development environment setup so that you can *Tango with Django!* You'll need to ensure that you have all the necessary components installed on your computer. This chapter outlines the five key components that you need to be aware of, setup and use. These are listed below. 3 | 4 | * Working with the [terminal](https://en.wikipedia.org/wiki/Terminal_emulator) or [Command Prompt](https://en.wikipedia.org/wiki/Cmd.exe). 5 | * *Python* and your *Python* installation. 6 | * The Python Package Manager *pip* and *virtual environments*. 7 | * Your *Integrated Development Environment (IDE)*, if you choose to use one. 8 | * A *Version Control System (VCS)*, *Git*. 9 | 10 | If you already have Python 2.7/3.4/3.5 and Django 1.9/1.10 installed on your computer, and are familiar with the technologies mentioned, then you can skip straight to the [Django Basics chapter](#chapter-django-basics). Otherwise, below we provide an overview of the different components and why they are important. We also provide a series of pointers on how to setup the various components. 11 | 12 | I> ### Your Development Environment 13 | I> Setting up your development environment is pretty tedious and often frustrating. It's not something that you'd do everyday. Below, we have put together the list of core technologies you need to get started and pointers on how to install them. 14 | I> 15 | I> From experience, we can also say that it's a good idea when setting your development environment up to note down the steps you took. You'll need them again one day - whether because you have purchased a new computer, or you have been asked to help someone else set their computer up! Taking a note of everything you do will save you time and effort in the future. Don't just think short term! 16 | 17 | ## Python 18 | To work with Tango with Django, we require you to have installed on your computer a copy of the Python programming language. Any version from the `2.7` family - with a minimum of `2.7.5` - or version `3.4+` will work fine. If you're not sure how to install Python and would like some assistance, have a look at [the chapter dealing with installing Python](#section-system-setup-python). 19 | 20 | I> ### Not sure how to use Python? 21 | I> If you haven't used Python before - or you simply wish to brush up on your skills - then we highly recommend that you check out and work through one or more of the following guides: 22 | I> 23 | I> * [**Learn Python in 10 Minutes**](http://www.korokithakis.net/tutorials/python/) by Stavros; 24 | I> * [**The Official Python Tutorial**](http://docs.python.org/2/tutorial/); 25 | I> * [**Think Python: How to Think like a Computer Scientist**](http://www.greenteapress.com/thinkpython/) by Allen B. Downey; or 26 | I> * [**Learn to Program**](https://www.coursera.org/course/programming1) by Jennifer Campbell and Paul Gries. 27 | I> 28 | I> These will get you familiar with the basics of Python so you can start developing using Django. Note you don't need to be an expert in Python to work with Django. Python is awesome and you can pick it up as you go, if you already know another programming language. 29 | 30 | 31 | 32 | ## The Python Package Manager 33 | Pip is the python [package manager](https://en.wikipedia.org/wiki/Package_manager). The package manager allows you install various libraries for the Python programming language to enhance its functionality. 34 | 35 | A package manager, whether for Python, your [operating system](https://en.wikipedia.org/wiki/Advanced_Packaging_Tool) or [some other environment](https://docs.npmjs.com/cli/install), is a software tool that automates the process of installing, upgrading, configuring and removing *packages* - that is, a package of software which you can use on your computer. This is opposed to downloading, installing and maintaining software manually. Maintaining Python packages is pretty painful. Most packages often have *dependencies* so these need to be installed too. Then these packages may conflict or require particular versions which need to be resolved. Also, the system path to these packages needs to be specified and maintained. Luckily *pip* handles all this for you - so you can sit back and relax. 36 | 37 | Try and run pip with the command `$ pip`. If the command is not found, you'll need to install pip itself - check out the [system setup chapter](#chapter-system-setup) for more information. You should also ensure that the following packages are installed on your system. Run the following commands to install Django and [pillow](https://pillow.readthedocs.io/en/5.0.0/) (an image manipulation library for Python). 38 | 39 | {lang="bash",linenos=off} 40 | $ pip install -U django==1.9.10 41 | $ pip install pillow 42 | 43 | I> ### Problems Installing `pillow`? 44 | I> When installing Pillow, you may receive an error stating that the installation failed due to a lack of JPEG support. 45 | I> This error is shown as the following: 46 | I> 47 | I> {lang="text",linenos=off} 48 | I> ValueError: jpeg is required unless explicitly disabled using 49 | I> --disable-jpeg, aborting 50 | I> 51 | I> If you receive this error, try installing Pillow *without* JPEG support enabled, with the following command. 52 | I> 53 | I> {lang="text",linenos=off} 54 | I> pip install pillow --global-option="build_ext" 55 | I> --global-option="--disable-jpeg" 56 | I> 57 | I> While you obviously will have a lack of support for handling JPEG images, Pillow should then install without problem. Getting Pillow installed is enough for you to get started with this tutorial. For further information, check out the [Pillow documentation](http://pillow.readthedocs.io/en/3.2.x/installation.html). 58 | 59 | 60 | ## Virtual Environments 61 | 62 | We're almost all set to go! However, before we continue, it's worth pointing out that while this setup is fine to begin with, there are some drawbacks. What if you had another Python application that requires a different version to run, or you wanted to switch to the new version of Django, but still wanted to maintain your Django 1.9 project? 63 | 64 | The solution to this is to use [virtual environments](http://simononsoftware.com/virtualenv-tutorial/). Virtual environments allow multiple installations of Python and their relevant packages to exist in harmony. This is the generally accepted approach to configuring a Python setup nowadays. 65 | 66 | Setting up a virtual environment is not necessarily but it is highly recommended. The [virtual environment chapter](#chapter-virtual-environments) details how to setup, create and use virtual environments. 67 | 68 | 69 | ## Integrated Development Environment 70 | While not absolutely necessary, a good Python-based IDE can be very helpful to you during the development process. Several exist, with perhaps [*PyCharm*](http://www.jetbrains.com/pycharm/) by JetBrains and *PyDev* (a plugin of the [Eclipse IDE](http://www.eclipse.org/downloads/)) standing out as popular choices. The [Python Wiki](http://wiki.python.org/moin/IntegratedDevelopmentEnvironments) provides an up-to-date list of Python IDEs. 71 | 72 | Research which one is right for you, and be aware that some may require you to purchase a licence. Ideally, you'll want to select an IDE that supports integration with Django. 73 | 74 | We use PyCharm as it supports virtual environments and Django integration - though you will have to configure the IDE accordingly. We don't cover that here - although JetBrains do provide a [guide on setting PyCharm up](https://www.jetbrains.com/help/pycharm/2016.1/creating-and-running-your-first-django-project.html). 75 | 76 | ## Code Repository 77 | We should also point out that when you develop code, you should always house your code within a version-controlled repository such as [SVN](http://subversion.tigris.org/) or [Git](http://git-scm.com/). We won't be explaining this right now, so that we can get stuck into developing an application in Django. We have however written a [chapter providing a crash course on Git](#chapter-git) for your reference that you can refer to later on. **We highly recommend that you set up a Git repository for your own projects.** 78 | 79 | X> ###Exercises 80 | X> 81 | X> To get comfortable with your environment, try out the following exercises. 82 | X> 83 | X> - Install Python 2.7.5+/3.4+ and Pip. 84 | X> - Play around with your *command line interface (CLI)* and create a directory called `code`, which we use to create our projects in. 85 | X> - Setup your Virtual Environment (optional) 86 | X> - Install the Django and Pillow packages 87 | X> - Setup an account on a Git Repository site like: GitHub, BitBucket, etc if you haven't already done so. 88 | X> - Download and setup an Integrated Development Environment like [PyCharm](https://www.jetbrains.com/pycharm/) 89 | X> 90 | X> As previously stated, we've made the code for the book and application available on our [GitHub repository](https://github.com/leifos/tango_with_django_19/). 91 | X> 92 | X> - If you spot any errors or problem, please let us know by making a change request on GitHub. 93 | X> - If you have any problems with the exercises, you can check out the repository to see how we completed them. 94 | 95 | D> ### What is a Directory? 96 | D> In the text above, we refer to creating a *directory*. But what exactly is a *directory*? If you have used a Windows computer up until now, you'll know a directory as a *folder*. The concept of a folder is analogous to a directory - it is a cataloguing structure that contains references to other files and directories. 97 | -------------------------------------------------------------------------------- /manuscript/images/ch-deploy-hello-world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch-deploy-hello-world.png -------------------------------------------------------------------------------- /manuscript/images/ch-deploy-pa-interface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch-deploy-pa-interface.png -------------------------------------------------------------------------------- /manuscript/images/ch1-rango-cat-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch1-rango-cat-page.png -------------------------------------------------------------------------------- /manuscript/images/ch1-rango-index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch1-rango-index.png -------------------------------------------------------------------------------- /manuscript/images/ch10-bbcnews.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch10-bbcnews.png -------------------------------------------------------------------------------- /manuscript/images/ch10-cookie-visits.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch10-cookie-visits.png -------------------------------------------------------------------------------- /manuscript/images/ch10-sessionid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch10-sessionid.png -------------------------------------------------------------------------------- /manuscript/images/ch10-test-cookie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch10-test-cookie.png -------------------------------------------------------------------------------- /manuscript/images/ch12-about-bootstrap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch12-about-bootstrap.png -------------------------------------------------------------------------------- /manuscript/images/ch12-about-nostyling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch12-about-nostyling.png -------------------------------------------------------------------------------- /manuscript/images/ch12-styled-index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch12-styled-index.png -------------------------------------------------------------------------------- /manuscript/images/ch12-styled-login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch12-styled-login.png -------------------------------------------------------------------------------- /manuscript/images/ch12-styled-register.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch12-styled-register.png -------------------------------------------------------------------------------- /manuscript/images/ch14-bing-account.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch14-bing-account.png -------------------------------------------------------------------------------- /manuscript/images/ch14-bing-python-search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch14-bing-python-search.png -------------------------------------------------------------------------------- /manuscript/images/ch14-bing-search-api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch14-bing-search-api.png -------------------------------------------------------------------------------- /manuscript/images/ch14-webhose-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch14-webhose-dashboard.png -------------------------------------------------------------------------------- /manuscript/images/ch14-webhose-query.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch14-webhose-query.png -------------------------------------------------------------------------------- /manuscript/images/ch3-about-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch3-about-page.png -------------------------------------------------------------------------------- /manuscript/images/ch3-django-powered-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch3-django-powered-page.png -------------------------------------------------------------------------------- /manuscript/images/ch3-hey-there.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch3-hey-there.png -------------------------------------------------------------------------------- /manuscript/images/ch3-url-chain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch3-url-chain.png -------------------------------------------------------------------------------- /manuscript/images/ch4-first-template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch4-first-template.png -------------------------------------------------------------------------------- /manuscript/images/ch4-rango-bold-context.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch4-rango-bold-context.png -------------------------------------------------------------------------------- /manuscript/images/ch4-rango-picture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch4-rango-picture.png -------------------------------------------------------------------------------- /manuscript/images/ch4-rango-site-with-alt-text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch4-rango-site-with-alt-text.png -------------------------------------------------------------------------------- /manuscript/images/ch4-rango-site-with-pic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch4-rango-site-with-pic.png -------------------------------------------------------------------------------- /manuscript/images/ch5-admin-completed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch5-admin-completed.png -------------------------------------------------------------------------------- /manuscript/images/ch5-admin-first.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch5-admin-first.png -------------------------------------------------------------------------------- /manuscript/images/ch5-admin-populated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch5-admin-populated.png -------------------------------------------------------------------------------- /manuscript/images/ch5-admin-second.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch5-admin-second.png -------------------------------------------------------------------------------- /manuscript/images/ch6-exercises.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch6-exercises.png -------------------------------------------------------------------------------- /manuscript/images/ch6-rango-categories-index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch6-rango-categories-index.png -------------------------------------------------------------------------------- /manuscript/images/ch6-rango-links.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch6-rango-links.png -------------------------------------------------------------------------------- /manuscript/images/ch7-add-cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch7-add-cat.png -------------------------------------------------------------------------------- /manuscript/images/ch9-rango-login-message.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch9-rango-login-message.png -------------------------------------------------------------------------------- /manuscript/images/ch9-rango-register-form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/ch9-rango-register-form.png -------------------------------------------------------------------------------- /manuscript/images/css-box-model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/css-box-model.png -------------------------------------------------------------------------------- /manuscript/images/css-cascading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/css-cascading.png -------------------------------------------------------------------------------- /manuscript/images/css-class.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/css-class.png -------------------------------------------------------------------------------- /manuscript/images/css-colours.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/css-colours.png -------------------------------------------------------------------------------- /manuscript/images/css-ex1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/css-ex1.png -------------------------------------------------------------------------------- /manuscript/images/css-ex10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/css-ex10.png -------------------------------------------------------------------------------- /manuscript/images/css-ex11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/css-ex11.png -------------------------------------------------------------------------------- /manuscript/images/css-ex12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/css-ex12.png -------------------------------------------------------------------------------- /manuscript/images/css-ex13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/css-ex13.png -------------------------------------------------------------------------------- /manuscript/images/css-ex14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/css-ex14.png -------------------------------------------------------------------------------- /manuscript/images/css-ex15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/css-ex15.png -------------------------------------------------------------------------------- /manuscript/images/css-ex16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/css-ex16.png -------------------------------------------------------------------------------- /manuscript/images/css-ex17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/css-ex17.png -------------------------------------------------------------------------------- /manuscript/images/css-ex18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/css-ex18.png -------------------------------------------------------------------------------- /manuscript/images/css-ex19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/css-ex19.png -------------------------------------------------------------------------------- /manuscript/images/css-ex2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/css-ex2.png -------------------------------------------------------------------------------- /manuscript/images/css-ex20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/css-ex20.png -------------------------------------------------------------------------------- /manuscript/images/css-ex3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/css-ex3.png -------------------------------------------------------------------------------- /manuscript/images/css-ex4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/css-ex4.png -------------------------------------------------------------------------------- /manuscript/images/css-ex5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/css-ex5.png -------------------------------------------------------------------------------- /manuscript/images/css-ex6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/css-ex6.png -------------------------------------------------------------------------------- /manuscript/images/css-ex7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/css-ex7.png -------------------------------------------------------------------------------- /manuscript/images/css-ex8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/css-ex8.png -------------------------------------------------------------------------------- /manuscript/images/css-ex9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/css-ex9.png -------------------------------------------------------------------------------- /manuscript/images/css-id.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/css-id.png -------------------------------------------------------------------------------- /manuscript/images/css-nesting-blocks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/css-nesting-blocks.png -------------------------------------------------------------------------------- /manuscript/images/css-render.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/css-render.png -------------------------------------------------------------------------------- /manuscript/images/exercises-categories.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/exercises-categories.png -------------------------------------------------------------------------------- /manuscript/images/exercises-main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/exercises-main.png -------------------------------------------------------------------------------- /manuscript/images/exercises-profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/exercises-profile.png -------------------------------------------------------------------------------- /manuscript/images/exercises-results.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/exercises-results.png -------------------------------------------------------------------------------- /manuscript/images/exercises-suggestion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/exercises-suggestion.png -------------------------------------------------------------------------------- /manuscript/images/git-sequence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/git-sequence.png -------------------------------------------------------------------------------- /manuscript/images/rango-erd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/rango-erd.png -------------------------------------------------------------------------------- /manuscript/images/rango-ntier-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/rango-ntier-architecture.png -------------------------------------------------------------------------------- /manuscript/images/title_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/manuscript/images/title_page.png -------------------------------------------------------------------------------- /manuscript/link_checker.py: -------------------------------------------------------------------------------- 1 | # Checks for broken links in the book chapters, printing the status of each link found to stdout. 2 | # The Python package 'requests' must be installed and available for this simple module to work. 3 | # Author: David Maxwell 4 | # Date: 2017-02-14 5 | import re 6 | import requests 7 | 8 | def main(chapters_list_filename, hide_success=True): 9 | """ 10 | hide_success = a boolean switch that determines whether to show URLs that return a HTTP 200. 11 | If set to true, only URLs that fail will be printed. 12 | """ 13 | chapters_f = open(chapters_list_filename, 'r') 14 | pattern = re.compile(r'\[([^]]+)]\(\s*(http[s]?://[^)]+)\s*\)') # http://stackoverflow.com/a/23395483 15 | 16 | print 'filename\tline_no\ttitle\turl\tstatus_code' 17 | 18 | for filename in chapters_f: 19 | filename = filename.strip() 20 | 21 | if not filename or filename.startswith('{'): # Skip non-filename lines 22 | continue 23 | 24 | chapter_f = open(filename, 'r') 25 | line_no = 1 26 | 27 | for line in chapter_f: 28 | line = line.strip() 29 | 30 | for match in re.findall(pattern, line): 31 | title = match[0] 32 | url = match[1] 33 | 34 | if '127.0.0.1' in url or 'localhost' in url: # Don't check localhost URLs 35 | continue 36 | 37 | request = None 38 | status_code = -1 39 | 40 | try: 41 | request = requests.get(url) 42 | status_code = request.status_code 43 | except requests.exceptions.ConnectionError: 44 | request = None 45 | status_code = 'FAILED_TO_CONNECT' 46 | 47 | if hide_success and status_code == 200: 48 | continue 49 | 50 | title = title.replace('\t', ' ') 51 | 52 | print '{filename}\t{line_no}\t{title}\t{url}\t{status_code}'.format(filename=filename, 53 | line_no=line_no, 54 | title=title, 55 | url=url, 56 | status_code=status_code) 57 | 58 | line_no = line_no + 1 59 | 60 | chapter_f.close() 61 | 62 | chapters_f.close() 63 | 64 | if __name__ == '__main__': 65 | main('Book.txt', hide_success=False) -------------------------------------------------------------------------------- /manuscript/out.txt: -------------------------------------------------------------------------------- 1 | $ python manage.py migrate 2 | 3 | Operations to perform: 4 | Apply all migrations: admin, contenttypes, auth, sessions 5 | Running migrations: 6 | Applying contenttypes.0001_initial... OK 7 | Applying auth.0001_initial... OK 8 | Applying admin.0001_initial... OK 9 | Applying sessions.0001_initial... OK 10 | 11 | 12 | The migrate command looks at INSTALLED\_APPS in `settings.py` and for each app creates any necessary database tables. The database is configured according to the database settings 13 | in `settings.py` file and the database migrations for each app. You’ll see a message for each 14 | migration that is applied. 15 | 16 | If you’re interested, run the command-line client (#TODO(leifos):which client?) 17 | for your database and type dt (PostgreSQL), SHOW TABLES; (MySQL), or 18 | .schema (SQLite) to display the tables Django created. 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | There are many Django 27 | applications you can 28 | [download](https://code.djangoproject.com/wiki/DjangoResources#Djangoapplicationcomponents) 29 | and use in your projects. 30 | 31 | 32 | For Django to pick your 33 | mappings up, this tuple *must* be called `urlpatterns`. The 34 | `urlpatterns` tuple contains a series of calls to the 35 | `django.conf.urls.url()` function, with each call handling a unique 36 | mapping. 37 | 38 | 39 | 40 | **Footnotes** 41 | 42 | [1]: There are many applications available out there that you can use in your project. Take a look at 43 | [PyPI](https://pypi.python.org/pypi?%3Aaction=search&term=django&submit=search) 44 | and [Django Packages](https://www.djangopackages.com/) to search for 45 | reusable apps which you can drop into your projects. -------------------------------------------------------------------------------- /manuscript/sort_acknowledgements.py: -------------------------------------------------------------------------------- 1 | # Use this script to order people by their first name. 2 | # Watch for UNICODE 3 | 4 | f = open('chapter-summary.md', 'r') 5 | in_acks_list = False 6 | 7 | people = [] 8 | 9 | 10 | for line in f: 11 | line = line.strip() 12 | 13 | if '%% BEGIN ACKNOWLEDGEMENTS LIST' in line: 14 | in_acks_list = True 15 | continue 16 | 17 | if '%% END ACKNOWLEDGEMENTS LIST' in line: 18 | in_acks_list = False 19 | break 20 | 21 | if in_acks_list and line != "": 22 | if line.startswith('['): 23 | name = line[1:line.index(']')] 24 | elif line.startswith('**['): 25 | name = line[3:line.index(']')] 26 | line = line[3:] 27 | elif line.startswith('**'): 28 | name = line[2:] 29 | line = line[2:] 30 | else: 31 | name = line 32 | 33 | if name[-1] == ',': 34 | name = name[:-1] 35 | 36 | if line[-2:len(line)] == '**': 37 | line = line[0:len(line)-2] 38 | 39 | if ']' in line: 40 | if not line.startswith('['): 41 | line = '[{0}'.format(line) 42 | 43 | name = name.lower() 44 | people.append((name, line)) 45 | 46 | # Sort 47 | sorted_people = sorted(people, key=lambda x: x[0]) 48 | 49 | print 50 | 51 | count = 0 52 | 53 | for person in sorted_people: 54 | if count % 2 == 0: 55 | print '**{person}**'.format(person=person[1]) 56 | else: 57 | print person[1] 58 | 59 | count = count + 1 60 | 61 | print 62 | 63 | f.close() -------------------------------------------------------------------------------- /manuscript/todo.txt: -------------------------------------------------------------------------------- 1 | - In the Git chapter, we need to improve the cloning process so that we have a secure way for usernames and passwords to be stored without the need so people don't have to continually reenter them! 2 | - In the Git chapter, we need to go over the rollback stuff again, there may be a better way of doing this. 3 | 4 | 5 | - From Mike Gleen::: Another note: Sometimes I get confused with the various directories. In the case of creating the "templates" directory, maybe it would help to say something like: Create it in the directory containing manage.py. 6 | 7 | November 2017 8 | ~~~~~~~~~~~~~ 9 | - Update the cookies chapter to reflect the changes in the Google Chrome cookies interface. 10 | - Check out the Webhose API 11 | - Check out the PythonAnywhere setup — David Manlove’s e-mail -------------------------------------------------------------------------------- /official_django_tutorial.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangowithcode/tango_with_django_19/cf64796c05eafc73385a6a604de65f3c6b0c1648/official_django_tutorial.pdf --------------------------------------------------------------------------------