# -*-coding: utf8 -*- from __future__ import unicode_literals, absolute_import, print_function from django.contrib.auth.models import User from django.db import models from django.conf import settings from gm_types.gaia import ( SOCIAL_FOLLOW_STATUS, ) from social.consts import SUOZHANG_UID from social.utils import social_cache from talos.cache.gaia import follow_cache from talos.libs.datetime_utils import ts_now_as_score from talos.services import UserService class UserFollow(models.Model): class Meta: verbose_name = '用户关注关系' unique_together = ('user', 'follow') app_label = 'social' user = models.ForeignKey(User, related_name="fans", verbose_name="用户") follow = models.ForeignKey(User, verbose_name="关注的用户") bond = models.BooleanField(default=True, verbose_name='关注关系是否还有效') update_time = models.DateTimeField(verbose_name='关注时间', null=True) # 2017.5.31 涨粉策略 is_virtual_fan = models.BooleanField(verbose_name=u'是否是虚拟粉丝', default=False) class _FollowInfoCacheStore(object): _cache = follow_cache def __init__(self, uid): self.uid = uid @staticmethod def get_follow_key(follow_id): return 'follow_' + str(follow_id) def follow(self, follow_id): key = self.get_follow_key(follow_id) self._cache.sadd(key, self.uid) def unfollow(self, follow_id): key = self.get_follow_key(follow_id) self._cache.srem(key, self.uid) def get_count(self): key = self.get_follow_key(self.uid) num = self._cache.scard(key) return num def clear_count(self): key = self.get_follow_key(self.uid) self._cache.delete(key) def get_all_fans(self, follow_id): key = self.get_follow_key(follow_id) ids = self._cache.smembers(key) return [int(fans_id) for fans_id in ids] class _SocialInfoCacheStore(object): """redis cmd wrapper.""" _cache = social_cache # redis client def __init__(self, uid): self.uid = uid # big v mailbox cache self._big_v_cache_key = 'bv:mb:{uid}'.format(uid=uid) # last time check ts for user for big v @property def _big_v_last_check_ts_key(self): return 'bv:mb:cts:{{big_v_uid}}:{uid}'.format(uid=self.uid) def last_check_mailbox_bigv_ts(self, big_v_uid): k = self._big_v_last_check_ts_key.format(big_v_uid=big_v_uid) ts = self._cache.get(k) or 0 return int(ts) def update_last_check_mailbox_bigv_ts(self, big_v_uid): k = self._big_v_last_check_ts_key.format(big_v_uid=big_v_uid) return self._cache.set(k, ts_now_as_score()) def delete_bigv_mailbox_last_check_ts(self, big_v_uid): k = self._big_v_last_check_ts_key.format(big_v_uid=big_v_uid) return self._cache.delete(k) class _FansIterator(object): """iterate through fans list.""" def __init__(self, social_info): self.social_info = social_info self.next_i = 0 self.steps = 10 self._data = [] def _load_more(self): if not self._data: self._data = self.social_info.fans(self.next_i, self.steps) if not self._data: raise StopIteration self.next_i += self.steps def __iter__(self): self._load_more() while self._data: d = self._data.pop() yield d self._load_more() class SocialInfo(object): """user social information.""" @classmethod def social_info(cls, uid): """get instance by uid.""" return cls(uid) def __init__(self, uid): self.uid = uid self._cache_store = _SocialInfoCacheStore(self.uid) self._follow_cache = _FollowInfoCacheStore(self.uid) def unsubscribe_from_bigv_mailbox(self, big_v_uid): return self._cache_store.delete_bigv_mailbox_last_check_ts(big_v_uid) @classmethod def is_big_v(cls, uid): """check if user is a big v. args: uid, should be int NOTE: currently, we only check if the uid is suozhang_uid, we can redefine this method to check if the fans count of this user large than 2k or some other threshold. """ try: uid = int(uid) except: return False return uid == SUOZHANG_UID @property def fans_count(self): # avoid scan table if self.uid == SUOZHANG_UID: return 9999999 return UserFollow.objects.filter(follow_id=self.uid, bond=True).count() @property def following_count(self): return UserFollow.objects.filter(user_id=self.uid, bond=True).count() def get_new_follow_count(self): num = self._follow_cache.get_count() if num >= 99: num = 99 return num def fans(self, start_num=0, count=10): """get fans uid list.""" rels = UserFollow.objects.filter(follow_id=self.uid, bond=True).order_by('-update_time') result = [] for rel in rels[start_num:start_num + count]: result.append(rel.user_id) return result def following(self, start_num=0, count=10): """get following uids.""" rels = UserFollow.objects.filter(user_id=self.uid, bond=True).order_by('-update_time') result = [] for rel in rels[start_num:start_num + count]: result.append(rel.follow_id) return result def is_following_user(self, uid): """check if user following uid.""" rel = UserFollow.objects.using(settings.ZHENGXING_DB).filter(user_id=self.uid, follow_id=uid, bond=True).count() return rel > 0 def is_following_user_for_live(self, uid): """check if user following uid. (由于dbmw数据同步问题,导致无法及时获取关注状态走原始表) """ rel = UserFollow.objects.using(settings.ZHENGXING_DB).filter(user_id=self.uid, follow_id=uid, bond=True).count() return rel > 0 def is_following_users(self, uids): rels = UserFollow.objects.filter(user_id=self.uid, follow_id__in=uids, bond=True) if not rels: return {} result = {rel.follow_id: True for rel in rels} for uid in uids: if uid not in result: result[uid] = False return result def follow_status(self, uid): is_following_user = SOCIAL_FOLLOW_STATUS.FOLLOWING if self.is_following_user( uid) else SOCIAL_FOLLOW_STATUS.STRANGER is_followed_user = SOCIAL_FOLLOW_STATUS.FOLLOWED if self.is_followed_user( uid) else SOCIAL_FOLLOW_STATUS.STRANGER status = is_following_user + is_followed_user return status def is_followed_user(self, uid): rel = UserFollow.objects.filter(user_id=uid, follow_id=self.uid, bond=True).count() return rel > 0 def add_fans(self, uid, is_virtual_fan=False): try: u = User.objects.filter(id=uid).first() if u: rel = UserFollow(user_id=uid, follow_id=self.uid, bond=True, is_virtual_fan=is_virtual_fan) rel.save() except: # logging_exception() pass def del_fans(self, uid): try: rel = UserFollow.objects.get(user_id=uid, follow_id=self.uid) except UserFollow.DoesNotExist: return rel.bond = False rel.save() def get_fans_iterator(self): return _FansIterator(self) @classmethod def add_to_fans(cls, to_uid, uid, is_virtual_fan=False): m = cls(to_uid) return m.add_fans(uid, is_virtual_fan=is_virtual_fan) @classmethod def del_from_fans(cls, from_uid, uid): m = cls(from_uid) return m.del_fans(uid) def follow(self, uid): """add uid into following list.""" if uid == self.uid: return me = UserService.get_user_by_user_id(self.uid) target = UserService.get_user_by_user_id(uid) if not (me and target): return rel, created = UserFollow.objects.get_or_create(user=me, follow=target) self._follow_cache.follow(uid) if (not created) and rel.bond: return elif not created: rel.bond = True rel.save() return SocialInfo.add_to_fans(uid, self.uid) def clear_follow_num(self): self._follow_cache.clear_count() def unfollow(self, uid): """remove uid from following list.""" try: rel = UserFollow.objects.get(user_id=self.uid, follow_id=uid) rel.bond = False rel.save() except UserFollow.DoesNotExist: return SocialInfo.del_from_fans(uid, self.uid) self._follow_cache.unfollow(uid) # delete bigv mailbox check ts if self.is_big_v(uid): self.unsubscribe_from_bigv_mailbox(uid)