Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
11 changes: 0 additions & 11 deletions .homework.json

This file was deleted.

39 changes: 39 additions & 0 deletions API_docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# StatTracker API

** Activities

* To see a list of all activities for your user:
* curl -X GET -H "Authorization: Basic [email protected]:password" http://localhost:5000/api/v1/activities

* To create an activity:
* curl -X POST -H "Authorization: Basic [email protected]:password" -H "Content-type: application/json" -d '{"name": "climb steps", "goal": "lose weight", "description": "climb 4 per day"}' http://localhost:5000/api/v1/activities/add

* To see a specific activity:
* curl -X GET -H "Authorization: Basic [email protected]:password" http://localhost:5000/api/v1/activities/<id>

* Update an activity
* curl -X PUT -H "Authorization: Basic [email protected]:password" -H "Content-type: application/json" -d '{"name": "Climb the steps", "goal": "lose weight", "description": "climb 4 per day"}' http://localhost:5000/api/v1/activities/update/<id>

* Delete an activity (also needs to be able to remove logs)
* curl -X DELETE -H "Authorization: Basic [email protected]:password" http://localhost:5000/api/v1/activities/delete/<id>





** Logs

* See a list of all logs for your user:
* curl -X GET -H "Authorization: Basic [email protected]:password" http://localhost:5000/api/v1/logs

* To see a specific log:
* curl -X GET -H "Authorization: Basic [email protected]:password" http://localhost:5000/api/v1/logs/<id>

* To create a log
* curl -X POST -H "Authorization: Basic [email protected]:password" -H "Content-type: application/json" -d '{"item_id":4, "value": "900", "logged_at": "2015/01/01"}' http://localhost:5000/api/v1/logs/add

* To update a log
* curl -X PUT -H "Authorization: Basic [email protected]:password" -H "Content-type: application/json" -d '{"value": "900", "logged_at": "2015/01/01"}' http://localhost:5000/api/v1/logs/update/<id>

* Delete a log
* curl -X DELETE -H "Authorization: Basic [email protected]:password" http://localhost:5000/api/v1/logs/delete/<id>
1 change: 1 addition & 0 deletions Procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
web: gunicorn manager.app:app --log-file=-
117 changes: 30 additions & 87 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,113 +1,56 @@
# Stat Tracker

## Description

Build an application people can use to track any stats they want about themselves.
Quickstart
----------

## Objectives
Run the following commands to bootstrap your environment.

### Learning Objectives

After completing this assignment, you should understand:
```
cd stat_tracker
pip install -r requirements.txt
python manage.py db init
python manage.py server
```

* ...

### Performance Objectives
Deployment
----------

After completing this assignment, you should be able to:
In your production environment, make sure you have an application.cfg
file in your instance directory.

* ...

## Details
Shell
-----

### Deliverables
To open the interactive shell, run:

* A Git repo called stat-tracker containing at least:
* `README.md` file explaining how to run your project
* a `requirements.txt` file
* a way to seed your application with data
* An instance of your app running on Heroku
python manage.py shell

### Requirements
By default, you will have access to `app` and `db`.

* No PEP8 or Pyflakes warnings or errors
* Meets API specifications

## Normal Mode
Running Tests
-------------

You are going to build an application to track personal statistics. A personal statistic is a numerical record for a person in a time series by day. For example, let's say I wanted to track how many flights of stairs I walked up in a day. My last week might look like:
To run all tests, run:

Date | Flights
---------- | -------
02/19/2015 | 8
02/20/2015 | 6
02/21/2015 | 7
02/22/2015 | 6
02/23/2015 | 8
02/24/2015 | 4
02/25/2015 | 6
python manage.py test

Users of your application can create as many different things to track as they want. They should have an easy-to-use interface to track their stats, allowing them to enter the number for the current day or any previous day.

You should allow for:
Migrations
----------

* User registration
* User login
* Creating a new stat to track
* Recording a stat for a day
* Editing a stat for a day
* Showing a chart for a stat for any series of dates, defined by a start and stop date. The default should be the last 30 days.
Whenever a database migration needs to be made, run the following commmand:

For the chart, you can use whatever you like. Matplotlib is our old friend, but can be unwieldy. [Bokeh](http://bokeh.pydata.org/en/latest/) and [Plotly](https://plot.ly/python/) are other good choices to use with HTML.
python manage.py db migrate

You should also have an API. One of the ways people expect to use this application is via their phone, so you'll need a REST API.
This will generate a new migration script. Then run:

### API Specification
python manage.py db upgrade

For your API, I'm specifying the endpoints you'll need and what they should do. The URLs I'm using are not prefixed: yours should be.
All the endpoints require authentication using HTTP Basic Auth.
to apply the migration.

Verb | URL | Action
------ | ---- | -------
GET | /stats | Show a list of all stats I am tracking, and links to their individual pages
POST | /stats | Create a new stat for me to track.
GET | /stats/{id} | Show information about one stat I am tracking, and give me the data I have recorded for that stat.
PUT | /stats/{id} | Update one stat I am tracking, changing attributes such as name or type. Does not allow for changing tracked data.
DELETE | /stats/{id} | Delete a stat I am tracking. This should remove tracked data for that stat as well.
POST or PUT | /stats/{id}/data | Add tracked data for a day. The JSON sent with this should include the day tracked. You can also override the data for a day already recorded.
DELETE | /stats/{id}/data | Remove tracked data for a day. You should send JSON that includes the date to be removed.

I am not specifying what the JSON these return should look like, but you should feel free to follow one of the many competing standards. [JSON API](http://jsonapi.org/) is very comprehensive.


## Hard Mode

In addition to the requirements from **Normal Mode**:

* Users should be able to record different types of stats. You can choose the types, but here are some suggestions:
* Clicker-style stats. The UI on these should change so you have a way to increase them by one via a button click. Good for tracking things as you're doing them.
* Time-goal stats. The stat has a beginning value, ending value, and ending date. Track as normal, but you should be able to see if you're on track to meet your goal. Examples: weight loss, building up for a long run.
* Yes-no stats. Did I do this today? This is often called the "Seinfeld calendar" or [chain calendar](http://chaincalendar.com/about).
* Stats on a scale instead of unbounded. Example: On a scale of 1 to 5, what's my happiness level today?

* Make sure your interface [is responsive](https://developers.google.com/web/fundamentals/layouts/rwd-fundamentals/) and works well via mobile.


## Nightmare Mode

* Give users a way to invite other users to collaborate/compete on a stat with them. Users can only add/edit their own data, but the stat charts will show everyone competing.


## Additional Resources

* [JSON API](http://jsonapi.org/)
* [Bokeh](http://bokeh.pydata.org/en/latest/)
* [Plotly](https://plot.ly/python/)
* [Flask-RESTful](https://flask-restful.readthedocs.org/en/0.3.1/). A Flask plugin that could help or make this much worse.
* [RESTless](http://restless.readthedocs.org/en/latest/). Another Python library that could help or harm.
* [Kube](http://imperavi.com/kube/). A simpler CSS framework I've been using.
* [Peewee](https://peewee.readthedocs.org/en/latest/index.html). A less-featureful, but perhaps easier to use ORM.

## Credit

...
For a full migration command reference, run `python manage.py db --help`.
52 changes: 52 additions & 0 deletions manage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/usr/bin/env python
import os

from flask.ext.script import Manager, Shell, Server
from flask.ext.migrate import MigrateCommand
from flask.ext.script.commands import ShowUrls, Clean

from stat_tracker.app import create_app, db, models
from stat_tracker.generate_seed_data import create_specified_user, \
create_items, create_action

HERE = os.path.abspath(os.path.dirname(__file__))
TEST_PATH = os.path.join(HERE, 'tests')

app = create_app()
manager = Manager(app)
manager.add_command('server', Server())
manager.add_command('db', MigrateCommand)
manager.add_command('show-urls', ShowUrls())
manager.add_command('clean', Clean())


@manager.shell
def make_shell_context():
""" Creates a python REPL with several default imports
in the context of the app
"""

return dict(app=app, db=db)


@manager.command
def test():
"""Run the tests."""
import pytest
exit_code = pytest.main([TEST_PATH, '--verbose'])
return exit_code

@manager.command
def seed():
"""Seed database."""
action_num = 27
user = create_specified_user('[email protected]', 'password', 'Zack')
created_item_list = create_items(num=5, user_id=user.id)
for item in created_item_list:
create_action(item.id, num=action_num)
action_count = len(created_item_list) * action_num
print('Items: {} Actions: {}'.format(len(created_item_list), action_count))


if __name__ == '__main__':
manager.run()
1 change: 1 addition & 0 deletions migrations/README
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Generic single-database configuration.
Binary file added migrations/__pycache__/env.cpython-34.pyc
Binary file not shown.
45 changes: 45 additions & 0 deletions migrations/alembic.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# A generic, single database configuration.

[alembic]
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s

# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false


# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic

[handlers]
keys = console

[formatters]
keys = generic

[logger_root]
level = WARN
handlers = console
qualname =

[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine

[logger_alembic]
level = INFO
handlers =
qualname = alembic

[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic

[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
73 changes: 73 additions & 0 deletions migrations/env.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from __future__ import with_statement
from alembic import context
from sqlalchemy import engine_from_config, pool
from logging.config import fileConfig

# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config

# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)

# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
from flask import current_app
config.set_main_option('sqlalchemy.url', current_app.config.get('SQLALCHEMY_DATABASE_URI'))
target_metadata = current_app.extensions['migrate'].db.metadata

# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.

def run_migrations_offline():
"""Run migrations in 'offline' mode.

This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.

Calls to context.execute() here emit the given string to the
script output.

"""
url = config.get_main_option("sqlalchemy.url")
context.configure(url=url)

with context.begin_transaction():
context.run_migrations()

def run_migrations_online():
"""Run migrations in 'online' mode.

In this scenario we need to create an Engine
and associate a connection with the context.

"""
engine = engine_from_config(
config.get_section(config.config_ini_section),
prefix='sqlalchemy.',
poolclass=pool.NullPool)

connection = engine.connect()
context.configure(
connection=connection,
target_metadata=target_metadata
)

try:
with context.begin_transaction():
context.run_migrations()
finally:
connection.close()

if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

22 changes: 22 additions & 0 deletions migrations/script.py.mako
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""${message}

Revision ID: ${up_revision}
Revises: ${down_revision}
Create Date: ${create_date}

"""

# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}

from alembic import op
import sqlalchemy as sa
${imports if imports else ""}

def upgrade():
${upgrades if upgrades else "pass"}


def downgrade():
${downgrades if downgrades else "pass"}
Loading