Skip to content

Commit

Permalink
2024 Updates (#1)
Browse files Browse the repository at this point in the history
Updates:
  + Simplifies spreadsheet model class interface, by abstracting the logic into, and using, the [`gspread_models` package](https://pypi.org/project/gspread-models/).
  + Bumps version of the "google-auth" GitHub Action.
  + Moves client side logic (the icon class and navbar color class definitions) into the bootstrap template HTML file.
  + Allows page-specific customization of HTML `<title>` tag content, as desired.
 
FYI:
  + Google OAuth no longer provides the user's "locale" (language)

Known Issues:
  + #5 (Broken Profile Image)

Next Steps:
  + #3 (Markdown Instructions)
  • Loading branch information
s2t2 authored Apr 9, 2024
1 parent 22a27de commit 2260d3c
Show file tree
Hide file tree
Showing 41 changed files with 687 additions and 876 deletions.
42 changes: 23 additions & 19 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
@@ -1,33 +1,24 @@
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python

name: Python application

on:
push:
branches: [ main ]
branches: [ "main" ]
pull_request:
branches: [ main ]
# branches: [ "main" ]

permissions:
contents: read

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

# USE OFFICIAL GOOGLE ACTION TO CREATE A CREDENTIALS JSON FILE
# ... https://github.com/google-github-actions/auth
- id: 'auth'
name: 'Authenticate to Google Cloud'
uses: 'google-github-actions/auth@v0'
with:
# uses this encrypted secret set via github repo settings
# which is essentially a copy of the JSON credentials file contents (for the dev project)
credentials_json: '${{ secrets.GOOGLE_API_CREDENTIALS }}'
# this will create a credentials file with a randomized name
create_credentials_file: true
- uses: actions/checkout@v4

- name: Set up Python 3.10
uses: actions/setup-python@v3
Expand All @@ -47,10 +38,23 @@ jobs:
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
# USE OFFICIAL GOOGLE ACTION TO CREATE A CREDENTIALS JSON FILE
# ... https://github.com/google-github-actions/auth
- id: 'auth'
name: 'Authenticate to Google Cloud'
uses: 'google-github-actions/auth@v2'
with:
# uses this encrypted secret set via github repo settings
# which is essentially a copy of the JSON credentials file contents
credentials_json: '${{ secrets.GOOGLE_API_CREDENTIALS }}'
# this will create a credentials file with a randomized name
create_credentials_file: true


# RUN TESTS
- name: Test with pytest
env:
# access path of credentials file created by earlier auth step:
#GOOGLE_APPLICATION_CREDENTIALS: ${{ steps.auth.outputs.credentials_file_path }}
GOOGLE_SHEETS_TEST_DOCUMENT_ID: ${{ secrets.GOOGLE_SHEETS_TEST_DOCUMENT_ID }}
GOOGLE_CREDENTIALS_FILEPATH: ${{ steps.auth.outputs.credentials_file_path }}
run: |
CI=true TEST_SLEEP=20 pytest
CI=true pytest
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ google-credentials.json
google-credentials-prod.json
*-credentials.json
google-credentials*.json

gha-creds-*.json



Expand Down
2 changes: 1 addition & 1 deletion LICENSE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Creative Commons Attribution 4.0 International License

Copyright (c) 2023 Michael J Rossetti
Copyright (c) 2024 Michael J Rossetti

This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 International License</a>.

Expand Down
1 change: 0 additions & 1 deletion Procfile

This file was deleted.

104 changes: 48 additions & 56 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# flask-sheets-template-2023
# flask-sheets-template-2024

A web application starter template, created in Python with the Flask framework. Allows users to login with their Google accounts (via OAuth). Interfaces with a Google Sheets database.

![](https://user-images.githubusercontent.com/1328807/160312385-7ffbbada-4363-4b48-873d-9eca868afef0.png)
![](./docs/images/products-page-screenshot.png)

## Prerequisites

Expand All @@ -11,7 +11,7 @@ This application requires a Python development environment:
+ Git
+ Anaconda, Python, Pip

For beginners, here are some instructions for how to install Anaconda, and [set up your local Python development environment](https://github.com/prof-rossetti/intro-to-python/blob/main/exercises/local-dev-setup/README.md#anaconda-python-and-pip).
For beginners, here are some instructions for how to install Anaconda, and [set up your local Python development environment](https://github.com/prof-rossetti/intro-to-python/blob/main/exercises/local-dev-setup/README.md#option-b-full-setup).

## Repo Setup

Expand All @@ -20,8 +20,8 @@ Make a copy of this template repo (as necessary). Clone your copy of the repo on
Setup and activate a new Anaconda virtual environment:

```sh
conda create -n flask-sheets-env-2023 python=3.10
conda activate flask-sheets-env-2023
conda create -n flask-sheets-2024 python=3.10
conda activate flask-sheets-2024
```

Install package dependencies:
Expand All @@ -32,108 +32,100 @@ pip install -r requirements.txt

## Services Setup

This app requires a few services, for user authentication and data storage. Follow the instructions below to setup these services.
This app requires a few services, for user authentication and data storage. Follow the instructions below to setup these services:

### Google Cloud Project
1. Follow the [Google Cloud Setup](./docs/GOOGLE_CLOUD.md) guide to configure a Google Cloud project to facilitate user logins and programmatic access to Google APIs. Obtain and configure client credentials (via environment variables) and service account credentials (via JSON file).

Visit the [Google Cloud Console](https://console.cloud.google.com). Create a new project, and name it. After it is created, select it from the project selection dropdown menu.

### Google OAuth Client

Visit the [API Credentials](https://console.cloud.google.com/apis/credentials) page for your Google Cloud project. Click the button with the plus icon to "Create Credentials", and choose "Create OAuth Client Id".

Click to "Configure Consent Screen". Leave the domain info blank, and leave the defaults / skip lots of the setup for now. If/when you deploy your app to a production server, you can return to populating this info (or you will be using a different project).

Return to actually creating the "OAuth Client Id". Choose a "Web application" type, give it a name, and set the following "Authorized Redirect URIs" (for now, while the project is still in development):

+ http://localhost:5000/auth/google/callback

After the client is created, note the `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET`, and set them as environment variables (see configuration section below).

### Google Cloud Service Account Credentials

To fetch data from the Google Sheets database (and use other Google APIs), the app will need access to a local "service account" credentials file.

From the [Google API Credentials](https://console.cloud.google.com/apis/credentials) page, create a new service account as necessary.

For the chosen service account, create new JSON credentials file as necessary from the "Keys" menu, then download the resulting JSON file into the root directory of this repo, specifically named "google-credentials.json".


### Google Sheets Database Setup

See the [Google Sheets Database Setup](/admin/SHEETS_DB.md) guide.

### Google Analytics Setup

If you would like to configure Google Analytics, consult the [Google Analytics Setup](/admin/GA.md) guide.
2. Follow the [Google Sheets Database Setup](./docs/GOOGLE_SHEETS.md) guide to setup the google sheets database.

3. If you would like to configure Google Analytics, optionally consult the [Google Analytics Setup](./docs/GOOGLE_ANALYTICS.md) guide.


## Configuration

### Environment Variables

Create a file called ".env" in the root directory of this repository, and populate it with environment variables to specify your own credentials, as obtained in the "Setup" section above:
We will use environment variables to pass secret credentials to the app in a secure, indirect way.

Create a file called ".env" in the root directory of this repository, and add contents like the following (specifying your own credentials, as obtained during the "Setup" section):

```sh
FLASK_APP="web_app"

#
# GOOGLE OAUTH
#
# GOOGLE OAUTH LOGIN
GOOGLE_CLIENT_ID="____________"
GOOGLE_CLIENT_SECRET="____________"

#
# GOOGLE SHEETS DATABASE
#
GOOGLE_SHEETS_DOCUMENT_ID="____________"

#
# GOOGLE ANALYTICS
#
GA_TRACKER_ID="UA-XXXXXXX-1"
GA_TRACKER_ID="____________"
```




## Usage

### Sheets Service
### Sheets Database

After you have set up the Google Sheets database, you should be able to use the spreadsheet service to interface with it at a low level (for example to list all the sheets in the document):

```sh
python -m app.spreadsheet_service
```

After configuring the Google Sheet database and populating it with products, you should be able to test out the app's ability to fetch products (and generate new orders):
Assuming the "products" sheet has been setup properly, you can use the model class to interface with it at a higher level (for example to populate the sheet with example records):

```sh
python -m app.sheets_service
python -m app.models.product
```

> NOTE: see the contents of the ["app/models/product.py"](/app/models/product.py) file for more details, and feel free to customize as desired.

### Web Application

Run the local web server (then visit localhost:5000 in a browser):
Run the local web server (then visit http://localhost:5000 in a browser):

```sh
FLASK_APP=web_app flask run
```




## Testing

Run tests:
We will use a separate Google Sheet "test document" to use during testing. This keeps development data seprate from test data, and allows for experimentation when testing. To setup the test document:
1. Create a new Google Sheet document, and note its identifier.
2. Set this test document identifier as the environment variable `GOOGLE_SHEETS_TEST_DOCUMENT_ID`, via the ".env" file.
3. Inside the test document, set up all the normal sheets that the app uses (products, orders, etc.).

Running tests:

```sh
pytest
```

> NOTE: we are using a live sheet for testing, so to avoid API rate limits, we are waiting / sleeping between each test, which makes the tests a bit slow for now
> NOTE: we are using a live sheet for testing, so to avoid API rate limits, we are waiting / sleeping between each test, which makes the tests a bit slow.
> NOTE: the "web_app_test" expects certain content on certain pages, so if / when you update the page contents, you will need to update the tests as well.
## Continuous Integration

## CI
See the [GitHub Actions Guide](/docs/GITHUB_ACTIONS.md) for more information about configuring the Continuous Integration (CI) build process.

See more information about the [CI](/admin/CI.md) build process.
Running tests in CI mode:

```sh
CI=true pytest
```

## Deploying
## Deploying / Hosting

See the [Deployer's Guide](/admin/RENDER.md) for instructions on deploying to a production server hosted by Render.
See the [Hosting Guide](/docs/RENDER.md) for instructions on deploying to a production server hosted by Render.



Expand Down
47 changes: 0 additions & 47 deletions admin/SHEETS_DB.md

This file was deleted.

2 changes: 1 addition & 1 deletion app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@

load_dotenv()

APP_ENV = os.getenv("APP_ENV", default="development") # IMPORTANT: set to "production" in production
APP_ENV = os.getenv("APP_ENV", default="development") # set to "production" in production
APP_VERSION = os.getenv("APP_VERSION", default="v0.0.1") # update upon new releases
25 changes: 25 additions & 0 deletions app/db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

import os

from dotenv import load_dotenv
from gspread_models.service import SpreadsheetService
from gspread_models.base import BaseModel

load_dotenv()

# google credentials:
DEFAULT_FILEPATH = os.path.join(os.path.dirname(__file__), "..", "google-credentials.json")
GOOGLE_CREDENTIALS_FILEPATH = os.getenv("GOOGLE_CREDENTIALS_FILEPATH", default=DEFAULT_FILEPATH)

# google sheets document:
GOOGLE_SHEETS_DOCUMENT_ID = os.getenv("GOOGLE_SHEETS_DOCUMENT_ID", default="OOPS, Please get the spreadsheet identifier from its URL, and set the 'GOOGLE_SHEETS_DOCUMENT_ID' environment variable accordingly...")

# configure the base model to use this info:
service = SpreadsheetService(
credentials_filepath=GOOGLE_CREDENTIALS_FILEPATH,
document_id=GOOGLE_SHEETS_DOCUMENT_ID
)
BaseModel.service = service

# now you can import the base model from here, and child model classes will use the configured document
# see: https://pypi.org/project/gspread-models/
35 changes: 35 additions & 0 deletions app/models/book.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@

#from app.db import BaseModel
#
#class Book(BaseModel):
#
# SHEET_NAME = "books"
#
# COLUMNS = ["title", "author", "year"]
#
# SEEDS = [
# {"title": "To Kill a Mockingbird", "author": "Harper Lee", "year": 1960},
# {"title": "1984", "author": "George Orwell", "year": 1949},
# {"title": "The Great Gatsby", "author": "F. Scott Fitzgerald", "year": 1925},
# {"title": "The Catcher in the Rye", "author": "J.D. Salinger", "year": 1951},
# {"title": "Pride and Prejudice", "author": "Jane Austen", "year": 1813},
# {"title": "To the Lighthouse", "author": "Virginia Woolf", "year": 1927},
# {"title": "The Hobbit", "author": "J.R.R. Tolkien", "year": 1937},
# {"title": "Moby-Dick", "author": "Herman Melville", "year": 1851},
# {"title": "Brave New World", "author": "Aldous Huxley", "year": 1932},
# {"title": "Alice's Adventures in Wonderland", "author": "Lewis Carroll", "year": 1865},
# {"title": "Harry Potter and the Philosopher's Stone", "author": "J.K. Rowling", "year": 1997},
# {"title": "Harry Potter and the Chamber of Secrets", "author": "J.K. Rowling", "year": 1998},
# ]
#
#
#if __name__ == "__main__":
#
# books = Book.all()
# print("FOUND", len(books), "BOOKS")
# if any(books):
# for book in books:
# print(book.title, book.author, book.year)
# else:
# Book.seed()
#
9 changes: 9 additions & 0 deletions app/models/login.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

#from app.db import BaseModel
#
#class Login(BaseModel):
#
# SHEET_NAME = "logins"
#
# COLUMNS = ["email", "verified", "first_name", "last_name", "profile_photo_url"]
#
Loading

0 comments on commit 2260d3c

Please sign in to comment.