├── .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 |
5 |
6 |
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 |
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 |
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 |
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 |
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 |