class Body(Model): """ A meeting body. This can be full council, a subcommittee, or "off-council" matters such as election debates. """ id = fields.IDField() name = fields.TextField(required=True) description = fields.TextField() start_datetime = fields.DateTime(required=True) end_datetime = fields.DateTime() is_active = fields.BooleanField(required=True) external_source_id = fields.TextField() class Meta: ignore_none_field = False @classmethod def Example(cls) -> Model: body = cls() body.name = "Full Council" body.is_active = True body.start_datetime = datetime.utcnow() return body _PRIMARY_KEYS = ("name", ) _INDEXES = ()
class Role(Model): """ A role is a person's job for a period of time in the city council. A person can (and generally does) have many roles. For example: a person has two terms as city council member for district four then a term as city council member for a citywide seat. Roles can also be tied to committee chairs. For example: a council member spends a term on the transportation committee and then spends a term on the finance committee. """ title = fields.TextField(required=True) person_ref = fields.ReferenceField(Person, required=True) body_ref = fields.ReferenceField(Body) seat_ref = fields.ReferenceField(Seat, required=True) start_datetime = fields.DateTime(required=True) end_datetime = fields.DateTime() external_source_id = fields.TextField() @classmethod def Example(cls) -> Model: role = cls() role.title = "Council President" role.person_ref = Person.Example() role.body_ref = Body.Example() role.seat_ref = Seat.Example() role.start_datetime = datetime.utcnow() return role _PRIMARY_KEYS = ("title", "person_ref", "body_ref", "seat_ref") _INDEXES = ()
class Transcript(Model): """ A transcript is a document per-session. """ session_ref = fields.ReferenceField(Session, required=True) file_ref = fields.ReferenceField(File, required=True) confidence = fields.NumberField(required=True) created = fields.DateTime(required=True) @classmethod def Example(cls) -> Model: transcript = cls() transcript.session_ref = Session.Example() transcript.file_ref = File.Example() transcript.confidence = 0.943 transcript.created = datetime.utcnow() return transcript _PRIMARY_KEYS = ("session_ref", "file_ref") _INDEXES = ( IndexedFieldSet(( IndexedField(name="session_ref", order=Order.ASCENDING), IndexedField(name="created", order=Order.DESCENDING), )), IndexedFieldSet(( IndexedField(name="session_ref", order=Order.ASCENDING), IndexedField(name="confidence", order=Order.DESCENDING), )), )
class Session(Model): """ A session is a working period for an event. For example, An event could have a morning and afternoon session. """ event_ref = fields.ReferenceField(Event, required=True) session_datetime = fields.DateTime(required=True) session_index = fields.NumberField(required=True) video_uri = fields.TextField(required=True, validator=validators.resource_exists) caption_uri = fields.TextField(validator=validators.resource_exists) external_source_id = fields.TextField() @classmethod def Example(cls) -> Model: session = cls() session.event_ref = Event.Example() session.session_index = 0 session.video_uri = ( "https://video.seattle.gov/media/council/brief_072219_2011957V.mp4" ) return session _PRIMARY_KEYS = ("event_ref", "video_uri") _INDEXES = ()
class Event(Model): """ An event can be a normally scheduled meeting, a special event such as a press conference or election debate, and, can be upcoming or historical. """ id = fields.IDField() body_ref = fields.ReferenceField(Body, required=True, auto_load=False) event_datetime = fields.DateTime(required=True) static_thumbnail_ref = fields.ReferenceField(File, auto_load=False) hover_thumbnail_ref = fields.ReferenceField(File, auto_load=False) agenda_uri = fields.TextField(validator=validators.resource_exists) minutes_uri = fields.TextField(validator=validators.resource_exists) external_source_id = fields.TextField() class Meta: ignore_none_field = False def set_validator_kwargs(self, kwargs: Dict) -> None: field = fields.TextField(validator=validators.resource_exists, validator_kwargs=kwargs) field.contribute_to_model(Event, "agenda_uri") field.contribute_to_model(Event, "minutes_uri") @classmethod def Example(cls) -> Model: event = cls() event.body_ref = Body.Example() event.event_datetime = datetime.utcnow() event.agenda_uri = ( "http://legistar2.granicus.com/seattle/meetings/2019/11/" "4169_A_Select_Budget_Committee_19-11-01_Committee_Agenda.pdf") event.minutes_uri = ( "http://legistar2.granicus.com/seattle/meetings/2019/7/" "4041_M_Council_Briefing_19-07-22_Committee_Minutes.pdf") return event _PRIMARY_KEYS = ("body_ref", "event_datetime") _INDEXES = ( IndexedFieldSet(( IndexedField(name="body_ref", order=Order.ASCENDING), IndexedField(name="event_datetime", order=Order.ASCENDING), )), IndexedFieldSet(( IndexedField(name="body_ref", order=Order.ASCENDING), IndexedField(name="event_datetime", order=Order.DESCENDING), )), )
class MatterStatus(Model): """ A matter status is the status of a matter at any given time. Useful for tracking the timelines of matters. I.E. Return me a timeline of matter x. The same matter will have multiple matter statuses. 1. MatterStatus of submitted 2. MatterStatus of passed 3. MatterStatus of signed 4. etc. """ id = fields.IDField() matter_ref = fields.ReferenceField(Matter, required=True, auto_load=False) # Optional because status can be updated out of event # i.e. Signed by Mayor event_minutes_item_ref = fields.ReferenceField(EventMinutesItem, auto_load=False) status = fields.TextField( required=True, validator=validators.create_constant_value_validator( MatterStatusDecision, True), ) update_datetime = fields.DateTime(required=True) external_source_id = fields.TextField() class Meta: ignore_none_field = False @classmethod def Example(cls) -> Model: matter_status = cls() matter_status.matter_ref = Matter.Example() matter_status.status = MatterStatusDecision.ADOPTED matter_status.update_datetime = datetime.utcnow() return matter_status _PRIMARY_KEYS = ("matter_ref", "status", "update_datetime") _INDEXES = ( IndexedFieldSet(( IndexedField(name="matter_ref", order=Order.ASCENDING), IndexedField(name="update_datetime", order=Order.ASCENDING), ), ), IndexedFieldSet(( IndexedField(name="matter_ref", order=Order.ASCENDING), IndexedField(name="update_datetime", order=Order.DESCENDING), ), ), )
class Session(Model): """ A session is a working period for an event. For example, An event could have a morning and afternoon session. """ id = fields.IDField() event_ref = fields.ReferenceField(Event, required=True, auto_load=False) session_datetime = fields.DateTime(required=True) session_index = fields.NumberField(required=True) session_content_hash = fields.TextField(required=True) video_uri = fields.TextField(required=True, validator=validators.resource_exists) caption_uri = fields.TextField(validator=validators.resource_exists) external_source_id = fields.TextField() class Meta: ignore_none_field = False def set_validator_kwargs(self, kwargs: Dict) -> None: field = fields.TextField(required=True, validator=validators.resource_exists, validator_kwargs=kwargs) field.contribute_to_model(Session, "video_uri") @classmethod def Example(cls) -> Model: session = cls() session.event_ref = Event.Example() session.session_index = 0 session.video_uri = ( "https://video.seattle.gov/media/council/brief_072219_2011957V.mp4" ) session.session_content_hash = ( "05bd857af7f70bf51b6aac1144046973bf3325c9101a554bc27dc9607dbbd8f5") return session _PRIMARY_KEYS = ("event_ref", "video_uri") _INDEXES = (IndexedFieldSet(( IndexedField(name="event_ref", order=Order.ASCENDING), IndexedField(name="session_index", order=Order.ASCENDING), )), )
class Transcript(Model): """ A transcript is a document per-session. """ id = fields.IDField() session_ref = fields.ReferenceField(Session, required=True, auto_load=False) file_ref = fields.ReferenceField(File, required=True, auto_load=False) generator = fields.TextField(required=True) confidence = fields.NumberField(required=True) created = fields.DateTime(required=True) class Meta: ignore_none_field = False @classmethod def Example(cls) -> Model: transcript = cls() transcript.session_ref = Session.Example() transcript.file_ref = File.Example() transcript.generator = "FakeGen -- v0.1.0" transcript.confidence = 0.943 transcript.created = datetime.utcnow() return transcript _PRIMARY_KEYS = ("session_ref", "file_ref") _INDEXES = ( IndexedFieldSet(( IndexedField(name="session_ref", order=Order.ASCENDING), IndexedField(name="created", order=Order.DESCENDING), )), IndexedFieldSet(( IndexedField(name="session_ref", order=Order.ASCENDING), IndexedField(name="confidence", order=Order.DESCENDING), )), )
class Picture(Model): image_id = fields.TextField() date_added = fields.DateTime() file_name = fields.TextField() blob_name = fields.TextField() user_id = fields.TextField() def __init__(self, image_id="", file_name="", blob_name="", user_id=""): self.image_id = image_id self.date_added = datetime.datetime.now() self.file_name = file_name self.blob_name = blob_name self.user_id = user_id def add_image(self, image_object, user_id): try: bucket = storage_client.get_bucket(CLOUD_STORAGE_BUCKET) # In case users upload pictures with the same file name, concatenate user's id with file name blob = bucket.blob(user_id + "_" + self.file_name) blob.upload_from_string( image_object.read(), content_type=image_object.content_type ) self.blob_name = blob.name self.user_id = user_id logger.info("Successfully uploaded image to Google Cloud Bucket") # Store object in Firestore to keep track of ID's db.collection("Images").document(self.image_id).set(self.__dict__) logger.info( "Successfully stored document with image_id: {} in Firestore".format( self.image_id ) ) return True except Exception as e: logger.error(str(e)) return False def delete_image(self, image_id, user_id): # Find Firestore document using image_id image = db.collection("Images").document(image_id) bucket = storage_client.get_bucket(CLOUD_STORAGE_BUCKET) image_dict = image.get().to_dict() # Check if the user credentials match the uploaded picture they wish to delete if image_dict["user_id"] != user_id: logger.error("This user does not have permission to delete the image") return False try: # Delete blob from Google Cloud bucket bucket.delete_blob(image_dict["blob_name"]) logger.debug("Deleted Blob: {}".format(image_dict["blob_name"])) # Delete Firestore image.delete() logger.debug( "Firestore document deleted for image_id: {}".format( image_dict["image_id"] ) ) return True except Exception as e: logger.debug(str(e)) logger.error("Cannot find blob in the bucket") return False def bulk_delete(self, user_id): images = db.collection("Images").stream() count = 0 try: for image in images: is_deleted = self.delete_image(image.get("image_id"), user_id) if is_deleted: db.collection("Images").document(image.get("image_id")).delete() count += 1 logger.debug("Deleted {} images".format(count)) return True except Exception as e: logger.error("Bulk-Delete Exception: {}".format(str(e))) return False
class Base(Model): created = fields.DateTime(auto=True) modified = AutoModifiedDateTime(auto=True) class Meta: abstract = True