-
Notifications
You must be signed in to change notification settings - Fork 11
/
models.py
175 lines (145 loc) · 6.59 KB
/
models.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
from collections import defaultdict
from datetime import datetime, timedelta
import logging
from random import getrandbits
from google.appengine.ext import ndb, blobstore
from google.appengine.api.memcache import Client
from google.appengine.api.images import Image, JPEG, get_serving_url, delete_serving_url
import cloudstorage as gcs
bbs_memcache = Client()
class Location(ndb.Model):
""" Location in the galaxy to depart or arrive to: Planet, Star, City """
name = ndb.StringProperty(required=True)
parent_location = ndb.KeyProperty()
all_cache_key = 'all_locations'
@classmethod
def save_from_request(cls, request):
location = cls(id=request.get('name'), name=request.get('name'))
parent = request.get('parent')
if parent: # in case we ever pass a parent location
location.parent_location = ndb.Key(cls, parent)
location.put()
@classmethod
def get_all(cls):
""" Return all memcached locations """
locations = bbs_memcache.get(cls.all_cache_key)
if locations is None:
locations = cls().query().fetch()
bbs_memcache.set(cls.all_cache_key, locations)
logging.info("{} locations saved to memcache".format(len(locations)))
return locations
@classmethod
def _clear_all_memcache(cls):
logging.info("Removing locations from memcache")
bbs_memcache.delete(cls.all_cache_key)
def _post_put_hook(self, future):
self._clear_all_memcache()
@classmethod
def _post_delete_hook(cls, key, future):
cls._clear_all_memcache()
class StarTrip(ndb.Model):
""" Trip across the galaxy from one origin to a destination """
description = ndb.TextProperty(required=True)
origin = ndb.KeyProperty(required=True)
destiny = ndb.KeyProperty(required=True)
date = ndb.DateProperty(required=True)
created = ndb.DateTimeProperty(auto_now_add=True)
available_seats = ndb.IntegerProperty(default=1)
booked_seats = ndb.IntegerProperty(default=0)
pilot_name = ndb.StringProperty()
price = ndb.IntegerProperty(default=0) # in Galactic Credits
@classmethod
def save_from_request(cls, request):
""" Save trip from http request parameters """
star_trip = cls()
star_trip.date = datetime.strptime(request.get('date'), '%Y-%m-%d')
star_trip.description = request.get('description')
star_trip.available_seats = int(request.get('seats'))
star_trip.pilot_name = request.get('pilot')
star_trip.origin = ndb.Key(Location, request.get('origin'))
star_trip.destiny = ndb.Key(Location, request.get('destiny'))
star_trip.price = int(request.get('price'))
star_trip.put()
@classmethod
def query_from_request(cls, request, limit=10):
""" TODO: pagination (cursors) """
try:
origin = ndb.Key(Location, request.get('origin'))
destiny = ndb.Key(Location, request.get('destiny'))
date = datetime.strptime(request.get('date'), '%Y-%m-%d').date()
except:
origin = destiny = date = None
if origin and destiny and date:
result = cls.query(cls.origin == origin, cls.destiny == destiny, cls.date == date
).order(-cls.date).fetch(limit)
else:
result = cls.query().order(-cls.date).fetch(limit)
params = {'searched_origin': origin, 'searched_destiny': destiny, 'searched_date': date}
return result, params
class SpaceShip(ndb.Model):
""" Space ship with a name and a picture """
name = ndb.StringProperty(required=True)
model = ndb.StringProperty()
description = ndb.TextProperty()
image_gcs_key = ndb.StringProperty()
@classmethod
def save_from_request(cls, request, image_blob):
image_gcs_key = cls.store_picture_from_content(image_blob.key())
ship = cls(id=request.get('name'), name=request.get('name'),
model=request.get('model'), description=request.get('description'),
image_gcs_key=image_gcs_key)
ship.put()
logging.info("Stored {}".format(ship.key))
@classmethod
def store_picture_from_content(cls, blob_key):
""" Resize picture and upload to Cloud Storage bucket. Return the GCS key """
data = blobstore.BlobReader(blob_key).read()
img = Image(data)
img.resize(width=800, height=600)
# img.im_feeling_lucky()
img = img.execute_transforms(output_encoding=JPEG)
new_gcs_key = cls.upload_blob_to_gcs(img, content_type='img/jpeg')
# delete original blob
delete_serving_url(blob_key)
blobstore.delete(blob_key)
return new_gcs_key
@staticmethod
def upload_blob_to_gcs(data, filename=None, bucket='/spaceships/', content_type='img/jpg', options=None):
""" Upload a blob to Google Cloud Storage and return its new blob key """
filename = filename or "%032x" % getrandbits(128)
options = options or {'x-goog-acl': 'public-read'}
with gcs.open(bucket + filename, 'w',
content_type=content_type,
options=options) as output:
output.write(data)
# Blobstore API requires extra /gs to distinguish against blobstore files.
gs_key = blobstore.create_gs_key('/gs' + bucket + filename)
logging.info("GCS File {} created, key={}".format(filename, gs_key))
return gs_key
@property
def image_url(self):
return get_serving_url(self.image_gcs_key)
class TopLocations(ndb.Model):
""" Rollup model to save the most frequent destinations in the Galaxy """
destinations = ndb.KeyProperty(repeated=True)
origins = ndb.KeyProperty(repeated=True)
instance_id = '1'
@classmethod
def run(cls, limit=5, days=5):
""" Run rollup: get the top 5 destinations and origins of the
last 5 days
"""
today = datetime.today()
t1 = today - timedelta(days=days)
query = StarTrip.query(StarTrip.date > t1)
origins = defaultdict(int)
destinations = defaultdict(int)
for trip in query.iter():
origins[trip.origin] += 1
destinations[trip.destiny] += 1
top_origins = sorted(origins.items(), key=lambda t: t[1], reverse=True)[:limit]
top_destinations = sorted(origins.items(), key=lambda t: t[1], reverse=True)[:limit]
top_locs = cls(id=cls.instance_id, origins=[o[0] for o in top_origins],
destinations=[d[0] for d in top_destinations])
top_locs.put()
logging.info("Top Locations saved: {}".format(top_locs))