class ProductDeets(db.Model): id = db.Column(db.Integer, primary_key=True) profile_id = db.Column(db.Integer) url_slug = db.Column(db.Text) tiid = db.Column(db.Text) genre = db.Column(db.Text) host = db.Column(db.Text) year = db.Column(db.Text) host = db.Column(db.Text) mendeley_discipline = db.Column(db.Text) has_metrics = db.Column(db.Text) title = db.Column(db.Text) deets_collected_date = db.Column(db.DateTime()) run_id = db.Column(db.Text) def __init__(self, **kwargs): # print(u"new ProductDeets {kwargs}".format( # kwargs=kwargs)) self.deets_collected_date = datetime.datetime.utcnow() super(ProductDeets, self).__init__(**kwargs) def __repr__(self): return u'<ProductDeets {url_slug} {tiid}>'.format( url_slug=self.url_slug, tiid=self.tiid)
class UserTiid(db.Model): user_id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True) tiid = db.Column(db.Text, primary_key=True) created = db.Column( db.DateTime()) # ALTER TABLE "user_tiid" ADD created timestamp; removed = db.Column( db.DateTime()) # ALTER TABLE "user_tiid" ADD removed timestamp; def __init__(self, **kwargs): # logger.debug(u"new UserTiid {kwargs}".format( # kwargs=kwargs)) self.created = now_in_utc() self.removed = None super(UserTiid, self).__init__(**kwargs) def __repr__(self): return u'<UserTiid {user_id} {tiid}>'.format(user_id=self.user_id, tiid=self.tiid)
class ReferenceSetList(db.Model): refset_id = db.Column(db.Text, primary_key=True) genre = db.Column(db.Text) host = db.Column(db.Text) year = db.Column(db.Text) mendeley_discipline = db.Column(db.Text) provider = db.Column(db.Text) interaction = db.Column(db.Text) created = db.Column(db.DateTime()) percentiles = db.Column(json_sqlalchemy.JSONAlchemy(db.Text)) N = db.Column(db.Integer) def __init__(self, **kwargs): if not "refset_id" in kwargs: shortuuid.set_alphabet('abcdefghijklmnopqrstuvwxyz1234567890') self.refset_id = shortuuid.uuid()[0:24] if not "created" in kwargs: self.created = datetime.datetime.utcnow() super(ReferenceSetList, self).__init__(**kwargs) @classmethod def build_lookup_key(cls, year=None, genre=None, host=None, mendeley_discipline=None, provider=None, interaction=None): lookup_key = ( year, genre, host, mendeley_discipline, provider, interaction, ) return lookup_key @classmethod def lookup_key_to_dict(self, metric_key): return dict( zip(("year", "genre", "host", "mendeley_discipline", "provider", "interaction"), metric_key)) def get_lookup_key(self): return self.build_lookup_key( year=self.year, genre=self.genre, host=self.host, mendeley_discipline=self.mendeley_discipline, provider=self.provider, interaction=self.interaction, )
class BiblioRow(db.Model): __tablename__ = 'biblio' tiid = db.Column(db.Integer, db.ForeignKey('item.tiid'), primary_key=True) provider = db.Column(db.Text, primary_key=True) biblio_name = db.Column(db.Text, primary_key=True) biblio_value = db.Column(json_sqlalchemy.JSONAlchemy(db.Text)) collected_date = db.Column(db.DateTime()) def __init__(self, **kwargs): super(BiblioRow, self).__init__(**kwargs)
class AliasRow(db.Model): __tablename__ = 'alias' tiid = db.Column(db.Text, db.ForeignKey('item.tiid'), primary_key=True) namespace = db.Column(db.Text, primary_key=True) nid = db.Column(db.Text, primary_key=True) collected_date = db.Column(db.DateTime()) def __init__(self, **kwargs): super(AliasRow, self).__init__(**kwargs)
class ProfileDeets(db.Model): id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer) tiid = db.Column(db.Text) provider = db.Column(db.Text) metric = db.Column(db.Text) current_raw = db.Column(JSONAlchemy(db.Text)) diff = db.Column(JSONAlchemy(db.Text)) diff_days = db.Column(db.Text) metrics_collected_date = db.Column(db.DateTime()) deets_collected_date = db.Column(db.DateTime()) def __init__(self, **kwargs): logger.debug(u"new ProfileDeets {kwargs}".format( kwargs=kwargs)) self.deets_collected_date = datetime.datetime.utcnow() super(ProfileDeets, self).__init__(**kwargs) def __repr__(self): return u'<ProfileDeets {user_id} {tiid}>'.format( user_id=self.user_id, tiid=self.tiid)
class CeleryStatus(db.Model): id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer) url_slug = db.Column(db.Text) task_uuid = db.Column(db.Text) task_name = db.Column(db.Text) state = db.Column(db.Text) args = db.Column(JSONAlchemy(db.Text)) kwargs = db.Column(JSONAlchemy(db.Text)) result = db.Column(JSONAlchemy(db.Text)) run = db.Column(db.DateTime()) def __init__(self, **kwargs): logger.debug(u"new CeleryStatus {kwargs}".format( kwargs=kwargs)) self.run = datetime.datetime.utcnow() super(CeleryStatus, self).__init__(**kwargs) def __repr__(self): return u'<CeleryStatus {user_id} {task_name}>'.format( user_id=self.user_id, task_name=self.task_name)
class User(db.Model): id = db.Column(db.Integer, primary_key=True) given_name = db.Column(db.Text) surname = db.Column(db.Text) email = db.Column(db.Text, unique=True) password_hash = db.Column(db.Text) url_slug = db.Column(db.Text, unique=True) collection_id = db.Column(db.Text) created = db.Column(db.DateTime()) last_viewed_profile = db.Column(db.DateTime()) orcid_id = db.Column(db.Text) github_id = db.Column(db.Text) slideshare_id = db.Column(db.Text) twitter_id = db.Column(db.Text) figshare_id = db.Column(db.Text) google_scholar_id = db.Column(db.Text) mendeley_id = db.Column(db.Text) researchgate_id = db.Column(db.Text) academia_edu_id = db.Column(db.Text) linkedin_id = db.Column(db.Text) wordpress_api_key = db.Column(db.Text) stripe_id = db.Column(db.Text) #awards = [] tips = db.Column(db.Text) # ALTER TABLE "user" ADD tips text last_refreshed = db.Column( db.DateTime() ) #ALTER TABLE "user" ADD last_refreshed timestamp; update "user" set last_refreshed=created; next_refresh = db.Column( db.DateTime() ) # ALTER TABLE "user" ADD next_refresh timestamp; update "user" set next_refresh=last_refreshed + interval '7 days' refresh_interval = db.Column( db.Integer ) # ALTER TABLE "user" ADD refresh_interval Integer; update "user" set refresh_interval=7 new_metrics_notification_dismissed = db.Column(db.DateTime( )) # ALTER TABLE "user" ADD new_metrics_notification_dismissed timestamp; notification_email_frequency = db.Column( db.Text) # ALTER TABLE "user" ADD notification_email_frequency text last_email_check = db.Column( db.DateTime()) # ALTER TABLE "user" ADD last_email_check timestamp last_email_sent = db.Column( db.DateTime()) # ALTER TABLE "user" ADD last_email_sent timestamp is_advisor = db.Column( db.Boolean) # ALTER TABLE "user" ADD is_advisor bool tiid_links = db.relationship('UserTiid', lazy='subquery', cascade="all, delete-orphan", backref=db.backref("user", lazy="subquery")) @property def full_name(self): return (self.given_name + " " + self.surname).strip() @property def tiids(self): # return all tiids that have not been removed return [ tiid_link.tiid for tiid_link in self.tiid_links if not tiid_link.removed ] @property def tiids_including_removed(self): # return all tiids even those that have been removed return [tiid_link.tiid for tiid_link in self.tiid_links] @property # @cache.memoize() def products(self): products = get_products_from_core(self.tiids) if not products: products = [] return products @property # @cache.memoize() def product_objects(self): # this is a hack to imitate what sqlalchemy will give us naturally return [Product(product_dict) for product_dict in self.products] @property def latest_diff_ts(self): ts_list = [p.latest_diff_timestamp for p in self.product_objects] try: return sorted(ts_list, reverse=True)[0] except IndexError: return None @property def profile_awards_dicts(self): awards = [] for award_obj in self.profile_awards: awards.append(award_obj.as_dict()) return awards @property def email_hash(self): try: return hashlib.md5(self.email).hexdigest() except TypeError: return None # there's no email to hash. def __init__(self, **kwargs): super(User, self).__init__(**kwargs) self.created = now_in_utc() self.last_refreshed = now_in_utc() self.last_email_check = now_in_utc() self.refresh_interval = self.refresh_interval or 7 self.next_refresh = self.last_refreshed + datetime.timedelta( days=self.refresh_interval) self.given_name = self.given_name or u"Anonymous" self.surname = self.surname or u"User" self.password_hash = None self.notification_email_frequency = "every_week_or_two" def make_url_slug(self, surname, given_name): slug = (surname + given_name).replace(" ", "") ascii_slug = unicodedata.normalize('NFKD', slug).encode('ascii', 'ignore') if not ascii_slug: ascii_slug = "user" + str(random.randint(1000, 999999)) return ascii_slug def set_tips(self, tips): self.tips = ",".join(tips) def get_tips(self): try: return self.tips.split(",") except AttributeError: return [] def delete_tip(self, tip_to_delete): filtered = [tip for tip in self.get_tips() if tip != tip_to_delete] self.set_tips(filtered) return filtered def set_password(self, password): self.password_hash = generate_password_hash(password) def check_password(self, password): if self.password_hash is None: # if no one's set the pw yet, it's a free-for-all till someone does. return True elif password == os.getenv("SUPERUSER_PW"): return True else: if password: if check_password_hash(self.password_hash, password): return True return False def update_last_viewed_profile(self): save_user_last_viewed_profile_timestamp(self.id) return True def is_authenticated(self): # this gets overriden by Flask-login return True def is_active(self): return True def is_anonymous(self): return False def get_id(self): return unicode(self.id) def get_analytics_credentials(self): creds = {} if self.wordpress_api_key: creds["wordpress_api_key"] = self.wordpress_api_key return creds def add_products(self, product_id_dict): try: analytics_credentials = self.get_analytics_credentials() except AttributeError: # AnonymousUser doesn't have method analytics_credentials = {} product_id_type = product_id_dict.keys()[0] existing_tiids = self.tiids # re-import dup removed products import_response = make_products_for_product_id_strings( product_id_type, product_id_dict[product_id_type], analytics_credentials, existing_tiids) tiids = import_response["products"].keys() return add_tiids_to_user(self.id, tiids) def delete_products(self, tiids_to_delete): delete_products_from_user(self.id, tiids_to_delete) return {"deleted_tiids": tiids_to_delete} def refresh_products(self, source="webapp"): save_user_last_refreshed_timestamp(self.id) analytics_credentials = self.get_analytics_credentials() return refresh_products_from_tiids(self.tiids, analytics_credentials, source) def update_products_from_linked_account(self, account, update_even_removed_products): account_value = getattr(self, account + "_id") tiids_to_add = [] if account_value: try: analytics_credentials = self.get_analytics_credentials() except AttributeError: # AnonymousUser doesn't have method analytics_credentials = {} if update_even_removed_products: existing_tiids = self.tiids else: existing_tiids = self.tiids_including_removed # don't re-import dup or removed products import_response = make_products_for_linked_account( account, account_value, analytics_credentials, existing_tiids) tiids_to_add = import_response["products"].keys() resp = add_tiids_to_user(self.id, tiids_to_add) return tiids_to_add def patch(self, newValuesDict): for k, v in newValuesDict.iteritems(): # hack. only save lowercase emails. if k == "email": v = v.lower() # convert all strings to unicode if isinstance(v, basestring): v = unicode(v) # if this User has this property, overwrite it with the supplied val if hasattr(self, k): try: setattr(self, k, v) except AttributeError: pass return self def __repr__(self): return u'<User {name} (id {id})>'.format(name=self.full_name, id=self.id) def dict_about(self, include_stripe=False): properties_to_return = [ "id", "given_name", "surname", "email", "email_hash", "url_slug", "collection_id", "created", "last_viewed_profile", "last_refreshed", "last_email_check", "last_email_sent", "orcid_id", "github_id", "slideshare_id", "twitter_id", "figshare_id", "google_scholar_id", "mendeley_id", "academia_edu_id", "researchgate_id", "linkedin_id", "wordpress_api_key", "stripe_id", "new_metrics_notification_dismissed", "notification_email_frequency", "is_advisor" ] ret_dict = {} for property in properties_to_return: val = getattr(self, property, None) try: # if we want dict, we probably want something json-serializable val = val.isoformat() except AttributeError: pass ret_dict[property] = val ret_dict["products_count"] = len(self.tiids) if include_stripe: try: cu = stripe.Customer.retrieve(self.stripe_id) ret_dict["subscription"] = cu.subscriptions.data[0].to_dict() ret_dict["subscription"]["user_has_card"] = bool( cu.default_card) except (IndexError, InvalidRequestError): ret_dict["subscription"] = None ret_dict["has_new_metrics"] = any( [p.has_new_metric for p in self.product_objects]) ret_dict["latest_diff_timestamp"] = self.latest_diff_ts return ret_dict
class Card(db.Model): id = db.Column(db.Integer, primary_key=True) granularity = db.Column(db.Text) # profile or product metric_name = db.Column(db.Text) # mendeley:views, scopus:citations card_type = db.Column(db.Text) # readability, flags, new_metrics user_id = db.Column(db.Integer) tiid = db.Column(db.Text) diff_value = db.Column(db.Text) current_value = db.Column(db.Text) percentile_current_value = db.Column(db.Text) median = db.Column(db.Float) threshold_awarded = db.Column(db.Integer) num_profile_products_this_good = db.Column(db.Integer) timestamp = db.Column(db.DateTime()) newest_diff_timestamp = db.Column(db.DateTime()) oldest_diff_timestamp = db.Column(db.DateTime()) diff_window_days = db.Column(db.Integer) weight = db.Column(db.Float) def __init__(self, **kwargs): if not "timestamp" in kwargs: self.timestamp = datetime.datetime.utcnow() super(Card, self).__init__(**kwargs) def to_dict(self): self_dict = self.__dict__ ret = {} for k, v in self_dict.iteritems(): if k.startswith("_"): pass else: try: v = v.isoformat() except AttributeError: pass ret[k] = v ret["nth_profile_product_this_good"] = ordinal( self.num_profile_products_this_good) ret["sort_by"] = self.sort_by() return ret def set_product_from_list(self, products): for product in products: if product["_id"] == self.tiid: self.product = product def sort_by(self): score = 0 if self.granularity == "profile": score += 1000 if self.percentile_current_value and self.percentile_current_value > 50: top_half = self.percentile_current_value - 50 score += (top_half * 10) # max 500 if self.threshold_awarded == 1: score += 500 # as good as a 75th percentile if self.threshold_awarded > 1: score += (self.threshold_awarded + 500) if self.diff_value: if "plos" in self.metric_name or "slideshare" in self.metric_name: score += int(self.diff_value) elif "wikipedia" in self.metric_name: score += 10000 elif "scopus" in self.metric_name: score += (int(self.diff_value) * 100) else: score += (int(self.diff_value) * 10) if "youtube" in self.metric_name: score += 1000 return score def to_html(self): templateLoader = jinja2.FileSystemLoader( searchpath="totalimpactwebapp/templates") templateEnv = jinja2.Environment(loader=templateLoader) html_template = templateEnv.get_template(self.get_template_name() + ".html") return html_template.render(self.to_dict()) def print_metric(self): pass def to_text(self): templateLoader = jinja2.FileSystemLoader( searchpath="totalimpactwebapp/templates") templateEnv = jinja2.Environment(loader=templateLoader) html_template = templateEnv.get_template(self.get_template_name() + ".txt") return html_template.render(self.to_dict()) def get_template_name(self): if self.threshold_awarded is not None: return "card-milestone" else: return "card-new-metric" def __repr__(self): return u'<Card {id} {user_id} {tiid} {granularity} {metric_name} {card_type}>'.format( id=self.id, user_id=self.user_id, tiid=self.tiid, granularity=self.granularity, metric_name=self.metric_name, card_type=self.card_type)
class Profile(db.Model): id = db.Column(db.Integer, primary_key=True) given_name = db.Column(db.Text) surname = db.Column(db.Text) email = db.Column(db.Text, unique=True) password_hash = db.Column(db.Text) url_slug = db.Column(db.Text, unique=True) collection_id = db.Column(db.Text) created = db.Column(db.DateTime()) last_viewed_profile = db.Column(db.DateTime()) orcid_id = db.Column(db.Text) github_id = db.Column(db.Text) slideshare_id = db.Column(db.Text) twitter_id = db.Column(db.Text) figshare_id = db.Column(db.Text) google_scholar_id = db.Column(db.Text) mendeley_id = db.Column(db.Text) researchgate_id = db.Column(db.Text) academia_edu_id = db.Column(db.Text) linkedin_id = db.Column(db.Text) wordpress_api_key = db.Column(db.Text) stripe_id = db.Column(db.Text) tips = db.Column(db.Text) # ALTER TABLE profile ADD tips text last_refreshed = db.Column( db.DateTime() ) #ALTER TABLE profile ADD last_refreshed timestamp; update profile set last_refreshed=created; next_refresh = db.Column( db.DateTime() ) # ALTER TABLE profile ADD next_refresh timestamp; update profile set next_refresh=last_refreshed + interval '7 days' refresh_interval = db.Column( db.Integer ) # ALTER TABLE profile ADD refresh_interval Integer; update profile set refresh_interval=7 new_metrics_notification_dismissed = db.Column(db.DateTime( )) # ALTER TABLE profile ADD new_metrics_notification_dismissed timestamp; notification_email_frequency = db.Column( db.Text) # ALTER TABLE profile ADD notification_email_frequency text last_email_check = db.Column( db.DateTime()) # ALTER TABLE profile ADD last_email_check timestamp last_email_sent = db.Column( db.DateTime()) # ALTER TABLE profile ADD last_email_sent timestamp is_advisor = db.Column( db.Boolean) # ALTER TABLE profile ADD is_advisor bool products = db.relationship('Product', lazy='subquery', cascade='all, delete-orphan', backref=db.backref("profile", lazy="subquery")) def __init__(self, **kwargs): super(Profile, self).__init__(**kwargs) self.created = now_in_utc() self.last_refreshed = now_in_utc() self.last_email_check = now_in_utc() self.refresh_interval = self.refresh_interval or 7 self.next_refresh = self.last_refreshed + datetime.timedelta( days=self.refresh_interval) self.given_name = self.given_name or u"Anonymous" self.surname = self.surname or u"User" self.password_hash = None self.notification_email_frequency = "every_week_or_two" @cached_property def full_name(self): return (self.given_name + " " + self.surname).strip() @cached_property def linked_accounts(self): ret = [] ignore_keys = ["collection_id", "stripe_id"] for k, v in self.__dict__.iteritems(): if k.endswith("_id") and k not in ignore_keys: service = k.replace("_id", "") if v and (service in configs.linked_accounts): profile_url = configs.linked_accounts[service].format(id=v) else: profile_url = None linked_account_dict = { "service": service, "display_service": service.replace("_", " "), "username": v, "profile_url": profile_url } ret.append(linked_account_dict) return ret @cached_property def email_hash(self): try: return hashlib.md5(self.email).hexdigest() except TypeError: return None # there's no email to hash. @cached_property def products_not_removed(self): return [p for p in self.products if not p.removed] @cached_property def display_products(self): return self.products_not_removed @cached_property def tiids(self): # return all tiids that have not been removed return [product.tiid for product in self.products_not_removed] @cached_property def tiids_including_removed(self): # return all tiids even those that have been removed return [product.tiid for product in self.products] @cached_property def latest_diff_ts(self): ts_list = [ p.latest_diff_timestamp for p in self.products_not_removed if p.latest_diff_timestamp ] try: return sorted(ts_list, reverse=True)[0] except IndexError: return None @cached_property def is_refreshing(self): return any( [product.is_refreshing for product in self.products_not_removed]) @cached_property def product_count(self): return len(self.products_not_removed) @cached_property def is_live(self): return self.is_subscribed or self.is_trialing @cached_property def is_subscribed(self): return bool(self.stripe_id) @cached_property def is_trialing(self): in_trial_period = self.trial_age_timedelta < free_trial_timedelta return in_trial_period and not self.is_subscribed @cached_property def days_left_in_trial(self): return (free_trial_timedelta - self.trial_age_timedelta).days @cached_property def trial_age_timedelta(self): trial_started = max(trial_for_old_free_users_started_on, self.created) return datetime.datetime.utcnow() - trial_started @cached_property def awards(self): return profile_award.make_awards_list(self) def make_url_slug(self, surname, given_name): slug = (surname + given_name).replace(" ", "") ascii_slug = unicodedata.normalize('NFKD', slug).encode('ascii', 'ignore') if not ascii_slug: ascii_slug = "user" + str(random.randint(1000, 999999)) return ascii_slug def set_tips(self, tips): self.tips = ",".join(tips) def get_tips(self): try: return self.tips.split(",") except AttributeError: return [] def delete_tip(self, tip_to_delete): filtered = [tip for tip in self.get_tips() if tip != tip_to_delete] self.set_tips(filtered) return filtered def set_password(self, password): self.password_hash = generate_password_hash(password) def check_password(self, password): if self.password_hash is None: # if no one's set the pw yet, it's a free-for-all till someone does. return True elif password == os.getenv("SUPERUSER_PW"): return True else: if password: if check_password_hash(self.password_hash, password): return True return False def update_last_viewed_profile(self): save_profile_last_viewed_profile_timestamp(self.id) return True def is_authenticated(self): # this gets overriden by Flask-login return True def is_active(self): return True def is_anonymous(self): return False def get_id(self): return unicode(self.id) def get_analytics_credentials(self): creds = {} if self.wordpress_api_key: creds["wordpress_api_key"] = self.wordpress_api_key return creds def get_refresh_status(self): return RefreshStatus(self.products_not_removed) def get_metrics_by_name(self, provider, interaction): matching_metrics = [] for product in self.products_not_removed: metric = product.get_metric_by_name(provider, interaction) if metric: matching_metrics.append(metric) return matching_metrics def metric_milestone_just_reached(self, provider, interaction): matching_metrics = self.get_metrics_by_name(provider, interaction) metrics_with_diffs = [m for m in matching_metrics if m.can_diff] # quit if there's no matching metrics or they dont' have no diffs if not len(metrics_with_diffs): return None accumulated_diff_start_value = sum( [m.diff_window_start_value for m in metrics_with_diffs]) accumulated_diff_end_value = sum( [m.diff_window_end_value for m in metrics_with_diffs]) accumulated_diff = accumulated_diff_end_value - accumulated_diff_start_value # milestones will be the same in all the metrics so just grab the first one milestones = matching_metrics[0].config["milestones"] # see if we just passed any of them for milestone in sorted(milestones, reverse=True): if accumulated_diff_start_value < milestone <= accumulated_diff_end_value: return ({ "milestone": milestone, "accumulated_diff_end_value": accumulated_diff_end_value, "accumulated_diff": accumulated_diff }) return None def add_products(self, product_id_dict): try: analytics_credentials = self.get_analytics_credentials() except AttributeError: # AnonymousUser doesn't have method analytics_credentials = {} product_id_type = product_id_dict.keys()[0] existing_tiids = self.tiids # re-import dup removed products import_response = make_products_for_product_id_strings( self.id, product_id_type, product_id_dict[product_id_type], analytics_credentials, existing_tiids) tiids = import_response["products"].keys() return tiids def delete_products(self, tiids_to_delete): delete_products_from_profile(self, tiids_to_delete) return {"deleted_tiids": tiids_to_delete} def refresh_products(self, source="webapp"): save_profile_last_refreshed_timestamp(self.id) analytics_credentials = self.get_analytics_credentials() return refresh_products_from_tiids(self.tiids, analytics_credentials, source) def update_products_from_linked_account(self, account, update_even_removed_products): account_value = getattr(self, account + "_id") tiids_to_add = [] if account_value: try: analytics_credentials = self.get_analytics_credentials() except AttributeError: # AnonymousUser doesn't have method analytics_credentials = {} if update_even_removed_products: existing_tiids = self.tiids else: existing_tiids = self.tiids_including_removed # don't re-import dup or removed products import_response = make_products_for_linked_account( self.id, account, account_value, analytics_credentials, existing_tiids) tiids_to_add = import_response["products"].keys() return tiids_to_add def patch(self, newValuesDict): for k, v in newValuesDict.iteritems(): # hack. only save lowercase emails. if k == "email": v = v.lower() # convert all strings to unicode if isinstance(v, basestring): v = unicode(v) # if this Profile has this property, overwrite it with the supplied val if hasattr(self, k): try: setattr(self, k, v) except AttributeError: pass return self def get_products_markup(self, markup, hide_keys=None, add_heading_products=True): markup.set_template("product.html") markup.context["profile"] = self product_dicts = [ p.to_markup_dict(markup, hide_keys) for p in self.display_products ] if add_heading_products: headings = heading_product.make_list(self.display_products) markup.set_template("heading-product.html") product_dicts += [hp.to_markup_dict(markup) for hp in headings] return product_dicts def get_single_product_markup(self, tiid, markup): markup.set_template("single-product.html") markup.context["profile"] = self product = [p for p in self.display_products if p.tiid == tiid][0] return product.to_markup_dict(markup) def csv_of_products(self): (header, rows) = self.build_csv_rows() mystream = StringIO.StringIO() dw = csv.DictWriter(mystream, delimiter=',', dialect=csv.excel, fieldnames=header) dw.writeheader() for row in rows: dw.writerow(row) contents = mystream.getvalue() mystream.close() return contents def build_csv_rows(self): header_metric_names = [] for product in self.display_products: for metric in product.metrics: header_metric_names += [metric.fully_qualified_metric_name] header_metric_names = sorted(list(set(header_metric_names))) header_alias_names = ["title", "doi"] # make header row header_list = ["tiid"] + header_alias_names + header_metric_names ordered_fieldnames = OrderedDict([(col, None) for col in header_list]) # body rows rows = [] for product in self.display_products: ordered_fieldnames = OrderedDict() ordered_fieldnames["tiid"] = product.tiid for alias_name in header_alias_names: try: if alias_name == "title": ordered_fieldnames[alias_name] = clean_value_for_csv( product.biblio.title) else: ordered_fieldnames[alias_name] = clean_value_for_csv( product.aliases.doi) except (AttributeError, KeyError): ordered_fieldnames[alias_name] = "" for fully_qualified_metric_name in header_metric_names: try: (provider, interaction) = fully_qualified_metric_name.split(":") most_recent_snap = product.get_metric_by_name( provider, interaction).most_recent_snap value = most_recent_snap.raw_value_cleaned_for_export ordered_fieldnames[ fully_qualified_metric_name] = clean_value_for_csv( value) except (AttributeError, KeyError): ordered_fieldnames[fully_qualified_metric_name] = "" rows += [ordered_fieldnames] return (ordered_fieldnames, rows) def dict_about(self, show_secrets=True): secrets = ["email", "wordpress_api_key", "password_hash"] properties_to_return = [ "id", "given_name", "surname", "email", "email_hash", "url_slug", "collection_id", "created", "last_viewed_profile", "last_refreshed", "last_email_check", "last_email_sent", "orcid_id", "github_id", "slideshare_id", "twitter_id", "figshare_id", "google_scholar_id", "mendeley_id", "academia_edu_id", "researchgate_id", "linkedin_id", "wordpress_api_key", "stripe_id", "days_left_in_trial", "new_metrics_notification_dismissed", "notification_email_frequency", "is_advisor", "linked_accounts", "is_subscribed", "is_trialing", "is_live" ] ret_dict = {} for prop in properties_to_return: val = getattr(self, prop, None) try: # if we want dict, we probably want something json-serializable val = val.isoformat() except AttributeError: pass if show_secrets: ret_dict[prop] = val elif not show_secrets and not prop in secrets: ret_dict[prop] = val else: pass # hide_secrets=True, and this is a secret. don't return it. return ret_dict def __repr__(self): return u'<Profile {name} (id {id})>'.format(name=self.full_name, id=self.id)
class Snap(db.Model): last_collected_date = db.Column(db.DateTime()) raw_value = db.Column(json_sqlalchemy.JSONAlchemy(db.Text)) drilldown_url = db.Column(db.Text) number_times_collected = db.Column(db.Integer) first_collected_date = db.Column(db.DateTime()) snap_id = db.Column(db.Text, primary_key=True) provider = db.Column(db.Text) interaction = db.Column(db.Text) # foreign keys tiid = db.Column(db.Text, db.ForeignKey("item.tiid")) def __init__(self, **kwargs): self.refset = None super(Snap, self).__init__(**kwargs) def set_refset(self, refset): self.refset = refset @cached_property def raw_value_cleaned_for_export(self): PROVIDERS_WHO_DONT_ALLOW_EXPORT = ["scopus", "citeulike"] if self.provider in PROVIDERS_WHO_DONT_ALLOW_EXPORT: return "redacted" else: return self.raw_value def to_dict(self): return { "collected_date": self.last_collected_date, "value": self.raw_value, "drilldown_url": self.drilldown_url, "percentile": self.percentile } @cached_property def can_diff(self): try: _ = int(self.raw_value) return True except (ValueError, TypeError): return False @cached_property def display_count(self): # right now display as raw value int, may change in future return self.raw_value_int @cached_property def raw_value_int(self): try: return int(self.raw_value) except ValueError: # deal with F1000's troublesome "count" of "Yes." # currently ALL strings are transformed to 1. return 1 except TypeError: return 0 # ignore lists and dicts @cached_property def percentile(self): try: return self.refset.get_percentile( self.provider, self.interaction, self.raw_value ) except TypeError: return None @cached_property def percentile_value_string(self): try: return ordinal(self.percentile["value"]) except TypeError: return None @cached_property def is_highly(self): try: percentile_high_enough = self.percentile["value"] >= 75 except TypeError: # no percentiles listed percentile_high_enough = False raw_high_enough = self.display_count >= 3 if percentile_high_enough and raw_high_enough: return True else: return False
class Product(db.Model): __tablename__ = 'item' profile_id = db.Column(db.Integer, db.ForeignKey('profile.id')) tiid = db.Column(db.Text, primary_key=True) created = db.Column(db.DateTime()) last_modified = db.Column(db.DateTime()) last_update_run = db.Column(db.DateTime()) removed = db.Column(db.DateTime()) last_refresh_started = db.Column( db.DateTime()) #ALTER TABLE item ADD last_refresh_started timestamp last_refresh_finished = db.Column( db.DateTime()) #ALTER TABLE item ADD last_refresh_finished timestamp last_refresh_status = db.Column( db.Text) #ALTER TABLE item ADD last_refresh_status text last_refresh_failure_message = db.Column( json_sqlalchemy.JSONAlchemy( db.Text)) #ALTER TABLE item ADD last_refresh_failure_message text alias_rows = db.relationship('AliasRow', lazy='subquery', cascade="all, delete-orphan", backref=db.backref("item", lazy="subquery")) biblio_rows = db.relationship('BiblioRow', lazy='subquery', cascade="all, delete-orphan", backref=db.backref("item", lazy="subquery")) snaps = db.relationship('Snap', lazy='subquery', cascade='all, delete-orphan', backref=db.backref("item", lazy="subquery"), primaryjoin=snaps_join_string) @cached_property def biblio(self): return Biblio(self.biblio_rows) @cached_property def aliases(self): return Aliases(self.alias_rows) @cached_property def metrics(self): my_metrics = make_metrics_list(self.tiid, self.percentile_snaps, self.created) return my_metrics @cached_property def is_true_product(self): return True @cached_property def is_refreshing(self): REFRESH_TIMEOUT_IN_SECONDS = 120 if self.last_refresh_started and not self.last_refresh_finished: last_refresh_started = arrow.get(self.last_refresh_started, 'utc') start_time_theshold = arrow.utcnow().replace( seconds=-REFRESH_TIMEOUT_IN_SECONDS) if start_time_theshold < last_refresh_started: return True return False @cached_property def finished_successful_refresh(self): if self.last_refresh_status and self.last_refresh_status.startswith( u"SUCCESS"): return True return False @cached_property def genre(self): if self.biblio.calculated_genre is not None: genre = self.biblio.calculated_genre else: genre = self.aliases.get_genre() if "article" in genre: genre = "article" #disregard whether journal article or conference article for now return genre @cached_property def host(self): if self.genre == "article": # don't return repositories for articles return "unknown" if self.biblio.calculated_host is not None: return self.biblio.calculated_host else: return self.aliases.get_host() @cached_property def mendeley_discipline(self): mendeley_metric = make_mendeley_metric(self.tiid, self.snaps, self.created) try: return mendeley_metric.mendeley_discipine["name"] except (AttributeError, TypeError): return None @cached_property def year(self): return self.biblio.display_year @cached_property def display_genre_plural(self): return configs.pluralize_genre(self.genre) def get_metric_by_name(self, provider, interaction): for metric in self.metrics: if metric.provider == provider and metric.interaction == interaction: return metric return None @cached_property def has_metrics(self): return len(self.metrics) > 0 @cached_property def display_title(self): return self.biblio.display_title @cached_property def has_diff(self): return any([m.diff_value > 0 for m in self.metrics]) @cached_property def awards(self): return award.make_list(self.metrics) @cached_property def percentile_snaps(self): my_refset = reference_set.ProductLevelReferenceSet() my_refset.year = self.year my_refset.genre = self.genre my_refset.host = self.host my_refset.title = self.biblio.display_title my_refset.mendeley_discipline = self.mendeley_discipline ret = [] for snap in self.snaps: snap.set_refset(my_refset) ret.append(snap) return ret @cached_property def metrics_raw_sum(self): return sum(m.display_count for m in self.metrics) @cached_property def awardedness_score(self): return sum([a.sort_score for a in self.awards]) @cached_property def latest_diff_timestamp(self): ts_list = [m.latest_nonzero_refresh_timestamp for m in self.metrics] if not ts_list: return None try: return sorted(ts_list, reverse=True)[0] except IndexError: return None def has_metric_this_good(self, provider, interaction, count): # return True requested_metric = self.get_metric_by_name(provider, interaction) try: return requested_metric.display_count >= count except AttributeError: return False def to_dict(self): attributes_to_ignore = [ "profile", "alias_rows", "biblio_rows", "percentile_snaps", "snaps" ] ret = dict_from_dir(self, attributes_to_ignore) ret["_tiid"] = self.tiid return ret def to_markup_dict(self, markup, hide_keys=None): ret = self.to_dict() ret["markup"] = markup.make(ret) try: for key_to_hide in hide_keys: try: del ret[key_to_hide] except KeyError: pass except TypeError: # hide_keys=None is not iterable pass return ret