Skip to content

Commit 08ee130

Browse files
committed
moved from scratchpad repo
1 parent 549272f commit 08ee130

File tree

115 files changed

+47785
-1
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

115 files changed

+47785
-1
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
*.pyc
2+
.idea
3+
lib

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,11 @@
1-
# flask-appengine-demo
1+
# flask-appengine-demo
2+
3+
a little MAGIC
4+
gae calls run.py
5+
run.py add flask lib to the syspath
6+
7+
8+
REST Api server
9+
has an admin api
10+
has a client api
11+
serves the playlist of a user

app.yaml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
application: application
2+
version: 1
3+
runtime: python27
4+
api_version: 1
5+
threadsafe: true
6+
7+
default_expiration: "5d"
8+
9+
builtins:
10+
- appstats: on
11+
- admin_redirect: on
12+
- deferred: on
13+
- remote_api: on
14+
15+
libraries:
16+
- name: jinja2
17+
version: "2.6"
18+
- name: markupsafe
19+
version: "0.15"
20+
21+
inbound_services:
22+
- warmup
23+
24+
25+
handlers:
26+
- url: /favicon.ico
27+
static_files: application/static/img/favicon.ico
28+
upload: application/static/img/favicon.ico
29+
30+
- url: /stylesheets
31+
static_dir: application/static/css
32+
33+
- url: /robots.txt
34+
static_files: application/static/robots.txt
35+
upload: application/static/robots.txt
36+
37+
- url: /gae_mini_profiler/static
38+
static_dir: lib/gae_mini_profiler/static
39+
40+
- url: /gae_mini_profiler/.*
41+
script: lib.gae_mini_profiler.main.application
42+
43+
- url: /static
44+
static_dir: application/static
45+
46+
- url: /example/.*
47+
script: run_example.example.app
48+
49+
- url: /.*
50+
script: run.application.app
51+

appengine_config.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"""
2+
App Engine config
3+
"""
4+
5+
6+
def gae_mini_profiler_should_profile_production():
7+
"""Uncomment the first two lines
8+
to enable GAE Mini Profiler on production for admin accounts"""
9+
# from google.appengine.api import users
10+
# return users.is_current_user_admin()
11+
return False
12+
13+
14+
from google.appengine.api import logservice
15+
16+
logservice.AUTOFLUSH_ENABLED = True
17+
logservice.AUTOFLUSH_EVERY_SECONDS = None
18+
logservice.AUTOFLUSH_EVERY_BYTES = None
19+
logservice.AUTOFLUSH_EVERY_LINES = 100
20+

application/__init__.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"""
2+
Initialize Flask app
3+
4+
"""
5+
6+
from flask import Flask
7+
8+
# from flask_debugtoolbar import DebugToolbarExtension
9+
from gae_mini_profiler import profiler, templatetags
10+
from werkzeug.debug import DebuggedApplication
11+
12+
13+
app = Flask('application')
14+
app.config.from_object('application.settings')
15+
16+
# Enable jinja2 loop controls extension
17+
app.jinja_env.add_extension('jinja2.ext.loopcontrols')
18+
19+
@app.context_processor
20+
def inject_profiler():
21+
return dict(profiler_includes=templatetags.profiler_includes())
22+
23+
24+
# Pull in URL dispatch routes
25+
import urls
26+
27+
# Flask-DebugToolbar (only enabled when DEBUG=True)
28+
# toolbar = DebugToolbarExtension(app)
29+
30+
# Werkzeug Debugger (only enabled when DEBUG=True)
31+
if app.debug:
32+
app = DebuggedApplication(app, evalex=True)
33+
34+
# GAE Mini Profiler (only enabled on dev server)
35+
app = profiler.ProfilerWSGIMiddleware(app)

application/decorators.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""
2+
decorators.py
3+
4+
Decorators for URL handlers
5+
"""
6+
7+
from functools import wraps
8+
from google.appengine.api import users
9+
from flask import redirect, request, abort
10+
11+
12+
def login_not_required(func):
13+
"""does not require login credentials"""
14+
@wraps(func)
15+
def decorated_view(*args, **kwargs):
16+
if not users.get_current_user():
17+
pass
18+
#return redirect(users.create_login_url(request.url))
19+
return func(*args, **kwargs)
20+
return decorated_view
21+
22+
23+
def login_required(func):
24+
"""Requires standard login credentials"""
25+
@wraps(func)
26+
def decorated_view(*args, **kwargs):
27+
if not users.get_current_user():
28+
return redirect(users.create_login_url(request.url))
29+
return func(*args, **kwargs)
30+
return decorated_view
31+
32+
33+
def admin_required(func):
34+
"""Requires App Engine admin credentials"""
35+
@wraps(func)
36+
def decorated_view(*args, **kwargs):
37+
if users.get_current_user():
38+
if not users.is_current_user_admin():
39+
abort(401) # Unauthorized
40+
return func(*args, **kwargs)
41+
return redirect(users.create_login_url(request.url))
42+
return decorated_view

application/generate_keys.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#!/usr/bin/env python
2+
# encoding: utf-8
3+
"""
4+
generate_keys.py
5+
6+
Generate CSRF and Session keys, output to secret_keys.py file
7+
8+
Usage:
9+
generate_keys.py [-f]
10+
11+
Outputs secret_keys.py file in current folder
12+
13+
By default, an existing secret_keys file will not be replaced.
14+
Use the '-f' flag to force the new keys to be written to the file
15+
16+
17+
"""
18+
19+
import string
20+
import os.path
21+
22+
from optparse import OptionParser
23+
from random import choice
24+
from string import Template
25+
26+
27+
# File settings
28+
file_name = 'secret_keys.py'
29+
file_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), file_name)
30+
31+
file_template = Template('''# CSRF- and Session keys
32+
33+
CSRF_SECRET_KEY = '$csrf_key'
34+
SESSION_KEY = '$session_key'
35+
''')
36+
37+
38+
# Get options from command line
39+
parser = OptionParser()
40+
parser.add_option("-f", "--force", dest="force",
41+
help="force overwrite of existing secret_keys file", action="store_true")
42+
parser.add_option("-r", "--randomness", dest="randomness",
43+
help="length (randomness) of generated key; default = 24", default=24)
44+
(options, args) = parser.parse_args()
45+
46+
47+
def generate_randomkey(length):
48+
"""Generate random key, given a number of characters"""
49+
chars = string.letters + string.digits
50+
return ''.join([choice(chars) for i in range(length)])
51+
52+
53+
def write_file(contents):
54+
with open(file_path, 'wb') as f:
55+
f.write(contents)
56+
57+
58+
def generate_keyfile(csrf_key, session_key):
59+
"""Generate random keys for CSRF- and session key"""
60+
output = file_template.safe_substitute(dict(
61+
csrf_key=csrf_key, session_key=session_key
62+
))
63+
if os.path.exists(file_path):
64+
if options.force is None:
65+
print "Warning: secret_keys.py file exists. Use 'generate_keys.py --force' to force overwrite."
66+
else:
67+
write_file(output)
68+
else:
69+
write_file(output)
70+
71+
72+
def main():
73+
r = options.randomness
74+
csrf_key = generate_randomkey(r)
75+
session_key = generate_randomkey(r)
76+
generate_keyfile(csrf_key, session_key)
77+
78+
79+
if __name__ == "__main__":
80+
main()
81+

application/models.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""
2+
models.py
3+
4+
App Engine datastore models
5+
"""
6+
7+
8+
from google.appengine.ext import ndb
9+
10+
11+
class PurchaseLogMonthly(ndb.Model):
12+
name = ndb.StringProperty(indexed=False)
13+
ofYearMonth = ndb.IntegerProperty(indexed=False)
14+
ofYear = ndb.IntegerProperty(indexed=False)
15+
ofMonth = ndb.IntegerProperty(indexed=False)
16+
17+
class PurchaseLogEntry(ndb.Model):
18+
purchasedOn = ndb.DateTimeProperty(auto_now_add=False)
19+
userName = ndb.StringProperty(indexed=False)
20+
videoName = ndb.StringProperty(indexed=False)
21+
videoPrice = ndb.IntegerProperty()
22+
23+
24+
class UserCounters(ndb.Model):
25+
name = ndb.StringProperty(indexed=False)
26+
lastLogin = ndb.DateTimeProperty(auto_now_add=True)
27+
videoWatchHistory = ndb.StringProperty()
28+
loginCounter = ndb.IntegerProperty()
29+
videoWatchCounter = ndb.IntegerProperty()
30+
31+
32+
class GlobalCountersDaily(ndb.Model):
33+
name = ndb.StringProperty(indexed=False)
34+
ofDay = ndb.DateProperty(auto_now_add=False)
35+
36+
class GlobalCountersTotal(ndb.Model):
37+
name = ndb.StringProperty(indexed=False)
38+
39+
class GlobalCounters(ndb.Model):
40+
videoWatchCounter = ndb.IntegerProperty()
41+
loginCounter = ndb.IntegerProperty()
42+
purchasesCounter = ndb.IntegerProperty()
43+
revenueCounter = ndb.IntegerProperty()
44+

application/playlists/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

application/playlists/models.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
"""
2+
models.py
3+
4+
App Engine datastore models
5+
"""
6+
7+
8+
from google.appengine.ext import ndb
9+
10+
11+
# We set the same parent key on the 'Playlist' to ensure one playlist
12+
# per entity group. Queries across the single entity group
13+
# will be consistent. However, the write rate to a single entity group
14+
# should be limited to ~1/second.
15+
16+
def _playlists_key(playlist_name):
17+
"""Constructs a Datastore key for a playlist entity"""
18+
key = ndb.Key(playlist_name, playlist_name)
19+
return key
20+
21+
22+
class Playlists(ndb.Model):
23+
24+
name = ndb.StringProperty(required=True, indexed=True)
25+
videoUri = ndb.StringProperty(indexed=False)
26+
available = ndb.BooleanProperty(indexed=False, default=True)
27+
tags = ndb.StringProperty(repeated=True)
28+
createdAt = ndb.DateTimeProperty(indexed=False, auto_now_add=True)
29+
30+
@classmethod
31+
def all(cls):
32+
playlists = []
33+
for entry in cls.query():
34+
playlist = cls._createDictionaryFrom(entry)
35+
playlists.append(playlist)
36+
return playlists
37+
38+
@classmethod
39+
def new(cls, playlist_name):
40+
ancestor_key = _playlists_key(playlist_name)
41+
return Playlists(parent=ancestor_key)
42+
43+
@classmethod
44+
def byKey(cls, playlist_name, video_id):
45+
ancestor_key = _playlists_key(playlist_name)
46+
key = ndb.Key(Playlists, video_id, parent=ancestor_key)
47+
entry = key.get()
48+
video = cls._createDictionaryFrom(entry)
49+
return video
50+
51+
@classmethod
52+
def allVideos(cls, playlist_name):
53+
ancestor_key = _playlists_key(playlist_name)
54+
videos = []
55+
for entry in cls.query(ancestor=ancestor_key):
56+
video = cls._createDictionaryFrom(entry)
57+
videos.append(video)
58+
return videos
59+
60+
@classmethod
61+
def deleteVideo(cls, playlist_name, playlist_id):
62+
ancestor_key = _playlists_key(playlist_name)
63+
key = ndb.Key(Playlists, playlist_id, parent=ancestor_key)
64+
key.delete()
65+
66+
@classmethod
67+
def _createDictionaryFrom(cls, entry):
68+
if entry is None:
69+
return {}
70+
video = {}
71+
video['parentId'] = entry.key.parent().id()
72+
video['parentKind'] = entry.key.parent().kind()
73+
video['id'] = entry.key.id()
74+
video['kind'] = entry.key.kind()
75+
video['name'] = entry.name
76+
video['videoUri'] = entry.videoUri
77+
return video
78+

application/playlists/testViews.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import sys
2+
print sys.path
3+
import views
4+
5+
if __name__ == '__main__':
6+
print views.api_get_playlists()

0 commit comments

Comments
 (0)