class Activity(PkModel): """ Public, real time, conversational """ __tablename__ = 'activities' name = Column(db.Enum('create', 'update', 'star', name="activity_type")) action = Column(db.String(32), nullable=True) # 'external', # 'boost', # 'sync', # 'post', # ... timestamp = Column(db.DateTime, nullable=False, default=dt.datetime.utcnow) content = Column(db.UnicodeText, nullable=True) user_id = reference_col('users', nullable=False) user = relationship('User', backref='activities') project_id = reference_col('projects', nullable=False) project = relationship('Project', backref='activities') project_progress = Column(db.Integer, nullable=True) project_score = Column(db.Integer, nullable=True) resource_id = reference_col('resources', nullable=True) resource = relationship('Resource', backref='activities') @property def data(self): return { 'id': self.id, 'name': self.name, 'time': int(mktime(self.timestamp.timetuple())), 'timesince': timesince(self.timestamp), 'date': self.timestamp, 'content': self.content or '', 'user_name': self.user.username, 'user_id': self.user.id, 'project_id': self.project.id, 'project_name': self.project.name, 'project_score': self.project_score or 0, 'project_phase': getProjectPhase(self.project), 'resource_id': self.resource_id, 'resource_type': getResourceType(self.resource), } def __init__(self, name, user_id, project_id, **kwargs): if name: db.Model.__init__(self, name=name, user_id=user_id, project_id=project_id, **kwargs) def __repr__(self): return '<Activity({name})>'.format(name=self.name)
class Category(SurrogatePK, Model): __tablename__ = 'categories' name = Column(db.String(80), nullable=False) description = Column(db.UnicodeText(), nullable=True) logo_color = Column(db.String(7), nullable=True) logo_icon = Column(db.String(20), nullable=True) # If specific to an event event_id = reference_col('events', nullable=True) event = relationship('Event', backref='categories') @property def project_count(self): if not self.projects: return 0 return len(self.projects) @property def data(self): return { 'id': self.id, 'name': self.name, 'description': self.description, } def __init__(self, name=None, **kwargs): if name: db.Model.__init__(self, name=name, **kwargs) def __repr__(self): return '<Category({name})>'.format(name=self.name)
class Activity(SurrogatePK, Model): __tablename__ = 'activities' name = Column( db.Enum( 'create', 'update', # 'boost', 'star', name="activity_type")) timestamp = Column(db.DateTime, nullable=False, default=dt.datetime.utcnow) user_id = reference_col('users', nullable=False) user = relationship('User', backref='activities') project_id = reference_col('projects', nullable=False) project = relationship('Project', backref='activities') @property def data(self): return { 'id': self.id, 'name': self.name, 'time': int(mktime(self.timestamp.timetuple())), 'date': self.timestamp, 'user_name': self.user.username, 'user_id': self.user.id, 'project_id': self.project.id, 'project_name': self.project.name, 'project_score': self.project.score } def __init__(self, name, user_id, project_id, **kwargs): if name: db.Model.__init__(self, name=name, user_id=user_id, project_id=project_id, **kwargs) def __repr__(self): return '<Activity({name})>'.format(name=self.name)
class Role(SurrogatePK, Model): __tablename__ = 'roles' name = Column(db.String(80), unique=True, nullable=False) user_id = reference_col('users', nullable=True) user = relationship('User', backref='roles') def __init__(self, name, **kwargs): """Create instance.""" db.Model.__init__(self, name=name, **kwargs) def __repr__(self): """Represent instance as a unique string.""" return '<Role({name})>'.format(name=self.name)
class Category(PkModel): """ Is it a bird? Is it a plane? """ __tablename__ = 'categories' name = Column(db.String(80), nullable=False) description = Column(db.UnicodeText(), nullable=True) logo_color = Column(db.String(7), nullable=True) logo_icon = Column(db.String(20), nullable=True) # If specific to an event event_id = reference_col('events', nullable=True) event = relationship('Event', backref='categories') def project_count(self): if not self.projects: return 0 return len(self.projects) @property def data(self): d = { 'id': self.id, 'name': self.name, 'description': self.description, 'logo_color': self.logo_color, 'logo_icon': self.logo_icon, } if self.event: d['event_id'] = self.event_id d['event_name'] = self.event.name d['event_url'] = self.event.url return d def set_from_data(self, data): self.name = data['name'] self.description = data['description'] self.logo_color = data['logo_color'] self.logo_icon = data['logo_icon'] if 'event_name' in data: ename = data['event_name'] evt = Event.query.filter_by(name=ename).first() if evt: self.event = evt def __init__(self, name=None, **kwargs): if name: db.Model.__init__(self, name=name, **kwargs) def __repr__(self): return '<Category({name})>'.format(name=self.name)
class Resource(PkModel): """ Somewhat graph-like in principle """ __tablename__ = 'resources' name = Column(db.String(80), nullable=False) type_id = Column(db.Integer(), nullable=True, default=0) created_at = Column(db.DateTime, nullable=False, default=dt.datetime.utcnow) # At which progress level did it become relevant progress_tip = Column(db.Integer(), nullable=True) # order = Column(db.Integer, nullable=True) source_url = Column(db.String(2048), nullable=True) is_visible = Column(db.Boolean(), default=True) # This is the text content of a comment or description content = Column(db.UnicodeText, nullable=True) # JSON blob of externally fetched structured content sync_content = Column(db.UnicodeText, nullable=True) # The project this is referenced in project_id = reference_col('projects', nullable=True) project = relationship('Project', backref='components') @property def of_type(self): return getResourceType(self) @property def data(self): return { 'id': self.id, 'date': self.created_at, 'name': self.name, 'of_type': self.type_id, 'url': self.source_url or '', 'content': self.content or '', 'project_id': self.project_id, # 'project_name': self.project.name } def __init__(self, name, project_id, **kwargs): db.Model.__init__( self, name=name, project_id=project_id, **kwargs ) def __repr__(self): return '<Resource({name})>'.format(name=self.name)
class Project(SurrogatePK, Model): __tablename__ = 'projects' name = Column(db.String(80), unique=True, nullable=False) summary = Column(db.String(120), nullable=True) image_url = Column(db.String(255), nullable=True) source_url = Column(db.String(255), nullable=True) webpage_url = Column(db.String(2048), nullable=True) is_webembed = Column(db.Boolean(), default=False) contact_url = Column(db.String(255), nullable=True) autotext_url = Column(db.String(255), nullable=True) is_autoupdate = Column(db.Boolean(), default=True) autotext = Column(db.UnicodeText(), nullable=True, default=u"") longtext = Column(db.UnicodeText(), nullable=False, default=u"") hashtag = Column(db.String(40), nullable=True) logo_color = Column(db.String(7), nullable=True) logo_icon = Column(db.String(40), nullable=True) created_at = Column(db.DateTime, nullable=False, default=dt.datetime.utcnow) updated_at = Column(db.DateTime, nullable=False, default=dt.datetime.utcnow) is_hidden = Column(db.Boolean(), default=False) # User who created the project user_id = reference_col('users', nullable=True) user = relationship('User', backref='projects') # Event under which this project belongs event_id = reference_col('events', nullable=True) event = relationship('Event', backref='projects') # And the optional event category category_id = reference_col('categories', nullable=True) category = relationship('Category', backref='projects') # Self-assessment and total score progress = Column(db.Integer(), nullable=True, default=-1) score = Column(db.Integer(), nullable=True, default=0) # Convenience query for latest activity def latest_activity(self): return Activity.query.filter_by(project_id=self.id).order_by( Activity.timestamp.desc()).limit(5) # Convenience query for all categories def categories_all(self, event=None): if self.event: return self.event.categories_for_event() if event is not None: return event.categories_for_event() return Category.query.order_by('name') # Self-assessment (progress) @property def phase(self): if self.progress is None: return "" return PROJECT_PROGRESS_PHASE[self.progress] @property def is_challenge(self): return self.progress < 0 @property def webembed(self): return format_webembed(self.webpage_url) @property def url(self): return "project/%d" % (self.id) @property def data(self): d = { 'id': self.id, 'name': self.name, 'score': self.score, 'phase': self.phase, 'summary': self.summary, 'hashtag': self.hashtag, 'contact_url': self.contact_url, 'image_url': self.image_url, } if self.category is not None: d['category'] = self.category.data return d def get_schema(self, host_url=''): return { "@type": "CreativeWork", "name": self.name, "description": re.sub('<[^>]*>', '', self.summary), "dateCreated": format_date(self.created_at, '%Y-%m-%dT%H:%M'), "dateUpdated": format_date(self.updated_at, '%Y-%m-%dT%H:%M'), "discussionUrl": self.contact_url, "image": self.image_url, "license": self.event.license, "url": host_url + self.url } def update(self): # Correct fields if self.category_id == -1: self.category_id = None if self.logo_icon.startswith('fa-'): self.logo_icon = self.logo_icon.replace('fa-', '') if self.logo_color == '#000000': self.logo_color = '' # Check update status self.is_autoupdate = bool(self.autotext_url and self.autotext_url.strip()) if not self.is_autoupdate: self.autotext = '' # Set the timestamp self.updated_at = dt.datetime.utcnow() if self.is_challenge: self.score = 0 else: # Calculate score based on base progress score = self.progress or 0 cqu = Activity.query.filter_by(project_id=self.id) c_s = cqu.filter_by(name="star").count() score = score + (2 * c_s) # c_a = cqu.filter_by(name="boost").count() # score = score + (10 * c_a) if self.summary is None: self.summary = '' if len(self.summary) > 3: score = score + 3 if self.image_url is None: self.image_url = '' if len(self.image_url) > 3: score = score + 3 if self.source_url is None: self.source_url = '' if len(self.source_url) > 3: score = score + 10 if self.webpage_url is None: self.webpage_url = '' if len(self.webpage_url) > 3: score = score + 10 if self.logo_color is None: self.logo_color = '' if len(self.logo_color) > 3: score = score + 1 if self.logo_icon is None: self.logo_icon = '' if len(self.logo_icon) > 3: score = score + 1 if self.longtext is None: self.longtext = '' if len(self.longtext) > 3: score = score + 1 if len(self.longtext) > 100: score = score + 4 if len(self.longtext) > 500: score = score + 10 if self.autotext is not None: if len(self.autotext) > 3: score = score + 1 if len(self.autotext) > 100: score = score + 4 if len(self.autotext) > 500: score = score + 10 self.score = score def __init__(self, name=None, **kwargs): if name: db.Model.__init__(self, name=name, **kwargs) def __repr__(self): return '<Project({name})>'.format(name=self.name)
class Activity(PkModel): """ Public, real time, conversational """ __tablename__ = 'activities' name = Column(db.Enum('review', 'boost', 'create', 'update', 'star', name="activity_type")) action = Column(db.String(32), nullable=True) # 'external', 'commit', 'sync', 'post', ... timestamp = Column(db.DateTime, nullable=False, default=dt.datetime.utcnow) content = Column(db.UnicodeText, nullable=True) ref_url = Column(db.String(2048), nullable=True) user_id = reference_col('users', nullable=True) user = relationship('User', backref='activities') project_id = reference_col('projects', nullable=True) project = relationship('Project', backref='activities') project_progress = Column(db.Integer, nullable=True) project_version = Column(db.Integer, nullable=True) project_score = Column(db.Integer, nullable=True) @property def data(self): localtime = current_app.tz.localize(self.timestamp) a = { 'id': self.id, 'time': int(mktime(self.timestamp.timetuple())), 'date': format_date(localtime, '%Y-%m-%dT%H:%M'), 'timesince': timesince(localtime), 'name': self.name, 'action': self.action or '', 'content': self.content or '', 'ref_url': self.ref_url or '', } if self.user: a['user_id'] = self.user.id a['user_name'] = self.user.username if self.project: a['project_id'] = self.project.id a['project_name'] = self.project.name a['project_score'] = self.project_score or 0 a['project_phase'] = getProjectPhase(self.project) return a def set_from_data(self, data): self.name = data['name'] self.action = data['action'] self.content = data['content'] self.ref_url = data['ref_url'] self.timestamp = dt.datetime.fromtimestamp(data['time']) if 'user_name' in data: uname = data['user_name'] user = User.query.filter_by(username=uname).first() if user: self.user = user def __init__(self, name, project_id, **kwargs): if name: db.Model.__init__( self, name=name, project_id=project_id, **kwargs ) def __repr__(self): return '<Activity({name})>'.format(name=self.name)
class Project(PkModel): """ You know, for kids! """ __versioned__ = {} __tablename__ = 'projects' name = Column(db.String(80), unique=True, nullable=False) summary = Column(db.String(140), nullable=True) hashtag = Column(db.String(40), nullable=True) image_url = Column(db.String(2048), nullable=True) source_url = Column(db.String(2048), nullable=True) webpage_url = Column(db.String(2048), nullable=True) contact_url = Column(db.String(2048), nullable=True) autotext_url = Column(db.String(2048), nullable=True) download_url = Column(db.String(2048), nullable=True) is_hidden = Column(db.Boolean(), default=False) is_webembed = Column(db.Boolean(), default=False) # remotely managed (by bot) is_autoupdate = Column(db.Boolean(), default=True) autotext = Column(db.UnicodeText(), nullable=True, default=u"") longtext = Column(db.UnicodeText(), nullable=False, default=u"") logo_color = Column(db.String(7), nullable=True) logo_icon = Column(db.String(40), nullable=True) # currently not used created_at = Column(db.DateTime, nullable=False, default=dt.datetime.utcnow) updated_at = Column(db.DateTime, nullable=False, default=dt.datetime.utcnow) # User who created the project user_id = reference_col('users', nullable=True) user = relationship('User', backref='projects') # Event under which this project belongs event_id = reference_col('events', nullable=True) event = relationship('Event', backref='projects') # And the optional event category category_id = reference_col('categories', nullable=True) category = relationship('Category', backref='projects') # Self-assessment and total score progress = Column(db.Integer(), nullable=True, default=-1) score = Column(db.Integer(), nullable=True, default=0) def latest_activity(self, max=5): """ Convenience query for latest activity """ q = Activity.query.filter_by(project_id=self.id) q = q.order_by(Activity.timestamp.desc()) return q.limit(max) def get_team(self): """ Return all starring users (A team) """ activities = Activity.query.filter_by( name='star', project_id=self.id ).all() members = [] for a in activities: if a.user and a.user not in members and a.user.active: members.append(a.user) return members def get_missing_roles(self): get_roles = Role.query.order_by('name') rollcall = [] for p in self.get_team(): for r in p.roles: if r not in rollcall: rollcall.append(r) return [r for r in get_roles if r not in rollcall and r.name] @property def team(self): """ Array of project team """ return [u.username for u in self.get_team()] def all_dribs(self): """ Query which formats the project's timeline """ activities = Activity.query.filter_by( project_id=self.id ).order_by(Activity.timestamp.desc()) dribs = [] prev = None only_active = False # show dribs from inactive users for a in activities: a_parsed = getActivityByType(a, only_active) if a_parsed is None: continue (author, title, text, icon) = a_parsed if prev is not None: # Skip repeat signals if prev['title'] == title and prev['text'] == text: # if prev['date']-a.timestamp).total_seconds() < 120: continue # Show changes in progress if prev['progress'] != a.project_progress: projectStage = getStageByProgress(a.project_progress) if projectStage is not None: dribs.append({ 'title': projectStage['phase'], 'date': a.timestamp, 'icon': 'arrow-up', 'name': 'progress', }) prev = { 'icon': icon, 'title': title, 'text': text, 'author': author, 'name': a.name, 'date': a.timestamp, 'ref_url': a.ref_url, 'progress': a.project_progress, 'id': a.id, } dribs.append(prev) if self.event.has_started or self.event.has_finished: dribs.append({ 'title': "Event started", 'date': self.event.starts_at, 'icon': 'calendar', 'name': 'start', }) if self.event.has_finished: dribs.append({ 'title': "Event finished", 'date': self.event.ends_at, 'icon': 'bullhorn', 'name': 'finish', }) return sorted(dribs, key=lambda x: x['date'], reverse=True) def categories_all(self, event=None): """ Convenience query for all categories """ if self.event: return self.event.categories_for_event() if event is not None: return event.categories_for_event() return Category.query.order_by('name') @property def stage(self): """ Assessment of progress stage with full data """ return getStageByProgress(self.progress) @property def phase(self): """ Assessment of progress as phase name """ return getProjectPhase(self) @property def is_challenge(self): """ True if this project is in challenge phase """ if self.progress is None: return False return self.progress <= PR_CHALLENGE @property def is_autoupdateable(self): """ True if this project can be autoupdated """ return self.autotext_url and self.autotext_url.strip() @property def webembed(self): """ Detect and return supported embed widgets """ return format_webembed(self.webpage_url) @property def longhtml(self): """ Process project longtext and return HTML """ if not self.longtext or len(self.longtext) < 3: return self.longtext # TODO: apply onebox filter # TODO: apply markdown filter return self.longtext @property def url(self): """ Returns local server URL """ return "project/%d" % (self.id) @property def data(self): d = { 'id': self.id, 'url': self.url, 'name': self.name, 'team': self.team, 'score': self.score, 'phase': self.phase, 'is_challenge': self.is_challenge, 'progress': self.progress, 'summary': self.summary or '', 'hashtag': self.hashtag or '', 'image_url': self.image_url or '', 'source_url': self.source_url or '', 'webpage_url': self.webpage_url or '', 'autotext_url': self.autotext_url or '', 'download_url': self.download_url or '', 'contact_url': self.contact_url or '', 'logo_color': self.logo_color or '', 'logo_icon': self.logo_icon or '', 'excerpt': '', } d['created_at'] = format_date(self.created_at, '%Y-%m-%dT%H:%M') d['updated_at'] = format_date(self.updated_at, '%Y-%m-%dT%H:%M') # Generate excerpt based on summary data if self.longtext and len(self.longtext) > 10: d['excerpt'] = self.longtext[:MAX_EXCERPT_LENGTH] if len(self.longtext) > MAX_EXCERPT_LENGTH: d['excerpt'] += '...' elif self.is_autoupdateable: if self.autotext and len(self.autotext) > 10: d['excerpt'] = self.autotext[:MAX_EXCERPT_LENGTH] if len(self.autotext) > MAX_EXCERPT_LENGTH: d['excerpt'] += '...' if self.user is not None: d['maintainer'] = self.user.username if self.event is not None: d['event_url'] = self.event.url d['event_name'] = self.event.name if self.category is not None: d['category_id'] = self.category.id d['category_name'] = self.category.name return d def get_schema(self, host_url=''): """ Schema.org compatible metadata """ # TODO: accurately detect project license based on component etc. if not self.event.community_embed: content_license = '' elif "creativecommons" in self.event.community_embed: content_license = "https://creativecommons.org/licenses/by/4.0/" else: content_license = '' cleansummary = None if self.summary: cleansummary = re.sub('<[^>]*>', '', self.summary) return { "@type": "CreativeWork", "name": self.name, "description": cleansummary, "dateCreated": format_date(self.created_at, '%Y-%m-%dT%H:%M'), "dateUpdated": format_date(self.updated_at, '%Y-%m-%dT%H:%M'), "discussionUrl": self.contact_url, "image": self.image_url, "license": content_license, "url": host_url + self.url } def set_from_data(self, data): self.name = data['name'] self.summary = data['summary'] self.hashtag = data['hashtag'] self.score = int(data['score']) self.progress = int(data['progress']) self.image_url = data['image_url'] self.source_url = data['source_url'] self.webpage_url = data['webpage_url'] self.autotext_url = data['autotext_url'] self.download_url = data['download_url'] self.contact_url = data['contact_url'] self.logo_color = data['logo_color'] self.logo_icon = data['logo_icon'] self.created_at = parse(data['created_at']) self.updated_at = parse(data['updated_at']) self.longtext = data['longtext'] self.autotext = data['autotext'] if 'is_autoupdate' in data: self.is_autoupdate = data['is_autoupdate'] if 'is_webembed' in data: self.is_webembed = data['is_webembed'] if 'maintainer' in data: uname = data['maintainer'] user = User.query.filter_by(username=uname).first() if user: self.user = user if 'category_name' in data: cname = data['category_name'] category = Category.query.filter_by(name=cname).first() if category: self.category = category def update(self): """ Process data submission """ # Correct fields if self.category_id == -1: self.category_id = None if self.logo_icon and self.logo_icon.startswith('fa-'): self.logo_icon = self.logo_icon.replace('fa-', '') if self.logo_color == '#000000': self.logo_color = '' # Set the timestamp self.updated_at = dt.datetime.utcnow() self.update_null_fields() self.score = self.calculate_score() def update_null_fields(self): if self.summary is None: self.summary = '' if self.image_url is None: self.image_url = '' if self.source_url is None: self.source_url = '' if self.webpage_url is None: self.webpage_url = '' if self.logo_color is None: self.logo_color = '' if self.logo_icon is None: self.logo_icon = '' if self.longtext is None: self.longtext = '' if self.autotext is None: self.autotext = '' def calculate_score(self): """ Calculate score of a project based on base progress """ if self.is_challenge: return 0 score = self.progress or 0 cqu = Activity.query.filter_by(project_id=self.id) c_s = cqu.count() # Get a point for every (join, update, ..) activity in dribs score = score + (1 * c_s) # Triple the score for every boost (upvote) # c_a = cqu.filter_by(name="boost").count() # score = score + (2 * c_a) # Add to the score for every complete documentation field if len(self.summary) > 3: score = score + 1 if len(self.image_url) > 3: score = score + 1 if len(self.source_url) > 3: score = score + 1 if len(self.webpage_url) > 3: score = score + 1 if len(self.logo_color) > 3: score = score + 1 if len(self.logo_icon) > 3: score = score + 1 # Get more points based on how much content you share if len(self.longtext) > 3: score = score + 1 if len(self.longtext) > 100: score = score + 3 if len(self.longtext) > 500: score = score + 5 # Points for external (Readme) content if len(self.autotext) > 3: score = score + 1 if len(self.autotext) > 100: score = score + 3 if len(self.autotext) > 500: score = score + 5 # Cap at 100% score = min(score, 100) return score def __init__(self, name=None, **kwargs): if name: db.Model.__init__(self, name=name, **kwargs) def __repr__(self): return '<Project({name})>'.format(name=self.name)
class Resource(PkModel): """ The kitchen larder """ __tablename__ = 'resources' name = Column(db.String(80), unique=True, nullable=False) type_id = Column(db.Integer(), nullable=True) created_at = Column(db.DateTime, nullable=False, default=dt.datetime.utcnow) is_visible = Column(db.Boolean(), default=False) progress_tip = Column(db.Integer(), nullable=True) source_url = Column(db.String(2048), nullable=True) download_url = Column(db.String(2048), nullable=True) summary = Column(db.String(140), nullable=True) sync_content = Column(db.UnicodeText(), nullable=True) content = Column(db.UnicodeText(), nullable=True) user_id = reference_col('users', nullable=True) user = relationship('User', backref='resources') @property def of_type(self): return getResourceType(self) @property def since(self): return timesince(self.created_at) @property def icon(self): if self.type_id >= 300: # tool return 'cloud' elif self.type_id >= 200: # code return 'gear' elif self.type_id >= 100: # data return 'cube' else: return 'leaf' def get_comments(self, max=5): return Activity.query.filter_by(resource_id=self.id).order_by( Activity.id.desc()).limit(max) def count_mentions(self): return Activity.query.filter_by(resource_id=self.id).group_by( Activity.id).count() def sync(self): """ Synchronize supported resources """ SyncResourceData(self) @property def data(self): return { 'id': self.id, 'name': self.name, 'since': self.since, 'type': self.of_type, 'url': self.source_url, # 'content': self.content, 'summary': self.summary, 'count': self.count_mentions() } def __init__(self, name=None, **kwargs): if name: db.Model.__init__(self, name=name, **kwargs) def __repr__(self): return '<Resource({name})>'.format(name=self.name)
class Project(PkModel): """ You know, for kids! """ __tablename__ = 'projects' name = Column(db.String(80), unique=True, nullable=False) summary = Column(db.String(120), nullable=True) image_url = Column(db.String(255), nullable=True) source_url = Column(db.String(255), nullable=True) webpage_url = Column(db.String(2048), nullable=True) is_webembed = Column(db.Boolean(), default=False) contact_url = Column(db.String(255), nullable=True) autotext_url = Column(db.String(255), nullable=True) is_autoupdate = Column(db.Boolean(), default=True) autotext = Column(db.UnicodeText(), nullable=True, default=u"") longtext = Column(db.UnicodeText(), nullable=False, default=u"") hashtag = Column(db.String(40), nullable=True) logo_color = Column(db.String(7), nullable=True) logo_icon = Column(db.String(40), nullable=True) created_at = Column(db.DateTime, nullable=False, default=dt.datetime.utcnow) updated_at = Column(db.DateTime, nullable=False, default=dt.datetime.utcnow) is_hidden = Column(db.Boolean(), default=False) # User who created the project user_id = reference_col('users', nullable=True) user = relationship('User', backref='projects') # Event under which this project belongs event_id = reference_col('events', nullable=True) event = relationship('Event', backref='projects') # And the optional event category category_id = reference_col('categories', nullable=True) category = relationship('Category', backref='projects') # Self-assessment and total score progress = Column(db.Integer(), nullable=True, default=-1) score = Column(db.Integer(), nullable=True, default=0) # Convenience query for latest activity def latest_activity(self, max=5): return Activity.query.filter_by(project_id=self.id).order_by( Activity.timestamp.desc()).limit(max) # Return all starring users (A team) def team(self): activities = Activity.query.filter_by(name='star', project_id=self.id).all() members = [] for a in activities: if not a.user in members and a.user.active: members.append(a.user) return members # Query which formats the project's timeline def all_signals(self): activities = Activity.query.filter_by(project_id=self.id).order_by( Activity.timestamp.desc()) signals = [] prev = None for a in activities: title = text = None author = a.user.username if a.action == 'sync': title = "Synchronized" text = "Readme fetched from source" elif a.action == 'post' and a.content is not None: title = "" text = a.content elif a.name == 'star': title = "Team forming" text = a.user.username + " has joined!" author = "" elif a.name == 'update': title = "" text = "Worked on documentation" elif a.name == 'create': title = "Project started" text = "Initialized by %s 🎉" % a.user.username author = "" else: continue # Check if user is still active if not a.user.active: continue # Check if last signal very similar if prev is not None: if (prev['title'] == title and prev['text'] == text # and (prev['date']-a.timestamp).total_seconds() < 120 ): continue prev = { 'title': title, 'text': text, 'author': author, 'date': a.timestamp, 'resource': a.resource, } signals.append(prev) if self.event.has_started or self.event.has_finished: signals.append({ 'title': "Hackathon started", 'date': self.event.starts_at }) if self.event.has_finished: signals.append({ 'title': "Hackathon finished", 'date': self.event.ends_at }) return sorted(signals, key=lambda x: x['date'], reverse=True) # Convenience query for all categories def categories_all(self, event=None): if self.event: return self.event.categories_for_event() if event is not None: return event.categories_for_event() return Category.query.order_by('name') # Self-assessment (progress) @property def phase(self): return getProjectPhase(self) @property def is_challenge(self): if self.progress is None: return False return self.progress < 0 @property def webembed(self): """ Detect and return supported embed widgets """ return format_webembed(self.webpage_url) @property def longhtml(self): """ Process project longtext and return HTML """ if not self.longtext or len(self.longtext) < 3: return self.longtext # TODO: apply onebox filter # TODO: apply markdown filter return self.longtext @property def url(self): """ Returns local server URL """ return "project/%d" % (self.id) @property def data(self): d = { 'id': self.id, 'url': self.url, 'name': self.name, 'score': self.score, 'phase': self.phase, 'progress': self.progress, 'summary': self.summary or '', 'hashtag': self.hashtag or '', 'image_url': self.image_url or '', 'source_url': self.source_url or '', 'webpage_url': self.webpage_url or '', 'contact_url': self.contact_url or '', 'logo_color': self.logo_color or '', } if self.user is not None: d['maintainer'] = self.user.username if self.event is not None: d['event_url'] = self.event.url d['event_name'] = self.event.name if self.category is not None: d['category_id'] = self.category.id d['category_name'] = self.category.name return d def get_schema(self, host_url=''): """ Schema.org compatible metadata """ return { "@type": "CreativeWork", "name": self.name, "description": re.sub('<[^>]*>', '', self.summary), "dateCreated": format_date(self.created_at, '%Y-%m-%dT%H:%M'), "dateUpdated": format_date(self.updated_at, '%Y-%m-%dT%H:%M'), "discussionUrl": self.contact_url, "image": self.image_url, "license": self.event.license, "url": host_url + self.url } def update(self): """ Process data submission """ # Correct fields if self.category_id == -1: self.category_id = None if self.logo_icon and self.logo_icon.startswith('fa-'): self.logo_icon = self.logo_icon.replace('fa-', '') if self.logo_color == '#000000': self.logo_color = '' # Check update status self.is_autoupdate = bool(self.autotext_url and self.autotext_url.strip()) if not self.is_autoupdate: self.autotext = '' # Set the timestamp self.updated_at = dt.datetime.utcnow() if self.is_challenge: self.score = 0 else: # Calculate score based on base progress score = self.progress or 0 cqu = Activity.query.filter_by(project_id=self.id) c_s = cqu.count() # Get a point for every (join, update, ..) activity in the project's signals score = score + (1 * c_s) # Triple the score for every boost (upvote) # c_a = cqu.filter_by(name="boost").count() # score = score + (2 * c_a) # Add to the score for every complete documentation field if self.summary is None: self.summary = '' if len(self.summary) > 3: score = score + 3 if self.image_url is None: self.image_url = '' if len(self.image_url) > 3: score = score + 3 if self.source_url is None: self.source_url = '' if len(self.source_url) > 3: score = score + 3 if self.webpage_url is None: self.webpage_url = '' if len(self.webpage_url) > 3: score = score + 3 if self.logo_color is None: self.logo_color = '' if len(self.logo_color) > 3: score = score + 3 if self.logo_icon is None: self.logo_icon = '' if len(self.logo_icon) > 3: score = score + 3 if self.longtext is None: self.longtext = '' # Get more points based on how much content you share if len(self.longtext) > 3: score = score + 1 if len(self.longtext) > 100: score = score + 4 if len(self.longtext) > 500: score = score + 10 # Points for external (Readme) content if self.autotext is not None: if len(self.autotext) > 3: score = score + 1 if len(self.autotext) > 100: score = score + 4 if len(self.autotext) > 500: score = score + 10 self.score = score def __init__(self, name=None, **kwargs): if name: db.Model.__init__(self, name=name, **kwargs) def __repr__(self): return '<Project({name})>'.format(name=self.name)
class Project(SurrogatePK, Model): __tablename__ = 'projects' name = Column(db.String(80), unique=True, nullable=False) summary = Column(db.String(120), nullable=True) image_url = Column(db.String(255), nullable=True) source_url = Column(db.String(255), nullable=True) webpage_url = Column(db.String(255), nullable=True) contact_url = Column(db.String(255), nullable=True) autotext_url = Column(db.String(255), nullable=True) is_autoupdate = Column(db.Boolean(), default=True) logo_color = Column(db.String(7), nullable=True) logo_icon = Column(db.String(40), nullable=True) longtext = Column(db.UnicodeText(), nullable=False, default=u"") hashtag = Column(db.String(40), nullable=True) created_at = Column(db.DateTime, nullable=False, default=dt.datetime.utcnow) updated_at = Column(db.DateTime, nullable=False, default=dt.datetime.utcnow) is_hidden = Column(db.Boolean(), default=False) # User who created the project user_id = reference_col('users', nullable=True) user = relationship('User', backref='projects') # Event under which this project belongs event_id = reference_col('events', nullable=True) event = relationship('Event', backref='projects') # And the optional event category category_id = reference_col('categories', nullable=True) category = relationship('Category', backref='projects') def categories_all(self): return Category.query.order_by('name') def categories_for_event(self, event_id): return Category.query.filter(or_( Category.event_id==None, Category.event_id==event_id )).order_by('name') # Self-assessment progress = Column(db.Integer(), nullable=True, default=0) @property def phase(self): if self.progress is None: return "" return PROJECT_PROGRESS_PHASE[self.progress] @property def is_challenge(self): return self.progress < 0 # Current tally score = Column(db.Integer(), nullable=True, default=0) @property def data(self): d = { 'id': self.id, 'name': self.name, 'score': self.score, 'phase': self.phase, 'summary': self.summary, 'hashtag': self.hashtag, 'contact_url': self.contact_url, 'image_url': self.image_url, } if self.category is not None: d['category_id'] = self.category.id d['category_name'] = self.category.name return d def update(self): # Correct fields if self.category_id == -1: self.category_id = None if self.logo_icon.startswith('fa-'): self.logo_icon = self.logo_icon.replace('fa-', '') if self.logo_color == '#000000': self.logo_color = '' # Set the timestamp self.updated_at = dt.datetime.utcnow() if self.is_challenge: self.score = 0 else: # Calculate score based on base progress score = self.progress or 0 cqu = Activity.query.filter_by(project_id=self.id) c_s = cqu.filter_by(name="star").count() score = score + (2 * c_s) # c_a = cqu.filter_by(name="boost").count() # score = score + (10 * c_a) if self.summary is None: self.summary = '' if len(self.summary) > 3: score = score + 3 if self.image_url is None: self.image_url = '' if len(self.image_url) > 3: score = score + 3 if self.source_url is None: self.source_url = '' if len(self.source_url) > 3: score = score + 10 if self.webpage_url is None: self.webpage_url = '' if len(self.webpage_url) > 3: score = score + 10 if self.logo_color is None: self.logo_color = '' if len(self.logo_color) > 3: score = score + 1 if self.logo_icon is None: self.logo_icon = '' if len(self.logo_icon) > 3: score = score + 1 if self.longtext is None: self.longtext = '' if len(self.longtext) > 3: score = score + 1 if len(self.longtext) > 100: score = score + 4 if len(self.longtext) > 500: score = score + 10 self.score = score def __init__(self, name=None, **kwargs): if name: db.Model.__init__(self, name=name, **kwargs) def __repr__(self): return '<Event({name})>'.format(name=self.name)