class Test(Document): schema_version = StringField(validate=validate.Length(max=10), default='1') test_suite = StringField(validate=validate.Length(max=100), required=True) test_cases = ListField(StringField(validate=validate.Length(max=100))) variables = DictField() path = StringField(validate=validate.Length(max=300), default='') author = ReferenceField('User') create_date = DateTimeField() update_date = DateTimeField() organization = ReferenceField('Organization') team = ReferenceField('Team') staled = BooleanField(default=False) package = ReferenceField('Package') package_version = StringField() class Meta: collection_name = 'tests' def __eq__(self, other): for key, value in self.items(): if key == 'id': continue if key == 'create_date' or key == 'update_date': continue if value != other[key]: # missing is substituted to None in __getitem__, ie. other[key] if value is missing and other[key] is None: continue return False return True def __hash__(self): return hash(str(self.pk))
class Task(Document): schema_version = StringField(validate=validate.Length(max=10), default='1') test = ReferenceField('Test') test_suite = StringField( validate=validate.Length(max=100)) # embedded document from Test testcases = ListField(StringField()) schedule_date = DateTimeField(default=datetime.datetime.utcnow) run_date = DateTimeField() status = StringField(validate=validate.Length(max=50), default='waiting') comment = StringField(validate=validate.Length(max=1000)) kickedoff = IntField(validate=validate.Range(min=0), default=0) endpoint_list = ListField(UUIDField()) parallelization = BooleanField(default=False) endpoint_run = ReferenceField('Endpoint') priority = IntField(validate=validate.Range(min=QUEUE_PRIORITY_MIN, max=QUEUE_PRIORITY_MAX), default=QUEUE_PRIORITY_DEFAULT) variables = DictField() tester = ReferenceField('User') upload_dir = StringField(validate=validate.Length(max=100)) test_results = ListField(ReferenceField('TestResult')) organization = ReferenceField( 'Organization') # embedded document from Test team = ReferenceField('Team') # embedded document from Test class Meta: collection_name = 'tasks'
class User(UsersMixin): def _validate_industries(field, value): for industry in value: yield from industry.industry.fetch(no_data=True) def _validate_invited_by(field, value): from models.utils import is_valid_invited_by if not (yield from is_valid_invited_by(value)): raise ValidationError('There is no such user or group code.') fullname = fields.StrField(required=True) email = fields.EmailField(unique=True, required=True) nickname = fields.StrField(unique=True, required=True, validate=validate.Regexp(r'[\w\d]+')) birthday = fields.DateTimeField(required=True, format=DATE_FORMAT) phone_numbers = fields.ListField( fields.StrField(validate=validate.Regexp(r'[+][\w\d]+')), required=True) photo = fields.StrField(required=True) bio = fields.StrField(required=False, validate=validate.Length(max=320)) organizations = fields.StrField(required=False, validate=validate.Length(max=320)) education = fields.StrField(required=False, validate=validate.Length(max=320)) personal_invite_code = fields.StrField( unique=True, required=True, validate=validate.Length(equal=INVITE_CODE_LENGTH)) social_links = fields.ListField(fields.EmbeddedField(SocialLink)) current_location = fields.EmbeddedField(Location) preferred_ways_to_meet = fields.ListField( fields.StrField(validate=validate.OneOf(PREFERRED_WAYS_TO_MEET)), unique=True) join_date = fields.DateTimeField(missing=datetime.utcnow()) is_active = fields.BoolField(missing=False) is_deleted = fields.BoolField() date_deleted = fields.DateTimeField() is_notifications_enabled = fields.BoolField(missing=True) skills = fields.ListField(fields.ReferenceField(Tag), required=True) interests = fields.ListField(fields.ReferenceField(Tag)) industries = fields.ListField(fields.EmbeddedField(Industry), required=True, io_validate=_validate_industries) languages = fields.ListField(fields.EmbeddedField(Language)) looking_for = fields.ListField(fields.ReferenceField(LookingFor)) bubbles = fields.ListField(fields.EmbeddedField(Bubble)) invited_type = fields.StrField(required=True, validate=validate.OneOf(INVITE_TYPE)) invited_by = fields.StrField(required=True, io_validate=_validate_invited_by)
class Event(Document): schema_version = StringField(validate=validate.Length(max=10), default='1') code = IntField(required=True) message = DictField() organization = ReferenceField('Organization', required=True) team = ReferenceField('Team', default=None) status = StringField(validate=validate.Length(max=10), default='Triggered') date = DateTimeField(default=datetime.datetime.utcnow) class Meta: collection_name = 'events'
class Endpoint(Document): schema_version = StringField(validate=validate.Length(max=10), default='1') name = StringField(validate=validate.Length(max=100)) tests = ListField(ReferenceField('Test')) status = StringField(default='Offline', validate=validate.Length(max=20)) enable = BooleanField(default=True) last_run_date = DateTimeField() organization = ReferenceField('Organization') team = ReferenceField('Team') uid = UUIDField() class Meta: collection_name = 'endpoints'
class TestResult(Document): schema_version = StringField(validate=validate.Length(max=10), default='1') test_case = StringField(validate=validate.Length(max=100), required=True) test_site = StringField(validate=validate.Length(max=50)) task = ReferenceField('Task') test_date = DateTimeField(default=datetime.datetime.utcnow) duration = IntField() summary = StringField(validate=validate.Length(max=200)) status = StringField(validate=validate.Length(max=10), default='FAIL') more_result = DictField() class Meta: collection_name = 'test_results'
class Team(Document): schema_version = StringField(validate=validate.Length(max=10), default='1') name = StringField(validate=validate.Length(max=100), required=True) email = EmailField() registered_on = DateTimeField(default=datetime.datetime.utcnow) owner = ReferenceField('User') members = ListField(ReferenceField('User'), default=[]) editors = ListField(ReferenceField('User'), default=[]) introduction = StringField(validate=validate.Length(max=500)) avatar = StringField(validate=validate.Length(max=100)) organization = ReferenceField('Organization') path = StringField() class Meta: collection_name = 'teams'
class EventQueue(Document): schema_version = StringField(validate=validate.Length(max=10), default='1') events = ListField(ReferenceField('Event'), default=[]) rw_lock = BooleanField(default=False) class Meta: collection_name = 'event_queues' async def acquire_lock(self): for i in range(LOCK_TIMEOUT): if await self.collection.find_one_and_update( { '_id': self.pk, 'rw_lock': False }, {'$set': { 'rw_lock': True }}): return True await asyncio.sleep(0.1) else: return False async def release_lock(self): await self.collection.find_one_and_update({'_id': self.pk}, {'$set': { 'rw_lock': False }}) async def pop(self): if not await self.acquire_lock(): return None await self.reload() if len(self.events) == 0: await self.release_lock() return None event = self.events.pop(0) event = await event.fetch() await self.commit() await self.release_lock() return event async def push(self, event): if not await self.acquire_lock(): raise RuntimeError('failed to acquire queue lock') self.events.append(event) await self.commit() await self.release_lock() async def flush(self, cancelled=False): if not await self.acquire_lock(): raise RuntimeError('failed to acquire queue lock') if cancelled: for event in self.events: event.status = 'Cancelled' await event.commit() self.events = [] await self.commit() await self.release_lock() return True
class GetInviteCodeRequest(Document): email = fields.EmailField(unique=True, required=True) code = fields.StrField(unique=True, required=False, validate=validate.Length(equal=INVITE_CODE_LENGTH)) state = fields.StrField(required=True, validate=validate.OneOf(GET_INVITE_CODE_STATE)) created = fields.DateTimeField(missing=datetime.now())
class Organization(Document): schema_version = StringField(validate=validate.Length(max=10), default='1') name = StringField(validate=validate.Length(max=50), required=True) fullname = StringField(validate=validate.Length(max=100)) email = EmailField() registered_on = DateTimeField(default=datetime.datetime.utcnow) teams = ListField(ReferenceField('Team'), default=[]) introduction = StringField(validate=validate.Length(max=500)) website = URLField() owner = ReferenceField('User') members = ListField(ReferenceField('User'), default=[]) editors = ListField(ReferenceField('User'), default=[]) region = StringField() avatar = StringField(validate=validate.Length(max=100)) path = StringField() personal = BooleanField(default=False) class Meta: collection_name = 'organizations'
class RegisterUserSchema(UserSchema): phone = fields.StrField(required=True, validate=validate.Regexp(r'[+][\w\d]+')) industry = marshmallow_fields.Nested(IndustryTagSchema, required=True) skills = fields.ListField(fields.StrField(), required=True, validate=validate.Length(min=1, max=10)) interests = fields.ListField(fields.StrField(), validate=validate.Length(min=1, max=10)) class Meta: fields = ( 'email', 'password', 'fullname', 'nickname', 'birthday', 'phone', 'current_location', 'invited_type', 'invited_by', 'skills', 'interests', 'industry', ) dateformat = DATE_FORMAT @post_load def make_user(self, data): data['phone_numbers'] = [data.pop('phone')] industry = data.pop('industry') skills = data.pop('skills') interests = data.pop('interests') user = User(**data) user.set_password(data['password']) return { 'user': user, 'industry': industry, 'skills': skills, 'interests': interests, }
class UsersMixin(Document): password = fields.StrField(required=True, validate=validate.Length(min=5)) class Meta: allow_inheritance = True abstract = True def set_password(self, password: str): self.password = get_hash(password) def check_password(self, password: str) -> bool: return safe_str_cmp(self.password, get_hash(password))
class CafeEmployee(Document): username = fields.StringField(required=True, unique=True, validate=[ validate.Length(min=6), validate.Length(max=60), validate.Regexp(r"[a-zA-Z ']+") ]) password = fields.StringField(required=True, unique=True, validate=[ validate.Length(min=8), validate.Length(max=100), validate.Regexp(r"[a-zA-Z0-9 ']+") ]) id_cafe = fields.ReferenceField(Cafe) #fields.ObjectIdField # group = fields.IntegerField() token = fields.StringField(required=True) class Meta: collection = db.employee
class InviteGroup(Document): code = fields.StrField(unique=True, required=True, validate=validate.Length(equal=INVITE_CODE_LENGTH)) limit = fields.IntField(required=True, validate=validate.Range(min=1)) available_uses = fields.IntField(required=True, validate=validate.Range(min=0)) # expiration_date = fields.DateTimeField() async def create_new_code(self, limit, code=None): from models.utils import create_new_invite_code self.code = code if code is not None else await create_new_invite_code( ) self.limit = limit self.available_uses = limit await self.commit() return self
class User(Document): # pylint: disable=abstract-method """ A user in the private database. """ token = fields.StrField(required=True, unique=True, validate=validate.Length(equal=64)) # The following 4 values could be unconfigured # Since the server only updates these after it validates credentials, # if both login and password exist, they're guaranteed to be valid credentials login = fields.StrField(required=False, unique=True, validate=validate.Regexp(r"\d+")) password = BinaryField(required=False) # Populated when credentials are set/updated # A value of null indicates either credentials are unset, # or the courses are in the process of being populated # An empty array indicates no courses found courses = fields.ListField(fields.ObjectIdField(), required=False, allow_none=True) # Should be set as soon as valid credentials are detected email = fields.EmailField(required=False, allow_none=True) active = fields.BoolField(default=True) errors = fields.ListField(fields.EmbeddedField(LockboxFailure), default=[]) last_fill_form_result = fields.EmbeddedField(FillFormResult, default=None, allow_none=True) grade = fields.IntField(required=False, allow_none=True, default=None) first_name = fields.StrField(required=False, allow_none=True, default=None, validate=lambda s: s is None or len(s)) last_name = fields.StrField(required=False, allow_none=True, default=None, validate=lambda s: s is None or len(s))
class BlacklistToken(Document): """ Token Model for storing JWT tokens """ token = StringField(validate=validate.Length(max=500), required=True, unique=True) blacklisted_on = DateTimeField(default=datetime.datetime.utcnow) class Meta: collection_name = 'blacklist_tokens' def __repr__(self): return '<id: token: {}'.format(self.token) @staticmethod async def check_blacklist(auth_token): # check whether auth token has been blacklisted res = await BlacklistToken.find_one({'token': str(auth_token)}) if res is None: return False return True
class InviteCodeSchema(Schema): invite_code = fields.StrField( required=True, validate=validate.Length(equal=INVITE_CODE_LENGTH))
class Thread(Document): """ We will mimic reddit, with votable threads. Each thread may have either a body text or a link, but not both. """ # __tablename__ = 'threads_thread' title = fields.StrField(validate=validate.Length(max=THREAD.MAX_TITLE), default=None) # b.StringField(THREAD.MAX_TITLE) text = fields.StrField(default=None, validate=validate.Length(max=THREAD.MAX_BODY)) link = fields.StrField(default=None, validate=validate.Length(max=THREAD.MAX_LINK)) thumbnail = fields.StrField(default=None, validate=validate.Length(max=THREAD.MAX_LINK)) # NOTE: this should be ReferenceField user_id = fields.ObjectIdField() # Integer? subreddit_id = fields.ObjectIdField() # user = fields.ReferenceField("User") # Integer? subreddit = fields.ReferenceField("Subreddit") comment_ids = fields.ListField(fields.ObjectIdField()) comments = fields.ListField(fields.ReferenceField("Comment")) date_created = fields.DateTimeField(default=datetime.datetime.now(), missing=datetime.datetime.now()) date_modified = fields.DateTimeField( default=datetime.datetime.now(), missing=datetime.datetime.now(), ) status = fields.IntegerField(default=THREAD.ALIVE) num_votes = fields.IntegerField(default=0, missing=0) hotness = fields.IntegerField(default=0, missing=0) def update(self): if not self.date_created: self.date_created = datetime.datetime.now() self.date_modified = datetime.datetime.now() # self.subreddit = self.get_subreddit() # self.user = self.get_user() self.set_hotness() self.extract_thumbnail() return self class Meta: # collection = db.threads collection_name = "threads" indexes = ('username', '$text', 'title') # def get_subreddit(self): # return subreddits_model.Subreddit.find({"id": self.subreddit_id})[0] # def get_user(self): # return user_model.User.find({"id": self.user_id})[0] # def __init__(self, title, text, link, user_id, subreddit_id): # self.title = title # self.text = text # self.link = link # self.user_id = user_id # self.subreddit_id = subreddit_id # self.extract_thumbnail() # # self.created_on = datetime.datetime.utcnow() # self.updated_on = datetime.datetime.utcnow() # # self.upvotes = 0 # # self.set_hotness() # self.add_thread() # # # def get_thread(self): # thread = {"title": self.title, # "text" : self.text, # "link": self.link, # "user_id": self.user_id, # "subreddit_id": self.subreddit_id, # "created_on": self.created_on, # "updated_on": self.updated_on, # "upvotes": self.upvotes, # "hotness": self.hotness} # return thread # # def add_thread(self): # thread = self.get_thread() # id = db.threads.insert_one(thread).inserted_id # self.set_id(id) # return self # # def set_id(self, id): # self.id = ObjectId(id) # # def __repr__(self): # return '<Thread %r>' % (self.title) # # def get_comments(self, order_by='timestamp'): # """ # default order by timestamp # return only top levels! # """ # if order_by == 'timestamp': # return self.comments.filter_by(depth=1).\ # order_by(db.desc(Comment.created_on)).all()[:THREAD.MAX_COMMENTS] # else: # return self.comments.filter_by(depth=1).\ # order_by(db.desc(Comment.created_on)).all()[:THREAD.MAX_COMMENTS] # # def get_status(self): # """ # returns string form of status, 0 = 'dead', 1 = 'alive' # """ # return THREAD.STATUS[self.status] # def get_age(self): """ returns the raw age of this thread in seconds """ return (self.date_created - datetime.datetime(1970, 1, 1)).total_seconds() def get_hotness(self): """ returns the reddit hotness algorithm (votes/(age^1.5)) """ order = math.log(max(abs(self.num_votes), 1), 10) # Max/abs are not needed in our case seconds = self.get_age() - 1134028003 return round(order + seconds / 45000, 6) def set_hotness(self): """ returns the reddit hotness algorithm (votes/(age^1.5)) """ self.hotness = self.get_hotness() def pretty_date(self, typeof='created'): """ returns a humanized version of the raw age of this thread, eg: 34 minutes ago versus 2040 seconds ago. """ if typeof == 'created': return utils.pretty_date(self.date_created) elif typeof == 'updated': return utils.pretty_date(self.date_modified) def add_comment(self, comment_text, comment_parent_id, user_id): """ add a comment to this particular thread """ user = user_model.User.find_one({'id': ObjectId(user_id)}) if comment_parent_id.strip().replace(" ", ""): # parent_comment = Comment.query.get_or_404(comment_parent_id) # if parent_comment.depth + 1 > THREAD.MAX_COMMENT_DEPTH: # flash('You have exceeded the maximum comment depth') app.logger.error("have parent?: {}".format(comment_parent_id)) app.logger.debug(type(comment_parent_id)) comment_parent_id = ObjectId(comment_parent_id) comment = Comment(thread_id=ObjectId(self.id), user_id=user.id, text=comment_text, parent_id=comment_parent_id, parent=Comment.find_one( {"id": comment_parent_id}), user=user, thread=self, date_created=datetime.datetime.now(), date_modified=datetime.datetime.now()) else: comment = Comment(thread_id=ObjectId(self.id), user_id=ObjectId(user_id), text=comment_text, user=user, thread=self, date_created=datetime.datetime.now(), date_modified=datetime.datetime.now()) # db.session.add(comment) # db.session.commit() comment.commit() comment.set_depth() app.logger.debug("comment depth: {}".format(comment.depth)) comment.commit() self.comments.append(comment) self.comment_ids.append(comment.id) return comment # def get_voter_ids(self): # """ # return ids of users who voted this thread up # """ # upvotes = ThreadUpvote.find({"thread_id":self.id}) # # rs = db.engine.execute(select) # # ids = rs.fetchall() # list of tuples # ids = [v.user_id for v in upvotes] # return ids def has_voted(self, user_id): """ did the user vote already """ # select_votes = thread_upvotes.select( # db.and_( # thread_upvotes.c.user_id == user_id, # thread_upvotes.c.thread_id == self.id # ) # ) # rs = db.engine.execute(select_votes) select_votes = (ThreadUpvote.find( {"$and": [{ "user_id": ObjectId(user_id) }, { "thread_id": self.id }]})).count() return False if select_votes == 0 else True def vote(self, user_id): """ allow a user to vote on a thread. if we have voted already (and they are clicking again), this means that they are trying to unvote the thread, return status of the vote for that user """ already_voted = self.has_voted(user_id) vote_status = None if not already_voted: # vote up the thread # db.engine.execute( # thread_upvotes.insert(), # user_id = user_id, # thread_id = self.id # ) upvote_data = {"user_id": ObjectId(user_id), "thread_id": self.id} upvote = ThreadUpvote(**upvote_data) upvote.commit() self.num_votes = self.num_votes + 1 vote_status = True else: query = { "$and": [{ "user_id": ObjectId(user_id) }, { "thread_id": ObjectId(self.id) }] } app.logger.debug("thread_id: {}".format(self.id)) app.logger.debug(ThreadUpvote.find(query).count()) app.logger.debug(ThreadUpvote.__dict__) app.logger.debug(ThreadUpvote.opts.__dict__) # NOTE: Document.delete does not take query, but Document itself # upvote = ThreadUpvote.delete(query) upvote = ThreadUpvote.delete(ThreadUpvote.find_one(query)) # upvote.commit() # NOTE: othe alternative is using pymongo api # upvote = db.thread_upvotes.remove(query) # NOTE: no need to do save: # db.thread_upvotes.save() app.logger.debug(upvote) self.num_votes = self.num_votes - 1 vote_status = False app.logger.debug("vote_status: {}".format(vote_status)) self.commit() # db.session.commit() # for the vote count return vote_status def extract_thumbnail(self): """ ideally this type of heavy content fetching should be put on a celery background task manager or at least a crontab.. instead of setting it to run literally as someone posts a thread. but once again, this repo is just a simple example of a reddit-like crud application! """ DEFAULT_THUMBNAIL = 'https://reddit.codelucas.com/static/imgs/reddit-camera.png' if self.link: thumbnail = media.get_top_img(self.link) if not thumbnail: thumbnail = DEFAULT_THUMBNAIL self.thumbnail = thumbnail self.commit() def get_comments(self): app.logger.debug( list( Comment.find({ "$and": [{ "thread_id": self.id }, { "parent": { "$exists": True, "$eq": None } }] }))) comments = Comment.find({ "$and": [{ "thread_id": self.id }, { "parent": { "$exists": True, "$eq": None } }] }) # return list(Comment.find({"thread_id":self.id})) return list(comments) def get_all_comments(self): app.logger.debug(list(Comment.find({"thread_id": self.id}))) return list(Comment.find({"thread_id": self.id})) def get_comment_count(self): return Comment.find({"thread_id": self.id}).count()
class Comment(Document): """ This class is here because comments can only be made on threads, so it is contained completly in the threads module. Note the parent_id and children values. A comment can be commented on, so a comment has a one to many relationship with itself. Backrefs: A comment can refer to its parent thread with 'thread' A comment can refer to its parent comment (if exists) with 'parent' """ # __tablename__ = 'threads_comment' text = fields.StringField(default=None, validate=validate.Length(max=THREAD.MAX_BODY)) user_id = fields.ObjectIdField() # Integer? thread_id = fields.ObjectIdField() # Integer? user = fields.ReferenceField("User") # Integer? thread = fields.ReferenceField("Thread") # Integer? parent_id = fields.ObjectIdField(missing=None) # children = fields.ReferenceField('Comment') parent = fields.ReferenceField('Comment', missing=None) depth = fields.IntField(default=1, missing=1) # start at depth 1 date_created = fields.DateTimeField(default=datetime.datetime.now(), missing=datetime.datetime.now()) date_modified = fields.DateTimeField(default=datetime.datetime.now(), missing=datetime.datetime.now()) num_votes = fields.IntegerField(default=0, missing=0) class Meta: collection_name = 'comments' def update(self): if not self.date_created: self.date_created = datetime.datetime.now() self.date_modified = datetime.datetime.now() # self.thread = self.get_thread() return self def __repr__(self): return '<Comment %r>' % (self.text[:25]) # def __init__(self, thread_id, user_id, text, parent_id=None): # self.thread_id = thread_id # self.user_id = user_id # self.text = text # self.parent_id = parent_id # # def get_comment(self): # comment = {"thread_id": self.thread_id, # "user_id" : self.user_id, # "text": self.text, # "parent_id": self.parent_id, # } # return comment # def get_all_children(self): # def get_children(parent_id, children=[]): # children += Comment.find({"parent_id":parent_id}) # return children # # def recursive_children(parent_id, children=[]): # if children: # for c in children: # return recursive_children(c.id) # # for c in get_children(): # recursive_children(parent_id, children) # # for c in Comment.find({"parent_id":parent_id}): # children += get_children(c.id, children=children) # # parent_id = copy.copy(self.id) # Comment.find({"parent_id": self.id}) # # children = Comment.find({"parent_id":parent_id}) # # grandchildren = copy.copy(children) # for child in copy.copy(children): # grandchildren += get_recursive_children(child, def get_children(self): return Comment.find({"parent_id": self.id}) def get_children_count(self): return self.get_children().count() def set_depth(self): """ call after initializing """ app.logger.debug("comments: {}".format([x for x in Comment.find()])) app.logger.debug("comments: {}".format(list(Comment.find()))) for c in Comment.find(): app.logger.debug("comment: {}".format(c)) app.logger.debug("self.id, parent.id: {}".format( (self.id, self.parent_id))) num_children = self.get_children_count() app.logger.debug("num_children: {}".format(num_children)) app.logger.debug("children: \n{}".format( [x for x in self.get_children()])) # if num_children > 0: if self.parent is not None: self.depth = self.parent.fetch().depth + 1 self.commit() def get_comments(self, order_by='timestamp'): """ default order by timestamp Note: Only getting depth = 1 comments for simplicity """ app.logger.debug("in comments model.get_comments") if order_by == 'timestamp': # return self.children.order_by(db.desc(Comment.created_on)).\ # all()[:THREAD.MAX_COMMENTS] comments = (Comment.find({ "parent_id": self.id }).sort([("date_created", pymongo.ASCENDING) ])[:THREAD.MAX_COMMENTS]) app.logger.debug("".join([ "\n\t{}: child of {}".format(x.text, x.parent.fetch().text) for x in comments ])) comments = (Comment.find({ "parent_id": self.id }).sort([("date_created", pymongo.ASCENDING) ])[:THREAD.MAX_COMMENTS]) app.logger.debug("returing: {}".format(comments)) comments = (Comment.find({ "parent_id": self.id }).sort([("date_created", pymongo.ASCENDING) ])[:THREAD.MAX_COMMENTS]) return list(comments) else: # return self.comments.order_by(db.desc(Comment.created_on)).\ # all()[:THREAD.MAX_COMMENTS] comments = (Comment.find({ "parent_id": self.id }).sort([("date_created", pymongo.DESCENDING) ])[:THREAD.MAX_COMMENTS]) app.logger.debug(comments) return comments def get_margin_left(self): """ nested comments are pushed right on a page -15px is our default margin for top level comments """ margin_left = 15 + ((self.depth - 1) * 32) margin_left = min(margin_left, 680) return str(margin_left) + "px" def get_age(self): """ returns the raw age of this thread in seconds """ return (self.date_created - datetime.datetime(1970, 1, 1)).total_seconds() def pretty_date(self, typeof='created'): """ returns a humanized version of the raw age of this thread, eg: 34 minutes ago versus 2040 seconds ago. """ if typeof == 'created': return utils.pretty_date(self.date_created) elif typeof == 'updated': return utils.pretty_date(self.date_modified) def has_voted(self, user_id): """ did the user vote already """ select_votes = (CommentUpvote.find({ "$and": [{ "user_id": ObjectId(user_id) }, { "comment_id": self.id }] })).count() return False if select_votes == 0 else True def vote(self, user_id): """ allow a user to vote on a thread. if we have voted already (and they are clicking again), this means that they are trying to unvote the thread, return status of the vote for that user """ already_voted = self.has_voted(user_id) vote_status = None if not already_voted: # vote up the thread # db.engine.execute( # thread_upvotes.insert(), # user_id = user_id, # thread_id = self.id # ) upvote_data = {"user_id": ObjectId(user_id), "comment_id": self.id} upvote = CommentUpvote(**upvote_data) upvote.commit() query = { "$and": [{ "user_id": ObjectId(user_id) }, { "comment_id": ObjectId(self.id) }] } app.logger.debug("is created {}".format( CommentUpvote.find_one(query).is_created)) self.num_votes = self.num_votes + 1 vote_status = True else: query = { "$and": [{ "user_id": ObjectId(user_id) }, { "comment_id": ObjectId(self.id) }] } app.logger.debug("comment_id: {}".format(self.id)) app.logger.debug(CommentUpvote.find(query).count()) app.logger.debug(CommentUpvote.__dict__) app.logger.debug(CommentUpvote.opts.__dict__) # NOTE: Document.delete does not take query, but Document itself # upvote = CommentUpvote.delete(query) upvoted_comment = CommentUpvote.find_one(query) if upvoted_comment: upvote = CommentUpvote.delete(upvoted_comment) app.logger.debug("upvoted_comment: {}".format(upvoted_comment)) # upvote.commit() # NOTE: othe alternative is using pymongo api # upvote = db.thread_upvotes.remove(query) # NOTE: no need to do save: # db.thread_upvotes.save() # app.logger.debug(upvote) self.num_votes = self.num_votes - 1 vote_status = False app.logger.debug("vote_status: {}".format(vote_status)) self.commit() # db.session.commit() # for the vote count return vote_status def comment_on(self): """ when someone comments on this particular comment """ pass
class User(Document): schema_version = StringField(validate=validate.Length(max=10), default='1') email = EmailField(required=True, unique=True) registered_on = DateTimeField(default=datetime.datetime.utcnow) name = StringField(validate=validate.Length(max=50)) password_hash = StringField(validate=validate.Length(max=100)) roles = ListField(StringField(validate=validate.Length(max=50))) avatar = StringField(validate=validate.Length(max=100)) introduction = StringField(validate=validate.Length(max=500)) organizations = ListField(ReferenceField('Organization'), default=[]) teams = ListField(ReferenceField('Team'), default=[]) region = StringField() class Meta: collection_name = 'users' @property def password(self): raise AttributeError('password: write-only field') @password.setter def password(self, password): self.password_hash = bcrypt.generate_password_hash(password).decode( 'utf-8') def check_password(self, password): return bcrypt.check_password_hash(self.password_hash, password) @staticmethod def encode_auth_token(user_id): """ Generates the Auth Token :return: string """ try: payload = { 'exp': datetime.datetime.utcnow() + datetime.timedelta(days=1, seconds=5), 'iat': datetime.datetime.utcnow(), 'sub': user_id } return jwt.encode(payload, key, algorithm='HS256') except Exception as e: logger.exception(e) return None @staticmethod async def decode_auth_token(auth_token): """ Decodes the auth token :param auth_token: :return: dict|string """ try: payload = jwt.decode(auth_token, key) is_blacklisted_token = await BlacklistToken.check_blacklist( auth_token) if is_blacklisted_token: return 'Token blacklisted. Please log in again.' else: return payload except jwt.ExpiredSignatureError: return 'Signature expired. Please log in again.' except jwt.InvalidTokenError: return 'Invalid token. Please log in again.' def is_collaborator(self): return 'collaborator' in self.roles def is_admin(self): return 'admin' in self.roles def __repr__(self): return "<User '{}'>".format(self.name)
class Package(Document): schema_version = StringField(validate=validate.Length(max=10), default='1') package_type = StringField(required=True) name = StringField(required=True) index_url = URLField(default='http://127.0.0.1:5000/pypi') files = ListField(ReferenceField(PackageFile), default=[]) proprietary = BooleanField(default=True) description = StringField() long_description = StringField() rating = FloatField(default=4) rating_times = IntField(default=1) download_times = IntField(default=0) organization = ReferenceField('Organization') team = ReferenceField('Team') uploader = ReferenceField('User') py_packages = ListField( StringField(), default=[]) # python packages defined by the test package upload_date = DateTimeField() modified = BooleanField(default=False) version_re = re.compile(r"^(?P<name>.+?)(-(?P<ver>\d.+?))-.*$").match class Meta: collection_name = 'packages' async def get_package_by_version(self, version=None): if version is None and len(self.files) > 0: return await self.files[0].fetch() for f in self.files: f = await f.fetch() if f.version == version: return f return None @async_property async def versions(self): return [(await f.fetch()).version for f in self.files] async def sort(self): files = [await f.fetch() for f in self.files] versions = [f.version for f in files] pairs = [(f, v) for f, v in zip(files, versions)] pairs.sort(key=lambda x: parse_version(x[1]), reverse=True) self.files = [f for f, v in pairs] await self.commit() @property def stars(self): return round(self.rating) @property def package_name(self): return self.name.replace('-', '_').replace(' ', '_') @property def latest_version(self): if len(self.files) > 0: return self.files[0].version return None def __hash__(self): return hash(str(self.pk)) def __repr__(self): return self.package_name
class TaskQueue(Document): ''' Per endpoint per priority queue ''' schema_version = StringField(validate=validate.Length(max=10), default='1') priority = IntField(validate=validate.Range(min=QUEUE_PRIORITY_MIN, max=QUEUE_PRIORITY_MAX), default=QUEUE_PRIORITY_DEFAULT) tasks = ListField(ReferenceField('Task'), default=[]) endpoint = ReferenceField('Endpoint') running_task = ReferenceField('Task', allow_none=True, default=missing) rw_lock = BooleanField(default=False) organization = ReferenceField('Organization') team = ReferenceField('Team') to_delete = BooleanField(default=False) class Meta: collection_name = 'task_queues' async def acquire_lock(self): for i in range(LOCK_TIMEOUT): if await self.collection.find_one_and_update( { '_id': self.pk, 'rw_lock': False }, {'$set': { 'rw_lock': True }}): return True await asyncio.sleep(0.1) else: return False async def release_lock(self): await self.collection.find_one_and_update({'_id': self.pk}, {'$set': { 'rw_lock': False }}) async def pop(self): if not await self.acquire_lock(): return None await self.reload() if len(self.tasks) == 0: await self.release_lock() return None task = self.tasks.pop(0) task = await task.fetch() self.running_task = task await self.commit() await self.release_lock() return task async def push(self, task): if not await self.acquire_lock(): raise RuntimeError('failed to acquire queue lock') self.tasks.append(task) await self.commit() await self.release_lock() async def flush(self, cancelled=False): if not await self.acquire_lock(): raise RuntimeError('failed to acquire queue lock') if cancelled: for task in self.tasks: task.status = 'cancelled' await task.commit() self.tasks = [] await self.commit() await self.release_lock() return True