def promoted_comments(user, earliest_timestamp_cutoff=None, comments_to_skip=set()): following_ids = user.redis.following.smembers() earliest_timestamp_cutoff = _tighten_earliest_timestamp_cutoff(earliest_timestamp_cutoff) # Get top sticker and earliest sticker per comment. promotions = [] for comment_id, group in _promotion_stickers_iterator(following_ids): if comment_id in comments_to_skip: continue top, _ = max(group, key=lambda e: stickers.get(e[0]['sticker_type_id']).cost) _, first_ts = min(group, key=lambda e: e[1]) if earliest_timestamp_cutoff is not None and float(first_ts) > float(earliest_timestamp_cutoff): continue promotions.append({ 'type': 'promotion', 'comment_id': comment_id, 'ts': first_ts, 'sticker_type_id': top['sticker_type_id'], 'username': top['username'], }) return promotions
def promoted_comments(user, earliest_timestamp_cutoff=None, comments_to_skip=set()): following_ids = user.redis.following.smembers() earliest_timestamp_cutoff = _tighten_earliest_timestamp_cutoff( earliest_timestamp_cutoff) # Get top sticker and earliest sticker per comment. promotions = [] for comment_id, group in _promotion_stickers_iterator(following_ids): if comment_id in comments_to_skip: continue top, _ = max(group, key=lambda e: stickers.get(e[0]['sticker_type_id']).cost) _, first_ts = min(group, key=lambda e: e[1]) if earliest_timestamp_cutoff is not None and float(first_ts) > float( earliest_timestamp_cutoff): continue promotions.append({ 'type': 'promotion', 'comment_id': comment_id, 'ts': first_ts, 'sticker_type_id': top['sticker_type_id'], 'username': top['username'], }) return promotions
def handle(self, *args, **options): purchased = defaultdict(int) used = defaultdict(int) for row in fact_query.trailing_days(9): if row.get('metric') == 'shop_sticker_purchased': sticker = stickers.get(row.get('type_id')) if sticker.name not in purchased: purchased[sticker.name] = defaultdict(int) purchased[sticker.name]["quantity"] += 1 purchased[sticker.name]["total_spent"] += sticker.cost if row.get('metric') == 'shop_sticker_used': sticker = stickers.get(row.get('type_id')) if sticker.name not in used: used[sticker.name] = defaultdict(int) used[sticker.name]["quantity"] += 1 used[sticker.name]["total_spent"] += sticker.cost print "Purchased:", purchased print "Used:", used
def test_purchase_sticker_unlimited_inventory(self): sticker = stickers.get("banana") assert not sticker.is_limited_inventory() user = create_rich_user() user.kv.stickers.add_limited_sticker(sticker) # Purchase economy.purchase_stickers(user, sticker.type_id, 1) # SHOULD be able to buy again for i in range(0, 10): economy.purchase_stickers(user, sticker.type_id, 1)
def test_sticker(self): html = self._html() self.assertFalse(self.css_select(html, '.frowny')) stick = stickers.get('frowny') result = self.api_post('/api/sticker/comment', { 'type_id': str(stick.type_id), 'comment_id': self.comment.id, }) html = self._html() self.assertTrue(self.css_select(html, '.frowny'))
def add_sticker_to_comment(request, comment_id, type_id, epic_message=None): """ Stickers a comment. You can be logged in or out. """ sticker = stickers.get(int(type_id)) comment = get_object_or_404(models.Comment, pk=comment_id) if epic_message and len(epic_message) > knobs.STICKER_MESSAGE_MAX_LENGTH: raise ServiceError("Message is too long.") elif epic_message and not (sticker.cost and sticker.cost >= knobs.EPIC_STICKER_COST_THRESHOLD): raise ServiceError("Messages can only be attached to epic stickers.") # Calculate if this user has exceeded the stickering rate limit. prefix = 'user:%s:stick_limit:' % request.user.id if not RateLimit(prefix + 'h', 600, 60 * 60).allowed() or not RateLimit( prefix + 'd', 1000, 8 * 60 * 60).allowed(): Metrics.sticker_ratelimit.record(request, sticker_type=sticker.type_id, comment=comment.id) raise ServiceError("Attempting to sticker too quickly.") prev_top_sticker = comment.details().top_sticker() remaining = _sticker_comment(request, comment, sticker.type_id, epic_message=epic_message) Metrics.sticker.record(request, sticker_type=sticker.type_id, comment=comment.id) comment_details = comment.details() top_sticker = comment_details.top_sticker() @bgwork.defer def update_stickered_users(): get_most_stickered_unfollowed_users(request.user).force() if prev_top_sticker is None or prev_top_sticker['type_id'] != top_sticker: @bgwork.defer def update_footer(): if comment.footer.should_exist(): comment.footer.call_update_in_new_process() return { 'new_counts': comment_details.sticker_counts, 'sorted_counts': comment_details.sorted_sticker_counts(), 'top_sticker': top_sticker, 'remaining': remaining, }
def _make_stickers(self, sticks=['smiley', 'banana', 'frowny', 'frowny'], top='banana', per=2): self.top_id = stickers.get(top).type_id self.stickers = map(stickers.get, sticks) self.cmt = self.post_comment(reply_content=create_content().id) from canvas import economy for sticker in self.stickers: for _ in xrange(per): user = create_rich_user() if sticker.cost: user.kv.stickers.add_limited_sticker(sticker) economy.purchase_stickers(user, sticker.type_id, 1) #user.redis.user_kv.hset('sticker:%s:count' % STORE_ITEM, 1) self.api_post('/api/sticker/comment', { 'type_id': sticker.type_id, 'comment_id': self.cmt.id, }, user=user)
def test_is_purchasable(self): user = create_rich_user() # Grab a purchasable sticker banana = stickers.get("banana") user.kv.stickers.add_limited_sticker(self.sticker) self.assertTrue(banana.cost) # Purchase it twice # @TODO: we should rafactor the api calls to be able to take # arguments that are not wrapped in the request payload. request = FakeRequest(user) request.POST = dict(item_type="sticker", item_id=banana.type_id) request.method = "POST" request.body = {} for _ in range(0, 2): # Buy store_buy(request) self.assertTrue(banana.is_purchasable(user))
def test_notifies_author(self): author = utils.create_user() comment = utils.create_comment() comment.author = author comment.save() another_user = utils.create_user() comment_sticker = CommentSticker(comment=comment, type_id=stickers.get("num1").type_id, timestamp=time.time(), ip="127.0.0.1", user=another_user) pn = Actions.stickered(another_user, comment_sticker) ex = expander.get_expander(pn)() recipients = ex.decide_recipients(pn) self.assertEqual(len(recipients), 1) self.assertIn(author, recipients)
def store_buy(request, item_type, item_id, quantity=1): #item_id could be a sticker.type_id or a Sticker. #TODO ^^^ is this true? item_id comes from JSON, so how would it be a Sticker? --alex try: quantity = int(quantity) except TypeError: raise ServiceError('Invalid parameter values.') if quantity == 0: raise ServiceError("Can't buy 0 stickers.") if not stickers.get(item_id).is_purchasable(request.user): raise ServiceError('Sticker is unpurchasable for you.') if item_type == 'sticker': try: remaining = economy.purchase_stickers(request.user, item_id, quantity) except economy.InvalidPurchase, ip: raise ServiceError(*ip.args)
def add_sticker_to_comment(request, comment_id, type_id, epic_message=None): """ Stickers a comment. You can be logged in or out. """ sticker = stickers.get(int(type_id)) comment = get_object_or_404(models.Comment, pk=comment_id) if epic_message and len(epic_message) > knobs.STICKER_MESSAGE_MAX_LENGTH: raise ServiceError("Message is too long.") elif epic_message and not (sticker.cost and sticker.cost >= knobs.EPIC_STICKER_COST_THRESHOLD): raise ServiceError("Messages can only be attached to epic stickers.") # Calculate if this user has exceeded the stickering rate limit. prefix = 'user:%s:stick_limit:' % request.user.id if not RateLimit(prefix+'h', 200, 60*60).allowed() or not RateLimit(prefix+'d', 300, 8*60*60).allowed(): Metrics.sticker_ratelimit.record(request, sticker_type=sticker.type_id, comment=comment.id) raise ServiceError("Attempting to sticker too quickly.") prev_top_sticker = comment.details().top_sticker() remaining = _sticker_comment(request, comment, sticker.type_id, epic_message=epic_message) Metrics.sticker.record(request, sticker_type=sticker.type_id, comment=comment.id) comment_details = comment.details() top_sticker = comment_details.top_sticker() @bgwork.defer def update_stickered_users(): get_most_stickered_unfollowed_users(request.user).force() if prev_top_sticker is None or prev_top_sticker['type_id'] != top_sticker: @bgwork.defer def update_footer(): if comment.footer.should_exist(): comment.footer.call_update_in_new_process() return { 'new_counts': comment_details.sticker_counts, 'sorted_counts': comment_details.sorted_sticker_counts(), 'top_sticker': top_sticker, 'remaining': remaining, }
def test_smiley_vs_frowny(self): self._make_stickers() counts = self.cmt.details().sorted_sticker_counts() self.assertEqual(counts[2]['type_id'], stickers.get('smiley').type_id) self.assertEqual(counts[1]['type_id'], stickers.get('frowny').type_id)
def base_context(request): """ `nav_category` gets a default value here, but you can override it by setting a `nav_category` property on the `request` object to the value you want. """ user = request.user following, following_truncated = [], [] if user.is_authenticated(): following = Category.objects.filter(followers__user=user) following = following.order_by("name") _following = [cat.details() for cat in following] _following = sorted(_following, key=lambda cat: cat["followers"], reverse=True) _following = _following[: knobs.FOLLOWING_MENU_COLUMNS] following_truncated = sorted(_following, key=lambda cat: cat["name"]) else: following = list(Category.objects.filter(id__in=Category.get_whitelisted()).order_by("name")) if not following_truncated: following_truncated = following stickers = canvas_stickers.all_details(user) sticker_event = canvas_stickers.get_active_event() try: draw_from_scratch_content = Content.all_objects.get(id=Content.DRAW_FROM_SCRATCH_PK).details() except Content.DoesNotExist: draw_from_scratch_content = None context = { "following": [cat.details() for cat in following], "following_truncated": following_truncated, "following_menu_length": len(following_truncated), "sticker_pack_minimized": bool(request.COOKIES.get("sticker_pack_minimized")), "global_stickers": stickers, "stickers_primary": canvas_stickers.primary_types, "stickers_inventory": canvas_stickers.get_inventory(user), "stickers_seasonal": canvas_stickers.get_active_seasonal_stickers(), "stickers_sharing": canvas_stickers.sharing_types, "stickers_actions": canvas_stickers.get_actions(user), "seasonal_event": canvas_stickers.get_active_event(), "num1_sticker": canvas_stickers.get("num1"), "current_user": user, # Note that we grab an empty UserInfo for users that do not already have userinfo. # TODO now that we have canvas_auth.models.AnonymousUser, this *should* be unnecessary. "current_user_info": user.userinfo if hasattr(user, "userinfo") else UserInfo(), "draw_from_scratch_content": draw_from_scratch_content, "CANVAS_SUB_SITE": settings.CANVAS_SUB_SITE, "PROJECT": settings.PROJECT, "TRACKING_ENABLED": settings.TRACKING_ENABLED, } # The key here can't be the same as in locals, otherwise the key in locals takes precedence in # render_to_response. get_nav = Category.get(request.GET.get("nav")) if get_nav and get_nav.disabled: get_nav = None if get_nav == Category.MY and not request.user.is_authenticated(): # Explicit ?nav=following links are meaningless for logged-out users get_nav = Category.ALL default_category = Category.get_default(request.user) try: nav_category = request.nav_category nav_category_is_default = False except AttributeError: nav_category = get_nav or default_category nav_category_is_default = True try: nav_tag = request.nav_tag except AttributeError: nav_tag = None context["enable_timeline"] = context["current_user_info"].enable_timeline context["enable_timeline_posts"] = context["current_user_info"].enable_timeline_posts context["nav_tag"] = nav_tag context["nav_category"] = nav_category.details() context["nav_category_is_default"] = nav_category_is_default context["default_category"] = default_category.details() context["follows_category"] = context["nav_category"] in context["following"] # These sorts are not tied to a group. context["personal_sorts"] = ["pinned", "flagged"] context["unsticky_sorts"] = context["personal_sorts"] + ["hot", "stickered"] context["group_found_limit"] = Category.FOUND_LIMIT context["knobs"] = knobs context["show_post_thread_button"] = True context["fb_namespace"] = settings.FACEBOOK_NAMESPACE context["fb_app_id"] = settings.FACEBOOK_APP_ID # Sticker attract mode. if request.user.is_authenticated(): context["show_attract_mode"] = False else: context["show_attract_mode"] = not "first_page_view" in request.session request.session["first_page_view"] = True for setting in ["DEBUG", "DOMAIN", "UGC_HOST"]: context[setting] = getattr(settings, setting) # # Realtime. # if nav_category == Category.MY: context["category_channel"] = [cat.posts_channel.sync() for cat in following] else: context["category_channel"] = [nav_category.posts_channel.sync()] context["flag_channel"] = "" if user.is_authenticated(): if user.can_moderate_flagged: unjudged = Comment.unjudged_flagged().count() context["flagged_unjudged"] = unjudged context["flag_channel"] = CommentFlag.flag_channel.sync() context["user_channel"] = user.redis.channel.sync() context["user_pinned_channel"] = user.redis.pinned_posts_channel.sync() context["last_sticker"] = last_sticker.get_info(request.user) return context
def test_sticker_image_by_id(self): banana = stickers.get("banana") tag = jinja_tags.sticker_image(banana.type_id) self.assertNumCssMatches(1, tag, '.banana')
def get_star_sticker(): from canvas import stickers return stickers.get(settings.STAR_STICKER_TYPE_ID)
def test_sticker_image_with_size(self): banana = stickers.get("banana") tag = jinja_tags.sticker_image(banana, image_size="original") self.assertNumCssMatches(1, tag, '.banana') self.assertNumCssMatches(1, tag, '.original')
def test_get_nonexistent_sticker(self): for key in -666, "bogus_thing": sticker = stickers.get(key) self.assertEqual(0.0, sticker.value) self.assertEqual(0, sticker.type_id) self.assertEqual('dummy', sticker.name)
def after_setUp(self): self.user = create_user() self._epic_sticker = stickers.get('nyancat')
def base_context(request): """ `nav_category` gets a default value here, but you can override it by setting a `nav_category` property on the `request` object to the value you want. """ user = request.user following, following_truncated = [], [] if user.is_authenticated(): following = Category.objects.filter(followers__user=user) following = following.order_by('name') _following = [cat.details() for cat in following] _following = sorted(_following, key=lambda cat: cat['followers'], reverse=True) _following = _following[:knobs.FOLLOWING_MENU_COLUMNS] following_truncated = sorted(_following, key=lambda cat: cat['name']) else: following = list( Category.objects.filter( id__in=Category.get_whitelisted()).order_by('name')) if not following_truncated: following_truncated = following stickers = canvas_stickers.all_details(user) sticker_event = canvas_stickers.get_active_event() try: draw_from_scratch_content = Content.all_objects.get( id=Content.DRAW_FROM_SCRATCH_PK).details() except Content.DoesNotExist: draw_from_scratch_content = None context = { 'following': [cat.details() for cat in following], 'following_truncated': following_truncated, 'following_menu_length': len(following_truncated), 'sticker_pack_minimized': bool(request.COOKIES.get('sticker_pack_minimized')), 'global_stickers': stickers, 'stickers_primary': canvas_stickers.primary_types, 'stickers_inventory': canvas_stickers.get_inventory(user), 'stickers_seasonal': canvas_stickers.get_active_seasonal_stickers(), 'stickers_sharing': canvas_stickers.sharing_types, 'stickers_actions': canvas_stickers.get_actions(user), 'seasonal_event': canvas_stickers.get_active_event(), 'num1_sticker': canvas_stickers.get('num1'), 'current_user': user, # Note that we grab an empty UserInfo for users that do not already have userinfo. #TODO now that we have canvas_auth.models.AnonymousUser, this *should* be unnecessary. 'current_user_info': user.userinfo if hasattr(user, "userinfo") else UserInfo(), 'draw_from_scratch_content': draw_from_scratch_content, 'CANVAS_SUB_SITE': settings.CANVAS_SUB_SITE, 'PROJECT': settings.PROJECT, } # The key here can't be the same as in locals, otherwise the key in locals takes precedence in # render_to_response. get_nav = Category.get(request.GET.get('nav')) if get_nav and get_nav.disabled: get_nav = None if get_nav == Category.MY and not request.user.is_authenticated(): # Explicit ?nav=following links are meaningless for logged-out users get_nav = Category.ALL default_category = Category.get_default(request.user) try: nav_category = request.nav_category nav_category_is_default = False except AttributeError: nav_category = get_nav or default_category nav_category_is_default = True try: nav_tag = request.nav_tag except AttributeError: nav_tag = None context['enable_timeline'] = context['current_user_info'].enable_timeline context['enable_timeline_posts'] = context[ 'current_user_info'].enable_timeline_posts context['nav_tag'] = nav_tag context['nav_category'] = nav_category.details() context['nav_category_is_default'] = nav_category_is_default context['default_category'] = default_category.details() context['follows_category'] = context['nav_category'] in context[ 'following'] # These sorts are not tied to a group. context['personal_sorts'] = ['pinned', 'flagged'] context['unsticky_sorts'] = context['personal_sorts'] + [ 'hot', 'stickered' ] context['group_found_limit'] = Category.FOUND_LIMIT context['knobs'] = knobs context['show_post_thread_button'] = True context['fb_namespace'] = settings.FACEBOOK_NAMESPACE # Sticker attract mode. if request.user.is_authenticated(): context['show_attract_mode'] = False else: context['show_attract_mode'] = not 'first_page_view' in request.session request.session['first_page_view'] = True for setting in ['DEBUG', 'DOMAIN', 'UGC_HOST']: context[setting] = getattr(settings, setting) # # Realtime. # if nav_category == Category.MY: context['category_channel'] = [ cat.posts_channel.sync() for cat in following ] else: context['category_channel'] = [nav_category.posts_channel.sync()] context['flag_channel'] = '' if user.is_authenticated(): if user.can_moderate_flagged: unjudged = Comment.unjudged_flagged().count() context['flagged_unjudged'] = unjudged context['flag_channel'] = CommentFlag.flag_channel.sync() context['user_channel'] = user.redis.channel.sync() context['user_pinned_channel'] = user.redis.pinned_posts_channel.sync() context['last_sticker'] = last_sticker.get_info(request.user) return context
def sticker_image(sticker, image_size="small", classes=""): """ `sticker` can be a sticker name or ID. """ sticker = stickers.get(sticker) return Markup(u'<span class="sticker_container {0} {1} {2}" data-type_id="{3}"></span>'.format(image_size, sticker.name, classes, sticker.type_id))
def base_context(request): """ `nav_category` gets a default value here, but you can override it by setting a `nav_category` property on the `request` object to the value you want. """ user = request.user following, following_truncated = [], [] if user.is_authenticated(): following = Category.objects.filter(followers__user=user) following = following.order_by('name') _following = [cat.details() for cat in following] _following = sorted(_following, key=lambda cat: cat['followers'], reverse=True) _following = _following[:knobs.FOLLOWING_MENU_COLUMNS] following_truncated = sorted(_following, key=lambda cat: cat['name']) else: following = list(Category.objects.filter(id__in=Category.get_whitelisted()).order_by('name')) if not following_truncated: following_truncated = following stickers = canvas_stickers.all_details(user) sticker_event = canvas_stickers.get_active_event() try: draw_from_scratch_content = Content.all_objects.get(id=Content.DRAW_FROM_SCRATCH_PK).details() except Content.DoesNotExist: draw_from_scratch_content = None context = { 'following': [cat.details() for cat in following], 'following_truncated': following_truncated, 'following_menu_length': len(following_truncated), 'sticker_pack_minimized': bool(request.COOKIES.get('sticker_pack_minimized')), 'global_stickers': stickers, 'stickers_primary': canvas_stickers.primary_types, 'stickers_inventory': canvas_stickers.get_inventory(user), 'stickers_seasonal': canvas_stickers.get_active_seasonal_stickers(), 'stickers_sharing': canvas_stickers.sharing_types, 'stickers_actions': canvas_stickers.get_actions(user), 'seasonal_event': canvas_stickers.get_active_event(), 'num1_sticker': canvas_stickers.get('num1'), 'current_user': user, # Note that we grab an empty UserInfo for users that do not already have userinfo. #TODO now that we have canvas_auth.models.AnonymousUser, this *should* be unnecessary. 'current_user_info': user.userinfo if hasattr(user, "userinfo") else UserInfo(), 'draw_from_scratch_content': draw_from_scratch_content, 'CANVAS_SUB_SITE': settings.CANVAS_SUB_SITE, 'PROJECT': settings.PROJECT, } # The key here can't be the same as in locals, otherwise the key in locals takes precedence in # render_to_response. get_nav = Category.get(request.GET.get('nav')) if get_nav and get_nav.disabled: get_nav = None if get_nav == Category.MY and not request.user.is_authenticated(): # Explicit ?nav=following links are meaningless for logged-out users get_nav = Category.ALL default_category = Category.get_default(request.user) try: nav_category = request.nav_category nav_category_is_default = False except AttributeError: nav_category = get_nav or default_category nav_category_is_default = True try: nav_tag = request.nav_tag except AttributeError: nav_tag = None context['enable_timeline'] = context['current_user_info'].enable_timeline context['enable_timeline_posts'] = context['current_user_info'].enable_timeline_posts context['nav_tag'] = nav_tag context['nav_category'] = nav_category.details() context['nav_category_is_default'] = nav_category_is_default context['default_category'] = default_category.details() context['follows_category'] = context['nav_category'] in context['following'] # These sorts are not tied to a group. context['personal_sorts'] = ['pinned', 'flagged'] context['unsticky_sorts'] = context['personal_sorts'] + ['hot', 'stickered'] context['group_found_limit'] = Category.FOUND_LIMIT context['knobs'] = knobs context['show_post_thread_button'] = True context['fb_namespace'] = settings.FACEBOOK_NAMESPACE # Sticker attract mode. if request.user.is_authenticated(): context['show_attract_mode'] = False else: context['show_attract_mode'] = not 'first_page_view' in request.session request.session['first_page_view'] = True for setting in ['DEBUG', 'DOMAIN', 'UGC_HOST']: context[setting] = getattr(settings, setting) # # Realtime. # if nav_category == Category.MY: context['category_channel'] = [cat.posts_channel.sync() for cat in following] else: context['category_channel'] = [nav_category.posts_channel.sync()] context['flag_channel'] = '' if user.is_authenticated(): if user.can_moderate_flagged: unjudged = Comment.unjudged_flagged().count() context['flagged_unjudged'] = unjudged context['flag_channel'] = CommentFlag.flag_channel.sync() context['user_channel'] = user.redis.channel.sync() context['user_pinned_channel'] = user.redis.pinned_posts_channel.sync() context['last_sticker'] = last_sticker.get_info(request.user) return context