class Base(db.Model): """ General abstractions for all models """ __abstract__ = True id = db.Column(db.Integer, primary_key=True) date_created = db.Column(db.DateTime, default=db.func.now()) date_updated = db.Column(db.DateTime, default=db.func.now(), onupdate=db.func.now()) def to_dict(self, filters=[]): _d = {} for column in self.__table__.columns: _d[column.name] = getattr(self, column.name) d = translate_model_data_to_json_safe_data(_d) if len(filters) > 0: filtered_keys = set(filters) for k in filtered_keys: if k in d: del d[k] return d def to_json(self, filters=[]): return json.dumps(self.to_dict(filters=filters))
class Site(Base): """ The model for a Site object """ __tablename__ = "sites" handle = db.Column(db.String(), unique=True, nullable=False) user_id = db.Column(db.ForeignKey(User.id), nullable=False) def set_first_handle(self, unsanitizied_str): self.handle = slugify(unsanitizied_str) try: self.save() except IntegrityError: # That handle already exists, rollback db.session().rollback() # Find existing sites that begin with that handle # NOTE: This query can be optimized: # https://gist.github.com/hest/8798884 existing_site_count = Site.query.filter( Site.handle.startswith(self.handle)).count() self.handle += "-{}".format(existing_site_count + 1) return self.set_first_handle(self.handle) return self.handle def __repr__(self): return "<Site {}>".format(self.handle)
class User(Base): """ User class """ __tablename__ = "users" email = db.Column(db.String(254), unique=True, nullable=False) name = db.Column(db.String()) apple_id = db.Column(db.String()) def __repr__(self): return "<User {}>".format(self.email)
class AuthToken(Base): """ Authorization tokens """ __tablename__ = "tokens" token = db.Column(UUID(as_uuid=True), default=uuid.uuid4, nullable=False, unique=True) user_id = db.Column(db.ForeignKey(User.id), nullable=False) expired = db.Column(db.Boolean, nullable=False, default=False) def __repr__(self): return "<Token {}>".format(self.token)
class Media(Base): """ A media object, like an image or a video """ __tablename__ = "media" uuid = db.Column(UUID(as_uuid=True), unique=True, nullable=False) media_type = db.Column(db.Enum(MediaType), nullable=False) url = db.Column(db.String, nullable=False) url_optimized = db.Column(db.String, nullable=False) url_poster = db.Column(db.String) showcase = db.Column(db.Boolean, default=False, nullable=False) width = db.Column(db.Integer, nullable=False) height = db.Column(db.Integer, nullable=False) aspect = db.Column(db.String, nullable=False) shot_exposure = db.Column(db.String) shot_aperture = db.Column(db.String) shot_speed = db.Column(db.String) shot_focal_length = db.Column(db.String) camera_make = db.Column(db.String) camera_model = db.Column(db.String) average_color = db.Column(db.String) def __init__(self, file): self.file = file # Try to cast the file_type as a MediaType try: self.media_type = MediaType(file.content_type) except Exception: raise InvalidMediaTypeException( "Sorry '{}' is not a supported format".format( file.content_type)) self.uuid = uuid.uuid4() def set_exif(self, image): try: exif = { ExifTags.TAGS[k]: v for k, v in image._getexif().items() if k in ExifTags.TAGS } except Exception: return exposure = exif.get("ExposureTime", None) if exposure is not None: self.shot_exposure = "{}/{}".format(exposure[0], exposure[1]) aperture = exif.get("FNumber", None) if aperture is not None: self.shot_aperture = "ƒ{}".format( round(aperture[0] / aperture[1], 1)) self.shot_speed = exif.get("ISOSpeedRatings", None) self.shot_focal_length = str(exif.get("FocalLengthIn35mmFilm", None)) + "mm" self.camera_make = exif.get("Make", None) self.camera_model = exif.get("Model", None) # Maps manufacturer names to friendlier names self.camera_make = EXIF_NAME_MAPS.get(self.camera_make, self.camera_make) self.camera_model = EXIF_NAME_MAPS.get(self.camera_model, self.camera_model) pass def set_static_info(self, image): self.width = image.width self.height = image.height self.aspect = str(round(self.width / self.height, 2)) def optimize(self): if self.media_type in STATIC_MEDIA_TYPES: image = Image.open(self.file) self.set_static_info(image) self.set_exif(image) file_names = self.optimize_static(image) return file_names elif self.media_type is MediaType.GIF: file_names = self.optimize_gif() return file_names else: raise Exception( "Called optimize on media that cannot be optimized") def set_average_color(self, image): def rgb_to_hex(rgb): return '%02x%02x%02x' % rgb try: color_thief = ColorThiefFromImage(image) avg_color = rgb_to_hex(color_thief.get_color(quality=1)) except Exception: self.average_color = DEFAULT_AVG_COLOR return DEFAULT_AVG_COLOR self.average_color = avg_color return avg_color def optimize_gif(self): # Save GIF to FS raw_gif_file_name = str(self.uuid) + "." + self.media_type.ext() self.file.save(raw_gif_file_name) # Cast GIF to Clip clip = mp.VideoFileClip(raw_gif_file_name) temp_poster_file_name = str(self.uuid) + ".poster.jpeg" poster = Image.open(raw_gif_file_name) poster.convert("RGB").save(temp_poster_file_name) self.set_average_color(poster) self.set_static_info(poster) # Save optimized MP4 clip optimized_mp4_filename = MEDIA_NAME.format(self.aspect, self.average_color, self.uuid, ".thumb", "mp4") clip.write_videofile(optimized_mp4_filename, codec="libx264", bitrate="3000k", progress_bar=False, verbose=False, ffmpeg_params=[ "-movflags", "faststart", "-pix_fmt", "yuv420p", "-vf", "scale=896:-2" ]) gif_file_name = MEDIA_NAME.format(self.aspect, self.average_color, self.uuid, "", self.media_type.ext()) os.rename(raw_gif_file_name, gif_file_name) # Make poster poster_jpeg_file_name = MEDIA_NAME.format(self.aspect, self.average_color, self.uuid, ".poster", "jpeg") os.rename(temp_poster_file_name, poster_jpeg_file_name) return [gif_file_name, optimized_mp4_filename, poster_jpeg_file_name] def optimize_static(self, image): # Rotate based on EXIF data try: for orientation in ExifTags.TAGS.keys(): if ExifTags.TAGS[orientation] == 'Orientation': break exif = dict(image._getexif().items()) if exif[orientation] == 3: image = image.rotate(180, expand=True) elif exif[orientation] == 6: image = image.rotate(270, expand=True) elif exif[orientation] == 8: image = image.rotate(90, expand=True) except (AttributeError, KeyError, IndexError): pass # Convert to JPG image = image.convert('RGB') # Generate smaller thumbnail image_thumb = image.copy() image_thumb.thumbnail(OPTIMAL_CANVAS_SIZE, Image.ANTIALIAS) self.set_average_color(image_thumb) raw_image_file_name = MEDIA_NAME.format(self.aspect, self.average_color, self.uuid, "", self.media_type.ext()) image.save(raw_image_file_name, format=self.media_type.name) optimized_image_file_name = MEDIA_NAME.format(self.aspect, self.average_color, self.uuid, ".thumb", "jpeg") image_thumb.save(optimized_image_file_name, quality=OPTIMAL_QUALITY, format=self.media_type.name) return [raw_image_file_name, optimized_image_file_name]
class Event(Base): """ Event class """ __tablename__ = "events" user_id = db.Column(db.ForeignKey(User.id)) type = db.Column(db.String, nullable=False) resource = db.Column(db.String) device_family = db.Column(db.String) device_model = db.Column(db.String) os_family = db.Column(db.String) os_version = db.Column(db.String) browser_family = db.Column(db.String) browser_version = db.Column(db.String) is_mobile = db.Column(db.Boolean) ip_address = db.Column(db.String) def set_agent_props(self): agent_props = parse(request.headers.get('User-Agent')) self.device_family = agent_props.device.family self.device_model = agent_props.device.model self.os_family = agent_props.os.family self.os_version = agent_props.os.version_string self.browser_family = agent_props.browser.family self.browser_version = agent_props.browser.version_string self.is_mobile = agent_props.is_mobile def set_ip_address(self): r_addr = request.remote_addr client_ip = request.headers.get("X-Forwarded-For", r_addr) self.ip_address = client_ip def __repr__(self): return "<Event {} {}>".format(self.type, self.email)
class Post(Base): """ General Post """ __tablename__ = "posts" slug = db.Column(db.String, nullable=False, unique=True, default=_make_slug) comment = db.Column(db.String) public = db.Column(db.Boolean, nullable=False, default=False) location_lat = db.Column(db.Float) location_lon = db.Column(db.Float) location_name = db.Column(db.String) review = db.Column(db.Integer) link_name = db.Column(db.String) link_uri = db.Column(db.String) love_count = db.Column(db.Integer, default=0) media = db.Column(ARRAY(db.String, dimensions=1)) topics = db.Column(ARRAY(db.String, dimensions=1)) tweet_id = db.Column(db.String) user_id = db.Column(db.ForeignKey(User.id), nullable=False) site_id = db.Column(db.ForeignKey(Site.id), nullable=False) def _fetch_friendly_location(self): GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY", None) # Check to make sure we have a key if GOOGLE_API_KEY is None: return # Check that this call is necessary if self.location_lat is None or self.location_lon is None: return loc_resp = requests.get( "https://maps.googleapis.com/maps/api/geocode/json?" "latlng={},{}&key={}".format( self.location_lat, self.location_lon, GOOGLE_API_KEY ) ).json() # Check for valid results if len(loc_resp["results"]) == 0: return address_comps = loc_resp["results"][0]["address_components"] locality_comps = list(filter( lambda ac: "locality" in ac["types"], address_comps )) if len(locality_comps) == 0: return self.location_name = locality_comps[0]["long_name"] def increment_love_count(self, factor=1): # Increments the love counter when an object is loved self.love_count += factor