├── .github └── CODE_OF_CONDUCT.md ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── docs ├── feedback.png ├── names.py └── requirements.txt └── problems ├── data_science ├── Analysis-Key.ipynb ├── Analysis-Workshop.ipynb ├── Introduction-to-Text-Analysis-with-sklearn-preview.ipynb ├── Introduction_to_Text_Analysis_with_sklearn.ipynb ├── battery_life │ └── README.md ├── chipmunks │ ├── chipmunk.csv │ ├── data_science_project_night_4_18_19.ipynb │ ├── logo@3x.png │ └── requirements.txt ├── diversif.ai.ipynb ├── github_jobs_api │ ├── README.md │ ├── requirements.txt │ └── upsetplot-example.png ├── home_credit_default_risk │ ├── Pipfile │ ├── Pipfile.lock │ ├── README.md │ ├── default_risk.ipynb │ ├── default_risk_column_descriptions.csv │ ├── default_risk_test_data.csv │ ├── default_risk_train_data.csv │ └── perfect_deliverable.csv ├── requirements.txt ├── talks.csv ├── team_project.ipynb ├── text-analysis.jpg └── trackcoder.ipynb ├── py101 ├── make_a_game │ ├── Pipfile │ ├── Pipfile.lock │ ├── README.md │ ├── lib │ │ ├── __init__.py │ │ └── player.py │ ├── run.py │ └── tests │ │ ├── __init__.py │ │ └── test_player.py ├── python_koans │ └── README.md ├── python_team_project │ ├── README.md │ ├── app.py │ └── requirements.txt ├── testing │ ├── EnableTravisCI.png │ ├── Makefile │ ├── Pipfile │ ├── Pipfile.lock │ ├── README.md │ ├── pytest.ini │ ├── team_organizer.py │ ├── test_team_organizer.py │ └── travis-build-img.png └── trackcoder │ ├── Makefile │ ├── Pipfile │ ├── Pipfile.lock │ ├── README.md │ ├── app.py │ ├── csv.png │ ├── project.png │ └── to_do_list.db └── webdev ├── django_pn_tracker ├── .dockerignore ├── .gitignore ├── README.md ├── db.sqlite3 ├── django_pn_tracker │ ├── __init__.py │ ├── apps │ │ ├── __init__.py │ │ └── challenges │ │ │ ├── __init__.py │ │ │ ├── admin.py │ │ │ ├── apps.py │ │ │ ├── forms.py │ │ │ ├── migrations │ │ │ ├── 0001_initial.py │ │ │ └── __init__.py │ │ │ ├── models.py │ │ │ ├── templates │ │ │ └── challenges │ │ │ │ ├── delete.html │ │ │ │ ├── edit.html │ │ │ │ └── list.html │ │ │ ├── tests.py │ │ │ ├── urls.py │ │ │ └── views.py │ ├── settings.py │ ├── static │ │ ├── css │ │ │ ├── bootstrap.min.css │ │ │ └── master.css │ │ └── js │ │ │ ├── bootstrap.min.js │ │ │ └── main.js │ ├── templates │ │ ├── base.html │ │ └── index.html │ ├── urls.py │ ├── views.py │ └── wsgi.py ├── manage.py ├── requirements.txt └── setup.cfg ├── django_rest_framework_api ├── Pipfile ├── Pipfile.lock └── README.md ├── flask_collage ├── README.md └── solutions │ ├── dormamu_bargain │ └── README.md │ └── testflask.py ├── flask_exchange_rates └── README.md ├── flask_team_project ├── README.md ├── app.py ├── requirements.txt └── templates │ ├── rsvps.html │ └── teams.html └── flask_trackcoder ├── README.md ├── app.py ├── dashboard.gif ├── requirements.txt ├── static └── styles │ └── dashboard.css └── templates └── tasks.html /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | [Here](http://www.chipy.org/pages/conduct/) is our code of conduct. 3 | 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | 91 | # mypy 92 | .mypy_cache/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.6" 4 | # add other problem directories 5 | env: 6 | - TEST_DIR=problems/py101/testing 7 | 8 | script: 9 | - cd $TEST_DIR && make -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 1. Python Project Night - Hands on Python programming for Chicago Pythonistas 2 | 3 | Welcome to the Python Projects Night organized by Chicago Python User Group or ChiPy. 4 | Here you will find python challenges that we solve together as a groups every third 5 | Thursday of the month at 6:30 pm at Braintree's Chicago Office. This repository has the 6 | collection of the Challenge problems that we solve at Project Nights. 7 | 8 | [Talk to us on Slack](https://chipy.slack.com/messages/C4SRS5G3B/details/) 9 | 10 | ## 1.1. Code of Conduct 11 | 12 | [Here](http://www.chipy.org/pages/conduct/) is our code of conduct. 13 | 14 | ## 1.2. Contributors 15 | 16 | [![](https://sourcerer.io/fame/tathagata/chicagopython/CodingWorkshops/images/0)](https://sourcerer.io/fame/tathagata/chicagopython/CodingWorkshops/links/0)[![](https://sourcerer.io/fame/tathagata/chicagopython/CodingWorkshops/images/1)](https://sourcerer.io/fame/tathagata/chicagopython/CodingWorkshops/links/1)[![](https://sourcerer.io/fame/tathagata/chicagopython/CodingWorkshops/images/2)](https://sourcerer.io/fame/tathagata/chicagopython/CodingWorkshops/links/2)[![](https://sourcerer.io/fame/tathagata/chicagopython/CodingWorkshops/images/3)](https://sourcerer.io/fame/tathagata/chicagopython/CodingWorkshops/links/3)[![](https://sourcerer.io/fame/tathagata/chicagopython/CodingWorkshops/images/4)](https://sourcerer.io/fame/tathagata/chicagopython/CodingWorkshops/links/4)[![](https://sourcerer.io/fame/tathagata/chicagopython/CodingWorkshops/images/5)](https://sourcerer.io/fame/tathagata/chicagopython/CodingWorkshops/links/5)[![](https://sourcerer.io/fame/tathagata/chicagopython/CodingWorkshops/images/6)](https://sourcerer.io/fame/tathagata/chicagopython/CodingWorkshops/links/6)[![](https://sourcerer.io/fame/tathagata/chicagopython/CodingWorkshops/images/7)](https://sourcerer.io/fame/tathagata/chicagopython/CodingWorkshops/links/7) 17 | 18 | 19 | If you have found a bug, typo or want to help out, please look at the contributing guidelines. 20 | 21 | ## 1.3. Challenge Problems 22 | 23 | Challenge problems are fun, hands-on coding exercises covering a variety of topics -- such as pure problem solving, web development, and data science. The participants of Project Night are assigned to teams of four, and then solve the problem together in an hour. Teams are designed to have diverse experience levels, giving team members equal opportunity to learn and share ideas. 24 | 25 | ### 1.3.1. What you need 26 | 27 | - Wi-fi powered laptop, power chord 28 | - Python: 3.5 (We can not guarantee help if you are using Python 2.7) 29 | - Text editor: Atom, Sublime Text, Visual Studio Code 30 | - OS: GNU/Linux. OS X, Windows 31 | 32 | ## 1.4. Previous Projects 33 | 34 | Here is a list of projects we have solved previosuly. These are great exercises 35 | if you are planning to get level up your skills. Try them out and if you need any 36 | help [ask us on slack](https://chipy.slack.com/messages/C4SRS5G3B/details/) 37 | 38 | ### 1.4.1. Python101 39 | 40 | #### 1.4.1.1. Python Koans 41 | 42 | For this exercise we will learn the Zen of Python using Test Driven Development. 43 | Python Koans is a suite of broken tests, which are written against Python code that 44 | demonstrate how to write idiomatic python. 45 | Your job is to fix the broken tests by filling in the missing parts of the code. 46 | 47 | [Solve it!](problems/py101/python_koans) 48 | 49 | #### 1.4.1.2. Python Team Projects 50 | 51 | The organizers of Project Nights need your help! Grouping attendees for Project Night team project is a manual task. Why do it manually, when we can automate it? We open up the problem to you. 52 | 53 | [Solve it!](problems/py101/python_team_project) 54 | 55 | #### 1.4.1.3. Intro to PyTest and Travis-CI 56 | 57 | This is an introduction to testing using pytest that uses the same problem as above of grouping project night attendees into teams of 4. 58 | 59 | [Solve it!](problems/py101/testing) 60 | 61 | ### 1.4.2. WebDev 62 | 63 | #### 1.4.2.1. Flask Team Projects 64 | 65 | Build a web app for to group the Python project night attendees. 66 | 67 | [Solve it!](problems/webdev/flask_team_project) 68 | 69 | #### 1.4.2.2. Flask Collage 70 | 71 | Build a small web app using Flask which accepts the meetup.com event id for tonight 72 | as a parameter and would fetch the profile pictures of all the attendees to create a 73 | collage. 74 | 75 | [Solve it!](problems/webdev/flask_collage) 76 | 77 | ### 1.4.3. Data Science 78 | 79 | #### 1.4.3.1. Data Analysis With Pandas 80 | 81 | Pandas is the defacto package for data analysis in Python. In this project, we are going to use the basics of pandas to analyze the interests of project Night's attendees. What can we learn about the Python Project Night's attendees by mining their meetup.com 82 | profile data? 83 | 84 | [Solve it!](problems/data_science/Analysis-Workshop.ipynb) 85 | 86 | #### 1.4.3.2. Diversif.ai 87 | 88 | Diversity in tech communities has been a widely addressed topic. As one of the most active tech community in the world, in this exercise we would try to measure some aspects of diversity in tech community. We will use image recognition on the meetup.com profile pictures of the members of ChiPy user group and determine determine how diverse our attendees are. Then we will compare the same with other tech groups in the city and around the world. 89 | 90 | [Solve it!](problems/data_science/diversif.ai.ipynb) 91 | 92 | #### 1.4.3.3. Introduction to Text Analysis with sklearn 93 | 94 | This is a gentle introduction to text analysis with sklearn. We build a recommnedation system that predicts 95 | Pycon conference talks based on previous history of Pycon talks. 96 | -------------------------------------------------------------------------------- /docs/feedback.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chicagopython/CodingWorkshops/45c00db2e154ed164b5299ed9794f721208ab3fb/docs/feedback.png -------------------------------------------------------------------------------- /docs/names.py: -------------------------------------------------------------------------------- 1 | from nameparser import HumanName 2 | import pandas as pd 3 | import numpy as np 4 | 5 | import time 6 | 7 | ''' 8 | download data from meetup.com 9 | convert it to xlsx 10 | #TODO - use meetup.com api if we continue to use the meetup.com's garbage rsvp system 11 | ''' 12 | 13 | df = pd.read_excel('input.xlsx') 14 | df.rename(columns={'Please enter your full name as it appears on your ID. This is required for security reasons for access to the venue.':'FullName'}, inplace=True) 15 | 16 | df1 = df.replace(np.nan, '', regex=True) 17 | res = pd.DataFrame() 18 | 19 | res['MeetupName'] = df1['Name'] 20 | res['LastName'] = df1.apply(lambda x: HumanName(x['FullName']).last if x['FullName'] else '', axis=1) 21 | res['FirstName'] = df1.apply(lambda x: HumanName(x['FullName']).first if x['FullName'] else '', axis=1) 22 | res['Middle'] = df1.apply(lambda x: HumanName(x['FullName']).middle if x['FullName'] else '', axis=1) 23 | res['Suffix'] = df1.apply(lambda x: HumanName(x['FullName']).suffix if x['FullName'] else '', axis=1) 24 | 25 | pd.options.display.max_rows = 999 26 | r=res.reindex(columns=['MeetupName','FirstName','Middle','LastName','Suffix']) 27 | 28 | timestr = time.strftime("%Y%m%d-%H%M%S") 29 | writer = pd.ExcelWriter(f'output_{timestr}.xlsx') 30 | r.to_excel(writer,'Sheet1') 31 | writer.save() 32 | 33 | ''' 34 | email to: 35 | chicagooffice AT braintreepayments DOT com 36 | community AT getbraintree DOT com 37 | ray 38 | zax 39 | ''' -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | nameparser 2 | pandas -------------------------------------------------------------------------------- /problems/data_science/Analysis-Workshop.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "### Data Analysis using Pandas\n", 8 | "Pandas has become the defacto package for data analysis. In this workshop, we are going to use the basics of pandas to analyze the interests of today's group. We are going to use meetup.com's api and fetch the list of interests that are listed in each of our meetup.com profile. We will compute which interests are common, which are uncommon, and find out which of the two members have most similar interests. Lets get started by importing the essentials.\n", 9 | "\n", 10 | "You would need meetup.com's python api and pandas installed." 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": null, 16 | "metadata": { 17 | "collapsed": true 18 | }, 19 | "outputs": [], 20 | "source": [ 21 | "import meetup.api\n", 22 | "import pandas as pd\n", 23 | "from IPython.display import Image, display, HTML\n", 24 | "from itertools import combinations" 25 | ] 26 | }, 27 | { 28 | "cell_type": "markdown", 29 | "metadata": {}, 30 | "source": [ 31 | "Next we need your meetup.com API. You will find it https://secure.meetup.com/meetup_api/key/ \n", 32 | "Also we need today's event id. The event id created under Chicago Pythonistas is **233460758** and that under Chicago Python user group is **236205125**. Use the one that has the higher number of RSVPs so that you get more data points. As an additional exercise, you might go for merging the two sets of RSVPs - but that's not needed for the workshop." 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": null, 38 | "metadata": { 39 | "collapsed": true 40 | }, 41 | "outputs": [], 42 | "source": [ 43 | "API_KEY = ''\n", 44 | "event_id=''" 45 | ] 46 | }, 47 | { 48 | "cell_type": "markdown", 49 | "metadata": {}, 50 | "source": [ 51 | "The following function uses the api and loads the data into a pandas data frame. Note we are a bit sloppy both in style and how we load the data. In actual production code, we should add adequate logging with well-defined exceptions to indicate what's going wrong." 52 | ] 53 | }, 54 | { 55 | "cell_type": "code", 56 | "execution_count": 114, 57 | "metadata": { 58 | "collapsed": false 59 | }, 60 | "outputs": [], 61 | "source": [ 62 | "def get_members(event_id):\n", 63 | " client = meetup.api.Client(API_KEY)\n", 64 | " rsvps=client.GetRsvps(event_id=event_id, urlname='_ChiPy_')\n", 65 | " member_id = ','.join([str(i['member']['member_id']) for i in rsvps.results])\n", 66 | " return client.GetMembers(member_id=member_id)\n", 67 | "\n", 68 | "def get_topics(members):\n", 69 | " topics = set()\n", 70 | " for member in members.results:\n", 71 | " try:\n", 72 | " for t in member['topics']:\n", 73 | " topics.add(t['name'])\n", 74 | " except:\n", 75 | " pass\n", 76 | "\n", 77 | " return list(topics)\n", 78 | "\n", 79 | "def df_topics(event_id):\n", 80 | " members = get_members(event_id=event_id)\n", 81 | " topics = get_topics(members)\n", 82 | " columns=['name','id','thumb_link'] + topics\n", 83 | " \n", 84 | " data = [] \n", 85 | " for member in members.results:\n", 86 | " topic_vector = [0]*len(topics)\n", 87 | " for topic in member['topics']:\n", 88 | " index = topics.index(topic['name']) \n", 89 | " topic_vector[index-1] = 1\n", 90 | " try:\n", 91 | " data.append([member['name'], member['id'], member['photo']['thumb_link']] + topic_vector)\n", 92 | " except:\n", 93 | " pass\n", 94 | " return pd.DataFrame(data=data, columns=columns)\n", 95 | " \n", 96 | " #df.to_csv('output.csv', sep=\";\")" 97 | ] 98 | }, 99 | { 100 | "cell_type": "markdown", 101 | "metadata": {}, 102 | "source": [ 103 | "So you need to call the df_topics function with the event id and it would give you back a pandas dataframe containing basic information of a member and along with all possible interests. If the member has indicated interest, that column will have a one, if not then the column will have a zero." 104 | ] 105 | }, 106 | { 107 | "cell_type": "markdown", 108 | "metadata": {}, 109 | "source": [ 110 | "### Load data from meetup.com into a dataframe by calling df_topics with the event id as parameter" 111 | ] 112 | }, 113 | { 114 | "cell_type": "code", 115 | "execution_count": null, 116 | "metadata": { 117 | "collapsed": false 118 | }, 119 | "outputs": [], 120 | "source": [] 121 | }, 122 | { 123 | "cell_type": "markdown", 124 | "metadata": {}, 125 | "source": [ 126 | "### What does the first and last 10 rows of the dataset look like?" 127 | ] 128 | }, 129 | { 130 | "cell_type": "code", 131 | "execution_count": null, 132 | "metadata": { 133 | "collapsed": true 134 | }, 135 | "outputs": [], 136 | "source": [] 137 | }, 138 | { 139 | "cell_type": "markdown", 140 | "metadata": {}, 141 | "source": [ 142 | "### What are the column names?" 143 | ] 144 | }, 145 | { 146 | "cell_type": "code", 147 | "execution_count": null, 148 | "metadata": { 149 | "collapsed": true 150 | }, 151 | "outputs": [], 152 | "source": [] 153 | }, 154 | { 155 | "cell_type": "markdown", 156 | "metadata": {}, 157 | "source": [ 158 | "### Additional Exercise: Can you merge the two data for two events into one data frame and remove the dups?" 159 | ] 160 | }, 161 | { 162 | "cell_type": "code", 163 | "execution_count": null, 164 | "metadata": { 165 | "collapsed": true 166 | }, 167 | "outputs": [], 168 | "source": [] 169 | }, 170 | { 171 | "cell_type": "markdown", 172 | "metadata": {}, 173 | "source": [ 174 | "### What are the top 10 most common interests of today’s attendees?" 175 | ] 176 | }, 177 | { 178 | "cell_type": "code", 179 | "execution_count": null, 180 | "metadata": { 181 | "collapsed": false 182 | }, 183 | "outputs": [], 184 | "source": [] 185 | }, 186 | { 187 | "cell_type": "markdown", 188 | "metadata": {}, 189 | "source": [ 190 | "### What is the third most popular and third least popular topic of interest? Are there ties?" 191 | ] 192 | }, 193 | { 194 | "cell_type": "code", 195 | "execution_count": null, 196 | "metadata": { 197 | "collapsed": false 198 | }, 199 | "outputs": [], 200 | "source": [] 201 | }, 202 | { 203 | "cell_type": "markdown", 204 | "metadata": {}, 205 | "source": [ 206 | "### Which members have the third most popular interest?" 207 | ] 208 | }, 209 | { 210 | "cell_type": "code", 211 | "execution_count": null, 212 | "metadata": { 213 | "collapsed": false 214 | }, 215 | "outputs": [], 216 | "source": [] 217 | }, 218 | { 219 | "cell_type": "markdown", 220 | "metadata": {}, 221 | "source": [ 222 | "### Which members have the third most popular interest?" 223 | ] 224 | }, 225 | { 226 | "cell_type": "code", 227 | "execution_count": null, 228 | "metadata": { 229 | "collapsed": false 230 | }, 231 | "outputs": [], 232 | "source": [] 233 | }, 234 | { 235 | "cell_type": "markdown", 236 | "metadata": {}, 237 | "source": [ 238 | "### Which memebers have the highest number of topics of interest?" 239 | ] 240 | }, 241 | { 242 | "cell_type": "code", 243 | "execution_count": null, 244 | "metadata": { 245 | "collapsed": false, 246 | "scrolled": true 247 | }, 248 | "outputs": [], 249 | "source": [] 250 | }, 251 | { 252 | "cell_type": "markdown", 253 | "metadata": {}, 254 | "source": [ 255 | "### What is the average number of topics of interest?" 256 | ] 257 | }, 258 | { 259 | "cell_type": "code", 260 | "execution_count": null, 261 | "metadata": { 262 | "collapsed": false 263 | }, 264 | "outputs": [], 265 | "source": [] 266 | }, 267 | { 268 | "cell_type": "markdown", 269 | "metadata": {}, 270 | "source": [ 271 | "### Which two members have the most common overlap of interests?" 272 | ] 273 | }, 274 | { 275 | "cell_type": "code", 276 | "execution_count": null, 277 | "metadata": { 278 | "collapsed": false 279 | }, 280 | "outputs": [], 281 | "source": [] 282 | }, 283 | { 284 | "cell_type": "markdown", 285 | "metadata": {}, 286 | "source": [ 287 | "### How many members are there who have no overlaps at all?" 288 | ] 289 | }, 290 | { 291 | "cell_type": "code", 292 | "execution_count": null, 293 | "metadata": { 294 | "collapsed": true 295 | }, 296 | "outputs": [], 297 | "source": [] 298 | }, 299 | { 300 | "cell_type": "markdown", 301 | "metadata": {}, 302 | "source": [ 303 | "### Given a member which other member(s) have the most common interests?" 304 | ] 305 | }, 306 | { 307 | "cell_type": "code", 308 | "execution_count": null, 309 | "metadata": { 310 | "collapsed": true 311 | }, 312 | "outputs": [], 313 | "source": [] 314 | } 315 | ], 316 | "metadata": { 317 | "kernelspec": { 318 | "display_name": "Python 2", 319 | "language": "python", 320 | "name": "python2" 321 | }, 322 | "language_info": { 323 | "codemirror_mode": { 324 | "name": "ipython", 325 | "version": 2 326 | }, 327 | "file_extension": ".py", 328 | "mimetype": "text/x-python", 329 | "name": "python", 330 | "nbconvert_exporter": "python", 331 | "pygments_lexer": "ipython2", 332 | "version": "2.7.11" 333 | } 334 | }, 335 | "nbformat": 4, 336 | "nbformat_minor": 0 337 | } 338 | -------------------------------------------------------------------------------- /problems/data_science/battery_life/README.md: -------------------------------------------------------------------------------- 1 | # Predicting Battery Life - Challenge #1: Gathering Data 2 | 3 | Portable electronics such as mobile phones and laptops have become a near necessity in our daily lives; and those devices share one essential resource in common: battery life. Have you ever sat on the floor to be by a power outlet for your laptop or cell phone? How about delayed leaving for an event because you had to make sure your phone was charged? In a perfect world, we wouldn't have to worry about battery life, but in the absence of the miracle battery, users must rely on indicators of remaining battery life. 4 | 5 | While there's still ongoing research into the capacity of batteries over time, the question of what percentage of charge remains has largely been solved in our everyday electronics. Most operating systems offer a way to display the percentage of battery life remaining. However, features that predict time remaining on the battery have been notoriously inaccurate, to the point where such features have been removed or hidden by default. Wouldn't it be nice if we could accurately predict when our phone was going to "die?" 6 | 7 | **Your goal is going to be work toward that solution by gathering data to build a machine learning model predicting remaining battery life. You are tasked with determining what data we might want to collect for such a model, determining a strategy for ongoing collection of that data, actually collecting it, and organizing it into a form that will be usable to the machine-learning models of choice.** There are no right or wrong answer here, just things that are feasible and ultimately help drive better predictions. 8 | 9 | Before digging in, some background reading on how batteries work and what kinds of data/models can be useful is likely in order. Feel free to find your own resources, but here are a few: 10 | - Overview of research and features: https://arxiv.org/pdf/1801.04069.pdf 11 | - Battery Terminology: http://web.mit.edu/evt/summary_battery_specifications.pdf 12 | - Battery Discharge Formulas: https://planetcalc.com/2283/ 13 | 14 | Once you're ready to collect data, you'll likely want to collect running process and/or system utilization data as at least part of the data you collect. Gathering such data can vary drastically by hardware and operating system. To get you started, here are a few options to make extracting the data easier: 15 | - The [psutil](https://psutil.readthedocs.io/en/latest/) library in python has cross-OS support, but only collects some such data. 16 | - On Windows, there's the [wmi](http://timgolden.me.uk/python/wmi/tutorial.html) library. 17 | - On most distributions of Linux and MacOS, the standard librarys' os, sys, and subprocess modules can actually get you rolling pretty quickly, once you track down where system logs are stored! 18 | 19 | The rest is up to you, but some questions you might want to consider: 20 | - When tracking battery/system data, how are you accounting for a device sometimes being plugged in? 21 | - How will you account for different battery types, device types, and operating systems? 22 | - Besides the obvious battery and system-related data, what features might help predict battery life? 23 | - How can you collect enough data from enough sources to successfully train a model? 24 | -------------------------------------------------------------------------------- /problems/data_science/chipmunks/data_science_project_night_4_18_19.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "![_](logo@3x.png)\n", 8 | "\n", 9 | "# Oh, no! We've had a data crash.\n", 10 | "As ChiPy leadership was preparing for [PyCon](https://us.pycon.org/2019/) at the end of this month, they found that the dataset on our infamous *ChiPy chipmunks* has disapeared. While they transition from Oracle to Postgres, the leadership team has enlisted your help as data scientists to analyze some salvaged chipmunk data. The PyCon organizers had a few questions about coding in Chicago, ChiPy, and chipmunks that need answers. We will get to those questions shortly, but first let's get to the data. If you haven't set up your environment, make sure to checkout the project night page on the [website](http://chicagopython.github.io/posts/chipy-chipmunks).\n", 11 | "\n", 12 | "\n", 13 | "## Reading in the Data\n", 14 | "The salvaged chipmunk dataset is `chipmunk.csv`. The wonderful [pandas](https://pandas.pydata.org/) library, built on [numpy](http://www.numpy.org/), will let the team read in the data. \n", 15 | "\n", 16 | "

ChiPy Check-in

\n", 17 | "\n", 18 | "Now is a good time to check in with the team. Is anyone familiar with `pandas` and `numpy`? Discuss with your team what these libraries are, what they allow data scientists to do, and then decide on what `pandas` function will read in our data." 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": null, 24 | "metadata": {}, 25 | "outputs": [], 26 | "source": [ 27 | "import numpy as np\n", 28 | "import pandas as pd\n", 29 | "import matplotlib.pyplot as plt\n", 30 | "import seaborn as sns\n", 31 | "%matplotlib inline" 32 | ] 33 | }, 34 | { 35 | "cell_type": "code", 36 | "execution_count": null, 37 | "metadata": {}, 38 | "outputs": [], 39 | "source": [ 40 | "# Read in the data" 41 | ] 42 | }, 43 | { 44 | "cell_type": "markdown", 45 | "metadata": {}, 46 | "source": [ 47 | "## Exploring the data\n", 48 | "We need to be familiar with our data before we can answer questions about ChiPy and our chipmunks. Let's start with some questions we would ask of *any* dataset:\n", 49 | "\n", 50 | "- How many rows are in this dataset? What does each row represent?\n", 51 | "- What does the data look like? Check the first 5 rows\n", 52 | "- Is there missing data? If so, how much is missing?\n", 53 | "- What columns are categorical?\n", 54 | "- What are the unique number of observations for each column?\n" 55 | ] 56 | }, 57 | { 58 | "cell_type": "code", 59 | "execution_count": null, 60 | "metadata": {}, 61 | "outputs": [], 62 | "source": [ 63 | "## Check the number of rows" 64 | ] 65 | }, 66 | { 67 | "cell_type": "code", 68 | "execution_count": null, 69 | "metadata": {}, 70 | "outputs": [], 71 | "source": [ 72 | "## See first 5 rows of data" 73 | ] 74 | }, 75 | { 76 | "cell_type": "code", 77 | "execution_count": null, 78 | "metadata": {}, 79 | "outputs": [], 80 | "source": [ 81 | "## Check for missing data" 82 | ] 83 | }, 84 | { 85 | "cell_type": "code", 86 | "execution_count": null, 87 | "metadata": {}, 88 | "outputs": [], 89 | "source": [ 90 | "## Check for categorical data and unique number of values\n" 91 | ] 92 | }, 93 | { 94 | "cell_type": "markdown", 95 | "metadata": {}, 96 | "source": [ 97 | "## Was there missing data?\n", 98 | "\n", 99 | "We will keep exploring the data and start answering questions soon, but first let's address missing data (if there is any). What columns have missing data? What kind of data is missing?\n", 100 | "\n", 101 | "

ChiPy Check-in

\n", 102 | "\n", 103 | "This a great point for discussion. If there is missing data - why might it be missing? Discuss some possible reasons with your team and decide on a reason that makes sense.\n", 104 | "\n", 105 | "[Imputation](https://en.wikipedia.org/wiki/Imputation_(statistics)) is the process of replacing missing data with some estimated value. The process can be as complicated (or simple) as you would like it to be! Given the possible reason for our missing data, what is an acceptable imputation?\n", 106 | "\n", 107 | "Impute any missing data in your dataset and note what assumptions you made as a team. If you are not sure how to replace data in `pandas`, feel free to use google like a proper data scientist." 108 | ] 109 | }, 110 | { 111 | "cell_type": "code", 112 | "execution_count": null, 113 | "metadata": {}, 114 | "outputs": [], 115 | "source": [ 116 | "# Replace any missing data here" 117 | ] 118 | }, 119 | { 120 | "cell_type": "code", 121 | "execution_count": null, 122 | "metadata": {}, 123 | "outputs": [], 124 | "source": [ 125 | "# Check your data for missing values to see if it worked!" 126 | ] 127 | }, 128 | { 129 | "cell_type": "markdown", 130 | "metadata": {}, 131 | "source": [ 132 | "# Stakeholder Questions\n", 133 | "## Question #1\n", 134 | "\n", 135 | "The great folks at PyCon want to know all about ChiPy and our chipmunks. They have heard that **ChiPy is an inclusive and open community**. Can we support that claim with our data? Given that the `ChiPy` column takes a value of `1` for a ChiPy chipmunk and a value of `0` for chipmunks not in ChiPy, start to explore this question.\n", 136 | "\n", 137 | "Some ideas to get you started:\n", 138 | "- Are chipmunks of different species represented in ChiPy?\n", 139 | "- Are chipmunks of different sizes represented in ChiPy?\n", 140 | "- Are chipmunks of different careers represented in ChiPy?\n", 141 | "- Are spotted and not spotted chipmunks represented in ChiPy?\n", 142 | "\n", 143 | "

ChiPy Check-in

\n", 144 | "\n", 145 | "There are no right or wrong answers here, only well supported or poorly supported ones! Discuss as a group the aspects of the data you have looked at and if it constitutes enough evidence to justify an answer." 146 | ] 147 | }, 148 | { 149 | "cell_type": "code", 150 | "execution_count": null, 151 | "metadata": {}, 152 | "outputs": [], 153 | "source": [ 154 | "## Exploration of species" 155 | ] 156 | }, 157 | { 158 | "cell_type": "code", 159 | "execution_count": null, 160 | "metadata": {}, 161 | "outputs": [], 162 | "source": [ 163 | "## Exploration of size" 164 | ] 165 | }, 166 | { 167 | "cell_type": "code", 168 | "execution_count": null, 169 | "metadata": {}, 170 | "outputs": [], 171 | "source": [ 172 | "## Exploration of careers" 173 | ] 174 | }, 175 | { 176 | "cell_type": "code", 177 | "execution_count": null, 178 | "metadata": {}, 179 | "outputs": [], 180 | "source": [ 181 | "## Exploration of spotted vs non-spotted" 182 | ] 183 | }, 184 | { 185 | "cell_type": "markdown", 186 | "metadata": {}, 187 | "source": [ 188 | "## Question #2\n", 189 | "The word on the street at PyCon is that chipmunks that live in Chicago enjoy coding more than those that don't. Is this not true? Given that the `chicago` column takes a value of `1` for chipmunks that live in Chicago and a value of `0` for chipmunks that do not, explore this question.\n", 190 | "\n", 191 | "- Visualize the distributions of `coding_enjoyment` for chipmunks that do and do not live in Chicago.\n", 192 | "- Come up with a way to test our question.\n", 193 | "\n", 194 | "

ChiPy Check-in

\n", 195 | "\n", 196 | "Coming up with a proper way to test stakeholder questions can be an artform as well as a science. We have imported a few statistical tests below that may (or may not) be appropriate for our question. First consider a way to frame our question as something to *disprove* (those familiar with jargon, let's construct a [null hypothesis](https://en.wikipedia.org/wiki/Null_hypothesis)) - then conduct a test that may disprove it. Reading the documentation for the imported tests below may prove to be very helpful!" 197 | ] 198 | }, 199 | { 200 | "cell_type": "code", 201 | "execution_count": null, 202 | "metadata": {}, 203 | "outputs": [], 204 | "source": [ 205 | "from scipy.stats import ttest_ind, levene, chisquare" 206 | ] 207 | }, 208 | { 209 | "cell_type": "code", 210 | "execution_count": null, 211 | "metadata": {}, 212 | "outputs": [], 213 | "source": [ 214 | "## Beautiful plot" 215 | ] 216 | }, 217 | { 218 | "cell_type": "code", 219 | "execution_count": null, 220 | "metadata": {}, 221 | "outputs": [], 222 | "source": [ 223 | "## Statistical Test" 224 | ] 225 | }, 226 | { 227 | "cell_type": "markdown", 228 | "metadata": {}, 229 | "source": [ 230 | "## Question #2, Continued\n", 231 | "\n", 232 | "We have now compared two groups of chipmunks - those that live in Chicago and those that do not - and have either rejected or failed to reject a null hypothesis. What values did the statistical test return and what do they mean? Can we be confident in our results? How confident?\n", 233 | "\n", 234 | "Regardless of our test results, what are the limitations of the test? One limitation is that we have information in our data that is *related* to being in Chicago and might also have an effect on enjoyment of coding. [Regression analysis](https://en.wikipedia.org/wiki/Regression_analysis) will allow us to examine the relationship between living in Chicago and enjoyment of coding while controlling for membership in ChiPy. Use the `statsmodels` package to regress `chicago` and `ChiPy` on `coding_enjoyment`. See [this example](https://www.statsmodels.org/stable/index.html) for assistance.\n", 235 | "\n", 236 | "

ChiPy Check-in

\n", 237 | "\n", 238 | "This regression model still has limitations, and there could be an entire project night on this task alone. What steps would need to be taken if we controlled for more characteristics of our data?\n", 239 | "\n", 240 | "This is also a good time to discuss what kind of information we are looking for in our regression model. What are coefficients and what do they mean? What is a p-value? Is it similar to a p-value from the statistical tests above?\n", 241 | "\n", 242 | "Lastly, modeling is fun, but don't forget the original question! Do chipmunks that live in Chicago enjoy coding more than those that don't?\n" 243 | ] 244 | }, 245 | { 246 | "cell_type": "code", 247 | "execution_count": null, 248 | "metadata": {}, 249 | "outputs": [], 250 | "source": [ 251 | "import statsmodels.api as sm\n", 252 | "import statsmodels.formula.api as smf" 253 | ] 254 | }, 255 | { 256 | "cell_type": "code", 257 | "execution_count": null, 258 | "metadata": {}, 259 | "outputs": [], 260 | "source": [ 261 | "# Regression model and summary" 262 | ] 263 | }, 264 | { 265 | "cell_type": "markdown", 266 | "metadata": {}, 267 | "source": [ 268 | "## Question #3\n", 269 | "ChiPy leadership wants to send 20 lucky ChiPy chipmunks to cheer the lovely folks at PyCon. However, it's unlikely that the data recovery efforts will be able to recover who is/isn't a member of ChiPy! ChiPy leadership has asked us to develop a predictive model to identify members as part of the process to allocate the 20 free tickets.\n", 270 | "\n", 271 | "To do this we will:\n", 272 | "- Make a train/test split to evaluate our model\n", 273 | "- Scale our data\n", 274 | "- Fit several models\n", 275 | "- Decide on an evaluation metric\n", 276 | "- Select this best model\n", 277 | "\n", 278 | "

ChiPy Check-in

\n", 279 | "\n", 280 | "The cell below transforms our data so that every feature (jargon for column) is numeric. Discuss with your team why this is could be an important step. Engineering features could also be an entire project night!" 281 | ] 282 | }, 283 | { 284 | "cell_type": "code", 285 | "execution_count": null, 286 | "metadata": {}, 287 | "outputs": [], 288 | "source": [ 289 | "wide_data = pd.get_dummies(df, drop_first=True)" 290 | ] 291 | }, 292 | { 293 | "cell_type": "code", 294 | "execution_count": null, 295 | "metadata": {}, 296 | "outputs": [], 297 | "source": [ 298 | "wide_data.head()" 299 | ] 300 | }, 301 | { 302 | "cell_type": "code", 303 | "execution_count": null, 304 | "metadata": {}, 305 | "outputs": [], 306 | "source": [ 307 | "from sklearn.model_selection import train_test_split\n", 308 | "from sklearn.linear_model import LogisticRegression\n", 309 | "from sklearn.naive_bayes import BernoulliNB\n", 310 | "from sklearn.neighbors import KNeighborsClassifier" 311 | ] 312 | }, 313 | { 314 | "cell_type": "code", 315 | "execution_count": null, 316 | "metadata": {}, 317 | "outputs": [], 318 | "source": [ 319 | "X_train, X_test, y_train, y_test = train_test_split(wide_data.drop('ChiPy', axis=1), \n", 320 | " wide_data.ChiPy, \n", 321 | " test_size=0.33, \n", 322 | " random_state=42)" 323 | ] 324 | }, 325 | { 326 | "cell_type": "code", 327 | "execution_count": null, 328 | "metadata": {}, 329 | "outputs": [], 330 | "source": [ 331 | "### Scale data" 332 | ] 333 | }, 334 | { 335 | "cell_type": "code", 336 | "execution_count": null, 337 | "metadata": {}, 338 | "outputs": [], 339 | "source": [ 340 | "### Train models" 341 | ] 342 | }, 343 | { 344 | "cell_type": "markdown", 345 | "metadata": {}, 346 | "source": [ 347 | "

ChiPy Check-in

\n", 348 | "\n", 349 | "Having the proper evaluation metric is the most important process in predictive modeling. Below we have imported accuracy, precision, and recall. What are each of these metrics and when should they be used? Given that we want to give 20 PyCon tickets to only ChiPy chipmunks, which metric is most appropriate here? Black box evaluation methods like `classification_report` will not be helpful here given the constraint of only having 20 tickets. " 350 | ] 351 | }, 352 | { 353 | "cell_type": "code", 354 | "execution_count": null, 355 | "metadata": {}, 356 | "outputs": [], 357 | "source": [ 358 | "from sklearn.metrics import precision_score, recall_score, accuracy_score, confusion_matrix" 359 | ] 360 | }, 361 | { 362 | "cell_type": "code", 363 | "execution_count": null, 364 | "metadata": {}, 365 | "outputs": [], 366 | "source": [ 367 | "### Get predictions..." 368 | ] 369 | }, 370 | { 371 | "cell_type": "code", 372 | "execution_count": null, 373 | "metadata": {}, 374 | "outputs": [], 375 | "source": [ 376 | "### Evaluate models, optimizing your predictions for 20 chipmunks!" 377 | ] 378 | }, 379 | { 380 | "cell_type": "code", 381 | "execution_count": null, 382 | "metadata": {}, 383 | "outputs": [], 384 | "source": [] 385 | } 386 | ], 387 | "metadata": { 388 | "kernelspec": { 389 | "display_name": "Python 3", 390 | "language": "python", 391 | "name": "python3" 392 | }, 393 | "language_info": { 394 | "codemirror_mode": { 395 | "name": "ipython", 396 | "version": 3 397 | }, 398 | "file_extension": ".py", 399 | "mimetype": "text/x-python", 400 | "name": "python", 401 | "nbconvert_exporter": "python", 402 | "pygments_lexer": "ipython3", 403 | "version": "3.6.5" 404 | } 405 | }, 406 | "nbformat": 4, 407 | "nbformat_minor": 2 408 | } 409 | -------------------------------------------------------------------------------- /problems/data_science/chipmunks/logo@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chicagopython/CodingWorkshops/45c00db2e154ed164b5299ed9794f721208ab3fb/problems/data_science/chipmunks/logo@3x.png -------------------------------------------------------------------------------- /problems/data_science/chipmunks/requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | pandas 3 | matplotlib 4 | seaborn 5 | sklearn 6 | statsmodels 7 | scipy 8 | jupyter 9 | -------------------------------------------------------------------------------- /problems/data_science/github_jobs_api/README.md: -------------------------------------------------------------------------------- 1 | # GitHub Jobs API 2 | 3 | The intro for this workshop is on the [Python Project Night webpage](http://chicagopython.github.io/posts/github-jobs-api/). We want to explore the GitHub Jobs API and see what we can learn. 4 | 5 | 6 | * Their wepage is: https://jobs.github.com 7 | * If we navigate to the bottom of the page, we will see their API documentation: https://jobs.github.com/api 8 | 9 | 10 | ## Challenge #0: Getting acquainted 11 | 12 | The first part of using an API us understanding how to interface with it. The GitHub Jobs API is an HTTP endpoint where the user sends an HTTP request (like for a webpage) and gets back a response. For the GitHub API, the response will be a JSON-formatted string. You can even see it in your browser: 13 | 14 | * This URL is for the regular HTML: https://jobs.github.com/positions?description=python&location=chicago 15 | * This URL is for the JSON: https://jobs.github.com/positions.json?description=python&location=chicago 16 | 17 | It is hard for a human to interpret JSON-formatted data; a viewer may help. To explore the format of the data, feel free to go to https://codebeautify.org/jsonviewer 18 | 19 | ### [codebeautify.org](https://codebeautify.org/jsonviewer) 20 | 21 | * Click the **Load URL** button and paste the URL for the JSON in the popup window. 22 | * You can also expand the window so it's not just half of the screen. 23 | 24 | Go back to the API documentation and iterate a bit between the viewer page and the docs until you understand the format of the API: 25 | 26 | * Try example queries with the other fields, such as **description**, **location**, **full_time** 27 | * Try the pagination 28 | 29 | 30 | ## Challenge #1: Pull the data 31 | 32 | OK, Python! Open up a text editor to save your code, and a Python shell to experiment in. You will probably want to use 33 | 34 | * [`requests`](https://2.python-requests.org) for the HTTP GET request -- `pip install requests` 35 | * You will have to pass query parameters using the `params` keyword argument ([documentation](https://2.python-requests.org/en/master/user/quickstart/#passing-parameters-in-urls)) 36 | * It has a [built-in JSON decoder](https://2.python-requests.org/en/master/user/quickstart/#json-response-content) 37 | 38 | 39 | Possible suggestions 40 | 41 | * It may be helpful to pull one single job at first to be able to parse the formatting 42 | ``` 43 | https://jobs.github.com/positions/.json 44 | ``` 45 | * You may want to write a function that wraps the HTTP request so you only need to send it parameters for the query 46 | * Advanced users may be interested in writing a [generator](https://docs.python.org/3/howto/functional.html#generators) for the pagination 47 | 48 | 49 | Don't spend too much time on this step -- the fun part is data exploration! 50 | 51 | 52 | ## Challenge #2: Exploratory Data Analysis (EDA) 53 | 54 | Exploratory Data Analysis is really what it sounds like: bumbling around in the weeds of the dataset _exploring_ what is there. So literally go key by key in the dictionary (JSON) response object and see what's there. You don't have to look at every key and value...but do dig a little into the things that seem promising, surprising, or interesting. 55 | 56 | * There's nothing wrong with simply counting results from queries with different **description** strings! 57 | * Though you can try things like checking for a string `"foo" in "FOO data bar data baz baz baz".lower()` 58 | * Or the adventurous may try regex, e.g. `import re; re.search("foo", "FOO data bar data baz baz baz", re.IGNORECASE)` 59 | * You may like the `Counter` object from [`collections`](https://docs.python.org/3/library/collections.html#counter-objects) in the Python standard library. 60 | 61 | For example, if you had the response data in an object called `results` you could group by company like this: 62 | 63 | ```python 64 | all_the_companies = Counter(result['company'] for result in results) 65 | ``` 66 | 67 | Or if you want to search for a specific set of languages (maybe the ones you know!)...you can do that. 68 | 69 | 70 | 71 | ## Challenge #3: Visualization 72 | 73 | Exploring data can be aided significantly by plotting the data. It's often easier to visualize insights than to learn them just from looking at data. Visualizations are also a great way to summarize information when sharing your findings. 74 | 75 | If you're not working in a Jupyter notebook, consider creating one now to make inspecting the visualizations easier. In your environment, `pip install jupyter`. Then type `jupyter notebook` from your terminal. 76 | 77 | One cool visualization you might consider making is an upset plot. This chart type allows you to plot multiple set intersections (like a Venn Diagram on steroids). Luckily, a Python library called UpsetPlot exists to make creation easy. Let's `pip install upsetplot` from our terminal (or `!pip install --quiet upsetplot` from within Jupyter). An example of what your upsetplot might look like: 78 | 79 | ![upsetplot](./upsetplot-example.png) 80 | 81 | ### Some questions you might want to consider: 82 | 83 | Does the dataset tell you anything about which companies use the GitHub for hiring at all? In which cities? Should we move to a different part of the country? Should I learn Go? Kubernetes? Is Data Science saturated? Growing? Does one company's job posts overwhelm the dataset? 84 | 85 | - Where might you get additional information? 86 | - How often should you pull the data? What would capture over time give you? 87 | - Choose your own adventure and share with the group what you have learned! 88 | -------------------------------------------------------------------------------- /problems/data_science/github_jobs_api/requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | -------------------------------------------------------------------------------- /problems/data_science/github_jobs_api/upsetplot-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chicagopython/CodingWorkshops/45c00db2e154ed164b5299ed9794f721208ab3fb/problems/data_science/github_jobs_api/upsetplot-example.png -------------------------------------------------------------------------------- /problems/data_science/home_credit_default_risk/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | 8 | [packages] 9 | jupyterlab = "~=1.1.4" 10 | pandas = "==0.25.1" 11 | seaborn = "==0.9.0" 12 | sklearn = "==0.21.3" 13 | 14 | 15 | [requires] 16 | python_version = "3.7" -------------------------------------------------------------------------------- /problems/data_science/home_credit_default_risk/README.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | Many people struggle to get loans due to insufficient or non-existent credit histories. And, unfortunately, this population is often taken advantage of by untrustworthy lenders. 4 | 5 | Tonight's project examines a dataset from a real bank that focuses on lending to people with little or no credit history. Their goal is to ensure that clients capable of repayment are not rejected. You will explore the dataset and make predictions whether someone will default or not, based on their application for a loan. 6 | 7 | ## Your Task 8 | 9 | Your goal is to train a binary classification model on the data in `default_risk_train_data.csv` that optimized area under the ROC curve between the predicted probability and the observed target. For each `SK_ID_CURR` in `default_risk_train_data.csv`, you must predict a probability for the TARGET variable. Your deliverable to the bank will be a CSV with predictions for each SK_ID_CURR in the test set. 10 | 11 | ## Setup 12 | 13 | 1. For this challenge you will need Python 3.7, pipenv, and git installed. If you're not familiar with pipenv, it's a packaing tool for Python that effectively replaced the pip+virtualenv+requirements.txt workflow. If you already have pip installed, the easiest way to install pipenv is with `pip install --user pipenv`; however, a better way for Mac/Linux Homebrew users is to instead run `brew install pipenv`. More options can be found [here](https://pipenv-fork.readthedocs.io/en/latest/install.html#installing-pipenv). 14 | 15 | 2. The project is in the ChiPy project night repo. If you do not have the repository already, run 16 | 17 | ``` 18 | git clone https://github.com/chicagopython/CodingWorkshops.git 19 | ``` 20 | 21 | 3. Navigate to the folder for this challenge: 22 | 23 | ``` 24 | cd CodingWorkshops/problems/data_science/home_credit_default_risk 25 | ``` 26 | 27 | 4. Run `pipenv install`, which will install all of the libraries we have recommended for this exercise. 28 | 5. After you've installed all of the libraries, run `pipenv shell`, which will turn on a virtual environment running Python 3.7. 29 | 6. From within the shell, run `jupyter lab default_risk.ipynb` to launch the pre-started notebook. 30 | 7. To exit the pipenv shell when you are done, simply type `exit`. 31 | 32 | ## What's in this repository? 33 | 34 | There are three data files, one metadata file, and a jupyter notebook. 35 | 36 | 1. default_risk_train_data.csv -- The data you will use to train your models. Includes all potential features and the target. 37 | 2. default_risk_test_data.csv -- The data you will use to test your models. Includes all potential features, but NOT the target (which theoretically reflect unknown future default status). 38 | 3. perfect_deliverable.csv -- The CSV with perfect predictions for each SK_ID_CURR in the test set. You should only use this at the very end to test the model and NEVER factor it into training your model. To prevent overfitting, you should test models sparingly. This is the same format the final deliverable should be submitted to the bank in. 39 | 4. default_risk_column_descriptions.csv -- Descriptive metadata for the columns found in the train and test datasets. 40 | 5. default_risk.ipynb -- The jupyer notebook where all coding should be completed, unless you opt to work in a different environment. 41 | 42 | This project is based on a Kaggle competition, with a subset of the data provided for the sake of download size. Note that this data has not been cleaned for you, and you should expect to deal with real world data issues, such as missing values, bad values, class imbalances, etc. 43 | 44 | ## So what should we do? 45 | 46 | To successfully complete this challenge, you'll need to: 47 | 1. become an expert on the data, 48 | 2. clean the data, 49 | 3. engineer the features for your model(s), 50 | 4. test/validate your models, 51 | 5. generate the deliverable the bank expects. 52 | 53 | Here are some tips/questions to consider along the way: 54 | - Identify which columns are numerical and which are categorical 55 | - Which columns are missing values, and what should be done about the missing values? 56 | - Which features are relevant and why? 57 | - Which features might you want to remove? 58 | - What new features might you create? 59 | - How will you deal with categorical data (e.g. Label Encoding, One-Hot encoding, etc). 60 | - Is there any class imbalance? 61 | - What models will you try? sklearn has been installed in your environment; and [linear regression](https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LinearRegression.html), [logistic regression](https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegression.html#sklearn.linear_model.LogisticRegression), and [random forest](https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestClassifier.html#sklearn.ensemble.RandomForestClassifier) models have been imported in the given notebook. Feel free, however, to use the library/models of your choice. 62 | -------------------------------------------------------------------------------- /problems/data_science/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask 2 | Flask_RESTful 3 | numpy 4 | pandas 5 | python-dateutil 6 | pytz 7 | scikit-learn 8 | scipy 9 | six 10 | sklearn 11 | jupyter 12 | altair 13 | vega 14 | -------------------------------------------------------------------------------- /problems/data_science/team_project.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Data Analysis using Pandas\n", 8 | "Pandas has become the defacto package for data analysis. In this workshop, we are going to use the basics of pandas to analyze the interests of today's group. We are going to use meetup.com's api and fetch the list of interests that are listed in each of our meetup.com profile. We will compute which interests are common, which are uncommon, and find out how we can use topics of common interest to form teams for project night. \n", 9 | "\n", 10 | "Lets get started by importing the essentials. You would need meetup.com's python api and pandas installed." 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": 1, 16 | "metadata": {}, 17 | "outputs": [], 18 | "source": [ 19 | "!pip install meetup-api\n", 20 | "import meetup.api\n", 21 | "import pandas as pd\n", 22 | "from IPython.display import Image, display, HTML\n", 23 | "from itertools import combinations\n", 24 | "import sys" 25 | ] 26 | }, 27 | { 28 | "cell_type": "markdown", 29 | "metadata": {}, 30 | "source": [ 31 | "Next one of you need get a meetup.com API key. You will find it https://secure.meetup.com/meetup_api/key/ Also you'll need tonight's event id. Tonight's event id is the nine digit number in the meetup url." 32 | ] 33 | }, 34 | { 35 | "cell_type": "code", 36 | "execution_count": 73, 37 | "metadata": {}, 38 | "outputs": [], 39 | "source": [ 40 | "API_KEY = '3f6d3275d3b6314e73453c4aa27'\n", 41 | "event_id='239174132'" 42 | ] 43 | }, 44 | { 45 | "cell_type": "markdown", 46 | "metadata": {}, 47 | "source": [ 48 | "The following function uses the api and loads the data into a pandas data frame." 49 | ] 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": 74, 54 | "metadata": {}, 55 | "outputs": [], 56 | "source": [ 57 | "def get_members(event_id):\n", 58 | " client = meetup.api.Client(API_KEY)\n", 59 | " rsvps=client.GetRsvps(event_id=event_id, urlname='_ChiPy_')\n", 60 | " member_id = ','.join([str(i['member']['member_id']) for i in rsvps.results])\n", 61 | " return client.GetMembers(member_id=member_id)\n", 62 | "\n", 63 | "def get_topics(members):\n", 64 | " topics = set()\n", 65 | " for member in members.results:\n", 66 | " try:\n", 67 | " for t in member['topics']:\n", 68 | " topics.add(t['name'])\n", 69 | " except:\n", 70 | " print(\"Unexpected error:\", sys.exc_info()[0])\n", 71 | " raise\n", 72 | "\n", 73 | " return list(topics)\n", 74 | "\n", 75 | "def df_topics(event_id):\n", 76 | " members = get_members(event_id=event_id)\n", 77 | " topics = get_topics(members)\n", 78 | " columns=['name','id','thumb_link'] + topics\n", 79 | " \n", 80 | " data = [] \n", 81 | " for member in members.results:\n", 82 | " topic_vector = [0]*len(topics)\n", 83 | " for topic in member['topics']:\n", 84 | " index = topics.index(topic['name']) \n", 85 | " topic_vector[index] = 1\n", 86 | " try:\n", 87 | " data.append([member['name'], member['id'], member['photo']['thumb_link']] + topic_vector)\n", 88 | " except KeyError:\n", 89 | " data.append([member['name'], member['id'], 'NA'] + topic_vector)\n", 90 | " except:\n", 91 | " print(\"Unexpected error:\", sys.exc_info()[0])\n", 92 | " raise\n", 93 | " return pd.DataFrame(data=data, columns=columns)" 94 | ] 95 | }, 96 | { 97 | "cell_type": "markdown", 98 | "metadata": {}, 99 | "source": [ 100 | "### Q1: Load data from meetup.com into a dataframe by calling df_topics.\n", 101 | "You'll need to call the `df_topics` function with the `event_id` and assign it to a variable to use it for the following questions." 102 | ] 103 | }, 104 | { 105 | "cell_type": "code", 106 | "execution_count": null, 107 | "metadata": {}, 108 | "outputs": [], 109 | "source": [] 110 | }, 111 | { 112 | "cell_type": "markdown", 113 | "metadata": {}, 114 | "source": [ 115 | "### Q2: What are the column names of the dataframe?" 116 | ] 117 | }, 118 | { 119 | "cell_type": "code", 120 | "execution_count": null, 121 | "metadata": {}, 122 | "outputs": [], 123 | "source": [] 124 | }, 125 | { 126 | "cell_type": "markdown", 127 | "metadata": {}, 128 | "source": [ 129 | "### Q3: How do you check the index of the dataframe? Can you set the index of the data frame to be the names column? " 130 | ] 131 | }, 132 | { 133 | "cell_type": "code", 134 | "execution_count": null, 135 | "metadata": {}, 136 | "outputs": [], 137 | "source": [] 138 | }, 139 | { 140 | "cell_type": "markdown", 141 | "metadata": {}, 142 | "source": [ 143 | "### Q4: How would you get the transpose of the dataframe?" 144 | ] 145 | }, 146 | { 147 | "cell_type": "code", 148 | "execution_count": null, 149 | "metadata": {}, 150 | "outputs": [], 151 | "source": [] 152 | }, 153 | { 154 | "cell_type": "markdown", 155 | "metadata": {}, 156 | "source": [ 157 | "### Q5: What does the first and last 10 rows of the dataset look like?" 158 | ] 159 | }, 160 | { 161 | "cell_type": "code", 162 | "execution_count": null, 163 | "metadata": { 164 | "collapsed": true 165 | }, 166 | "outputs": [], 167 | "source": [] 168 | }, 169 | { 170 | "cell_type": "markdown", 171 | "metadata": {}, 172 | "source": [ 173 | "### Q6: Write the data out to a csv file. Only include names and topics. Do not include member id and thumblink." 174 | ] 175 | }, 176 | { 177 | "cell_type": "code", 178 | "execution_count": null, 179 | "metadata": { 180 | "collapsed": true 181 | }, 182 | "outputs": [], 183 | "source": [] 184 | }, 185 | { 186 | "cell_type": "markdown", 187 | "metadata": {}, 188 | "source": [ 189 | "### Q7: How many unique topics of interest do we have?" 190 | ] 191 | }, 192 | { 193 | "cell_type": "code", 194 | "execution_count": null, 195 | "metadata": { 196 | "collapsed": true 197 | }, 198 | "outputs": [], 199 | "source": [] 200 | }, 201 | { 202 | "cell_type": "markdown", 203 | "metadata": {}, 204 | "source": [ 205 | "### Q8: Write a function that takes a name and gives back all of his/her interest." 206 | ] 207 | }, 208 | { 209 | "cell_type": "code", 210 | "execution_count": null, 211 | "metadata": { 212 | "collapsed": true 213 | }, 214 | "outputs": [], 215 | "source": [] 216 | }, 217 | { 218 | "cell_type": "markdown", 219 | "metadata": {}, 220 | "source": [ 221 | "### Q9: Write a function that takes a topic of interest and gives back names who are interested." 222 | ] 223 | }, 224 | { 225 | "cell_type": "code", 226 | "execution_count": null, 227 | "metadata": { 228 | "collapsed": true 229 | }, 230 | "outputs": [], 231 | "source": [] 232 | }, 233 | { 234 | "cell_type": "markdown", 235 | "metadata": {}, 236 | "source": [ 237 | "### Q10: Who has the highest number of topics? How many topics is he/she interested in?" 238 | ] 239 | }, 240 | { 241 | "cell_type": "code", 242 | "execution_count": null, 243 | "metadata": { 244 | "collapsed": true 245 | }, 246 | "outputs": [], 247 | "source": [] 248 | }, 249 | { 250 | "cell_type": "markdown", 251 | "metadata": {}, 252 | "source": [ 253 | "### Q11: Which is the most common topic of intertest? Which is the least popular topic of interest?" 254 | ] 255 | }, 256 | { 257 | "cell_type": "code", 258 | "execution_count": null, 259 | "metadata": { 260 | "collapsed": true 261 | }, 262 | "outputs": [], 263 | "source": [] 264 | }, 265 | { 266 | "cell_type": "markdown", 267 | "metadata": {}, 268 | "source": [ 269 | "### Q12: Which names are associated with the topics of interest found in the previous question?" 270 | ] 271 | }, 272 | { 273 | "cell_type": "code", 274 | "execution_count": null, 275 | "metadata": {}, 276 | "outputs": [], 277 | "source": [] 278 | }, 279 | { 280 | "cell_type": "markdown", 281 | "metadata": {}, 282 | "source": [ 283 | "### Q13: Draw a plot that shows the frequency of each topic." 284 | ] 285 | }, 286 | { 287 | "cell_type": "code", 288 | "execution_count": null, 289 | "metadata": { 290 | "collapsed": true 291 | }, 292 | "outputs": [], 293 | "source": [] 294 | }, 295 | { 296 | "cell_type": "markdown", 297 | "metadata": {}, 298 | "source": [ 299 | "### Q14: Are there topic(s) common to all the members of your team?" 300 | ] 301 | }, 302 | { 303 | "cell_type": "code", 304 | "execution_count": null, 305 | "metadata": {}, 306 | "outputs": [], 307 | "source": [] 308 | }, 309 | { 310 | "cell_type": "markdown", 311 | "metadata": {}, 312 | "source": [ 313 | "### Q15: Write a function that will take the names of your team members and rank every pair by the number of topics common among them. So if the team has A, B, C and D, an example could be\n", 314 | "\n", 315 | " A, B - 6\n", 316 | " A, C - 5\n", 317 | " A, D - 4\n", 318 | " B, C - 3\n", 319 | " B, D - 2\n", 320 | " C, D - 1\n", 321 | "\n", 322 | "### Note the pairs are sorted in the number of topics common among them.\n" 323 | ] 324 | }, 325 | { 326 | "cell_type": "code", 327 | "execution_count": null, 328 | "metadata": {}, 329 | "outputs": [], 330 | "source": [] 331 | } 332 | ], 333 | "metadata": { 334 | "kernelspec": { 335 | "display_name": "Python 3", 336 | "language": "python", 337 | "name": "python3" 338 | }, 339 | "language_info": { 340 | "codemirror_mode": { 341 | "name": "ipython", 342 | "version": 3 343 | }, 344 | "file_extension": ".py", 345 | "mimetype": "text/x-python", 346 | "name": "python", 347 | "nbconvert_exporter": "python", 348 | "pygments_lexer": "ipython3", 349 | "version": "3.4.3" 350 | } 351 | }, 352 | "nbformat": 4, 353 | "nbformat_minor": 2 354 | } 355 | -------------------------------------------------------------------------------- /problems/data_science/text-analysis.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chicagopython/CodingWorkshops/45c00db2e154ed164b5299ed9794f721208ab3fb/problems/data_science/text-analysis.jpg -------------------------------------------------------------------------------- /problems/py101/make_a_game/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | 8 | [packages] 9 | pytest = "*" 10 | black = "*" 11 | 12 | [requires] 13 | python_version = "3.7" 14 | -------------------------------------------------------------------------------- /problems/py101/make_a_game/Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "51f48cd2be9338b63b497d9d423d9b639fd84e7e61efb768858d5aef9b173fc8" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.7" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "appdirs": { 20 | "hashes": [ 21 | "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", 22 | "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e" 23 | ], 24 | "version": "==1.4.3" 25 | }, 26 | "atomicwrites": { 27 | "hashes": [ 28 | "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", 29 | "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6" 30 | ], 31 | "version": "==1.3.0" 32 | }, 33 | "attrs": { 34 | "hashes": [ 35 | "sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", 36 | "sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399" 37 | ], 38 | "version": "==19.1.0" 39 | }, 40 | "black": { 41 | "hashes": [ 42 | "sha256:09a9dcb7c46ed496a9850b76e4e825d6049ecd38b611f1224857a79bd985a8cf", 43 | "sha256:68950ffd4d9169716bcb8719a56c07a2f4485354fec061cdd5910aa07369731c" 44 | ], 45 | "index": "pypi", 46 | "version": "==19.3b0" 47 | }, 48 | "click": { 49 | "hashes": [ 50 | "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", 51 | "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" 52 | ], 53 | "version": "==7.0" 54 | }, 55 | "importlib-metadata": { 56 | "hashes": [ 57 | "sha256:9ff1b1c5a354142de080b8a4e9803e5d0d59283c93aed808617c787d16768375", 58 | "sha256:b7143592e374e50584564794fcb8aaf00a23025f9db866627f89a21491847a8d" 59 | ], 60 | "markers": "python_version < '3.8'", 61 | "version": "==0.20" 62 | }, 63 | "more-itertools": { 64 | "hashes": [ 65 | "sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", 66 | "sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4" 67 | ], 68 | "version": "==7.2.0" 69 | }, 70 | "packaging": { 71 | "hashes": [ 72 | "sha256:a7ac867b97fdc07ee80a8058fe4435ccd274ecc3b0ed61d852d7d53055528cf9", 73 | "sha256:c491ca87294da7cc01902edbe30a5bc6c4c28172b5138ab4e4aa1b9d7bfaeafe" 74 | ], 75 | "version": "==19.1" 76 | }, 77 | "pluggy": { 78 | "hashes": [ 79 | "sha256:0825a152ac059776623854c1543d65a4ad408eb3d33ee114dff91e57ec6ae6fc", 80 | "sha256:b9817417e95936bf75d85d3f8767f7df6cdde751fc40aed3bb3074cbcb77757c" 81 | ], 82 | "version": "==0.12.0" 83 | }, 84 | "py": { 85 | "hashes": [ 86 | "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", 87 | "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53" 88 | ], 89 | "version": "==1.8.0" 90 | }, 91 | "pyparsing": { 92 | "hashes": [ 93 | "sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80", 94 | "sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4" 95 | ], 96 | "version": "==2.4.2" 97 | }, 98 | "pytest": { 99 | "hashes": [ 100 | "sha256:95d13143cc14174ca1a01ec68e84d76ba5d9d493ac02716fd9706c949a505210", 101 | "sha256:b78fe2881323bd44fd9bd76e5317173d4316577e7b1cddebae9136a4495ec865" 102 | ], 103 | "index": "pypi", 104 | "version": "==5.1.2" 105 | }, 106 | "six": { 107 | "hashes": [ 108 | "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", 109 | "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" 110 | ], 111 | "version": "==1.12.0" 112 | }, 113 | "toml": { 114 | "hashes": [ 115 | "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", 116 | "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e" 117 | ], 118 | "version": "==0.10.0" 119 | }, 120 | "wcwidth": { 121 | "hashes": [ 122 | "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", 123 | "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" 124 | ], 125 | "version": "==0.1.7" 126 | }, 127 | "zipp": { 128 | "hashes": [ 129 | "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", 130 | "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335" 131 | ], 132 | "version": "==0.6.0" 133 | } 134 | }, 135 | "develop": {} 136 | } 137 | -------------------------------------------------------------------------------- /problems/py101/make_a_game/README.md: -------------------------------------------------------------------------------- 1 | # Make a Game 2 | 3 | ## Overview 4 | 5 | For a long time, computer games made use of few, if any, graphics. Many of them were text based adventures that you could run directly on your command line. Some examples included: 6 | 7 | - [Zork](https://en.wikipedia.org/wiki/Zork) 8 | - Adventureland 9 | - [Dwarf Fortress](https://en.wikipedia.org/wiki/Dwarf_Fortress) 10 | 11 | and many others. Players would input their directions using words and the computer would return back what happened. 12 | 13 | ## Your Task 14 | 15 | Your task for this evening is to, working together, create something fun to play! Your group will take turns typing (in other words, one computer per group and only one person typing at a time) and helping to develop (offering ideas, thoughts on what to do next, etc.). It can be helpful to have another person with their computer open to research, but ultimately, this is a group effort! Everyone should have a chance to write code, offer suggestions, research libraries, etc. 16 | 17 | ## Setup 18 | 19 | 1. You'll need one computer that your group will share that can install and run [Pipenv](https://pipenv-fork.readthedocs.io/en/latest/). While an OS-X or Linux machine will likely do the best for this step, a Windows machine will be able to do it as well. If you run into any challenges installing Pipenv, please ask for help! 20 | 2. The project is in the ChiPy project night repo. If you do not have the repository already, run 21 | 22 | ``` 23 | git clone https://github.com/chicagopython/CodingWorkshops.git 24 | ``` 25 | 26 | 3. Navigate to the folder for this challenge: 27 | 28 | ``` 29 | cd CodingWorkshops/problems/py101/make_a_game 30 | ``` 31 | 32 | 4. Run `pipenv install`, which will install all of the libraries we have recommended for this exercise. 33 | 5. After you've installed all of the libraries, run `pipenv shell`, which will turn on a virtual environment running Python 3.7. 34 | 6. Run `python run.py` to see the program in its current state or `pytest -vv` to run all tests. 35 | 7. If you make changes, this project uses a library called [Black](https://github.com/psf/black) to automatically format the code for you (this known as a [linter](https://en.wikipedia.org/wiki/Lint_(software)). To run it, from the root of the directory, run `black .` 36 | 37 | ## What's in this repository? 38 | 39 | In this repository is a basic shell of a game. This game sets up a `Player()` which parrots back what the player writes to it until they decide to leave. Some of the key features here that you might want to use or modify or extend are: 40 | 41 | 1. _Tests_ -- in the `tests/` folder are a series of tests to make sure that the `Player()` object continues to work as expected. As you add new functionality, you might want to practice [test-driven development](https://en.wikipedia.org/wiki/Test-driven_development) to ensure that your code continues to work as you want it to! 42 | 2. _run.py_ -- This is the main file that the player will run to play the game. One thing to note is the section that starts with `while player.in_game:` -- this section sets up a loop that will keep running until the `in_game` attribute is set to False. This way, your players can continue to do things and the game won't run once through the code and immediately finish. You'll likely add extra things into this section. 43 | 3. _Player() class_ -- This class holds information about the player -- what its name is, what message it wants to repeat, whether it still wants to play the game...classes are useful for persisting or modifying some sort of collected state or values about a "thing", as well as defining actions that that thing may take. For example, our `Player()` can currently `say_hello()` and it has an `in_game` status that can be either `True` or `False`. A different object might have different behaviors or different attributes that can be set. Depending on your game, you may want to set up more of these classes -- for example, you could set up a `Map()` class to hold onto information about a map (what room the player is currently in, what rooms they can go to, etc.) or an `Enemy()` class (what the enemy can do, how it interacts with the player, whether it is defeated or not, etc. 44 | 45 | ## So what should we do? 46 | 47 | A good way to begin might be the following: 48 | 49 | 1. Decide what type of game you want to make: do you want to make a madlibs clone? Tic-tac-toe? A small dungeon? A word game? Put together a couple of ideas and identify what you'd like to build (and don't worry if you don't finish in time! This exercise is for you to be introduced to some Python concepts, not to emerge with a fully-developed game). 50 | 2. Identify what basic building blocks you would need to interact with in the game. For example, if you were making a madlibs clone, you would want to identify what the user could enter, some scripts for those words to be entered into, and something that reads the story out after all the words have been entered. This can help with figuring out the basic flow of the game (for example, you would not want the story to be revealed before all the words are entered!) 51 | 3. Start adding code and testing the game -- you could both add automated tests (like the ones in `tests/` or try playing your game to see if it works. 52 | 53 | Happy Developing! 54 | -------------------------------------------------------------------------------- /problems/py101/make_a_game/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chicagopython/CodingWorkshops/45c00db2e154ed164b5299ed9794f721208ab3fb/problems/py101/make_a_game/lib/__init__.py -------------------------------------------------------------------------------- /problems/py101/make_a_game/lib/player.py: -------------------------------------------------------------------------------- 1 | class Player: 2 | def __init__(self): 3 | self._in_game = True 4 | self.name = None 5 | 6 | def say_hello(self): 7 | return "Hello!" 8 | 9 | def set_name(self, name): 10 | self.name = name 11 | 12 | def repeat(self, message): 13 | return message 14 | 15 | @property 16 | def in_game(self): 17 | return self._in_game 18 | 19 | @in_game.setter 20 | def in_game(self, game_state): 21 | if not isinstance(game_state, bool): 22 | raise Exception("Must set game state to True or False") 23 | self._in_game = game_state 24 | -------------------------------------------------------------------------------- /problems/py101/make_a_game/run.py: -------------------------------------------------------------------------------- 1 | from lib.player import Player 2 | 3 | if __name__ == "__main__": 4 | player = Player() 5 | player_name = input("Hello! What is your name? ") 6 | player.set_name(player_name) 7 | print(f"Welcome {player.name} to this new game!") 8 | print("Right now, all we can do is repeat what you say.") 9 | print("Edit this program to add in more functionality!") 10 | print("To stop playing, type quit") 11 | 12 | while player.in_game: 13 | message = input("What would you like me to repeat? (type quit to exit) ") 14 | if message == "quit": 15 | print("Roger that, thanks for playing!") 16 | player.in_game = False 17 | else: 18 | print(player.repeat(message)) 19 | -------------------------------------------------------------------------------- /problems/py101/make_a_game/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chicagopython/CodingWorkshops/45c00db2e154ed164b5299ed9794f721208ab3fb/problems/py101/make_a_game/tests/__init__.py -------------------------------------------------------------------------------- /problems/py101/make_a_game/tests/test_player.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from lib.player import Player 4 | 5 | 6 | class TestPlayer: 7 | def test_player_says_hello(self): 8 | assert Player().say_hello() == "Hello!" 9 | 10 | def test_player_sets_name(self): 11 | player = Player() 12 | player.set_name("Player Name") 13 | assert player.name == "Player Name" 14 | 15 | def test_player_repeats_message(self): 16 | assert Player().repeat("A message") == "A message" 17 | 18 | def test_player_updates_play_state(self): 19 | player = Player() 20 | # assert default player state 21 | assert player.in_game is True 22 | # assert we can set player to inactive 23 | player.in_game = False 24 | # assert that the player state has changed 25 | assert player.in_game is False 26 | 27 | def test_cannot_update_player_to_invalid_state(self): 28 | player = Player() 29 | with pytest.raises(Exception): 30 | player.in_game = "Some kind of invalid state!" 31 | -------------------------------------------------------------------------------- /problems/py101/python_koans/README.md: -------------------------------------------------------------------------------- 1 | For this exercise we will learn the Zen of Python using Test Driven Development. 2 | Python Koans is a suite of broken tests, which are written against Python code that demonstrate how to Pythonic code. 3 | Your job is to fix the broken tests by filling in the missing parts of the code. 4 | 5 | * Download the zip of python_koans from [here](https://github.com/tathagata/python_koans/archive/chipy_mentorship_coding_dojo.zip). This is a fork of the original repository without some of the simpler examples. 6 | 7 | * Unzip the archive. Change into the directory created and then depending on which version of Python you 8 | would be using, change into python2 or python3 directory. 9 | 10 | * Run `./run.sh` or `./run.bat` depending on if you are in a unix or windows environment. 11 | 12 | * You'll see an output like 13 | > Thinking AboutLists 14 | > test_creating_lists has damaged your karma. 15 | > 16 | > You have not yet reached enlightenment ... 17 | > AssertionError: '-=> FILL ME IN! <=-' != 0 18 | 19 | > Please meditate on the following code: 20 | > File "/Users/t/Downloads/python_koans-chipy_mentorship_coding_dojo_2/python3/koans/about_lists.py", line 14, in test_creating_lists 21 | > self.assertEqual(__, len(empty_list)) 22 | > 23 | > 24 | > You have completed 0 koans and 1 lessons. 25 | > You are now 206 koans and 36 lessons away from reaching enlightenment. 26 | > 27 | > Beautiful is better than ugly. 28 | 29 | * Open the file that follows "Please meditate on the following code" in your text editor and put the appropriate fix. 30 | 31 | * Run `./run.sh` or `./run.bat` depending on if you are in a unix or windows environment. If your fix is correct, you'll see the error message has been replaced with a new one. Great! you have fixed one test, so now move on to the next one by repeating the above steps. 32 | -------------------------------------------------------------------------------- /problems/py101/python_team_project/README.md: -------------------------------------------------------------------------------- 1 | The organizers of Project Nights need your help! Grouping people for 2 | team projects is a manual task. Why do it manually, when 3 | we can automate it? 4 | 5 | ### Is this project for you 6 | Before you progress further, let's check if we are ready to solve this. You should 7 | - Have a personal computer with working wifi and power cord 8 | - Have Python 3 installed on your computer. Yep, Python 3 only. 9 | - Have [Atom](https://atom.io/) or [Sublime Text](https://www.sublimetext.com/3) installed in your computer. 10 | - Have written & ran programs in Python from the command line 11 | - Have some idea about lists, dictionaries and functions 12 | - Have some idea about `virtualenv` and installing packages with `pip` 13 | 14 | This project is not tested using Jupyter Notebook, PyCharm, 15 | Spider, or any other ide/text editor/programming environment for that matter. 16 | Atom or Sublime Text and the command line are the only supported development environment for this project. 17 | 18 | Short url for this page: **https://git.io/vdv43** 19 | 20 | Sounds reasonable? Then let's dive in - and build an awesome command line app using python. 21 | 22 | 23 | ### Can command line applications be cool 24 | You bet! 25 | Checkout this PyCon 2017 video on which this project is based 26 | 27 | Amjith Ramanujam Awesome Command Line Tools PyCon 2017 29 | 30 | The slides are available [here](https://speakerdeck.com/pycon2017/amjith-ramanujam-awesome-command-line-tools). 31 | 32 | ### What is a Team Project 33 | Glad you asked! A team project is an hour long problem solving session where each team 34 | consists of four members of different expertise level. The teams are formed from the 35 | list of attendees of the project night. 36 | 37 | 38 | ### The Objective 39 | Our objective is to build an awesome command line application in Python3 that 40 | - allows creating list of people who want to participate in a team project 41 | - once the list is created, the program automatically creates teams of four 42 | 43 | ### A Balanced Team 44 | To keep the team composition balanced in terms of experience, we want every team 45 | to have two members with more experience than the other two. 46 | Measuring experience is very subjective and difficult, but we will keep it simple. 47 | We will rely on a (not very scientific) metic - lines of code written till date. 48 | 49 | We will create a list by taking names of people from tonight's RSVP list. Along with their name we will also include the number of lines of code that person has written till date in Python or an equivalent language. Imagine this as a tool that one of the organizers uses to checkin attendees as they start coming in on the day of Project Night. 50 | 51 | And yeah, this number of lines can be just a rough estimate. As a 52 | reference, the linux kernel is over 23 million lines of code! 53 | 54 | ### Bootstrap 55 | - If you are familiar with `git`, run 56 | 57 | git clone https://github.com/chicagopython/CodingWorkshops.git 58 | 59 | - If not, go to https://github.com/chicagopython/CodingWorkshops 60 | - Click on the Download Zip and unzip the file that gets downloaded 61 | - From your command line, change directory to the path where you have downloaded it. 62 | - On linux or OS X 63 | 64 | > cd path/to/CodingWorkshops/problems/py101/python_team_project/ 65 | 66 | - On Windows 67 | 68 | > cd path\to\CodingWorkshops\problems\py101\python_team_project 69 | 70 | 71 | Here you will find the basic skeleton of the app under `app.py`. (after September 21, 2017) 72 | 73 | ### Set up virtualenv 74 | If you are using Linux or OS X, run the following to create a new virtualenv 75 | 76 | python3 -m venv venv 77 | source venv/bin/activate 78 | pip install -r requirements.txt 79 | python app.py 80 | 81 | On Windows, run the following 82 | 83 | python3 -m venv venv 84 | venv\Scripts\activate 85 | pip install -r requirements.txt 86 | python app.py 87 | 88 | [![asciicast](https://asciinema.org/a/M1hP91h153PuOPEjVYbot6jPj.png)](https://asciinema.org/a/M1hP91h153PuOPEjVYbot6jPj) 89 | 90 | Next let's get started by looking into the code. 91 | 92 | ## Feature 0: Look into app.py 93 | app.py is the script contains some code to get you started. 94 | We will be using two external libraries for this 95 | program. 96 | 97 | python_prompt_toolkit 98 | meetup-api 99 | 100 | - `prompt_toolkit` makes it easy for building awesome command line apps 101 | - `meetup-api` provides us with the data for the meetup 102 | - `asciinema` which is also in the `requirements.txt` isn't strictly necessary and we'll talk about it last 103 | 104 | `execute` function is where you would be writing your application logic. 105 | 106 | You should not require to make changes to `main` and `get_names` functions. In an upcoming project nights we will dig into `get_names` and make changes to it. 107 | 108 | Next let's run app.py 109 | 110 | python3 app.py 111 | 112 | This should drop you to a prompt. 113 | 114 | > 115 | 116 | Type in something to that prompt. 117 | 118 | > Hola amigo 119 | > You issued: Hola amigo 120 | 121 | Try a few more 122 | 123 | > Gracias 124 | > You issued:Gracias 125 | 126 | You can now press the up arrow key and access the history of the commands you have issued. To exit out of the program, you can type Ctrl-D. 127 | 128 | > 129 | GoodBye! 130 | 131 | ### Feature 1: Implement the Add command 132 | Next let's create a command where the user of the program can register new participants to build up the list of users from whom teams will be formed. 133 | 134 | The command should look like the following 135 | 136 | add 137 | 138 | where is the full name of the person as it appears in the 139 | meetup.com and is the number of lines of code 140 | that person has written in Python or a similar programming language in their life. 141 | 142 | > add Tathagata Dasgupta 1 143 | 144 | 145 | ### Feature 2: Add some error checking (optional) 146 | You might be asking what if the user incorrectly types something that is not a number 147 | for the `number of lines`. Indeed that would be incorrect. Show an error message 148 | if is not a number. 149 | 150 | > add Tathagata Dasgupta o 151 | ERROR: number of lines should be, er, a "number" 152 | 153 | Are there other error conditions that can arise? 154 | 155 | ## Feature 2: Implement a List command 156 | Next add a new command list. 157 | Show the number of people added and prints the total count 158 | and the median of the line count. 159 | 160 | > add Tathagata Dasgupta 1 161 | > add Jason Wirth 2 162 | > add Adam Bain 3 163 | > add Brian Ray 4 164 | > add Guido van Rossum 5 165 | > list 166 | > People added so far: 167 | Tathagata Dasgupta, 1 168 | Jason Wirth, 2 169 | Adam Bain, 3 170 | Brian Ray, 4 171 | Guido Van Rossum, 5 172 | 173 | Number of people: 5 174 | Median line count: 3 175 | 176 | Your output need not be exactly the same, but should show the 177 | correct data. The Median line count will be used in the next 178 | features. 179 | Hint: Python3 has the statistics module, so you can use 180 | 181 | import statistics 182 | statistics.median([1,2,3,4,5]) 183 | 184 | 185 | ## Feature 3: Add the teams command (optional) 186 | The next command we will implement is `teams` command. Let's say you 187 | have added a few people already and know what the median line count 188 | is for the people you have added so far. On issuing the `teams` command 189 | it should output teams of four such that each team contains 190 | - 2 person who have written less than the median lines of code 191 | - 2 person who has written more than written more than median 192 | 193 | If there are less the four people left to group, then group them 194 | together. 195 | 196 | With our running example, there would be a team of four, and the 197 | remaining 1 should be in another group. 198 | 199 | > teams 200 | Group 1: Tathagata Dasgupta, Jason Wirth, Brian Ray, Guido Van Rossum 201 | Group 2: Adam Bain 202 | 203 | 204 | ## Feature 4. Enhance Team command (optional) 205 | Add a unique team name 206 | 207 | ## Feature 5. Enhance Team command (optional) 208 | Make up random room names and add a room name for each team. 209 | 210 | ## Feature 6. Enhance Teams command (optional) 211 | Print the teams sorted with the average number of lines of code for each team. 212 | 213 | ## Feature 7. Auto-completion for commands (optional) 214 | Adding auto completion is easy with `prompt_toolkit`. In `app.py` the following line is used to include the 215 | `add` command to auto-completion. 216 | 217 | command_completer = WordCompleter(['add'], ignore_case=True) 218 | 219 | Add the remaining commands. 220 | 221 | 222 | ## Feature 8. Auto-completion for participant names (optional) 223 | Typing in names of the attendees of project night would be time consuming 224 | and error prone. Let's add auto-completion magic to it! 225 | 226 | The funcion `get_names` uses meetup-api and returns a list of names for the attendees. 227 | All you need to do is include a call to `get_names` in the command_completer. 228 | 229 | ## Feature 9. Tell the world (optional, OS X or Linux only) 230 | We have also installed asciinema - a tool that allows you 231 | to create recordings of your terminal sessions. In order to tell 232 | the world what your team has made, let's make a small recording. 233 | 234 | ascriinmea rec teamname.json 235 | 236 | Run your program and show off all the cool features you have built in your app. 237 | To finish recording hit Ctrl-D. 238 | Next play the recordings 239 | 240 | asciinema play teamname.json 241 | 242 | Once the playback looks good, upload it to the interwebs. 243 | 244 | asciinema upload teamname.json 245 | 246 | Finally, tweet the link to @chicagopython with "Python Project Night 247 | Mentorship". Include the twitter handles of your team members. 248 | 249 | Note: This is tested only in OS X. Let me know your experience for running it on 250 | other operating systems. 251 | If you see an error 252 | 253 | asciinema needs a UTF-8 native locale to run. Check the output of `locale` command. 254 | 255 | the run the following command before running asciinema. 256 | 257 | export LC_ALL=en_US.UTF-8 258 | 259 | 260 | Thanks! Thats all folks! 261 | If you found a bug or think you some instructions are missing - just open a issue in this repository. 262 | -------------------------------------------------------------------------------- /problems/py101/python_team_project/app.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | import sys 3 | 4 | from prompt_toolkit import prompt, AbortAction 5 | from prompt_toolkit.history import InMemoryHistory 6 | from prompt_toolkit.contrib.completers import WordCompleter 7 | import meetup.api 8 | 9 | def get_names(): 10 | client = meetup.api.Client('3f6d3275d3b6314e73453c4aa27') 11 | 12 | rsvps=client.GetRsvps(event_id='239174106', urlname='_ChiPy_') 13 | member_id = ','.join([str(i['member']['member_id']) for i in rsvps.results]) 14 | members = client.GetMembers(member_id=member_id) 15 | 16 | names = [] 17 | for member in members.results: 18 | try: 19 | names.append(member['name']) 20 | except: 21 | pass # ignore those who do not have a complete profile 22 | 23 | return names 24 | 25 | 26 | command_completer = WordCompleter(['add'], ignore_case=True) 27 | 28 | 29 | def execute(command): 30 | return "You issued:" + command 31 | 32 | 33 | def main(): 34 | history = InMemoryHistory() 35 | 36 | while True: 37 | try: 38 | text = prompt('> ', 39 | completer = command_completer, 40 | history=history, 41 | on_abort=AbortAction.RETRY) 42 | messages = execute(text) 43 | 44 | print(messages) 45 | except EOFError: 46 | break # Control-D pressed. 47 | 48 | print('GoodBye!') 49 | 50 | if __name__ == '__main__': 51 | main() 52 | -------------------------------------------------------------------------------- /problems/py101/python_team_project/requirements.txt: -------------------------------------------------------------------------------- 1 | prompt_toolkit==1.0.15 2 | meetup-api==0.1.1 3 | asciinema==1.4.0 4 | -------------------------------------------------------------------------------- /problems/py101/testing/EnableTravisCI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chicagopython/CodingWorkshops/45c00db2e154ed164b5299ed9794f721208ab3fb/problems/py101/testing/EnableTravisCI.png -------------------------------------------------------------------------------- /problems/py101/testing/Makefile: -------------------------------------------------------------------------------- 1 | all:setup test 2 | 3 | .PHONY: all help setup test 4 | 5 | help: 6 | @echo "Usage:" 7 | @echo " make help show this message" 8 | @echo " make setup create virtual environment and install dependencies" 9 | @echo " make test run the test suite" 10 | @echo " exit leave virtual environment" 11 | 12 | setup: 13 | pip install pipenv 14 | pipenv install --dev --three 15 | 16 | test: 17 | pipenv run -- pytest 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /problems/py101/testing/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | pytest = "==3.6.3" 8 | pytest-cov = "==2.5.1" 9 | pytest-xdist = "==1.19.1" 10 | "pytest-flake8" = "==1.0.1" 11 | 12 | [dev-packages] 13 | 14 | [requires] 15 | python_version = "3.6" 16 | -------------------------------------------------------------------------------- /problems/py101/testing/Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "cf193a18372660cf547b480da0f6d2960693fa8075bb401cf4f90bbc5e6de9e5" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.6" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "apipkg": { 20 | "hashes": [ 21 | "sha256:37228cda29411948b422fae072f57e31d3396d2ee1c9783775980ee9c9990af6", 22 | "sha256:58587dd4dc3daefad0487f6d9ae32b4542b185e1c36db6993290e7c41ca2b47c" 23 | ], 24 | "markers": "python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.2.*' and python_version != '3.0.*' and python_version != '3.3.*'", 25 | "version": "==1.5" 26 | }, 27 | "atomicwrites": { 28 | "hashes": [ 29 | "sha256:240831ea22da9ab882b551b31d4225591e5e447a68c5e188db5b89ca1d487585", 30 | "sha256:a24da68318b08ac9c9c45029f4a10371ab5b20e4226738e150e6e7c571630ae6" 31 | ], 32 | "version": "==1.1.5" 33 | }, 34 | "attrs": { 35 | "hashes": [ 36 | "sha256:4b90b09eeeb9b88c35bc642cbac057e45a5fd85367b985bd2809c62b7b939265", 37 | "sha256:e0d0eb91441a3b53dab4d9b743eafc1ac44476296a2053b6ca3af0b139faf87b" 38 | ], 39 | "version": "==18.1.0" 40 | }, 41 | "coverage": { 42 | "hashes": [ 43 | "sha256:03481e81d558d30d230bc12999e3edffe392d244349a90f4ef9b88425fac74ba", 44 | "sha256:0b136648de27201056c1869a6c0d4e23f464750fd9a9ba9750b8336a244429ed", 45 | "sha256:198626739a79b09fa0a2f06e083ffd12eb55449b5f8bfdbeed1df4910b2ca640", 46 | "sha256:28b2191e7283f4f3568962e373b47ef7f0392993bb6660d079c62bd50fe9d162", 47 | "sha256:2eb564bbf7816a9d68dd3369a510be3327f1c618d2357fa6b1216994c2e3d508", 48 | "sha256:337ded681dd2ef9ca04ef5d93cfc87e52e09db2594c296b4a0a3662cb1b41249", 49 | "sha256:3a2184c6d797a125dca8367878d3b9a178b6fdd05fdc2d35d758c3006a1cd694", 50 | "sha256:3c79a6f7b95751cdebcd9037e4d06f8d5a9b60e4ed0cd231342aa8ad7124882a", 51 | "sha256:3d72c20bd105022d29b14a7d628462ebdc61de2f303322c0212a054352f3b287", 52 | "sha256:3eb42bf89a6be7deb64116dd1cc4b08171734d721e7a7e57ad64cc4ef29ed2f1", 53 | "sha256:4635a184d0bbe537aa185a34193898eee409332a8ccb27eea36f262566585000", 54 | "sha256:56e448f051a201c5ebbaa86a5efd0ca90d327204d8b059ab25ad0f35fbfd79f1", 55 | "sha256:5a13ea7911ff5e1796b6d5e4fbbf6952381a611209b736d48e675c2756f3f74e", 56 | "sha256:69bf008a06b76619d3c3f3b1983f5145c75a305a0fea513aca094cae5c40a8f5", 57 | "sha256:6bc583dc18d5979dc0f6cec26a8603129de0304d5ae1f17e57a12834e7235062", 58 | "sha256:701cd6093d63e6b8ad7009d8a92425428bc4d6e7ab8d75efbb665c806c1d79ba", 59 | "sha256:7608a3dd5d73cb06c531b8925e0ef8d3de31fed2544a7de6c63960a1e73ea4bc", 60 | "sha256:76ecd006d1d8f739430ec50cc872889af1f9c1b6b8f48e29941814b09b0fd3cc", 61 | "sha256:7aa36d2b844a3e4a4b356708d79fd2c260281a7390d678a10b91ca595ddc9e99", 62 | "sha256:7d3f553904b0c5c016d1dad058a7554c7ac4c91a789fca496e7d8347ad040653", 63 | "sha256:7e1fe19bd6dce69d9fd159d8e4a80a8f52101380d5d3a4d374b6d3eae0e5de9c", 64 | "sha256:8c3cb8c35ec4d9506979b4cf90ee9918bc2e49f84189d9bf5c36c0c1119c6558", 65 | "sha256:9d6dd10d49e01571bf6e147d3b505141ffc093a06756c60b053a859cb2128b1f", 66 | "sha256:be6cfcd8053d13f5f5eeb284aa8a814220c3da1b0078fa859011c7fffd86dab9", 67 | "sha256:c1bb572fab8208c400adaf06a8133ac0712179a334c09224fb11393e920abcdd", 68 | "sha256:de4418dadaa1c01d497e539210cb6baa015965526ff5afc078c57ca69160108d", 69 | "sha256:e05cb4d9aad6233d67e0541caa7e511fa4047ed7750ec2510d466e806e0255d6", 70 | "sha256:f3f501f345f24383c0000395b26b726e46758b71393267aeae0bd36f8b3ade80" 71 | ], 72 | "markers": "python_version >= '2.6' and python_version != '3.1.*' and python_version != '3.2.*' and python_version < '4' and python_version != '3.0.*'", 73 | "version": "==4.5.1" 74 | }, 75 | "execnet": { 76 | "hashes": [ 77 | "sha256:a7a84d5fa07a089186a329528f127c9d73b9de57f1a1131b82bb5320ee651f6a", 78 | "sha256:fc155a6b553c66c838d1a22dba1dc9f5f505c43285a878c6f74a79c024750b83" 79 | ], 80 | "markers": "python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.2.*' and python_version != '3.0.*' and python_version != '3.3.*'", 81 | "version": "==1.5.0" 82 | }, 83 | "flake8": { 84 | "hashes": [ 85 | "sha256:7253265f7abd8b313e3892944044a365e3f4ac3fcdcfb4298f55ee9ddf188ba0", 86 | "sha256:c7841163e2b576d435799169b78703ad6ac1bbb0f199994fc05f700b2a90ea37" 87 | ], 88 | "version": "==3.5.0" 89 | }, 90 | "mccabe": { 91 | "hashes": [ 92 | "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", 93 | "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" 94 | ], 95 | "version": "==0.6.1" 96 | }, 97 | "more-itertools": { 98 | "hashes": [ 99 | "sha256:2b6b9893337bfd9166bee6a62c2b0c9fe7735dcf85948b387ec8cba30e85d8e8", 100 | "sha256:6703844a52d3588f951883005efcf555e49566a48afd4db4e965d69b883980d3", 101 | "sha256:a18d870ef2ffca2b8463c0070ad17b5978056f403fb64e3f15fe62a52db21cc0" 102 | ], 103 | "version": "==4.2.0" 104 | }, 105 | "pluggy": { 106 | "hashes": [ 107 | "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff", 108 | "sha256:d345c8fe681115900d6da8d048ba67c25df42973bda370783cd58826442dcd7c", 109 | "sha256:e160a7fcf25762bb60efc7e171d4497ff1d8d2d75a3d0df7a21b76821ecbf5c5" 110 | ], 111 | "markers": "python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.2.*' and python_version != '3.0.*' and python_version != '3.3.*'", 112 | "version": "==0.6.0" 113 | }, 114 | "py": { 115 | "hashes": [ 116 | "sha256:3fd59af7435864e1a243790d322d763925431213b6b8529c6ca71081ace3bbf7", 117 | "sha256:e31fb2767eb657cbde86c454f02e99cb846d3cd9d61b318525140214fdc0e98e" 118 | ], 119 | "markers": "python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.2.*' and python_version != '3.0.*' and python_version != '3.3.*'", 120 | "version": "==1.5.4" 121 | }, 122 | "pycodestyle": { 123 | "hashes": [ 124 | "sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766", 125 | "sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9" 126 | ], 127 | "version": "==2.3.1" 128 | }, 129 | "pyflakes": { 130 | "hashes": [ 131 | "sha256:08bd6a50edf8cffa9fa09a463063c425ecaaf10d1eb0335a7e8b1401aef89e6f", 132 | "sha256:8d616a382f243dbf19b54743f280b80198be0bca3a5396f1d2e1fca6223e8805" 133 | ], 134 | "version": "==1.6.0" 135 | }, 136 | "pytest": { 137 | "hashes": [ 138 | "sha256:0453c8676c2bee6feb0434748b068d5510273a916295fd61d306c4f22fbfd752", 139 | "sha256:4b208614ae6d98195430ad6bde03641c78553acee7c83cec2e85d613c0cd383d" 140 | ], 141 | "index": "pypi", 142 | "version": "==3.6.3" 143 | }, 144 | "pytest-cov": { 145 | "hashes": [ 146 | "sha256:03aa752cf11db41d281ea1d807d954c4eda35cfa1b21d6971966cc041bbf6e2d", 147 | "sha256:890fe5565400902b0c78b5357004aab1c814115894f4f21370e2433256a3eeec" 148 | ], 149 | "index": "pypi", 150 | "version": "==2.5.1" 151 | }, 152 | "pytest-flake8": { 153 | "hashes": [ 154 | "sha256:e5cdc4f459c9436ac6c649e428a014bb5988605858549397374ec29a776cae68", 155 | "sha256:ec248d4a215d6c7cd9d3ca48f365ece0e3892b46d626c22a95ccc80188ff35ed" 156 | ], 157 | "index": "pypi", 158 | "version": "==1.0.1" 159 | }, 160 | "pytest-forked": { 161 | "hashes": [ 162 | "sha256:e4500cd0509ec4a26535f7d4112a8cc0f17d3a41c29ffd4eab479d2a55b30805", 163 | "sha256:f275cb48a73fc61a6710726348e1da6d68a978f0ec0c54ece5a5fae5977e5a08" 164 | ], 165 | "version": "==0.2" 166 | }, 167 | "pytest-xdist": { 168 | "hashes": [ 169 | "sha256:237a0c30056d539de723a1461cf2817c04a9abc90ee1fe51ddfaecc643ef3022" 170 | ], 171 | "index": "pypi", 172 | "version": "==1.19.1" 173 | }, 174 | "six": { 175 | "hashes": [ 176 | "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", 177 | "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" 178 | ], 179 | "version": "==1.11.0" 180 | } 181 | }, 182 | "develop": {} 183 | } 184 | -------------------------------------------------------------------------------- /problems/py101/testing/README.md: -------------------------------------------------------------------------------- 1 | # 1. Introduction to PyTest and Continuous Integration 2 | 3 | 4 | 5 | - [1. Introduction to PyTest and Continuous Integration](#1-introduction-to-pytest-and-continuous-integration) 6 | - [1.1. Setup Instructions](#11-setup-instructions) 7 | - [1.1.1. Git and Github](#111-git-and-github) 8 | - [1.1.2. Travis setup](#112-travis-setup) 9 | - [1.2. Python](#12-python) 10 | - [1.3. Quick Git command refresher](#13-quick-git-command-refresher) 11 | - [1.4. Exercise 0: Project Setup](#14-exercise-0-project-setup) 12 | - [1.4.1. `team_organizer.py`](#141-team_organizerpy) 13 | - [1.4.2. `test_team_organizer.py`](#142-test_team_organizerpy) 14 | - [1.4.3. `Makefile`](#143-makefile) 15 | - [1.4.4. `Pipfile` and `Pipfile.lock`](#144-pipfile-and-pipfilelock) 16 | - [1.4.5. `pytest.ini`](#145-pytestini) 17 | - [1.4.6. `travis.yml`](#146-travisyml) 18 | - [1.4.7. Test your setup is working](#147-test-your-setup-is-working) 19 | - [1.5. Exercise 1: Build](#15-exercise-1-build) 20 | - [1.6. Exercise 2: Run the program](#16-exercise-2-run-the-program) 21 | - [1.7. Exercise 3: Running the tests](#17-exercise-3-running-the-tests) 22 | - [1.8. Execrise 4: Coverage](#18-execrise-4-coverage) 23 | - [1.9. Exercise 6: Fail, Fix, Pass](#19-exercise-6-fail-fix-pass) 24 | - [1.10. Exercise 7: Fixtures](#110-exercise-7-fixtures) 25 | - [1.11. Exercise 8: Implement the tests](#111-exercise-8-implement-the-tests) 26 | - [1.12. Exercise 9: Implement the tests first, then implement the feature](#112-exercise-9-implement-the-tests-first-then-implement-the-feature) 27 | 28 | 29 | 30 | Testing and Continuous Integration is at the heart of building good software. 31 | For this project we will be focus on writing tests for a given problem and use 32 | travis-ci for running the tests automatically everytime code is checked into Github. 33 | 34 | **Objectives**: 35 | In this project we will explore 36 | 37 | - Introduction to unit testing with pytest 38 | - How to setup continuous integration with Github and Travis-CI 39 | 40 | ## 1.1. Setup Instructions 41 | 42 | For doing this project you will need a Github account, a Travis-ci.org account and git 43 | installed locally. 44 | 45 | ### 1.1.1. Git and Github 46 | 47 | After completing the steps below you should have a github account and be able to push 48 | your local changes to this repository to github. 49 | 50 | - Follow the setup steps described [here](https://help.github.com/articles/set-up-git/) 51 | - Read the steps described in [fork a repo](https://help.github.com/articles/fork-a-repo) 52 | - Use the steps described above to fork this repository [CodingWorkshops](https://github.com/chicagopython/CodingWorkshops) 53 | 54 | The changes that you make as a part of this exercise, will be pushed to the fork you created for this 55 | repository. 56 | 57 | In case you have already have created a fork of this repository in your github account, you will 58 | want to bring it up to date with the recent changes. In that case, 59 | you will need to do the following: 60 | 61 | - [configuring a remote fork](https://help.github.com/articles/configuring-a-remote-for-a-fork/) 62 | - [syncing a fok](https://help.github.com/articles/syncing-a-fork/) 63 | 64 | ### 1.1.2. Travis setup 65 | 66 | Continuous Integrration is a critical part of building your software. It automatically runs 67 | the tests when you check in code into your version control (git) and paves the way for 68 | continuous delivery, i.e. release often and release early. 69 | In this section we will set up a Continuous Integration pipeline with Travis-ci. 70 | 71 | - First, head over to [Travis-CI.org](https://travis-ci.org/.) 72 | - Sign in with your Github account, and accept the terms and conditions. 73 | - On success, you will be landing on your profile page that lists the CodingWorkshop repository 74 | - Once you have located the repo, toggle the button next to the repository to enable travis CI 75 | 76 | ![travi-build-img](EnableTravisCI.png) 77 | 78 | If you have multiple repositories, you will have to search for the repository by typing in the name 79 | of the repository (CodingWorkshop) in the search bar on the dashboard page. 80 | 81 | ## 1.2. Python 82 | 83 | This project has made no attempt to be compatible with Python 2.7. 😎 84 | 85 | Recommended version: Python 3.6 86 | 87 | ## 1.3. Quick Git command refresher 88 | 89 | Below are the few most used git commands 90 | 91 | git checkout master # checkout to master branch 92 | git checkout -b feature/cool # crate a new branch feature/cool 93 | git add -u # stage all the updates for commit 94 | git commit -am "Adding changes and commiting with a comment" 95 | git push origin master # push commits to develop/ci branch 96 | 97 | Note for this exercise, we will be working on the master branch directly. However, 98 | that is NOT the best practice. Branches are cheap in git, so a new feature or fix 99 | would first go to a branch, get tested, code reviewed and finally merged to master. 100 | 101 | ## 1.4. Exercise 0: Project Setup 102 | 103 | After completing the steps in setup, you should have the cloned versoin of the fork of CodingWorkshop 104 | repository in your local machine. Lets take the time to look at the structure of this 105 | project. All code is located under `/problems/py101/testing` directory. So from your 106 | terminal go to the directory where you have cloned the repository. 107 | 108 | cd path/to/clone/problems/py101/testing 109 | 110 | Make sure you are in this directory for the remainder of this project. 111 | 112 | Run `pwd` (`cwd` for Windows) on the command prompt to find out which directory you 113 | are on. 114 | 115 | Your output should end in `problems/py101/testing` and contain the files described 116 | below. 117 | 118 | ### 1.4.1. `team_organizer.py` 119 | 120 | This file is a simplified implementation of the problem of grouping the project 121 | night attendees into teams of four based on the number of lines of code they have 122 | written such that in each team, two team members have more lines of code than the other. 123 | This is the system under test. 124 | 125 | ### 1.4.2. `test_team_organizer.py` 126 | 127 | This file is the test for the above module written using Pytest. 128 | 129 | These two files mentioned above are the only two files that we will be making 130 | modifications to for this project. 131 | 132 | ### 1.4.3. `Makefile` 133 | 134 | This file contains the commands that are required building the project. 135 | You can run `make help` to see what are the options. 136 | 137 | ### 1.4.4. `Pipfile` and `Pipfile.lock` 138 | 139 | These two files are used by `pipenv` to create a virtual enviornment that 140 | isolates all the dependencies of this project from other python projects in your computer. 141 | Learn more about [pipenv](https://docs.pipenv.org/). 142 | 143 | ### 1.4.5. `pytest.ini` 144 | 145 | This file contains the configuration for `pytest`. 146 | 147 | ### 1.4.6. `travis.yml` 148 | 149 | In addition to all the files in this directory, located at the root of the repository, 150 | is a file called `.travis.yml`. This is used by the continuous intergration tool travis-ci. 151 | This contains the information on how to build this python project. 152 | 153 | ### 1.4.7. Test your setup is working 154 | 155 | Just make a small edit on this file (README.md), commit and push the changes. 156 | 157 | git commit -am "Demo commit to check everything is working" 158 | git push origin master 159 | 160 | If travis-ci.org gets triggered and is all green, your push has successfully ran through 161 | the linting and testing pipeline. 162 | 163 | To display that badge of honor, click on the build button next on the travis page and select 164 | Markdown from the second dropdown. Copy the markdown code displayed and add it to the top 165 | of this file (README.md). 166 | 167 | ![travi-build-img](travis-build-img.png) 168 | 169 | If you run into issues, [ask your question on slack](https://chipy.slack.com/messages/C093F7W8P/details/) 170 | 171 | ## 1.5. Exercise 1: Build 172 | 173 | From the `/problems/py101/testing` directory, run 174 | 175 | make 176 | 177 | - Which packages got installed? 178 | - Which version of python is getting used? 179 | - How many tests pass, skipped and how long did it take? 180 | - Note a new directory `htmlcov` was created. We will revisit this in Exericse 5. 181 | - What is difference in output when you run the `make` command again? 182 | 183 | ## 1.6. Exercise 2: Run the program 184 | 185 | Start by running 186 | 187 | python team_organizer.py 188 | 189 | This will drop you to the program's interactive prompt. 190 | Below is a sample interaction where users named a, b, c, 191 | d, e and f are added using the add command. 192 | Following that, we run the `print` command where the users 193 | are grouped in to max of size four where two users have 194 | written less lines of code than the others. 195 | 196 | ``` 197 | t (master *) testing $ python team_organizer.py 198 | Welcome to Chicago Python Project Night Team Organizer 199 | org> help 200 | help 201 | 202 | Documented commands (type help ): 203 | ======================================== 204 | add help print 205 | 206 | Undocumented commands: 207 | ====================== 208 | exit 209 | 210 | org> help add 211 | help add 212 | Adds a new user. Needs Name slackhandle number_of_lines separated by space 213 | org> add a @a 100 214 | add a @a 100 215 | org> add b @b 200 216 | add b @b 200 217 | org> add c @c 300 218 | add c @c 300 219 | org> add d @d 400 220 | add d @d 400 221 | org> add e @e 500 222 | add e @e 500 223 | org> add f @f 50 224 | add f @f 50 225 | org> print 226 | print 227 | ['f, a, e, d'] 228 | b, c 229 | org> 230 | ``` 231 | 232 | ## 1.7. Exercise 3: Running the tests 233 | 234 | Run 235 | 236 | make test 237 | 238 | This will run the tests in the `test_team_organizer.py` file. 239 | 240 | Run 241 | 242 | pipenv run pytest --help 243 | 244 | Now check the flags that are present in the `pytest.ini` file against 245 | the output of the `--help` command to see what each one does. 246 | 247 | ## 1.8. Execrise 4: Coverage 248 | 249 | When we first ran `make`, `pytest` created a directory called `htmlcov` 250 | that show you the coverage information about `team_organizr,py` code. 251 | Open the `index.html` file inside `htmlcov` to check the lines that 252 | has not been covered by the tests in the `test_team_organizer.py`. 253 | 254 | What is the % coverage of the code at this point? 255 | Click on `team_organizer.py` to see which lines are outside coverage. 256 | 257 | ## 1.9. Exercise 6: Fail, Fix, Pass 258 | 259 | You are now all set to fix the tests. Goto `test_team_organizer.py` and 260 | find `test_add_a_person_with_lower_than_median` test. Notice this test is 261 | skipped when run with pytest. To fix it remove the decorator `pytest.mark.skip` 262 | and run `pytest` again. Commit the code and run 263 | 264 | make test 265 | 266 | Make the necessary changes so that the test passes. 267 | 268 | git commit -am "Fixed failing test" 269 | git push origin master 270 | 271 | Go to travis-ci.org and inspect the output before and after fixing the test. 272 | What is the coverage value at this point? 273 | 274 | ## 1.10. Exercise 7: Fixtures 275 | 276 | The purpose of test fixtures is to provide a fixed baseline upon which tests can 277 | reliably and repeatedly execute. 278 | 279 | We are making use of two fixtures - one factory method `person` that churns out Persons 280 | as needed by `organizer` fixture. 281 | 282 | `test_count_number_of_teams` is broken as well. How can you fix it? 283 | 284 | Tip: To run a singe test, use 285 | 286 | pipenv run pytest -k 287 | 288 | ## 1.11. Exercise 8: Implement the tests 289 | 290 | The two functions below have been left for you to implement. 291 | 292 | - test_add_a_person_who_has_never_written_code_before 293 | - test_add_two_person_with_same_name_but_different_slack_handles 294 | 295 | Note the names of the tests are long and verbose to give you an idea of what 296 | what exactly you need to test. 297 | 298 | Does implementing these tests have any effect on coverage results? 299 | Would it be still useful if there is no improvement in coverage? 300 | 301 | ## 1.12. Exercise 9: Implement the tests first, then implement the feature 302 | 303 | For the following two tests, first implement the test that asserts the 304 | expected behavior. From the test name it should be evident from the test name. 305 | If you run the tests at this point, they should fail. Then go back to 306 | `team_organizer.py` and implement the feature by changing the code. 307 | Once your implementation is complete, run `make test`. 308 | 309 | - test_adding_person_with_negative_lines_of_code_throws_exception 310 | - test_handle_duplicate_additions 311 | -------------------------------------------------------------------------------- /problems/py101/testing/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = --cov=team_organizer --cov-report=term-missing --flake8 --cov-report=html 3 | -------------------------------------------------------------------------------- /problems/py101/testing/team_organizer.py: -------------------------------------------------------------------------------- 1 | import cmd 2 | import collections 3 | 4 | Person = collections.namedtuple('Person', 'name slackhandle lines') 5 | 6 | 7 | def parse(arg): 8 | try: 9 | name, slackhandle, lines = arg.split() 10 | return Person(name, slackhandle, int(lines)) 11 | except ValueError: 12 | print("Invalid entry format") 13 | 14 | 15 | class Organizer: 16 | def __init__(self): 17 | self.d = collections.deque() 18 | 19 | def median_lines(self): 20 | return self.d[len(self.d) // 2].lines 21 | 22 | def add(self, p): 23 | '''Takes a person and adds it to the queue 24 | if the number of lines is higher than the median 25 | it adds to the end else it adds to the front ''' 26 | 27 | if len(self.d) == 0: 28 | self.d.append(p) 29 | return 30 | 31 | if self.median_lines() >= p.lines: 32 | self.d.appendleft(p) 33 | else: 34 | self.d.append(p) 35 | 36 | @staticmethod 37 | def _names(persons): 38 | return ', '.join(p.name for p in persons) 39 | 40 | def teams(self): 41 | ''' Makes teams by taking 2 from the front of the queue 42 | and two from the end of the queue till teams of 4 43 | can be made. ''' 44 | teams = [] 45 | while len(self.d) >= 4: 46 | ps = self.d.popleft(), self.d.popleft(), self.d.pop(), self.d.pop() 47 | teams.append([self._names(ps)]) 48 | 49 | if self.d: 50 | teams.append(self._names(self.d)) 51 | self.d.clear() 52 | return teams 53 | 54 | 55 | class OrganizerShell(cmd.Cmd): 56 | intro = 'Welcome to Chicago Python Project Night Team Organizer' 57 | prompt = 'org> ' 58 | 59 | def __init__(self): 60 | cmd.Cmd.__init__(self) 61 | self.org = Organizer() 62 | 63 | def do_add(self, arg): 64 | 'Adds a new user. Needs Name, slackhandle, number of lines' 65 | p = parse(arg) 66 | self.org.add(p) 67 | 68 | def do_print(self, arg): 69 | 'Print the team organization' 70 | for team in self.org.teams(): 71 | print(team) 72 | 73 | def do_exit(self, args): 74 | return True 75 | 76 | 77 | if __name__ == '__main__': 78 | OrganizerShell().cmdloop() 79 | -------------------------------------------------------------------------------- /problems/py101/testing/test_team_organizer.py: -------------------------------------------------------------------------------- 1 | from team_organizer import Person, Organizer 2 | import pytest 3 | import random 4 | import string 5 | 6 | 7 | @pytest.fixture 8 | def person(): 9 | ''' Factory function that returns a function to get new Person ''' 10 | def rand_data_person(): 11 | name = ''.join(random.sample(string.ascii_lowercase, 3)) 12 | handle = '@' + name 13 | lines = random.randint(0, 100000) 14 | return Person(name, handle, lines) 15 | return rand_data_person 16 | 17 | 18 | @pytest.fixture 19 | def organizer(person): 20 | '''Uses the person fixture factory to create an organizer 21 | and adds 4 persons to it.''' 22 | org = Organizer() 23 | for _ in range(4): 24 | org.add(person()) 25 | 26 | return org 27 | 28 | 29 | def test_add_person_with_higher_than_median(organizer): 30 | median_lines = organizer.median_lines() 31 | more_than_median_lines = median_lines + 1000 32 | p = Person('a', '@a', more_than_median_lines) 33 | organizer.add(p) 34 | 35 | assert organizer.d[len(organizer.d) - 1] == p 36 | 37 | 38 | @pytest.mark.skip(reason="broken test needs fixing") 39 | def test_add_a_person_with_lower_than_median(organizer): 40 | median_lines = organizer.median_lines() 41 | less_than_median_lines = median_lines - 1000 42 | p = Person('a', '@a', less_than_median_lines) 43 | organizer.add(p) 44 | 45 | assert 1/0 == 0 46 | assert organizer.d[0] == p 47 | pass 48 | 49 | 50 | # Pytest Fixture: https://docs.pytest.org/en/latest/fixture.html 51 | @pytest.mark.skip(reason="broken test needs fixing") 52 | @pytest.mark.parametrize("test_input,expected", [ 53 | ([Person('a', '@a', 1), 54 | Person('b', '@b', 2), 55 | Person('c', '@c', 3), 56 | Person('d', '@d', 4)], 1), 57 | ([Person('a', '@a', 1)], 1) 58 | ]) 59 | def test_count_number_of_teams(organizer, test_input, expected): 60 | for p in test_input: 61 | organizer.add(p) 62 | assert len(organizer.teams()) == expected 63 | 64 | 65 | def test_add_a_person_who_has_never_written_code_before(organizer): 66 | organizer.add(Person('a', '@a', 0)) 67 | pass 68 | 69 | 70 | def test_add_two_person_with_same_name_but_different_slack_handles(organizer): 71 | pass 72 | 73 | 74 | def test_add_a_person_who_supplied_negative_lines_of_code(organizer): 75 | 'Behavior not implemented. Decide & implement the behavior & the test.' 76 | pass 77 | 78 | 79 | def test_add_same_person_twice(): 80 | 'Behavior not implemented. Decide & implement the behavior & the test.' 81 | pass 82 | -------------------------------------------------------------------------------- /problems/py101/testing/travis-build-img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chicagopython/CodingWorkshops/45c00db2e154ed164b5299ed9794f721208ab3fb/problems/py101/testing/travis-build-img.png -------------------------------------------------------------------------------- /problems/py101/trackcoder/Makefile: -------------------------------------------------------------------------------- 1 | all:setup test 2 | 3 | .PHONY: all help setup shell 4 | 5 | help: 6 | @echo "Usage:" 7 | @echo " make help show this message" 8 | @echo " make setup create virtual environment and install dependencies" 9 | @echo " make test run the test suite" 10 | @echo " exit leave virtual environment" 11 | 12 | setup: 13 | pip install pipenv 14 | pipenv install --dev --three 15 | 16 | shell: 17 | pipenv shell 18 | 19 | 20 | -------------------------------------------------------------------------------- /problems/py101/trackcoder/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | pytest = "*" 8 | prompt_toolkit = "*" 9 | peewee = "*" 10 | click = "*" 11 | 12 | [requires] 13 | python_version = "3.6" -------------------------------------------------------------------------------- /problems/py101/trackcoder/Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "3ad590855f2e73831065bbf03fbd5a6aeb2d130f3a784d2e344ec219d3deae4d" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.6" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "atomicwrites": { 20 | "hashes": [ 21 | "sha256:0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0", 22 | "sha256:ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee" 23 | ], 24 | "version": "==1.2.1" 25 | }, 26 | "attrs": { 27 | "hashes": [ 28 | "sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69", 29 | "sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb" 30 | ], 31 | "version": "==18.2.0" 32 | }, 33 | "click": { 34 | "hashes": [ 35 | "sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d", 36 | "sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b" 37 | ], 38 | "index": "pypi", 39 | "version": "==6.7" 40 | }, 41 | "more-itertools": { 42 | "hashes": [ 43 | "sha256:c187a73da93e7a8acc0001572aebc7e3c69daf7bf6881a2cea10650bd4420092", 44 | "sha256:c476b5d3a34e12d40130bc2f935028b5f636df8f372dc2c1c01dc19681b2039e", 45 | "sha256:fcbfeaea0be121980e15bc97b3817b5202ca73d0eae185b4550cbfce2a3ebb3d" 46 | ], 47 | "version": "==4.3.0" 48 | }, 49 | "pathlib2": { 50 | "hashes": [ 51 | "sha256:8eb170f8d0d61825e09a95b38be068299ddeda82f35e96c3301a8a5e7604cb83", 52 | "sha256:d1aa2a11ba7b8f7b21ab852b1fb5afb277e1bb99d5dfc663380b5015c0d80c5a" 53 | ], 54 | "markers": "python_version < '3.6'", 55 | "version": "==2.3.2" 56 | }, 57 | "peewee": { 58 | "hashes": [ 59 | "sha256:bb9ee5e2ca601c82f5e3c8327f19af4b5f9941ae74ec520674165692d3d6ae7a" 60 | ], 61 | "index": "pypi", 62 | "version": "==3.7.0" 63 | }, 64 | "pluggy": { 65 | "hashes": [ 66 | "sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1", 67 | "sha256:95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1" 68 | ], 69 | "version": "==0.7.1" 70 | }, 71 | "prompt-toolkit": { 72 | "hashes": [ 73 | "sha256:12e076b21178064b5627f74c4819559c125e31046b55a28d5e024b85fef5617e", 74 | "sha256:f2289fe9dd7f27c305421bffe880a543b04cc67660796a2a912595dbcd0d209f", 75 | "sha256:ff58ce8bb82c11c43416dd3eec7701dcbe8c576e2d7649f1d2b9d21a2fd93808" 76 | ], 77 | "index": "pypi", 78 | "version": "==2.0.4" 79 | }, 80 | "py": { 81 | "hashes": [ 82 | "sha256:06a30435d058473046be836d3fc4f27167fd84c45b99704f2fb5509ef61f9af1", 83 | "sha256:50402e9d1c9005d759426988a492e0edaadb7f4e68bcddfea586bc7432d009c6" 84 | ], 85 | "version": "==1.6.0" 86 | }, 87 | "pytest": { 88 | "hashes": [ 89 | "sha256:453cbbbe5ce6db38717d282b758b917de84802af4288910c12442984bde7b823", 90 | "sha256:a8a07f84e680482eb51e244370aaf2caa6301ef265f37c2bdefb3dd3b663f99d" 91 | ], 92 | "index": "pypi", 93 | "version": "==3.8.0" 94 | }, 95 | "scandir": { 96 | "hashes": [ 97 | "sha256:04b8adb105f2ed313a7c2ef0f1cf7aff4871aa7a1883fa4d8c44b5551ab052d6", 98 | "sha256:1444134990356c81d12f30e4b311379acfbbcd03e0bab591de2696a3b126d58e", 99 | "sha256:1b5c314e39f596875e5a95dd81af03730b338c277c54a454226978d5ba95dbb6", 100 | "sha256:346619f72eb0ddc4cf355ceffd225fa52506c92a2ff05318cfabd02a144e7c4e", 101 | "sha256:44975e209c4827fc18a3486f257154d34ec6eaec0f90fef0cca1caa482db7064", 102 | "sha256:61859fd7e40b8c71e609c202db5b0c1dbec0d5c7f1449dec2245575bdc866792", 103 | "sha256:a5e232a0bf188362fa00123cc0bb842d363a292de7126126df5527b6a369586a", 104 | "sha256:c14701409f311e7a9b7ec8e337f0815baf7ac95776cc78b419a1e6d49889a383", 105 | "sha256:c7708f29d843fc2764310732e41f0ce27feadde453261859ec0fca7865dfc41b", 106 | "sha256:c9009c527929f6e25604aec39b0a43c3f831d2947d89d6caaab22f057b7055c8", 107 | "sha256:f5c71e29b4e2af7ccdc03a020c626ede51da471173b4a6ad1e904f2b2e04b4bd" 108 | ], 109 | "markers": "python_version < '3.5'", 110 | "version": "==1.9.0" 111 | }, 112 | "six": { 113 | "hashes": [ 114 | "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", 115 | "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" 116 | ], 117 | "version": "==1.11.0" 118 | }, 119 | "wcwidth": { 120 | "hashes": [ 121 | "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", 122 | "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" 123 | ], 124 | "version": "==0.1.7" 125 | } 126 | }, 127 | "develop": {} 128 | } 129 | -------------------------------------------------------------------------------- /problems/py101/trackcoder/app.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | import sys 3 | 4 | from prompt_toolkit import PromptSession 5 | from prompt_toolkit.history import InMemoryHistory 6 | from prompt_toolkit.completion import WordCompleter 7 | from peewee import * 8 | import datetime 9 | import click 10 | 11 | command_completer = WordCompleter(['add', 'show'], ignore_case=True) 12 | 13 | db = SqliteDatabase('to_do_list.db') 14 | 15 | 16 | class ToDo(Model): 17 | task = CharField(max_length=255) 18 | description = CharField(max_length=255) 19 | timestamp = DateTimeField(default=datetime.datetime.now) 20 | mins = IntegerField() 21 | done = BooleanField(default=True) 22 | 23 | class Meta: 24 | database = db 25 | 26 | 27 | def initialize(): 28 | """Connect to database, create tables if they don't exist""" 29 | db.connect() 30 | db.create_tables([ToDo], safe=True) 31 | 32 | def parse(input): 33 | """ 34 | a b 10 first blog post 35 | a c 10 finished cli 36 | a p 120 37 | """ 38 | input = input.strip() 39 | cmd, task, mins, description = ['']*4 40 | try: 41 | cmd, task, mins, *description = input.split() 42 | mins += 0 43 | description = ' '.join(description) 44 | return cmd, task, mins, description 45 | except TypeError: 46 | pass 47 | # return input, task, mins, description 48 | except ValueError: 49 | return input, task, mins, description 50 | 51 | 52 | def add(**kwargs): 53 | ToDo.create(task=kwargs['task'], 54 | mins=kwargs['mins'], 55 | description=kwargs['description']) 56 | 57 | 58 | def show(**kwargs): 59 | for t in ToDo.select(): 60 | print(t.task, t.description, t.mins, t.done) 61 | 62 | 63 | def execute(**kwargs): 64 | cmds = { 65 | 'add': add, 66 | 'show': show 67 | } 68 | 69 | cmds.get(kwargs['cmd'])(**kwargs) 70 | 71 | 72 | @click.command() 73 | @click.option('--interactive', '-i', help='needs some help text', is_flag=True, default=False) 74 | @click.option('--show', '-s', help='needs some help text', is_flag=True, default=False) 75 | @click.option('--add', '-a', nargs=3, type=(click.STRING, int, click.STRING), default=(None, None, None)) 76 | def main(interactive, add, show): 77 | initialize() 78 | 79 | if interactive: 80 | history = InMemoryHistory() 81 | session = PromptSession() 82 | 83 | while True: 84 | try: 85 | text = session.prompt('% ') 86 | except KeyboardInterrupt: 87 | continue 88 | except EOFError: 89 | break 90 | else: 91 | try: 92 | cmd, task, mins, description = parse(text) 93 | execute(cmd=cmd, task=task, mins=mins, description=description) 94 | except TypeError: 95 | print("Please check your input") 96 | elif show: 97 | execute(cmd='show') 98 | else: 99 | task, mins, description = add 100 | execute(cmd='add', task=task, mins=mins, description=description) 101 | print('GoodBye!') 102 | 103 | 104 | if __name__ == '__main__': 105 | main() 106 | -------------------------------------------------------------------------------- /problems/py101/trackcoder/csv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chicagopython/CodingWorkshops/45c00db2e154ed164b5299ed9794f721208ab3fb/problems/py101/trackcoder/csv.png -------------------------------------------------------------------------------- /problems/py101/trackcoder/project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chicagopython/CodingWorkshops/45c00db2e154ed164b5299ed9794f721208ab3fb/problems/py101/trackcoder/project.png -------------------------------------------------------------------------------- /problems/py101/trackcoder/to_do_list.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chicagopython/CodingWorkshops/45c00db2e154ed164b5299ed9794f721208ab3fb/problems/py101/trackcoder/to_do_list.db -------------------------------------------------------------------------------- /problems/webdev/django_pn_tracker/.dockerignore: -------------------------------------------------------------------------------- 1 | .dockerignore 2 | Dockerfile 3 | db.sqlite3 4 | __pycache__ 5 | *.pyc 6 | *.pyo 7 | *.pyd 8 | .Python 9 | env 10 | pip-log.txt 11 | pip-delete-this-directory.txt 12 | .tox 13 | .coverage 14 | .coverage.* 15 | .cache 16 | coverage.xml 17 | *,cover 18 | *.log 19 | .git 20 | .ropeproject 21 | node_modules 22 | -------------------------------------------------------------------------------- /problems/webdev/django_pn_tracker/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | *.pkl 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *.cover 48 | .hypothesis/ 49 | .pytest_cache/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | .static_storage/ 58 | .media/ 59 | local_settings.py 60 | 61 | # Flask stuff: 62 | instance/ 63 | .webassets-cache 64 | 65 | # Scrapy stuff: 66 | .scrapy 67 | 68 | # Sphinx documentation 69 | docs/_build/ 70 | 71 | # PyBuilder 72 | target/ 73 | 74 | # Jupyter Notebook 75 | .ipynb_checkpoints 76 | 77 | # pyenv 78 | .python-version 79 | 80 | # celery beat schedule file 81 | celerybeat-schedule 82 | 83 | # SageMath parsed files 84 | *.sage.py 85 | 86 | # Environments 87 | .env 88 | .venv 89 | env/ 90 | venv/ 91 | ENV/ 92 | env.bak/ 93 | venv.bak/ 94 | .envrc 95 | 96 | # Spyder project settings 97 | .spyderproject 98 | .spyproject 99 | 100 | # Rope project settings 101 | .ropeproject 102 | 103 | # mkdocs documentation 104 | /site 105 | 106 | # mypy 107 | .mypy_cache/ 108 | .ropeproject 109 | 110 | # editors / IDEs 111 | .vscode/ 112 | -------------------------------------------------------------------------------- /problems/webdev/django_pn_tracker/README.md: -------------------------------------------------------------------------------- 1 | In this project we will be building a CRUD-style web app using Django and SQLite3. 2 | 3 | ## The Project 4 | With growing Project Night attendance, ChiPy would like to better keep track of Challenge participation, and the CSV sign up sheets just aren't cutting it. In this project we'll upgrade things a notch by creating a web app (using Django) to keep track of our data (in a basic SQLite3 database). The goal of our app is to have an easy way to enter and view our data, all while maintaining attendee's privacy. To save some time, the Django project has already been created, as well as the framework for the structure for the app we'll be working on. To learn how to set up a Django app from scratch, check out https://docs.djangoproject.com/en/2.1/intro/tutorial01/#creating-a-project. 5 | 6 | By the end of this challenge, you'll have created a web app that looks something like this: 7 | 8 | ![screen shot 2018-08-15 at 12 48 46 pm](https://user-images.githubusercontent.com/19669890/44185211-951ebd80-a0d8-11e8-8d7f-515a0a99b2bf.png) 9 | 10 | ## Setup 11 | 1. Clone the project: 12 | 13 | > git clone https://github.com/chicagopython/CodingWorkshops.git 14 | 15 | 2. Set up a virtual environment, as desired: 16 | ``` 17 | # If you are using Linux or OS X, run the following: 18 | > python3 -m venv venv 19 | > source venv/bin/activate 20 | 21 | # On Windows, run the following: 22 | > python3 -m venv venv 23 | > venv\Scripts\activate 24 | ``` 25 | 3. Navigate to the right folder: 26 | 27 | > cd problems/webdev/django_pn_tracker 28 | 29 | 4. Install our python package requirements: 30 | 31 | > pip install -r requirements.txt 32 | 33 | 34 | ## Instructions 35 | In the steps that follow, instructions will generally reference exactly where code changes need to be made. In the files themselves you'll see large commented blocks of instructions indicating that you're in the right spot. If you don't see commented instructions, double check that you read the prompt correctly. Each step will also have a link to a resource that should directly help you solve the problem at hand. Even if you're not stuck, it's recommended to check out the link to improve your understanding of WHY we're doing what we're doing. 36 | 37 | To help visualize the locations of the file, here's the full file tree: 38 | ``` 39 | ├── db.sqlite3 40 | ├── django_pn_tracker 41 | │   ├── __init__.py 42 | │   ├── __pycache__ 43 | │   ├── apps 44 | │   │   ├── __init__.py 45 | │   │   ├── __pycache__ 46 | │   │   │   └── __init__.cpython-36.pyc 47 | │   │   └── challenges 48 | │   │   ├── __init__.py 49 | │   │   ├── __pycache__ 50 | │   │   ├── admin.py 51 | │   │   ├── apps.py 52 | │   │   ├── forms.py 53 | │   │   ├── migrations 54 | │   │   │   ├── 0001_initial.py 55 | │   │   │   ├── __init__.py 56 | │   │   │   └── __pycache__ 57 | │   │   ├── models.py 58 | │   │   ├── templates 59 | │   │   │   └── challenges 60 | │   │   │   ├── delete.html 61 | │   │   │   ├── edit.html 62 | │   │   │   └── list.html 63 | │   │   ├── tests.py 64 | │   │   ├── urls.py 65 | │   │   └── views.py 66 | │   ├── settings.py 67 | │   ├── static 68 | │   │   ├── css 69 | │   │   │   ├── bootstrap.min.css 70 | │   │   │   └── master.css 71 | │   │   └── js 72 | │   │   ├── bootstrap.min.js 73 | │   │   └── main.js 74 | │   ├── templates 75 | │   │   ├── base.html 76 | │   │   └── index.html 77 | │   ├── urls.py 78 | │   ├── views.py 79 | │   └── wsgi.py 80 | ├── manage.py 81 | ├── requirements.txt 82 | └── setup.cfg 83 | ``` 84 | 85 | ### Step 0: Run the app as is 86 | Before we dig in, let's see what the app currently looks like. This'll also confirm that install/setup went as planned. To run the app locally: 87 | 88 | > ./manage.py runserver 89 | 90 | Then open the link provided in the terminal: http://127.0.0.1:8000/ 91 | 92 | ### Step 1: Configure settings 93 | While our project already has a lot written, we need to configure settings for our new app. Django projects store these settings in `settings.py` by default. 94 | 95 | **a. Add our 'challenges' app to `INSTALLED_APPS`** in `settings.py`. This is actually done already, so the app would run as is without error. Still, check the comment in the code to see how we add apps. 96 | 97 | **b. Point Django to our sqlite db** called `db.sqlite3`. See https://docs.djangoproject.com/en/2.1/ref/settings/#databases for help with the syntax. 98 | 99 | ### Step 2: Create and integrate a new database table 100 | Our initial objective is to set up a table to display all of our challenge participantion records. Table schemas can be found in `models.py`. 101 | 102 | a. Several tables already exist, but we want to create a new table called `AttendeeInfo` to include: 103 | 104 | * `date` - Date of the event 105 | * `name` - the participant's name 106 | * `challenge` - the name of the challenge. Don't forget to account for the foreign key relationship with `Challenge` 107 | * `skills` - for now an integer representing a score in the range of 0-10. Read more avoud validators: https://docs.djangoproject.com/en/2.1/ref/validators/ 108 | 109 | Read more about models here: https://docs.djangoproject.com/en/2.1/topics/db/models/ 110 | 111 | **b. Create/complete migration.** In order for our changes to take effect we need to create a migration and then actually migrate it. 112 | 113 | > ./manage.py makemigrations 114 | 115 | This is a little bit of Django magic. Under the hood Django is automatically generating the SQL commands necessary to update your database. You can see the actual underlying commands in `apps/challenges/migrations`, where a file of commands is created each time we run makemigrations. 116 | 117 | Normally you would see a message like: 118 | 119 | Migrations for 'challenges': 120 | django_pn_tracker/apps/challenges/migrations/0002_auto_20180816_1445.py 121 | - 122 | ... 123 | 124 | However, since the table for our new model actually already exists in db.sqlite3 (purely for the sake of having example records for later steps), if everything is working correctly so far you should see: 125 | 126 | No changes detected 127 | 128 | In order to actually make our changes, run: 129 | 130 | > ./manage.py migrate 131 | 132 | c. **Register `models.AttendeeInfo` in `admin.py`.** Don't worry about what this does yet, we'll get to it in a later instruction. 133 | 134 | ### Step 3: Create a page to view the table's records 135 | Now that we've created our table, we want to create a page to view our new table's records. 136 | 137 | **a. Create the URL** we want for our page in `urls.py`. We will use `""` and reference `challenges_list` in views. Learn more about Django URLs: https://docs.djangoproject.com/en/2.1/topics/http/urls/ 138 | 139 | **b. Create the new `challenge_list` view in `views.py`** for our new page. Django has the concept of “views” to encapsulate the logic responsible for processing a user’s request and for returning the response. Syntactically, a view is just a regular python function (or class) that will be called when we travel to the associated url. To learn more about writing views, check out: https://docs.djangoproject.com/en/2.1/topics/http/views/ . In the case of challenge_list, use the following variable names: 140 | 141 | * `template_name` as the variable that points to `challenges/list.html`, 142 | * `attendees` should be all of our AttendeeInfo objects (see https://docs.djangoproject.com/en/2.1/topics/db/queries/#retrieving-objects), and 143 | * `context` should be a dictionary mapping the string `"attendees"` to our `attendees` variable. 144 | 145 | The names selected are only important to match the templates that we've already started for you. 146 | 147 | **c. Create a template.** Templates are the layers of your app that create the structure of the pages visible to users. Django uses a templating language that's very similar to HTML plus some interactivity with our python code, mostly via syntax surrounded by curly braces. Instead of starting totally from scratch, the template for our record listing is already started in `list.html`, so we'll just fill in the missing section (as indicated with comments). To learn more about template basics (and see some syntax examples) check out: https://docs.djangoproject.com/en/2.1/ref/templates/language/#templates . 148 | 149 | **d. Add a link to our new view in our main navigation bar.** This can be done in the body of `base.html`. 150 | 151 | ### Step 4: Add CRUD capability 152 | Now we can view our new table, but there's nothing in it! Let's create a way to add/edit/delete entries via forms on the front end. To do this, we'll take our steps from 3 and add a little more complexity ala forms: 153 | 154 | **a. Create the URL** we want for our page in `urls.py`. We will reference `challenges_add`, `challenges_edit`, and `challenges_delete` views. Note that edit and delete will reference existing objects - the url pattern therefore requires special syntax (revisit https://docs.djangoproject.com/en/2.1/topics/http/urls/ for help) 155 | 156 | **b. Create forms.** Set up `AttendeeEditForm` and `ConfirmForm` in `forms.py`. We will reference these in our views. Learn more about model forms here: https://docs.djangoproject.com/en/2.1/topics/forms/modelforms/ , and more about fields here: https://docs.djangoproject.com/en/2.1/ref/forms/fields/ . 157 | 158 | **c. Create the three new views in views.py for our new pages.** Mimic the template_name, attendees, and context variable names and style from challenge_list. Use the variable form to instantiate the form object. For example, paste this into challenge_add: `form = forms.AttendeeEditForm()` . See https://docs.djangoproject.com/en/2.1/topics/forms/#the-view and for help. 159 | 160 | **d. Create templates.** These are already started for you in `edit.html` and `delete.html`, so just fill in the missing section (as indicated). Note that add and edit will both use `edit.html`. Bonus hint: Are the edit and delete forms really different..? See https://docs.djangoproject.com/en/2.1/topics/forms/#the-template for help. 161 | 162 | **e. Add links.** Add links for edit and delete in a new column in the existing table (requires editing `list.html` again). 163 | 164 | ### Step 4: Add yourselves as records using our new forms 165 | Now that we've created our MVP, let's test it out by adding records for ourselves for this event. You'll notice that there's no event option in the dropdown for this Intro to Django event. For now, add yourselves under 'Demo Event'. 166 | 167 | ### Step 5: Add interface to add new events 168 | You've seen how to create a form and have a couple of example templates you've already worked with. Now it's time to do one from scratch. Create a form to add challenge records to the Challenge table. You will need a new template in the same folder as our delete.html, edit.html, and list.html. You'll also need to create a new form in forms.py. Lastly we'll need a way to get to our page to add a challenge - let's put it on the main navigation bar next to Challenges List (again in the body of base.html) 169 | 170 | ### Step 6: Add login requirements so our app isn't open to the world. 171 | After all, we'll have participants names and experience levels stored - that's sensitive information. You're on your own again! If you need help: 172 | * https://docs.djangoproject.com/en/2.1/topics/auth/default/#the-login-required-decorator 173 | * https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators 174 | -------------------------------------------------------------------------------- /problems/webdev/django_pn_tracker/db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chicagopython/CodingWorkshops/45c00db2e154ed164b5299ed9794f721208ab3fb/problems/webdev/django_pn_tracker/db.sqlite3 -------------------------------------------------------------------------------- /problems/webdev/django_pn_tracker/django_pn_tracker/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chicagopython/CodingWorkshops/45c00db2e154ed164b5299ed9794f721208ab3fb/problems/webdev/django_pn_tracker/django_pn_tracker/__init__.py -------------------------------------------------------------------------------- /problems/webdev/django_pn_tracker/django_pn_tracker/apps/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chicagopython/CodingWorkshops/45c00db2e154ed164b5299ed9794f721208ab3fb/problems/webdev/django_pn_tracker/django_pn_tracker/apps/__init__.py -------------------------------------------------------------------------------- /problems/webdev/django_pn_tracker/django_pn_tracker/apps/challenges/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chicagopython/CodingWorkshops/45c00db2e154ed164b5299ed9794f721208ab3fb/problems/webdev/django_pn_tracker/django_pn_tracker/apps/challenges/__init__.py -------------------------------------------------------------------------------- /problems/webdev/django_pn_tracker/django_pn_tracker/apps/challenges/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from . import models 3 | 4 | # Learn about the Django Admin: 5 | # https://docs.djangoproject.com/en/2.1/ref/contrib/admin/ 6 | class ChallengeAdmin(admin.ModelAdmin): 7 | list_display = ['name', 'description'] 8 | search_fields = ['name', 'description'] 9 | 10 | 11 | admin.site.register(models.ChallengeTools) 12 | admin.site.register(models.Challenge, ChallengeAdmin) 13 | 14 | ############################## 15 | # Register AttendeeInfo here # 16 | ############################## -------------------------------------------------------------------------------- /problems/webdev/django_pn_tracker/django_pn_tracker/apps/challenges/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ChallengesConfig(AppConfig): 5 | name = 'challenges' 6 | -------------------------------------------------------------------------------- /problems/webdev/django_pn_tracker/django_pn_tracker/apps/challenges/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from . import models 3 | 4 | 5 | ######################################## 6 | # Fill in all ###s in the forms below. # 7 | # Uncomment the forms when complete. # 8 | ######################################## 9 | 10 | # class AttendeeEditForm(forms.ModelForm): 11 | 12 | # class Meta: 13 | # fields = ### 14 | # model = ### 15 | 16 | 17 | # class ConfirmForm(forms.Form): 18 | # pass -------------------------------------------------------------------------------- /problems/webdev/django_pn_tracker/django_pn_tracker/apps/challenges/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1 on 2018-08-11 21:23 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='AttendeeInfo', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('created_date', models.DateTimeField(auto_now_add=True)), 20 | ('modified_date', models.DateTimeField(auto_now=True)), 21 | ('date', models.DateTimeField()), 22 | ('name', models.CharField(max_length=128, verbose_name='Participant Name')), 23 | ('skills', models.IntegerField(default=0)), 24 | ], 25 | options={ 26 | 'abstract': False, 27 | }, 28 | ), 29 | migrations.CreateModel( 30 | name='Challenge', 31 | fields=[ 32 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 33 | ('created_date', models.DateTimeField(auto_now_add=True)), 34 | ('modified_date', models.DateTimeField(auto_now=True)), 35 | ('name', models.CharField(max_length=128)), 36 | ('description', models.TextField(blank=True, null=True)), 37 | ], 38 | options={ 39 | 'abstract': False, 40 | }, 41 | ), 42 | migrations.CreateModel( 43 | name='ChallengeTools', 44 | fields=[ 45 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 46 | ('created_date', models.DateTimeField(auto_now_add=True)), 47 | ('modified_date', models.DateTimeField(auto_now=True)), 48 | ('name', models.CharField(max_length=128)), 49 | ('description', models.TextField(blank=True, null=True)), 50 | ], 51 | options={ 52 | 'verbose_name': 'Challenge Tool', 53 | 'verbose_name_plural': 'Challenge Tools', 54 | }, 55 | ), 56 | migrations.AddField( 57 | model_name='challenge', 58 | name='tools', 59 | field=models.ManyToManyField(blank=True, to='challenges.ChallengeTools'), 60 | ), 61 | migrations.AddField( 62 | model_name='attendeeinfo', 63 | name='challenge', 64 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='challenges.Challenge'), 65 | ), 66 | ] 67 | -------------------------------------------------------------------------------- /problems/webdev/django_pn_tracker/django_pn_tracker/apps/challenges/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chicagopython/CodingWorkshops/45c00db2e154ed164b5299ed9794f721208ab3fb/problems/webdev/django_pn_tracker/django_pn_tracker/apps/challenges/migrations/__init__.py -------------------------------------------------------------------------------- /problems/webdev/django_pn_tracker/django_pn_tracker/apps/challenges/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.core.validators import MaxValueValidator, MinValueValidator 3 | 4 | 5 | class DateModelBase(models.Model): 6 | # Learn about model fields: 7 | # https://docs.djangoproject.com/en/2.1/ref/models/fields/ 8 | created_date = models.DateTimeField(auto_now_add=True) 9 | modified_date = models.DateTimeField(auto_now=True) 10 | 11 | class Meta: 12 | # Learn about abstract base classes: 13 | # https://docs.djangoproject.com/en/2.1/topics/db/models/#abstract-base-classes 14 | abstract = True 15 | 16 | 17 | class ChallengeTools(DateModelBase, models.Model): 18 | name = models.CharField(max_length=128) 19 | description = models.TextField(blank=True, null=True) 20 | 21 | def __str__(self): 22 | return f"{self.name}" 23 | 24 | class Meta: 25 | # learn about django model Meta options: 26 | # https://docs.djangoproject.com/en/2.1/ref/models/options/ 27 | verbose_name = "Challenge Tool" 28 | verbose_name_plural = "Challenge Tools" 29 | 30 | 31 | class Challenge(DateModelBase, models.Model): 32 | name = models.CharField(max_length=128) 33 | description = models.TextField(blank=True, null=True) 34 | tools = models.ManyToManyField("challenges.ChallengeTools", blank=True) 35 | 36 | def __str__(self): 37 | return f"{self.name}" 38 | 39 | 40 | ######################################## 41 | # Fill in all ###s in the model below. # 42 | # Uncomment the class when complete. # 43 | ######################################## 44 | 45 | # class AttendeeInfo(###, ###): 46 | # date = ### 47 | # name = ### 48 | # challenge = ###(###, on_delete=models.CASCADE) 49 | # skills = ### 50 | # 51 | # def __str__(self): 52 | # return f"{self.challenge} {self.name}" 53 | -------------------------------------------------------------------------------- /problems/webdev/django_pn_tracker/django_pn_tracker/apps/challenges/templates/challenges/delete.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

Attendee Edit

5 | 6 |
7 | 10 | {% csrf_token %} 11 |
12 | 13 | 14 | {% endblock content %} -------------------------------------------------------------------------------- /problems/webdev/django_pn_tracker/django_pn_tracker/apps/challenges/templates/challenges/edit.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

Attendee Edit

5 | 6 |
7 | 10 | {% csrf_token %} 11 |
12 | 13 | 14 | {% endblock content %} -------------------------------------------------------------------------------- /problems/webdev/django_pn_tracker/django_pn_tracker/apps/challenges/templates/challenges/list.html: -------------------------------------------------------------------------------- 1 | '{% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 |
7 |

Attendee List

8 |
9 |
10 | Add 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 |
DateParticipant NameChallengeSkill LevelActions
27 |
28 | {{ pagination.links }} 29 | {{ pagination.info }} 30 |
31 |
32 |
33 |
34 | {% endblock content %} -------------------------------------------------------------------------------- /problems/webdev/django_pn_tracker/django_pn_tracker/apps/challenges/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /problems/webdev/django_pn_tracker/django_pn_tracker/apps/challenges/urls.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.urls import path 3 | from . import views 4 | 5 | ################################### 6 | # Add URLs to `urlpatterns` below # 7 | ################################### 8 | urlpatterns = [ 9 | 10 | ] 11 | -------------------------------------------------------------------------------- /problems/webdev/django_pn_tracker/django_pn_tracker/apps/challenges/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, redirect, get_object_or_404 2 | from . import models, forms 3 | 4 | 5 | ######################################## 6 | # Fill in all ###s in the views below. # 7 | # Uncomment the funcs when complete. # 8 | ######################################## 9 | 10 | # def challenge_list(request): 11 | # template_name = ### 12 | # attendees = ### 13 | # context = ### 14 | 15 | # return render(request, template_name, context=context) 16 | 17 | 18 | # def challenge_add(request): 19 | # template_name = ### 20 | # form = forms.AttendeeEditForm() 21 | # ############################################# 22 | # # Logic for capturing submitted information # 23 | # ############################################# 24 | # context = {"form": form} 25 | 26 | # return render(request, template_name, context=context) 27 | 28 | 29 | # def challenge_edit(request, id): 30 | # attendee = get_object_or_404(models.AttendeeInfo, id=id) 31 | # template_name = ### 32 | # form = ### 33 | # ############################# 34 | # # Logic for capturing edits # 35 | # ############################# 36 | # context = {"form": form} 37 | 38 | # return render(request, template_name, context=context) 39 | 40 | 41 | # def challenge_delete(request, id): 42 | # attendee = ### 43 | # template_name = ### 44 | # form = ### 45 | # ###################################### 46 | # # Logic for capturing delete request # 47 | # ###################################### 48 | # context = ### 49 | 50 | # return ### 51 | -------------------------------------------------------------------------------- /problems/webdev/django_pn_tracker/django_pn_tracker/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for regcounter project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.1. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.1/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/2.1/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 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = 'l#_95to^c9ga0f5g$#j8t$j8ykrpk^&o=18qnj(!8i#dgrc3)_' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | ############################################################# 31 | # Add 'django_pn_tracker.apps.challenges' to INSTALLED_APPS # 32 | ############################################################# 33 | 34 | INSTALLED_APPS = [ 35 | 'django.contrib.admin', 36 | 'django.contrib.auth', 37 | 'django.contrib.contenttypes', 38 | 'django.contrib.sessions', 39 | 'django.contrib.messages', 40 | 'django.contrib.staticfiles', 41 | 42 | 'django_pn_tracker.apps.challenges' 43 | ] 44 | 45 | MIDDLEWARE = [ 46 | 'django.middleware.security.SecurityMiddleware', 47 | 'django.contrib.sessions.middleware.SessionMiddleware', 48 | 'django.middleware.common.CommonMiddleware', 49 | 'django.middleware.csrf.CsrfViewMiddleware', 50 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 51 | 'django.contrib.messages.middleware.MessageMiddleware', 52 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 53 | ] 54 | 55 | ROOT_URLCONF = 'django_pn_tracker.urls' 56 | 57 | TEMPLATE_PATH = os.path.abspath( 58 | os.path.join(BASE_DIR, 'django_pn_tracker', 'templates')) 59 | 60 | TEMPLATES = [ 61 | { 62 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 63 | 'DIRS': [TEMPLATE_PATH], 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 | ], 72 | }, 73 | }, 74 | ] 75 | 76 | WSGI_APPLICATION = 'django_pn_tracker.wsgi.application' 77 | 78 | ############################## 79 | # Add database settings here # 80 | ############################## 81 | # https://docs.djangoproject.com/en/2.1/ref/settings/#databases for help 82 | 83 | ################################ 84 | # Add password validation here # 85 | ################################ 86 | # https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators 87 | 88 | ################################ 89 | # Add Internationalization here # 90 | ################################ 91 | # https://docs.djangoproject.com/en/2.1/topics/i18n/ 92 | 93 | LANGUAGE_CODE = 'en-us' 94 | 95 | TIME_ZONE = 'UTC' 96 | 97 | USE_I18N = True 98 | 99 | USE_L10N = True 100 | 101 | USE_TZ = True 102 | 103 | 104 | # Static files (CSS, JavaScript, Images) 105 | # https://docs.djangoproject.com/en/2.1/howto/static-files/ 106 | 107 | STATIC_URL = '/static/' 108 | 109 | STATIC_PATH = os.path.abspath( 110 | os.path.join(BASE_DIR, 'django_pn_tracker', 'static')) 111 | 112 | STATICFILES_DIRS = [ 113 | STATIC_PATH, 114 | ] -------------------------------------------------------------------------------- /problems/webdev/django_pn_tracker/django_pn_tracker/static/js/main.js: -------------------------------------------------------------------------------- 1 | (function ($, win, UNDEFINED) { 2 | 3 | $(".nav-list-expandable.nav-list-expanded > ul:eq(0)").show(); 4 | 5 | $(".nav-list-expandable > a").click(function() { 6 | var a = $(this), 7 | li = a.parent(), 8 | ul = li.children('ul').first(); 9 | 10 | if (ul.length > 0) { 11 | if (ul.is(":visible")) { 12 | li.removeClass("nav-list-expanded"); 13 | ul.slideUp(150); 14 | } 15 | else { 16 | li.addClass("nav-list-expanded"); 17 | ul.slideDown(250); 18 | } 19 | } 20 | }); 21 | 22 | })(jQuery, this); -------------------------------------------------------------------------------- /problems/webdev/django_pn_tracker/django_pn_tracker/templates/base.html: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 | 3 | 4 | 5 | ChiPy Python Project Night{% block title %}- HOME{% endblock title %} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {% block extra_head %}{% endblock extra_head %} 23 | 24 | 25 | 26 |
27 | 62 | 63 |
64 |
65 | 66 | {% block content %} {% endblock %} 67 | 68 |
69 | 70 |
71 | 72 |
73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 |
89 |
90 | 91 | 94 | 95 |
96 |
97 | 98 | 99 | -------------------------------------------------------------------------------- /problems/webdev/django_pn_tracker/django_pn_tracker/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 |
7 |

Home

8 |
9 |
10 |

Welcome to the Project Night Tracker! Our index page is all set up, but want to start tracking challenge participants. That's where you come in!

11 |
12 |
13 |
14 | {% endblock content %} -------------------------------------------------------------------------------- /problems/webdev/django_pn_tracker/django_pn_tracker/urls.py: -------------------------------------------------------------------------------- 1 | """regcounter URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.1/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: path('', 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: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path, include 18 | from django_pn_tracker import views 19 | 20 | 21 | urlpatterns = [ 22 | path('admin/', admin.site.urls), 23 | path('', views.index, name='index'), 24 | path('challenges/', include('django_pn_tracker.apps.challenges.urls')), 25 | ] 26 | -------------------------------------------------------------------------------- /problems/webdev/django_pn_tracker/django_pn_tracker/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | def index(request): 4 | template_name = "index.html" 5 | context = {} 6 | return render(request, template_name, context=context) -------------------------------------------------------------------------------- /problems/webdev/django_pn_tracker/django_pn_tracker/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for regcounter 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/2.1/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', 'regcounter.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /problems/webdev/django_pn_tracker/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', 'django_pn_tracker.settings') 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError as exc: 10 | raise ImportError( 11 | "Couldn't import Django. Are you sure it's installed and " 12 | "available on your PYTHONPATH environment variable? Did you " 13 | "forget to activate a virtual environment?" 14 | ) from exc 15 | execute_from_command_line(sys.argv) 16 | -------------------------------------------------------------------------------- /problems/webdev/django_pn_tracker/requirements.txt: -------------------------------------------------------------------------------- 1 | django==2.1 2 | -------------------------------------------------------------------------------- /problems/webdev/django_pn_tracker/setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = D203 3 | exclude = .git,__pycache__,docs/source/conf.py,old,build,dist,*/migrations/* 4 | max-complexity = 10 5 | max-line-length=99 6 | -------------------------------------------------------------------------------- /problems/webdev/django_rest_framework_api/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | 8 | [packages] 9 | Django = "~=2.2.6" 10 | djangorestframework = "~=3.10.3" 11 | 12 | 13 | [requires] 14 | python_version = "3.7" -------------------------------------------------------------------------------- /problems/webdev/django_rest_framework_api/Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "170332a1bcaeaed6de494dfff1768c99e7787199909f96206652f2ce0f2f2bc8" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.7" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "django": { 20 | "hashes": [ 21 | "sha256:16040e1288c6c9f68c6da2fe75ebde83c0a158f6f5d54f4c5177b0c1478c5b86", 22 | "sha256:89c2007ca4fa5b351a51a279eccff298520783b713bf28efb89dfb81c80ea49b" 23 | ], 24 | "index": "pypi", 25 | "version": "==2.2.7" 26 | }, 27 | "djangorestframework": { 28 | "hashes": [ 29 | "sha256:5488aed8f8df5ec1d70f04b2114abc52ae6729748a176c453313834a9ee179c8", 30 | "sha256:dc81cbf9775c6898a580f6f1f387c4777d12bd87abf0f5406018d32ccae71090" 31 | ], 32 | "index": "pypi", 33 | "version": "==3.10.3" 34 | }, 35 | "pytz": { 36 | "hashes": [ 37 | "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d", 38 | "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be" 39 | ], 40 | "version": "==2019.3" 41 | }, 42 | "sqlparse": { 43 | "hashes": [ 44 | "sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177", 45 | "sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873" 46 | ], 47 | "version": "==0.3.0" 48 | } 49 | }, 50 | "develop": {} 51 | } 52 | -------------------------------------------------------------------------------- /problems/webdev/django_rest_framework_api/README.md: -------------------------------------------------------------------------------- 1 | # Build an API with Django REST Framework 2 | 3 | ## Overview 4 | For this project, we will be creating a functioning REST API. REST APIs can help distribute useful information via GET requests, as well as post and alter databases in a user friendly fashion. 5 | 6 | This project will revolve around using Django and Django's REST framework to build an API for the dataset of your choice. Django is a full web framework capable of handling both back and front end portions of a web app; and the Django team has created great resources to make setting up a Django app quick and easy. 7 | 8 | While the project is structured around Django, feel free to use flask instead, if you're more comfortable. 9 | 10 | ## Environment Setup 11 | To avoid bloating of your primary working environment, we strongly recommend creating a virtual environment. The requirements.txt file includes the required packages, and the included versions have been tested for our needs - use different versions at your own risk. 12 | 13 | We also strong recommend using [Atom](https://atom.io/) or [Sublime Text](https://www.sublimetext.com/3) as your text editor. This project has also NOT been tested using Jupyter Notebook, PyCharm, 14 | Spider, or any other ide/text editor/programming environment. 15 | 16 | 1. For this challenge you will need Python 3.7, pipenv, and git installed. If you're not familiar with pipenv, it's a packaing tool for Python that effectively replaced the pip+virtualenv+requirements.txt workflow. If you already have pip installed, the easiest way to install pipenv is with `pip install --user pipenv`; however, a better way for Mac/Linux Homebrew users is to instead run `brew install pipenv`. More options can be found [here](https://pipenv-fork.readthedocs.io/en/latest/install.html#installing-pipenv). 17 | 18 | 2. The project is in the ChiPy project night repo. If you do not have the repository already, run 19 | 20 | ``` 21 | git clone https://github.com/chicagopython/CodingWorkshops.git 22 | ``` 23 | 24 | 3. Navigate to the folder for this challenge: 25 | 26 | ``` 27 | cd CodingWorkshops/problems/webdev/django_rest_framework_api 28 | ``` 29 | 30 | 4. Run `pipenv install`, which will install all of the libraries we have recommended for this exercise. 31 | 5. After you've installed all of the libraries, run `pipenv shell`, which will turn on a virtual environment running Python 3.7. 32 | 6. To exit the pipenv shell when you are done, simply type `exit`. 33 | 34 | ## Instructions 35 | 36 | ### Find a Database 37 | - Before advancing, find a database that you wish to use for a REST API. It helps if the data is something you are interested in, but don't waste too much time on this part. [Kaggle](https://www.kaggle.com/tags/databases) has a great selection of publicly available databases. If you are looking for something specific, Google has a stellar [database search](https://toolbox.google.com/datasetsearch) feature. 38 | 39 | ### Create Your First App 40 | 41 | - Create a Django app in a local directory of your choosing. Feel free to use the [Django tutorial]((https://docs.djangoproject.com/en/2.2/intro/tutorial01/)) to accomplish this, but please don't call your app the standard Polls App. Create a unique application inside of your Django directory to handle your database and models. Make sure the application is configured in your settings.py file! 42 | 43 | ### Create a Django Model 44 | - Create a Django model custom to your database. Feel free to take liberties like creating relational databases for your models. The model field types should match the intended fields of your database. Make sure to migrate your Django model when you are finished! 45 | 46 | ### Configure the REST framework 47 | - Make sure you appropriately configure Django REST Framework in your settings.py file. If you forget this step, Django to recognize the add on. 48 | 49 | ### Serialization 50 | - Before creating a url or view, serialize your data. This allows Django to render data into a JSON format. Make sure you designate the table (model) and fields (features) you wish to include in your REST API. 51 | 52 | ### Create a View 53 | - Use the standard Django REST framework to create your Django view. Django REST framework allows you to interact with your API in both JSON and a preset interactive template. If you feel like going the extra mile, make your database queryable to gather the information you need. 54 | 55 | ### Designating a URL 56 | - Finally, designate url addresses where your page views can be found. Make sure to create a URL scheme that makes sense to how the intended user will interact with your API. 57 | 58 | ### Running your Server 59 | - At this point it is time to test your API. This can be accomplished by the manage.py runserver command. Django's default location is localhost:8000/. From there, follow the naming scheme you created in your urls. Feel free to play with your API by using those filterable features you created! 60 | 61 | 62 | ## Useful Weblinks 63 | 64 | 65 | - Django Startup and Features 66 | 67 | https://docs.djangoproject.com/en/2.2/intro/tutorial01/ 68 | 69 | https://docs.djangoproject.com/en/2.2/ref/applications/ 70 | 71 | - Django Models 72 | 73 | https://docs.djangoproject.com/en/2.2/ref/models/fields/ 74 | 75 | https://docs.djangoproject.com/en/2.2/topics/db/models/#automatic-primary-key-fields 76 | 77 | - Django REST Framework 78 | 79 | https://www.django-rest-framework.org/#installation 80 | 81 | - Serialization 82 | 83 | https://www.django-rest-framework.org/api-guide/serializers/#modelserializer 84 | 85 | https://www.django-rest-framework.org/api-guide/serializers/#specifying-read-only-fields 86 | 87 | - Views and URLS 88 | 89 | https://www.django-rest-framework.org/tutorial/quickstart/#views 90 | 91 | https://www.django-rest-framework.org/tutorial/quickstart/#urls -------------------------------------------------------------------------------- /problems/webdev/flask_collage/README.md: -------------------------------------------------------------------------------- 1 | Build a small web app using Flask which accepts the meetup.com event id for tonight 2 | as a parameter and would fetch the profile pictures of all the attendees to create a 3 | collage. [Here](https://twitter.com/Tathagata/status/746302962830540801) is an example 4 | of such a collage. 5 | 6 | You'll need: 7 | 8 | - `pip install flask` 9 | - `pip install Flask-WTF` 10 | - `pip install meetup-api` 11 | 12 | How to create a basic Flask app: 13 | Follow the instructions [here](http://flask.pocoo.org/docs/0.11/quickstart/) 14 | 15 | ```python 16 | from flask import Flask 17 | app = Flask(__name__) 18 | 19 | @app.route('/') 20 | def hello_world(): 21 | return 'Hello World!' 22 | 23 | if __name__ == '__main__': 24 | app.run(debug=True) 25 | ``` 26 | `python testflask.py` 27 | 28 | To get you started, the following piece of code will help you fetch the thumbnail 29 | images from meetup.com. 30 | 31 | ```python 32 | import meetup.api 33 | client = meetup.api.Client('your_key') 34 | 35 | rsvps=client.GetRsvps(event_id='235484841', urlname='_ChiPy_') 36 | member_id = ','.join([str(i['member']['member_id']) for i in rsvps.results]) 37 | members = client.GetMembers(member_id=member_id) 38 | 39 | for member in members.results: 40 | try: 41 | print '{0},{1},{2}'.format(member['name'], member['id'], member['photo']['thumb_link']) 42 | except: 43 | pass # ignore those who do not have a complete profile 44 | ``` 45 | 46 | 1. Can you include the name along with the images in your collage? 47 | 48 | 2. Add a search box to your collage, where you can search some one by name. 49 | On a successful search, it should display that person and their name. On failure, it should give a proper error message. 50 | 51 | 3. Add the following list of questions to your search result page that collects feedback on what an attendee would like to do 52 | 53 | Choices: 54 | - Help others with Python 101 questions 55 | - Help others with Python Data Science questions 56 | - Help others with Python Web Dev questions 57 | - Python 101 course (Beginner) 58 | - Attend Coding Workshop (Intermediate) 59 | - Attend RasberryPi Lab (Intermediate) 60 | - Work on my own project, get help from others 61 | 62 | 4. Deploy your app to a public hosting, share the link with the world! 63 | 64 | 5. Currently we are accepting RSVPs on both ChiPy's site and Chicago Pythonista's 65 | meetup page. Can you fetch the thumbnails from both the pages, eliminate the 66 | duplicates, and merge them to generate the collage? 67 | 68 | -------------------------------------------------------------------------------- /problems/webdev/flask_collage/solutions/dormamu_bargain/README.md: -------------------------------------------------------------------------------- 1 | Team Name: Dormamu I've come to bargain 2 | ======================================= 3 | * Dr. Strange 4 | * The Ancient One 5 | * Dormammu 6 | * Thanos 7 | 8 | Our Solution 9 | ============ 10 | Take over. 11 | 12 | -------------------------------------------------------------------------------- /problems/webdev/flask_collage/solutions/testflask.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | app = Flask(__name__) 3 | import meetup.api 4 | 5 | def get_names(): 6 | client = meetup.api.Client('3f6d3275d3b6314e73453c4aa27') 7 | 8 | rsvps=client.GetRsvps(event_id='235484841', urlname='_ChiPy_') 9 | member_id = ','.join([str(i['member']['member_id']) for i in rsvps.results]) 10 | members = client.GetMembers(member_id=member_id) 11 | 12 | foo='' 13 | for member in members.results: 14 | try: 15 | 16 | foo+='''
17 | {0}
18 | Solo
19 | Team
20 | Line count: 21 |

'''.format(member['name'], member['id'], member['photo']['thumb_link']) 22 | except: 23 | pass # ignore those who do not have a complete profile 24 | 25 | return foo 26 | 27 | @app.route('/') 28 | def hello_world(): 29 | html = '
' + get_names() + '
' 30 | return html 31 | 32 | if __name__ == '__main__': 33 | app.run(debug=True) 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /problems/webdev/flask_exchange_rates/README.md: -------------------------------------------------------------------------------- 1 | # Mentorship Program Web Project 2 | 3 | ## Objective 4 | All developers in the modern day need to understand web technologies at some level. Whether you're interacting with a Jupyter notebook or querying a web api, understanding how a CLIENT requests information from a SERVER and to see how the SERVER produces its response is incredibly valuable. 5 | 6 | 7 | ## Overview 8 | We will make a web app that serves as both a CLIENT to an external api (exchangeratesapi.io). This app will show conversion rates for currencies, and then add some more complex data. 9 | 10 | 11 | ## Prerequisites 12 | For this project we recommend all use Atom (or Sublime) to write code and a shell/terminal to execute the program. All instructions will be given assuming a Python 3.6 install. 13 | 14 | You should probably have the [Flask documentation](http://flask.pocoo.org/docs/1.0/quickstart/) up as we go through the exercise. 15 | 16 | 17 | ## Initial Setup 18 | 19 | Create a folder for this project: `mkdir mentorship_web && cd mentorship_web` 20 | 21 | If you are using Linux or OS X, run the following to create a new virtualenv 22 | 23 | ``` 24 | python3 -m venv venv 25 | source venv/bin/activate 26 | ``` 27 | 28 | On Windows, run the following 29 | 30 | ``` 31 | python3 -m venv venv 32 | venv\Scripts\activate 33 | ``` 34 | 35 | Install Flask, our main web-app: `pip install flask` 36 | 37 | Create a new file called `app.py` 38 | 39 | ``` 40 | from flask import Flask 41 | app = Flask(__name__) 42 | 43 | 44 | @app.route('/') 45 | def hello_world(): 46 | return "Hello World!" 47 | 48 | if __name__ == '__main__': 49 | app.run(debug=True) 50 | ``` 51 | 52 | Run your flask app: `python app.py` 53 | 54 | It should display a link, paste that in your browser to see the running code. 55 | 56 | 57 | ## Display some Exchange Rates 58 | 59 | Define a dictionary with three entries like the following inside your hello route: 60 | 61 | ``` 62 | exchange_rates = { 63 | 'EUR': '...', 64 | 'GBP': '...', 65 | ' 66 | ``` 67 | 68 | Pick any currencies you want! We'll display the values as they convert to USD (because we are in Chicago). Better specify that now for clarity. Add this line just bellow your `app =` definition, we'll use it later: 69 | 70 | ``` 71 | BASE_CURRENCY = 'USD' 72 | ``` 73 | 74 | Of course, fill in the `...` with real currency values, otherwise our site is pointless! Use a tool like [X-Rates](https://www.x-rates.com/table/?from=USD&amount=1) to look up the currency conversions. 75 | 76 | Now update the return string in the hello route to include the information about these currencies. 77 | 78 | 79 | ## Make it Beautiful 80 | Returning strings is fine...I suppose. But I want big, beautiful HTML! Research Flask's [`render_template` function](http://flask.pocoo.org/docs/1.0/quickstart/#rendering-templates) and add some beautiful looking HTML to format your. Hint: You'll want to create a `templates/` folder in the same directory as your `app.py`, and if you aren't using a kind of loop in your template...it's going to get real tough for you later! 81 | 82 | Test your work by adding another currency to your `exchange_rates` dictionary and watch how your page changes. 83 | 84 | ## Refactor 85 | Define a function right under your `BASE_CURRENCY` definition that looks like the following: 86 | 87 | ``` 88 | def exchange_rates(): 89 | return # Move your dictionary here 90 | ``` 91 | 92 | Then replace any references in your code to use the function instead of defining the dictionary in your route! 93 | 94 | ## Automatic Data 95 | Now comes the fun part. What if we could get the exchange rates on a live feed from a third party service? We can with [ExchangeRatesAPI.io](http://exchangeratesapi.io). This is a free api that runs over simple HTTPS! We'll have something like the following: 96 | 97 | ``` 98 | def exchange_rates(): 99 | response = requests.get('https://api.exchangeratesapi.io/latest') 100 | return # What are we going to put here now? 101 | ``` 102 | 103 | Read up on [Requests' built in JSON Parsing](http://docs.python-requests.org/en/master/user/quickstart/#json-response-content) and try to extract the data from the API response. Your end goal is to return all of the 104 | currencies from this function. 105 | 106 | Hint: This problem is a great time to use the Python debugger ([`pdb`](https://docs.python.org/3/library/pdb.html))! Insert the following line before the `return` command and interact with your server on the command line. So cool! 107 | 108 | ``` 109 | import pdb; pdb.set_trace() 110 | ``` 111 | 112 | ### Correct Currency 113 | Are we sure the previous step is getting us the currency in our `BASE_CURRENCY` variable? Investigate the [api docs](http://exchangeratesapi.io) to find out how you can change the base currency to the one we want. 114 | 115 | 116 | # You DID it! Now what? 117 | From here on out we are trusting that you can use the documentation, look for resources on your own, and come up with clever solutions to these problems. Try each one, and if you get stuck, move on to a different one! Ask for help if you want more clarification or can't think of a way to do it. 118 | 119 | 120 | ## Information Overload 121 | Could we provide a form that would let people get only the currency they want? I don't need every currency. We'd probably use some kind of html form. And I bet Flask has some documentation on receiving requests from the client. 122 | 123 | e.g. Instead of `JPY->*` I only want to see `USD->GBP` or I only want to see `USD->JPY`. 124 | 125 | 126 | ## Order by Value 127 | How could we order the currencies by the value of the currency? 128 | 129 | e.g. If something is `.00001 USD`, lets list that last, and if something is `987654321 USD` lets list that first. 130 | 131 | 132 | ## User-Specified Base Currency 133 | Some users of our product have complained, rightfully, that they can only get the currency listed in `USD`. We should let them specify what currency they want. 134 | 135 | e.g. Instead of `USD->*` I want to see `CNY->*` 136 | 137 | ### Bonus: Links to each user-specified currency from the currency list 138 | Wouldn't it be great if each currency as it appeared would link to its own currency conversion? 139 | 140 | e.g. From the home page I could click on `JPY` to find out all of the conversions from `JPY->*`. 141 | 142 | 143 | ## Caching 144 | Do we really need to use an API request every time we do a call? How could we store the results of each run to avoid API abuse. -------------------------------------------------------------------------------- /problems/webdev/flask_team_project/README.md: -------------------------------------------------------------------------------- 1 | In this project we will be building a fully functional web app using 2 | Flask. 3 | 4 | ### The project 5 | In the [team project command line application](https://github.com/chicagopython/CodingWorkshops/tree/master/problems/py101/python_team_project), we built an awesome command line 6 | application for creating teams out of people who have RSVP-ed for a Python Project 7 | Night. However, it is much easier to give a link of your app to someone 8 | than asking them to use a command line. So, we will create a web app, that allows 9 | forming teams from the list of RSVP-s from meetup.com. We 10 | will ask for the number of lines of code that a person has written in 11 | Python or an equivalent language and use it for putting them in a team. The number of lines is just a rough estimate. As a reference, the linux kernel is over 23 million lines of code! 12 | 13 | In short, imagine this as a tool that one of the 14 | organizers uses to checkin attendees as they start coming in on the day of 15 | Project Night. 16 | 17 | Short url for this page: **https://git.io/vdQj6** 18 | 19 | ### Is this project for you 20 | Before you progress further, let's check if we are ready to solve this. You should 21 | - Have a personal computer with working wifi and power cord 22 | - Have Python 3 installed on your computer. Yep, Python 3 only. 23 | - Have [Atom](https://atom.io/) or [Sublime Text](https://www.sublimetext.com/3) installed in your computer. 24 | - Have written & ran programs in Python from the command line 25 | - Have some idea about lists, dictionaries and functions 26 | - Have created a virtual environment and installing packages with `pip` 27 | - You have read the [flask quick introduction](http://flask.pocoo.org/docs/0.12/quickstart/) 28 | 29 | ### What is not supported 30 | This project is not tested using Jupyter Notebook, PyCharm, 31 | Spider, or any other ide/text editor/programming environment for that matter. 32 | Atom or Sublime Text and the command line are the only supported development 33 | environment for this project. 34 | 35 | Sounds good? Then let's dive into building a fully functional web app using flask. 36 | 37 | ### Minimum Viable Product 38 | Our objective is to build an web based interface using Flask that 39 | - Shows a list of people who have RSVP-ed for the project Night 40 | - Each entry in the list should have 41 | - The name of the person 42 | - The meetup.com profile image of the person 43 | - An input text box that allows entering lines of code 44 | - On hitting the submit button we should get teams of four 45 | 46 | 47 | ### Flask 48 | For building the web interface, we will be using Flask. 49 | Flask is a micro web framework - it takes care of handling of the HTTP 50 | protocol for you and allows you focus on your application. It is flexible, 51 | lightweight yet powerful. 52 | 53 | ### Setup your environment 54 | #### Get the source code 55 | - If you are familiar with `git`, run 56 | 57 | git clone https://github.com/chicagopython/CodingWorkshops.git 58 | 59 | - If not, go to https://github.com/chicagopython/CodingWorkshops 60 | - Click on the Download Zip and unzip the file that gets downloaded 61 | - From your command line, change directory to the path where you have downloaded it. 62 | - On linux or OS X 63 | 64 | > cd path/to/CodingWorkshops/problems/webdev/flask_team_project/ 65 | 66 | - On Windows 67 | 68 | > cd path\to\CodingWorkshops\problems\webdev\flask_team_project 69 | 70 | 71 | Here you will find the basic skeleton of the app under `app.py`. 72 | 73 | ### Set up virtualenv 74 | If you are using Linux or OS X, run the following to create a new virtualenv 75 | 76 | python3 -m venv venv 77 | source venv/bin/activate 78 | pip install -r requirements.txt 79 | export FLASK_APP=app.py 80 | 81 | On Windows, run the following 82 | 83 | python3 -m venv venv 84 | venv\Scripts\activate 85 | pip install -r requirements.txt 86 | set %FLASK_APP%=app.py 87 | 88 | [![asciicast](https://asciinema.org/a/M1hP91h153PuOPEjVYbot6jPj.png)](https://asciinema.org/a/M1hP91h153PuOPEjVYbot6jPj) 89 | 90 | ### Feature 0: run app.py 91 | With your environment now set up run 92 | 93 | flask run 94 | 95 | And you'll see 🔥. 96 | 97 | The reason is there is a string in the `app.py` file that allows meetup.com to identify who is trying to get data from them. It is called the API key. The one currently in the code is one of my old ones. You need to get one for your team from [here](https://secure.meetup.com/meetup_api/key/) - obviously, you'll have to be logged into meetup.com to get the key. 98 | Plug in your key whereever most relevant in `app.py` and run the above command again. 99 | 100 | This will start a [web server](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/What_is_a_web_server) on port 5000. 101 | Next load up http://locahost:5000/rsvps in your web browser. 102 | 103 | This will show you the list of people who RSVPed for a previous meetup. 104 | Goto tonight's meetup page and get the meetup id from the url. 105 | 106 | https://www.meetup.com/_ChiPy_/events/244121900/ 107 | 108 | The last section of the url is the `event_id`. 109 | 110 | ### Feature 1: Read app.py 111 | `app.py` is the script is where the magic happens. 112 | 113 | Lets start at the routes: 114 | 115 | @app.route('/rsvps') 116 | def rsvps(): 117 | 118 | 119 | @app.route('/teams', methods=['GET', 'POST']) 120 | def teams(): 121 | 122 | Discuss among the team how render_template function is used in rsvps and teams 123 | function. 124 | 125 | Two useful tools are pretty print and `pdb` 126 | 127 | #### Pretty print 128 | 129 | >> from pprint import pprint as pp 130 | >> pp(member_rsvps) 131 | 132 | This will give you a better view of what the function `get_names()` returns. 133 | 134 | #### pdb 135 | Python comes with a debugger `pdb`. Here's a [cheat sheet](https://appletree.or.kr/quick_reference_cards/Python/Python%20Debugger%20Cheatsheet.pdf) 136 | 137 | You can stick the following line anywhere in the code and make it halt so that you can better inspect the data and flow. 138 | 139 | import pdb; pdb.set_trace() 140 | 141 | ### Feature 2: Show profile images in rsvps 142 | Make changes to rsvps.html (inside templates) to show images of next to the 143 | names of the people. 144 | 145 | ### Feature 3: Add a text box next for lines of code 146 | Add an input type textbox that will take a number as input 147 | 148 | ### Feature 4: Display the lines of code 149 | On hitting submit, the numbers you entered against each person should show up 150 | on the `/teams` page. 151 | 152 | ### Feature 5: Display teams 153 | As of now, everybody is listed under one team: Team 1. 154 | Split the list of people selected into teams of 4 155 | 156 | Your display of each team should include 157 | 158 | Team Number: XYZ 159 | Name of team member1, Lines of code, (pic) 160 | Name of team member2, Lines of code, (pic) 161 | Name of team member3, Lines of code, (pic) 162 | Name of team member4, Lines of code, (pic) 163 | (Total lines of code:) 164 | 165 | where things in () are optional. 166 | There is no specific criteria for creating the teams as of now. We handle that 167 | next. 168 | 169 | ### Feature 6: Tell the world 170 | Record a gif of your app in motion and tweet tweet the link to @chicagopython with "Python Project Night Mentorship". Include the twitter handles of your team members. 171 | 172 | ### Feature 7: Integrate team creating logic (optional) 173 | Code reuse is a hallmark well written code base. Of course, we are 174 | not talking about copy pasting the code, but using the abstractions that a 175 | programming language provides so that there is minimum duplication of code. 176 | 177 | Use the code that you wrote in the team project command line application. The logic 178 | that you have implemented earlier for grouping your list of people into teams 179 | should now be used for creating your teams. 180 | 181 | Thanks! Thats all folks! 182 | If you found a bug or think you some instructions are missing - just open a issue in this repository. 183 | -------------------------------------------------------------------------------- /problems/webdev/flask_team_project/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template, request 2 | from flask_debugtoolbar import DebugToolbarExtension 3 | 4 | app = Flask(__name__) 5 | app.debug = True 6 | import meetup.api 7 | app.config['SECRET_KEY'] = 'foobar' 8 | 9 | toolbar = DebugToolbarExtension(app) 10 | 11 | def get_names(): 12 | client = meetup.api.Client("3f6d3275d3b6314e73453c4aa27") 13 | 14 | rsvps=client.GetRsvps(event_id='235484841', urlname='_ChiPy_') 15 | member_id = ','.join([str(i['member']['member_id']) for i in rsvps.results]) 16 | members = client.GetMembers(member_id=member_id) 17 | 18 | foo={} 19 | for member in members.results: 20 | try: 21 | foo[member['name']] = member['photo']['thumb_link'] 22 | except: 23 | pass # ignore those who do not have a complete profile 24 | return foo 25 | 26 | member_rsvps=get_names() 27 | 28 | @app.route('/rsvps') 29 | def rsvps(): 30 | return render_template('rsvps.html', rsvps=member_rsvps) 31 | 32 | 33 | @app.route('/teams', methods=['GET', 'POST']) 34 | def teams(): 35 | results = request.form.to_dict() 36 | return render_template('teams.html', teams=[results]) 37 | 38 | if __name__ == '__main__': 39 | app.run(debug=True) 40 | -------------------------------------------------------------------------------- /problems/webdev/flask_team_project/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==0.12.2 2 | meetup-api==0.1.1 3 | Flask-WTF==0.14.2 4 | flask-debugtoolbar==0.10.1 5 | -------------------------------------------------------------------------------- /problems/webdev/flask_team_project/templates/rsvps.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% for name, img in rsvps.items() %} 6 | 7 | 10 | 13 | 14 | {% endfor %} 15 | 16 | 17 |
8 |
{{ name }} 9 |
11 | 12 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /problems/webdev/flask_team_project/templates/teams.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {%for team in teams%} 5 | 6 | Team: {{loop.index}} 7 | 8 |
9 | {%for name, line in team.items() %} 10 | 11 | {%endfor%} 12 |
{{name}}
15 | 16 | 17 | -------------------------------------------------------------------------------- /problems/webdev/flask_trackcoder/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | - [1. Flask Dashboard for tracking developer time](#1-flask-dashboard-for-tracking-developer-time) 4 | - [1.1. Is this project for you](#11-is-this-project-for-you) 5 | - [1.2. Reference documents](#12-reference-documents) 6 | - [1.3. What is not supported](#13-what-is-not-supported) 7 | - [1.4. Minimum Viable Product](#14-minimum-viable-product) 8 | - [1.5. Flask](#15-flask) 9 | - [1.6. Setup your environment](#16-setup-your-environment) 10 | - [1.6.1. Get the source code](#161-get-the-source-code) 11 | - [1.7. Files](#17-files) 12 | - [1.8. Set up virtualenv](#18-set-up-virtualenv) 13 | - [1.9. run app.py](#19-run-apppy) 14 | - [1.10. Shows the list of tasks that a mentee has added](#110-shows-the-list-of-tasks-that-a-mentee-has-added) 15 | - [1.11. Shows the list of tasks of a particular type](#111-shows-the-list-of-tasks-of-a-particular-type) 16 | - [1.12. Add a form to search for tasks](#112-add-a-form-to-search-for-tasks) 17 | - [1.13. Add a from to add new Task from the UI](#113-add-a-from-to-add-new-task-from-the-ui) 18 | - [1.14. Show the total time spent by the mentee for each of the task types](#114-show-the-total-time-spent-by-the-mentee-for-each-of-the-task-types) 19 | - [1.15. Improve the UI](#115-improve-the-ui) 20 | - [1.16. Integrate the task filtering with these aggregate metrics](#116-integrate-the-task-filtering-with-these-aggregate-metrics) 21 | - [1.17. Show the list of hashtags and their corresponding counts](#117-show-the-list-of-hashtags-and-their-corresponding-counts) 22 | - [Feedback](#feedback) 23 | 24 | 25 | # 1. Flask Dashboard for tracking developer time 26 | 27 | Chipy's mentorship program is an extra-ordinary jounery for becoming a better developer. As a mentee, you are expected to do a lot - you read new articles/books, write code, debug and troubleshoot, pair program with other mentees in coding workshop or your mentor. This is involves managing time efficiently and doing the effective things. But as the old adage goes, "you can't manage what you can't measure". 28 | 29 | This project is the second of the three part series of building tools for the mentees for tracking time. The end goal of such a tool will be to aggregate anonymous data and analyze how does a typical mentee spend on blogging (b), coding (c), debugging (d), pair program (p) with mentor or other mentees. 30 | 31 | In this project we will be building a fully functional web dashboard for tracking developer efforts using Flask. 32 | 33 | Short url for this page: **http://bit.ly/flask_trackcoder** 34 | 35 | ## 1.1. Is this project for you 36 | 37 | Before you progress further, let's check if we are ready to solve this. You should 38 | 39 | - Have a personal computer with working wifi and power cord 40 | - Have Python 3 installed on your computer. Yep, Python 3 only 41 | - Have [Atom](https://atom.io/) or [Sublime Text](https://www.sublimetext.com/3) installed in your computer 42 | - Have written & ran programs in Python from the command line 43 | - Have some idea about lists, dictionaries and functions 44 | - Have created a virtual environment and installing packages with `pip` 45 | - You have read the [flask quick introduction](http://flask.pocoo.org/docs/0.12/quickstart/) 46 | 47 | In addition, you should be familiar with [Part 1](https://github.com/chicagopython/CodingWorkshops/tree/master/problems/py101/trackcoder) of this three part exercise. 48 | 49 | ## 1.2. Reference documents 50 | 51 | Reading these links before attending project night, would help you a lot by providing 52 | the background needed to work through the exercieses. 53 | 54 | - [What is a Web server](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/What_is_a_web_server) 55 | - [Flask Quick Start](http://flask.pocoo.org/docs/1.0/quickstart/) 56 | - [Flask routing](http://flask.pocoo.org/snippets/57/) 57 | - [Flask Web Forms](https://pythonspot.com/flask-web-forms/) 58 | - [Flask Context processors](http://flask.pocoo.org/docs/1.0/templating/#context-processors) 59 | - [Aggregation in Peewee ORM](http://docs.peewee-orm.com/en/latest/peewee/query_examples.html#aggregation) 60 | 61 | ## 1.3. What is not supported 62 | 63 | This project is not tested using Jupyter Notebook, PyCharm, 64 | Spider, or any other ide/text editor/programming environment for that matter. 65 | Atom or Sublime Text and the command line are the only supported development 66 | environment for this project. 67 | 68 | Sounds good? Then let's dive into building a fully functional web app using flask. 69 | 70 | ## 1.4. Minimum Viable Product 71 | 72 | Our objective is to build a dashboard that tells the story of the progress 73 | of a mentee during the mentorship program. The key metrics on this dashboard 74 | comes from data captured by them using the command line tool built in Part 75 | 1 of this project. We want to get insights on the time spent in each task type, 76 | and the effect of each task on the entire progress. Later in Part 3 we will use 77 | data science approaches to analyze any patterns in this data. 78 | 79 | ![](dashboard.gif) 80 | 81 | This is just a reference implementation to give you an idea of what a dashboard 82 | might look like, but you are not required to stick to this and encouraged to 83 | think of your own design. 84 | 85 | ## 1.5. Flask 86 | 87 | For building the web interface, we will be using Flask. 88 | Flask is a micro web framework - it takes care of handling of the HTTP 89 | protocol for you and allows you focus on your application. It is flexible, 90 | lightweight yet powerful. 91 | 92 | ## 1.6. Setup your environment 93 | 94 | ### 1.6.1. Get the source code 95 | 96 | If you are familiar with `git`, run 97 | 98 | git clone https://github.com/chicagopython/CodingWorkshops.git 99 | 100 | - If not, go to https://github.com/chicagopython/CodingWorkshops 101 | - Click on the Download Zip and unzip the file that gets downloaded 102 | - From your command line, change directory to the path where you have downloaded it. 103 | - On linux or OS X 104 | 105 | > cd path/to/CodingWorkshops/problems/webdev/flask_trackcoder/ 106 | 107 | - On Windows 108 | 109 | > cd path\to\CodingWorkshops\problems\webdev\flask_trackcoder 110 | 111 | ## 1.7. Files 112 | 113 | - `app.py` - Here you will find the basic skeleton of the flask app. This is where you will be 114 | writing your python code for controlling the business logic of the problem. 115 | - `static/styles` - If you want to add static css and javascript, that goes here. [Docs](http://flask.pocoo.org/docs/1.0/tutorial/static/) 116 | - `templates` - This folder has the file `tasks.html` which serves as the view for your app, rendering the data on the browser. [Docs](http://flask.pocoo.org/docs/1.0/tutorial/templates/) 117 | 118 | ## 1.8. Set up virtualenv 119 | 120 | If you are using Linux or OS X, run the following to create a new virtualenv 121 | 122 | python3 -m venv venv 123 | source venv/bin/activate 124 | pip install -r requirements.txt 125 | export FLASK_APP=app.py 126 | 127 | On Windows, run the following 128 | 129 | python3 -m venv venv 130 | venv\Scripts\activate 131 | pip install -r requirements.txt 132 | set %FLASK_APP%=app.py 133 | 134 | ## 1.9. run app.py 135 | 136 | With your environment now set up run 137 | 138 | flask run 139 | 140 | This will start a web server on port 5000. 141 | Next load up http://localhost:5000/tasks in your web browser. 142 | 143 | This will show you the list of tasks that have been added to the database built and provided 144 | in Part 1 of our project. 145 | 146 | ## 1.10. Shows the list of tasks that a mentee has added 147 | 148 | `app.py` is the script is where the magic happens. 149 | 150 | Lets start with the routes: 151 | 152 | 153 | @app.route('/tasks/', methods=['GET', 'POST'], defaults={'task':''}) 154 | @app.route('/tasks/', methods=['GET', 'POST']) 155 | def tasks(task): 156 | tasks = [t for t in ToDo.select()] 157 | return render_template('tasks.html', tasks=tasks) 158 | 159 | Visit http://localhost:5000/tasks and you will see all the tasks. 160 | How many tasks do you see? 161 | 162 | Feel free to add your own tasks 163 | 164 | cd ../../py101/trackcoder/ # change directory to go to Part 1 165 | python app.py -a d 5 "#bootstrap trying to get form alignment working" 166 | 167 | Now if you refresh the page, you should be able to see the tasks you added on 168 | the dashboard. 169 | 170 | ## 1.11. Shows the list of tasks of a particular type 171 | 172 | Recall that we have the following task types. 173 | 174 | - blogging (b) 175 | - coding (c) 176 | - debugging (d) 177 | - pair programming at project night (p) 178 | - research (r) 179 | - meeting with mentor (m) 180 | 181 | Change the tasks route so that it takes a parameter of task type and filters 182 | out tasks of only that type. In absence of any task type, we should be 183 | able to see all the tasks. 184 | 185 | Note: The data comes from database which is located in the folder 186 | '../../py101/trackcoder/to_do_list.db'. Changing it to another sqlite file, 187 | would change what you see on the dashboard. 188 | 189 | Think how will you handle if an invalid task type is requested. 190 | 191 | ## 1.12. Add a form to search for tasks 192 | 193 | Add a search text box that allows the user to search with any word that might 194 | appear on the description of the task. 195 | 196 | Hint: Take a look at the example of Flask-WTF above. You'll also need to take 197 | a look at [how to do substring search](http://docs.peewee-orm.com/en/latest/peewee/query_operators.html?highlight=contains) usnig peewee. 198 | 199 | ## 1.13. Add a from to add new Task from the UI 200 | 201 | Add a form to the UI so that you can add a task from the web frontend. 202 | The task will take in three parameters 203 | 204 | - a task type 205 | - minutes spent 206 | - description 207 | 208 | Think about the UI from the user's perspective. Instead of having to fill in three fields 209 | you may choose to have one single text box, where they can enter 210 | 211 | d, 30, #flask form submission 212 | 213 | and you handle splitting the input into task type, minutes, description. 214 | 215 | ## 1.14. Show the total time spent by the mentee for each of the task types 216 | 217 | Find the cummulative time spent on each task type. These should be 218 | placed next to each other so that it is easy to compare how much time 219 | is devoted into each of the activity. 220 | 221 | Hint: While there might be different ways of doing this, you might find [context processors](http://flask.pocoo.org/docs/1.0/templating/#context-processors) 222 | useful. 223 | 224 | ## 1.15. Improve the UI 225 | 226 | UI/UX is critical to keep your audience engaged. The sample template `tasks.html`, comes with 227 | bootstap integrated, however it does not use any additional styling yet. 228 | 229 | Here is the template from Bootrap that is used in the demo above. 230 | https://getbootstrap.com/docs/4.0/examples/dashboard/. It can be downloaded from https://getbootstrap.com/docs/4.0/examples/. 231 | 232 | Feel free to go with any styling that goes with your team's design. 233 | 234 | Your html template should go to `template` directory and the `css` or `js` 235 | files should go into `static/styles` directory. 236 | 237 | ## 1.16. Integrate the task filtering with these aggregate metrics 238 | 239 | Now that you have a way to filter by task type and aggregated metrics on each task type, integrate these to provide a more unified user experience. Take a look at the demo above, on how it uses the cards almost as a button to filter each task type. Feel free to come up with your own design. 240 | 241 | Hint: Cards are a very common way of displaying aggregated information and Bootstrap natively supports cards. You can see the examples [here](https://getbootstrap.com/docs/4.0/components/card/). 242 | 243 | ## 1.17. Show the list of hashtags and their corresponding counts 244 | 245 | Adding hashtags on task descriptions allows aggregating the efforts 246 | on another dimension than the pre-defined task types. For example, if 247 | you want to see how much time was spent on debugging on 248 | "#data science" vs "asyncio", aggregating on tasks that have those 249 | hashtags is an easy way. 250 | 251 | Provide a way to aggregate the hashtags that has been used in the tasks 252 | and show the count next to each of them. Clicking on a particlar 253 | hashtag should show only the tasks that have that hashtag. 254 | 255 | # Feedback 256 | 257 | Thanks for attending this project night. We put in a lot of effor to 258 | make this useful for you. However, we can not make it better, unless 259 | we hear back from you on what you want and collect data to make changes. 260 | Please take a few moments to fill in the small form below and help us improve it. 261 | 262 | [![feedback](../../../docs/feedback.png)](https://docs.google.com/forms/d/e/1FAIpQLSePDQlWOibJrF7rI5KrYhzUSNfXp9GMP-6b-bjC8_qSFgYp-w/viewform?usp=pp_url&entry.813953991=https://bit.ly/flask_project) 263 | -------------------------------------------------------------------------------- /problems/webdev/flask_trackcoder/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, flash, redirect, render_template, \ 2 | request, url_for 3 | from peewee import * 4 | import datetime 5 | import logging 6 | import re 7 | from collections import Counter 8 | from wtforms import Form, StringField 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | app = Flask(__name__) 13 | app.debug = True 14 | app.config['SECRET_KEY'] = 'foobar' 15 | 16 | db = SqliteDatabase('../../py101/trackcoder/to_do_list.db') 17 | 18 | class ToDo(Model): 19 | task = CharField(max_length=255) 20 | description = CharField(max_length=255) 21 | timestamp = DateTimeField(default=datetime.datetime.now) 22 | mins = IntegerField() 23 | done = BooleanField(default=True) 24 | 25 | class Meta: 26 | database = db 27 | 28 | @app.route('/tasks/', methods=['GET', 'POST'], defaults={'tasks':None}) 29 | def tasks(tasks): 30 | tasks = [t for t in ToDo.select()] 31 | return render_template('tasks.html', tasks=tasks) 32 | 33 | if __name__ == '__main__': 34 | app.run(debug=True) 35 | -------------------------------------------------------------------------------- /problems/webdev/flask_trackcoder/dashboard.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chicagopython/CodingWorkshops/45c00db2e154ed164b5299ed9794f721208ab3fb/problems/webdev/flask_trackcoder/dashboard.gif -------------------------------------------------------------------------------- /problems/webdev/flask_trackcoder/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask 2 | peewee 3 | prompt_toolkit 4 | click 5 | Flask-WTF -------------------------------------------------------------------------------- /problems/webdev/flask_trackcoder/static/styles/dashboard.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-size: .875rem; 3 | } 4 | 5 | .feather { 6 | width: 16px; 7 | height: 16px; 8 | vertical-align: text-bottom; 9 | } 10 | 11 | /* 12 | * Sidebar 13 | */ 14 | 15 | .sidebar { 16 | position: fixed; 17 | top: 0; 18 | bottom: 0; 19 | left: 0; 20 | z-index: 100; /* Behind the navbar */ 21 | padding: 0; 22 | box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1); 23 | } 24 | 25 | .sidebar-sticky { 26 | position: -webkit-sticky; 27 | position: sticky; 28 | top: 48px; /* Height of navbar */ 29 | height: calc(100vh - 48px); 30 | padding-top: .5rem; 31 | overflow-x: hidden; 32 | overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */ 33 | } 34 | 35 | .sidebar .nav-link { 36 | font-weight: 500; 37 | color: #333; 38 | } 39 | 40 | .sidebar .nav-link .feather { 41 | margin-right: 4px; 42 | color: #999; 43 | } 44 | 45 | .sidebar .nav-link.active { 46 | color: #007bff; 47 | } 48 | 49 | .sidebar .nav-link:hover .feather, 50 | .sidebar .nav-link.active .feather { 51 | color: inherit; 52 | } 53 | 54 | .sidebar-heading { 55 | font-size: .75rem; 56 | text-transform: uppercase; 57 | } 58 | 59 | /* 60 | * Navbar 61 | */ 62 | 63 | .navbar-brand { 64 | padding-top: .75rem; 65 | padding-bottom: .75rem; 66 | font-size: 1rem; 67 | background-color: rgba(0, 0, 0, .25); 68 | box-shadow: inset -1px 0 0 rgba(0, 0, 0, .25); 69 | } 70 | 71 | .navbar .form-control { 72 | padding: .75rem 1rem; 73 | border-width: 0; 74 | border-radius: 0; 75 | } 76 | 77 | .form-control-dark { 78 | color: #fff; 79 | background-color: rgba(255, 255, 255, .1); 80 | border-color: rgba(255, 255, 255, .1); 81 | } 82 | 83 | .form-control-dark:focus { 84 | border-color: transparent; 85 | box-shadow: 0 0 0 3px rgba(255, 255, 255, .25); 86 | } 87 | 88 | /* 89 | * Utilities 90 | */ 91 | 92 | .border-top { border-top: 1px solid #e5e5e5; } 93 | .border-bottom { border-bottom: 1px solid #e5e5e5; } 94 | 95 | a.custom-card, 96 | a.custom-card:hover { 97 | color: inherit; 98 | } -------------------------------------------------------------------------------- /problems/webdev/flask_trackcoder/templates/tasks.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Dashboard Template for Bootstrap 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |

Tasks

25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | {%for t in tasks%} 38 | 39 | 40 | 42 | 43 | 44 | 45 | 46 | {%endfor%} 47 |
#taskdescriptionminstimestampdone
{{loop.index}} {{t.task}} 41 | {{t.description}} {{t.mins}} {{t.timestamp}} {{t.done}}
48 |
49 |
50 | 51 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 64 | 65 | 66 | 67 | 96 | 97 | 98 | --------------------------------------------------------------------------------