From 4ccf8e80c957b744d3b2190fa35d33943aa7d521 Mon Sep 17 00:00:00 2001 From: vagrant Date: Mon, 24 Oct 2016 23:08:27 +0000 Subject: [PATCH 01/25] initial commit --- squawker/schema.sql | 8 +++++-- squawker/server.py | 18 +++++++++++---- squawker/templates/index.html | 38 +++++++++++++++++++++++++++++++ src/pip-delete-this-directory.txt | 5 ++++ src/splinter | 1 + 5 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 squawker/templates/index.html create mode 100644 src/pip-delete-this-directory.txt create mode 160000 src/splinter diff --git a/squawker/schema.sql b/squawker/schema.sql index 5e67ffb..0604241 100644 --- a/squawker/schema.sql +++ b/squawker/schema.sql @@ -1,3 +1,7 @@ -- TODO change this -DROP TABLE IF EXISTS mytable; -CREATE TABLE mytable (id integer); +DROP TABLE IF EXISTS squawks; +CREATE TABLE squawks( + id INTEGER PRIMARY KEY AUTOINCREMENT, + message VARCHAR(140) NOT NULL, + timestamp INTEGER DEFAULT CURRENT_TIMESTAMP +); diff --git a/squawker/server.py b/squawker/server.py index 6ff24ba..3cb0474 100644 --- a/squawker/server.py +++ b/squawker/server.py @@ -1,4 +1,4 @@ -from flask import Flask, g +from flask import Flask, g, render_template, abort, request import sqlite3 @@ -37,11 +37,21 @@ def close_connection(exception): # ------------------------------ -@app.route('/') +@app.route('/', methods=["GET", "POST"]) def root(): conn = get_db() - # TODO change this - return "Hello World!" + if (request.method == "POST"): + message = request.form['message'] + if len(message) <= 140: + cc_object = conn.execute('INSERT INTO squawks (message) VALUES (?)', [message]) + conn.commit() + else: + abort(400) + + cc_object = conn.execute('SELECT * FROM squawks ORDER BY timestamp desc') + squawkers = cc_object.fetchall() + + return render_template('index.html', squawks=squawkers) if __name__ == '__main__': diff --git a/squawker/templates/index.html b/squawker/templates/index.html new file mode 100644 index 0000000..833d76c --- /dev/null +++ b/squawker/templates/index.html @@ -0,0 +1,38 @@ + + + + + + + + + +
+
+

Squawker > Twitter

+
+
+ + + +

Squawks

+
+
+ + +
+ + + +
+ +{% for message in squawks %} +
+

{{ message[2] }}

+

{{ message[1] }}

+
+{% endfor %} +
+ + + diff --git a/src/pip-delete-this-directory.txt b/src/pip-delete-this-directory.txt new file mode 100644 index 0000000..c8883ea --- /dev/null +++ b/src/pip-delete-this-directory.txt @@ -0,0 +1,5 @@ +This file is placed here by pip to indicate the source was put +here by pip. + +Once this package is successfully installed this source code will be +deleted (unless you remove this file). diff --git a/src/splinter b/src/splinter new file mode 160000 index 0000000..ba3afc8 --- /dev/null +++ b/src/splinter @@ -0,0 +1 @@ +Subproject commit ba3afc8a0750dcfea096f8f89adb58f8f8d78276 From f2aba96f0bc261d552b8a70ca133a696b94f016a Mon Sep 17 00:00:00 2001 From: vagrant Date: Mon, 24 Oct 2016 23:26:33 +0000 Subject: [PATCH 02/25] update index --- squawker/templates/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/squawker/templates/index.html b/squawker/templates/index.html index 833d76c..47b91c3 100644 --- a/squawker/templates/index.html +++ b/squawker/templates/index.html @@ -16,10 +16,10 @@

Squawker > Twitter

Squawks

- +
- +
From 06bc0c0f70faf2ab15e79633b7cec423af77c442 Mon Sep 17 00:00:00 2001 From: vagrant Date: Mon, 24 Oct 2016 23:30:06 +0000 Subject: [PATCH 03/25] max length is 140 --- squawker/templates/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/squawker/templates/index.html b/squawker/templates/index.html index 47b91c3..0f015b8 100644 --- a/squawker/templates/index.html +++ b/squawker/templates/index.html @@ -19,7 +19,7 @@

Squawks

- +
From 3abd8e4a1dadf40694c8e3b4e79670eb2da3b1a0 Mon Sep 17 00:00:00 2001 From: Aidan Feldman Date: Wed, 26 Oct 2016 11:11:39 -0400 Subject: [PATCH 04/25] add assignment requirements --- README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..46d8fec --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +# Squawker (Django) + +In this assignment, you will rebuild [Squawker](https://github.com/startup-systems/squawker) in [Django](https://www.djangoproject.com/). + +## Requirements + +* The homepage (`/`) contains: + * A single form, to post a new squawk (**5%**) + * All past squawks (**20%**) + * Sorted from newest to oldest (**10%**) +* Submitting the form: + 1. Creates a new squawk (**20%**) + 1. Shows / takes the user back to the homepage (**5%**) + * In other words, they should see the updated homepage with the new squawk. +* Squawks are limited to 140 characters + * Client-side (**5%**) + * Uses HTML5 form validation + * Server-side (**10%**) + * Responds with a status code of 400 if the form is submitted with invalid data. +* Passes Code Climate checks (**5%**) +* Works without JavaScript +* Deployed to Heroku at `-squawker.herokuapp.com` (**20%**) + +Visual styling is not considered as part of the score, though feel free to get creative! In other words, feel free to make your site pretty, but not a problem if it isn't. + +### Extra credit + +* Pagination (**10%**) + * The squawks are shown 20 at a time + * There's a `Next` link to see older squawks, if there are any From 43c5e5c3f53da55bfb78b898fe5db53e5cb12edf Mon Sep 17 00:00:00 2001 From: Aidan Feldman Date: Sat, 29 Oct 2016 17:46:48 -0400 Subject: [PATCH 05/25] add empty requirements file --- requirements.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e69de29 From 731edd50200619ad273a62209307584fba90229c Mon Sep 17 00:00:00 2001 From: Aidan Feldman Date: Sat, 29 Oct 2016 17:48:52 -0400 Subject: [PATCH 06/25] add basic python gitignore --- .gitignore | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7de88e3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +__pycache__/ +*.py[cod] +*$py.class +.cache/ +*.db +*.db3 +*.sqlite +*.sqlite3 From 187c71599e76135cceb0f255306fa9d8db967deb Mon Sep 17 00:00:00 2001 From: Aidan Feldman Date: Sat, 29 Oct 2016 17:50:45 -0400 Subject: [PATCH 07/25] add django things to gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 7de88e3..1d9bf5b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ __pycache__/ *.db3 *.sqlite *.sqlite3 +*.log +local_settings.py From b8ce4bb92044a3e8805919b8f7027a015cee0af9 Mon Sep 17 00:00:00 2001 From: Aidan Feldman Date: Sat, 29 Oct 2016 18:04:01 -0400 Subject: [PATCH 08/25] add instructions about how to run from the VM --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 46d8fec..b430419 100644 --- a/README.md +++ b/README.md @@ -28,3 +28,12 @@ Visual styling is not considered as part of the score, though feel free to get c * Pagination (**10%**) * The squawks are shown 20 at a time * There's a `Next` link to see older squawks, if there are any + +### Tips + +* You will set up Django in your fork of this repository yourself. +* Run Django with the following from within your [virtual machine](https://github.com/startup-systems/vm): + + ```sh + python3 manage.py runserver 0.0.0.0:8000 + ``` From 464908ca039234952d96ef8864b6c32e33cceca6 Mon Sep 17 00:00:00 2001 From: Aidan Feldman Date: Sun, 30 Oct 2016 10:57:41 -0400 Subject: [PATCH 09/25] add more setup tips to readme --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b430419..c59721a 100644 --- a/README.md +++ b/README.md @@ -31,9 +31,16 @@ Visual styling is not considered as part of the score, though feel free to get c ### Tips -* You will set up Django in your fork of this repository yourself. +* You will set up Django project in your copy of this repository yourself. To do so, run the following from this directory: + + ```sh + django-admin startproject squawker . + ``` + * Run Django with the following from within your [virtual machine](https://github.com/startup-systems/vm): ```sh python3 manage.py runserver 0.0.0.0:8000 ``` + +* Django will use SQLite3 as it's database by default, but you'll have to change this to PostgreSQL ("Postgres") before you deploy to Heroku. From 2692370f6159b51f4f0a5a069f4ecd571516ffb7 Mon Sep 17 00:00:00 2001 From: Aidan Feldman Date: Sun, 30 Oct 2016 10:59:15 -0400 Subject: [PATCH 10/25] add PEP8 configuration --- setup.cfg | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 setup.cfg diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..7c1de86 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[pep8] +ignore = E501 From 19b8d7b728581e94b1a6f9fa34587ed89e136ac6 Mon Sep 17 00:00:00 2001 From: Aidan Feldman Date: Sun, 30 Oct 2016 18:41:04 -0400 Subject: [PATCH 11/25] copy in (and tweak) squawker tests --- README.md | 12 +++ pytest.ini | 2 + tests/test_squawker.py | 166 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 pytest.ini create mode 100644 tests/test_squawker.py diff --git a/README.md b/README.md index c59721a..ee34e76 100644 --- a/README.md +++ b/README.md @@ -31,12 +31,24 @@ Visual styling is not considered as part of the score, though feel free to get c ### Tips +* You will need to set up the project dependencies yourself. Add the following to your [`requirements.txt`]: + + ``` + Django~=1.10.2 + pep8~=1.7.0 + pytest~=3.0.3 + pytest-django~=3.0.0 + pytest-json~=0.4.0 + git+https://github.com/startup-systems/splinter.git@acfac451ee3943e1e155d06249f6ed0aa851b948#egg=splinter[django] + ``` + * You will set up Django project in your copy of this repository yourself. To do so, run the following from this directory: ```sh django-admin startproject squawker . ``` +* If your project is named something other than `squawker`, you will need to modify the `DJANGO_SETTINGS_MODULE` value in [`pytest.ini`]. * Run Django with the following from within your [virtual machine](https://github.com/startup-systems/vm): ```sh diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..4fea6b6 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +DJANGO_SETTINGS_MODULE=squawker.settings diff --git a/tests/test_squawker.py b/tests/test_squawker.py new file mode 100644 index 0000000..0a85cca --- /dev/null +++ b/tests/test_squawker.py @@ -0,0 +1,166 @@ +import os +import pytest +import random +import re +from splinter import Browser +import string +import tempfile +import time + + +URL = '/' +PAGE_SIZE = 20 +# match case-insensitively +# http://stackoverflow.com/a/1625859/358804 +NEXT_XPATH = "//a[contains(translate(text(),'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ'), 'NEXT')]" + + +# http://pytest-django.readthedocs.io/en/latest/database.html#enabling-database-access-in-tests +pytestmark = pytest.mark.django_db + + +@pytest.fixture() +def browser(): + return Browser('django') + + +def random_string(minlength=5, maxlength=40): + charset = string.ascii_letters + string.digits + length = random.randint(minlength, maxlength) + return ''.join(random.choice(charset) for _ in range(length)) + + +def find_body_field(browser): + field = browser.find_by_css('input[type="text"],textarea').first + assert field is not None + return field + + +def create_squawk(browser, body): + browser.visit(URL) + + input_el = find_body_field(browser) + input_el.fill(body) + + button = browser.find_by_css('input[type="submit"],button[type="submit"]').first + assert button is not None + button.click() + + +def create_squawks(browser, count, delay=0): + bodies = ["Post {}".format(i) for i in range(count)] + for body in bodies: + create_squawk(browser, body) + if delay > 0: + time.sleep(delay) + + return bodies + + +def test_response_code(browser): + browser.visit(URL) + assert browser.status_code.is_success() + + +@pytest.mark.score(5) +def test_form_present(browser): + browser.visit(URL) + assert browser.is_element_present_by_tag('form') + + +@pytest.mark.score(20) +def test_all_squawks_present(browser): + num_squawks = random.randint(3, 9) + bodies = create_squawks(browser, num_squawks) + + # in case they didn't return to the homepage + browser.visit(URL) + for body in bodies: + assert browser.is_text_present(body) + + +@pytest.mark.score(10) +def test_reverse_chronological_order(browser): + # the SQLite3 `datetime` type is down to the second by default, so wait between creating each squawk + bodies = create_squawks(browser, 3, delay=1) + + bodies.reverse() + pattern = '.*'.join(bodies) + + browser.visit(URL) + + assert re.search(pattern, browser.html, re.DOTALL + re.MULTILINE) is not None + + +@pytest.mark.score(20) +def test_create_squawk(browser): + TEXT = random_string() + create_squawk(browser, TEXT) + browser.visit(URL) + assert browser.is_text_present(TEXT) + + +@pytest.mark.score(5) +def test_returns_to_homepage(browser): + TEXT = random_string() + create_squawk(browser, TEXT) + # the latter checks are needed because there seems to be a splinter(?) bug where it doesn't handle (certain?) redirects properly + assert browser.is_element_present_by_tag('form') or (browser.status_code.code == 405 and browser.url == 'http://localhost/') + + +@pytest.mark.score(5) +def test_client_side_validation(browser): + browser.visit(URL) + assert browser.is_element_present_by_css('[maxlength="140"],[pattern]') + + +@pytest.mark.score(10) +def test_server_side_validation(browser): + TEXT = random_string(minlength=141, maxlength=200) + create_squawk(browser, TEXT) + # TODO ignore if it's in the `value` of the `` + assert browser.is_text_not_present(TEXT) + + +@pytest.mark.score(2.5) +@pytest.mark.xfail +def test_page_size_limit(browser): + bodies = create_squawks(browser, PAGE_SIZE + 1, delay=1) + + browser.visit(URL) + assert browser.is_text_not_present(bodies[0]) + + +@pytest.mark.score(2.5) +@pytest.mark.xfail +def test_next_only_present_for_pagination(browser): + browser.visit(URL) + assert browser.is_element_not_present_by_xpath(NEXT_XPATH), "`Next` link should not be present." + + create_squawks(browser, PAGE_SIZE + 1) + + browser.visit(URL) + assert browser.is_element_present_by_xpath(NEXT_XPATH), "`Next` link should be present." + + +@pytest.mark.score(2.5) +@pytest.mark.xfail +def test_next_not_present_on_last_page(browser): + bodies = create_squawks(browser, PAGE_SIZE + 1) + + browser.visit(URL) + browser.find_by_xpath(NEXT_XPATH).first.click() + + assert browser.is_element_not_present_by_xpath(NEXT_XPATH), "`Next` link should not be present." + + +@pytest.mark.score(2.5) +@pytest.mark.xfail +def test_pagination(browser): + bodies = create_squawks(browser, PAGE_SIZE + 1, delay=1) + + browser.visit(URL) + browser.find_by_xpath(NEXT_XPATH).first.click() + + assert browser.is_text_present(bodies[0]) + assert browser.is_text_not_present(bodies[-1]) From e785e00c4099ef866226525186a6ad69ef8cf91c Mon Sep 17 00:00:00 2001 From: Aidan Feldman Date: Sun, 30 Oct 2016 18:46:55 -0400 Subject: [PATCH 12/25] fix up tips section --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ee34e76..2ace7e2 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,9 @@ Visual styling is not considered as part of the score, though feel free to get c * The squawks are shown 20 at a time * There's a `Next` link to see older squawks, if there are any -### Tips +## Tips -* You will need to set up the project dependencies yourself. Add the following to your [`requirements.txt`]: +* You will need to set up the project dependencies yourself. Add the following to your [`requirements.txt`](requirements.txt): ``` Django~=1.10.2 @@ -42,13 +42,13 @@ Visual styling is not considered as part of the score, though feel free to get c git+https://github.com/startup-systems/splinter.git@acfac451ee3943e1e155d06249f6ed0aa851b948#egg=splinter[django] ``` -* You will set up Django project in your copy of this repository yourself. To do so, run the following from this directory: +* You will set up Django project in your copy of this repository yourself. The easiest way to do this is to run the following from this directory: ```sh django-admin startproject squawker . ``` -* If your project is named something other than `squawker`, you will need to modify the `DJANGO_SETTINGS_MODULE` value in [`pytest.ini`]. +* If your project is named something other than `squawker`, you will need to modify the `DJANGO_SETTINGS_MODULE` value in [`pytest.ini`](pytest.ini). * Run Django with the following from within your [virtual machine](https://github.com/startup-systems/vm): ```sh From e5283d629b1352edc7d294d829976abaae43f13c Mon Sep 17 00:00:00 2001 From: Aidan Feldman Date: Sun, 30 Oct 2016 11:00:43 -0400 Subject: [PATCH 13/25] add travis config --- .travis.yml | 13 +++++++++++++ README.md | 42 ++++++++++++++++++++++++++++-------------- 2 files changed, 41 insertions(+), 14 deletions(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..86e146d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,13 @@ +language: python +python: +- '3.5' +cache: pip +script: +- pytest --json=report.json --tb short +- echo "Please ignore the following...this is for grading purposes only." +# https://github.com/travis-ci/travis-ci/issues/2158#issuecomment-42726890 +- echo "travis_fold:start:SCRIPT folding starts" +- echo '' +- cat report.json +- echo '' +- echo "travis_fold:end:SCRIPT folding ends" diff --git a/README.md b/README.md index 2ace7e2..63127d7 100644 --- a/README.md +++ b/README.md @@ -4,20 +4,21 @@ In this assignment, you will rebuild [Squawker](https://github.com/startup-syste ## Requirements -* The homepage (`/`) contains: - * A single form, to post a new squawk (**5%**) - * All past squawks (**20%**) - * Sorted from newest to oldest (**10%**) -* Submitting the form: - 1. Creates a new squawk (**20%**) - 1. Shows / takes the user back to the homepage (**5%**) - * In other words, they should see the updated homepage with the new squawk. -* Squawks are limited to 140 characters - * Client-side (**5%**) - * Uses HTML5 form validation - * Server-side (**10%**) - * Responds with a status code of 400 if the form is submitted with invalid data. -* Passes Code Climate checks (**5%**) +* Passes all of the following on Travis CI: + * The homepage (`/`) contains: + * A single form, to post a new squawk (**5%**) + * All past squawks (**20%**) + * Sorted from newest to oldest (**10%**) + * Submitting the form: + 1. Creates a new squawk (**20%**) + 1. Shows / takes the user back to the homepage (**5%**) + * In other words, they should see the updated homepage with the new squawk. + * Squawks are limited to 140 characters + * Client-side (**5%**) + * Uses HTML5 form validation + * Server-side (**10%**) + * Responds with a status code of 400 if the form is submitted with invalid data. + * Passes Code Climate checks (**5%**) * Works without JavaScript * Deployed to Heroku at `-squawker.herokuapp.com` (**20%**) @@ -56,3 +57,16 @@ Visual styling is not considered as part of the score, though feel free to get c ``` * Django will use SQLite3 as it's database by default, but you'll have to change this to PostgreSQL ("Postgres") before you deploy to Heroku. + +## Running tests locally + +Run the following from this directory: + +```shell +# run the pytests +pytest --tb short +# run the pep8 checks +pep8 +# check the extra credit +pytest --tb short --runxfail +``` From d57d11c0aa0421c6c541a9db130139749cb190c7 Mon Sep 17 00:00:00 2001 From: Aidan Feldman Date: Sun, 30 Oct 2016 11:00:34 -0400 Subject: [PATCH 14/25] add code climate config --- .codeclimate.yml | 18 ++++++++++++++++++ README.md | 4 ++++ 2 files changed, 22 insertions(+) create mode 100644 .codeclimate.yml diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 0000000..6357f3b --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,18 @@ +--- +engines: + duplication: + enabled: true + config: + languages: + - python + exclude_paths: + - tests/ + pep8: + enabled: true + radon: + enabled: true + config: + threshold: "C" +ratings: + paths: + - "**.py" diff --git a/README.md b/README.md index 63127d7..c418a81 100644 --- a/README.md +++ b/README.md @@ -70,3 +70,7 @@ pep8 # check the extra credit pytest --tb short --runxfail ``` + +### Code Climate checks + +To run locally, see [the instructions](https://docs.google.com/document/d/1-hk6GzhV1yHU1T0E7uqcdNTtvv3fuq1_WECQOWOT2zw/edit#heading=h.w5f2vmvyb0n). From e37653c5b6e8ed813e4dcff7832537a37b2e4989 Mon Sep 17 00:00:00 2001 From: Aidan Feldman Date: Sun, 30 Oct 2016 19:16:55 -0400 Subject: [PATCH 15/25] kick code climate From 50de8adccd864ef92a196ae56449f3c56dac0b02 Mon Sep 17 00:00:00 2001 From: Aidan Feldman Date: Mon, 31 Oct 2016 04:16:38 +0000 Subject: [PATCH 16/25] add more heroku instructions --- README.md | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 59 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c418a81..adf1e7c 100644 --- a/README.md +++ b/README.md @@ -30,9 +30,9 @@ Visual styling is not considered as part of the score, though feel free to get c * The squawks are shown 20 at a time * There's a `Next` link to see older squawks, if there are any -## Tips +## Setup -* You will need to set up the project dependencies yourself. Add the following to your [`requirements.txt`](requirements.txt): +1. You will need to set up the project dependencies yourself. Add the following to your [`requirements.txt`](requirements.txt): ``` Django~=1.10.2 @@ -43,20 +43,73 @@ Visual styling is not considered as part of the score, though feel free to get c git+https://github.com/startup-systems/splinter.git@acfac451ee3943e1e155d06249f6ed0aa851b948#egg=splinter[django] ``` -* You will set up Django project in your copy of this repository yourself. The easiest way to do this is to run the following from this directory: +1. You will set up Django project in your copy of this repository yourself. The easiest way to do this is to run the following from this directory: ```sh django-admin startproject squawker . ``` -* If your project is named something other than `squawker`, you will need to modify the `DJANGO_SETTINGS_MODULE` value in [`pytest.ini`](pytest.ini). -* Run Django with the following from within your [virtual machine](https://github.com/startup-systems/vm): +1. If your project is named something other than `squawker`, you will need to modify the `DJANGO_SETTINGS_MODULE` value in [`pytest.ini`](pytest.ini) to match. +1. Run Django with the following from within your [virtual machine](https://github.com/startup-systems/vm): ```sh python3 manage.py runserver 0.0.0.0:8000 ``` -* Django will use SQLite3 as it's database by default, but you'll have to change this to PostgreSQL ("Postgres") before you deploy to Heroku. +### Deploying to Heroku + +#### Support PostgreSQL + +Django will use SQLite3 as it's database by default, but you'll use PostgreSQL ("Postgres") on Heroku. To make the switch: + +1. Install the system-level dependencies. + + ```sh + sudo apt-get update + sudo apt-get install libpq-dev + ``` + +1. Add the following to your [`requirements.txt`](requirements.txt) file: + + ``` + dj-database-url~=0.4.1 + psycopg2~=2.6.2 + ``` + +1. Install the Python dependencies. + + ```sh + pip3 install -r requirements.txt + ``` + +1. In your `/settings.py` file: + + ```python + # add this near the top + import dj_database_url + + # replace the DATABASES config + DATABASES = { + "default": dj_database_url.config(default='sqlite:///db.sqlite3'), + } + ``` + +#### Specify Python version + +https://devcenter.heroku.com/articles/python-runtimes + +#### Additional setup + +1. Create a directory for static files inside the Django project directory. + + ```sh + mkdir -p /static + touch /static/.keep + ``` + +1. Follow [steps in Heroku documentation](https://devcenter.heroku.com/articles/django-app-configuration#migrating-an-existing-django-project). + * Skip the database connection parts, since we covered those already. +1. Commit all changes. ## Running tests locally From fc45b7dbee8c69cd7eba9be96e787a701100095b Mon Sep 17 00:00:00 2001 From: Aidan Feldman Date: Tue, 1 Nov 2016 01:29:25 +0000 Subject: [PATCH 17/25] check for status code of 400 when testing server-side validation Get around the problem of expecting valid HTML in the response. --- tests/test_squawker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_squawker.py b/tests/test_squawker.py index 0a85cca..f7ffcf1 100644 --- a/tests/test_squawker.py +++ b/tests/test_squawker.py @@ -119,7 +119,7 @@ def test_server_side_validation(browser): TEXT = random_string(minlength=141, maxlength=200) create_squawk(browser, TEXT) # TODO ignore if it's in the `value` of the `` - assert browser.is_text_not_present(TEXT) + assert browser.status_code.code == 400 or browser.is_text_not_present(TEXT) @pytest.mark.score(2.5) From 45c0026525cc48eb78571cc2535fec94581e466e Mon Sep 17 00:00:00 2001 From: vagrant Date: Tue, 1 Nov 2016 02:48:25 +0000 Subject: [PATCH 18/25] initial commit --- main/__init__.py | 0 main/admin.py | 5 ++ main/migrations/0001_initial.py | 21 +++++++ main/migrations/__init__.py | 0 main/models.py | 7 +++ main/templates/index.html | 38 ++++++++++++ main/tests.py | 3 + main/views.py | 16 +++++ manage.py | 10 ++++ requirements.txt | 8 +++ squawker/__init__.py | 0 squawker/settings.py | 103 ++++++++++++++++++++++++++++++++ squawker/urls.py | 23 +++++++ squawker/wsgi.py | 16 +++++ 14 files changed, 250 insertions(+) create mode 100644 main/__init__.py create mode 100644 main/admin.py create mode 100644 main/migrations/0001_initial.py create mode 100644 main/migrations/__init__.py create mode 100644 main/models.py create mode 100644 main/templates/index.html create mode 100644 main/tests.py create mode 100644 main/views.py create mode 100755 manage.py create mode 100644 squawker/__init__.py create mode 100644 squawker/settings.py create mode 100644 squawker/urls.py create mode 100644 squawker/wsgi.py diff --git a/main/__init__.py b/main/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/main/admin.py b/main/admin.py new file mode 100644 index 0000000..dbefa63 --- /dev/null +++ b/main/admin.py @@ -0,0 +1,5 @@ +from django.contrib import admin +from .models import Squawk + +# Register your models here. +admin.site.register(Squawk) diff --git a/main/migrations/0001_initial.py b/main/migrations/0001_initial.py new file mode 100644 index 0000000..06062f0 --- /dev/null +++ b/main/migrations/0001_initial.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Squawk', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('message', models.CharField(max_length=140)), + ('created', models.DateTimeField(auto_now_add=True)), + ], + ), + ] diff --git a/main/migrations/__init__.py b/main/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/main/models.py b/main/models.py new file mode 100644 index 0000000..b0533ea --- /dev/null +++ b/main/models.py @@ -0,0 +1,7 @@ +from django.db import models + +# Create your models here. +class Squawk(models.Model): + message = models.CharField(max_length=140) + created = models.DateTimeField(auto_now_add=True) + diff --git a/main/templates/index.html b/main/templates/index.html new file mode 100644 index 0000000..8322708 --- /dev/null +++ b/main/templates/index.html @@ -0,0 +1,38 @@ + + + + + + + + + +
+
+

Squawker > Twitter

+
+
+ + + +

Squawks

+ +{% csrf_token %} +
+ + +
+ + + +
+ +{% for squawk in squawks %} +
+

{{ squawk.message }}

+

{{ squawk.created }}

+
+{% endfor %} +
+ + diff --git a/main/tests.py b/main/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/main/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/main/views.py b/main/views.py new file mode 100644 index 0000000..1697a12 --- /dev/null +++ b/main/views.py @@ -0,0 +1,16 @@ +from django.shortcuts import render +from django.http import HttpResponse +from .models import Squawk + + +def root(request): + if (request.method == "POST"): + inputMessage = request.POST['message'] + if (len(inputMessage) <= 140): + squawk = Squawk(message=inputMessage) + squawk.save() + else: + return HttpResponseBadRequest("Error: Message Exceeded Max Length of 140") + + squawkers = Squawk.objects.all() + return render(request, "index.html", {"squawks":squawkers}) diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..ec6bfb9 --- /dev/null +++ b/manage.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "squawker.settings") + + from django.core.management import execute_from_command_line + + execute_from_command_line(sys.argv) diff --git a/requirements.txt b/requirements.txt index e69de29..21de154 100644 --- a/requirements.txt +++ b/requirements.txt @@ -0,0 +1,8 @@ +Django~=1.10.2 +pep8~=1.7.0 +pytest~=3.0.3 +pytest-django~=3.0.0 +pytest-json~=0.4.0 +git+https://github.com/startup-systems/splinter.git@acfac451ee3943e1e155d06249f6ed0aa851b948#egg=splinter[django] +dj-database-url~=0.4.1 +psycopg2~=2.6.2 diff --git a/squawker/__init__.py b/squawker/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/squawker/settings.py b/squawker/settings.py new file mode 100644 index 0000000..38c2947 --- /dev/null +++ b/squawker/settings.py @@ -0,0 +1,103 @@ +""" +Django settings for squawker project. + +Generated by 'django-admin startproject' using Django 1.8.7. + +For more information on this file, see +https://docs.djangoproject.com/en/1.8/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/1.8/ref/settings/ +""" + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +import os + +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = '&1!5zbuw_rhff)pn85p25akvr9r5$f)5(a$2rw7ko#kl07c@=z' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = ( + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'main' +) + +MIDDLEWARE_CLASSES = ( + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'django.middleware.security.SecurityMiddleware', +) + +ROOT_URLCONF = 'squawker.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'squawker.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/1.8/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} + + +# Internationalization +# https://docs.djangoproject.com/en/1.8/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.8/howto/static-files/ + +STATIC_URL = '/static/' diff --git a/squawker/urls.py b/squawker/urls.py new file mode 100644 index 0000000..37359d3 --- /dev/null +++ b/squawker/urls.py @@ -0,0 +1,23 @@ +"""squawker URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/1.8/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') +Including another URLconf + 1. Add an import: from blog import urls as blog_urls + 2. Add a URL to urlpatterns: url(r'^blog/', include(blog_urls)) +""" +from django.conf.urls import include, url +from django.contrib import admin +from main.views import root + +urlpatterns = [ + url(r'^admin/', include(admin.site.urls)), + url(r'^$', root) +] diff --git a/squawker/wsgi.py b/squawker/wsgi.py new file mode 100644 index 0000000..3d14a35 --- /dev/null +++ b/squawker/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for squawker project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "squawker.settings") + +application = get_wsgi_application() From 617b7495099e09958fef2efe2056ab6128a216d5 Mon Sep 17 00:00:00 2001 From: vagrant Date: Tue, 1 Nov 2016 03:00:59 +0000 Subject: [PATCH 19/25] push to heroku --- Procfile | 1 + requirements.txt | 1 + runtime.txt | 1 + squawker/settings.py | 10 ++++------ 4 files changed, 7 insertions(+), 6 deletions(-) create mode 100644 Procfile create mode 100644 runtime.txt diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..0785be5 --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: gunicorn squawker.wsgi --log-file - diff --git a/requirements.txt b/requirements.txt index 21de154..ad09ce1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,4 @@ pytest-json~=0.4.0 git+https://github.com/startup-systems/splinter.git@acfac451ee3943e1e155d06249f6ed0aa851b948#egg=splinter[django] dj-database-url~=0.4.1 psycopg2~=2.6.2 +gunicorn==19.6.0 diff --git a/runtime.txt b/runtime.txt new file mode 100644 index 0000000..c0354ee --- /dev/null +++ b/runtime.txt @@ -0,0 +1 @@ +python-3.5.2 diff --git a/squawker/settings.py b/squawker/settings.py index 38c2947..15e4e13 100644 --- a/squawker/settings.py +++ b/squawker/settings.py @@ -1,3 +1,5 @@ +# add this near the top +import dj_database_url """ Django settings for squawker project. @@ -74,15 +76,11 @@ # Database # https://docs.djangoproject.com/en/1.8/ref/settings/#databases - +# replace the DATABASES config DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), - } + "default": dj_database_url.config(default='sqlite:///db.sqlite3'), } - # Internationalization # https://docs.djangoproject.com/en/1.8/topics/i18n/ From d8218090cd85d4a605dff05134495fd703993cbe Mon Sep 17 00:00:00 2001 From: vagrant Date: Tue, 1 Nov 2016 23:26:40 +0000 Subject: [PATCH 20/25] add static root --- main/views.py | 3 ++- squawker/settings.py | 6 ++++-- squawker/static/.keep | 0 3 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 squawker/static/.keep diff --git a/main/views.py b/main/views.py index 1697a12..84fb1e4 100644 --- a/main/views.py +++ b/main/views.py @@ -1,4 +1,5 @@ from django.shortcuts import render +from django.http import HttpResponseBadRequest from django.http import HttpResponse from .models import Squawk @@ -12,5 +13,5 @@ def root(request): else: return HttpResponseBadRequest("Error: Message Exceeded Max Length of 140") - squawkers = Squawk.objects.all() + squawkers = Squawk.objects.order_by('-created') return render(request, "index.html", {"squawks":squawkers}) diff --git a/squawker/settings.py b/squawker/settings.py index 15e4e13..ad69a40 100644 --- a/squawker/settings.py +++ b/squawker/settings.py @@ -1,5 +1,3 @@ -# add this near the top -import dj_database_url """ Django settings for squawker project. @@ -11,6 +9,8 @@ For the full list of settings and their values, see https://docs.djangoproject.com/en/1.8/ref/settings/ """ +# add this near the top +import dj_database_url # Build paths inside the project like this: os.path.join(BASE_DIR, ...) import os @@ -99,3 +99,5 @@ # https://docs.djangoproject.com/en/1.8/howto/static-files/ STATIC_URL = '/static/' +PROJECT_DIR = os.path.dirname(os.path.abspath(__file__)) +STATIC_ROOT = os.path.join(PROJECT_DIR, 'static') diff --git a/squawker/static/.keep b/squawker/static/.keep new file mode 100644 index 0000000..e69de29 From b0ca0049f0721665213ad8390a1aba3d9b21431b Mon Sep 17 00:00:00 2001 From: vagrant Date: Tue, 1 Nov 2016 23:30:59 +0000 Subject: [PATCH 21/25] add allowed hosts --- squawker/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/squawker/settings.py b/squawker/settings.py index ad69a40..38428d5 100644 --- a/squawker/settings.py +++ b/squawker/settings.py @@ -27,7 +27,7 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = [] +ALLOWED_HOSTS = ['an536-squawker.herokuapp.com'] # Application definition From fcaa3a3b290390c5e7ac05d46f084812ccfa3887 Mon Sep 17 00:00:00 2001 From: vagrant Date: Tue, 1 Nov 2016 23:35:12 +0000 Subject: [PATCH 22/25] update indentation --- main/views.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/main/views.py b/main/views.py index 84fb1e4..53bf5b5 100644 --- a/main/views.py +++ b/main/views.py @@ -5,13 +5,13 @@ def root(request): - if (request.method == "POST"): - inputMessage = request.POST['message'] + if (request.method == "POST"): + inputMessage = request.POST['message'] if (len(inputMessage) <= 140): - squawk = Squawk(message=inputMessage) - squawk.save() - else: - return HttpResponseBadRequest("Error: Message Exceeded Max Length of 140") + squawk = Squawk(message=inputMessage) + squawk.save() + else: + return HttpResponseBadRequest("Error: Message Exceeded Max Length of 140") - squawkers = Squawk.objects.order_by('-created') - return render(request, "index.html", {"squawks":squawkers}) + squawkers = Squawk.objects.order_by('-created') + return render(request, "index.html", {"squawks":squawkers}) From 27ada0846941f8d936ea69befc052f711c45c037 Mon Sep 17 00:00:00 2001 From: vagrant Date: Tue, 1 Nov 2016 23:37:54 +0000 Subject: [PATCH 23/25] update indentation --- main/views.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/main/views.py b/main/views.py index 53bf5b5..a0a298b 100644 --- a/main/views.py +++ b/main/views.py @@ -7,11 +7,11 @@ def root(request): if (request.method == "POST"): inputMessage = request.POST['message'] - if (len(inputMessage) <= 140): - squawk = Squawk(message=inputMessage) - squawk.save() - else: - return HttpResponseBadRequest("Error: Message Exceeded Max Length of 140") + if (len(inputMessage) <= 140): + squawk = Squawk(message=inputMessage) + squawk.save() + else: + return HttpResponseBadRequest("Error: Message Exceeded Max Length of 140") squawkers = Squawk.objects.order_by('-created') return render(request, "index.html", {"squawks":squawkers}) From 1688b0ad1e792c142f9f753754c029e1225d374f Mon Sep 17 00:00:00 2001 From: vagrant Date: Tue, 1 Nov 2016 23:45:13 +0000 Subject: [PATCH 24/25] update indentation --- main/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/views.py b/main/views.py index a0a298b..030e1bd 100644 --- a/main/views.py +++ b/main/views.py @@ -7,7 +7,7 @@ def root(request): if (request.method == "POST"): inputMessage = request.POST['message'] - if (len(inputMessage) <= 140): + if (len(inputMessage) <= 140): squawk = Squawk(message=inputMessage) squawk.save() else: From f9915efdd3a22ccacabd08b2a0863a8502176621 Mon Sep 17 00:00:00 2001 From: vagrant Date: Sat, 18 Mar 2017 17:57:45 +0000 Subject: [PATCH 25/25] add charts --- squawker/templates/index.html | 132 ++++++++++++++++++++++++++++++++-- 1 file changed, 128 insertions(+), 4 deletions(-) diff --git a/squawker/templates/index.html b/squawker/templates/index.html index 0f015b8..0ca8477 100644 --- a/squawker/templates/index.html +++ b/squawker/templates/index.html @@ -5,17 +5,141 @@ + + - +
-

Squawker > Twitter

+

Buddy Bear

+
+ +
- +
+ +
+ + -

Squawks

+

Charts