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,
        )
Beispiel #2
0
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)
Beispiel #3
0
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)
Beispiel #4
0
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)
Beispiel #5
0
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)
Beispiel #6
0
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)
Beispiel #7
0
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
Beispiel #8
0
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)
Beispiel #10
0
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
Beispiel #11
0
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)
Beispiel #12
0
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