def get_updated_questions_for_user(self,user): #these are placeholders for separate query sets per question group #there are four groups - one for each EmailFeedSetting.feed_type #and each group has subtypes A and B #that's because of the strange thing commented below #see note on Q and F objects marked with todo tag q_sel_A = None q_sel_B = None q_ask_A = None q_ask_B = None q_ans_A = None q_ans_B = None q_all_A = None q_all_B = None now = datetime.datetime.now() #Q_set1 - base questionquery set for this user Q_set1 = Question.objects.exclude( last_activity_by=user ).exclude( last_activity_at__lt=user.date_joined#exclude old stuff ).exclude( deleted=True ).exclude( closed=True ).order_by('-last_activity_at') #todo: for some reason filter on did not work as expected ~Q(viewed__who=user) | # Q(viewed__who=user,viewed__when__lt=F('last_activity_at')) #returns way more questions than you might think it should #so because of that I've created separate query sets Q_set2 and Q_set3 #plus two separate queries run faster! #questions that are not seen by the user Q_set2 = Q_set1.filter(~Q(viewed__who=user)) #questions seen before the last modification Q_set3 = Q_set1.filter(Q(viewed__who=user,viewed__when__lt=F('last_activity_at'))) #todo may shortcirquit here is len(user_feeds) == 0 user_feeds = EmailFeedSetting.objects.filter(subscriber=user).exclude(frequency='n') if len(user_feeds) == 0: return {};#short cirquit for feed in user_feeds: #each group of updates has it's own cutoff time #to be saved as a new parameter for each query set #won't send email for a given question if it has been done #after the cutoff_time cutoff_time = now - EmailFeedSetting.DELTA_TABLE[feed.frequency] if feed.reported_at == None or feed.reported_at <= cutoff_time: Q_set_A = Q_set2#.exclude(last_activity_at__gt=cutoff_time)#report these excluded later Q_set_B = Q_set3#.exclude(last_activity_at__gt=cutoff_time) feed.reported_at = now feed.save()#may not actually report anything, depending on filters below if feed.feed_type == 'q_sel': q_sel_A = Q_set_A.filter(followed_by=user) q_sel_A.cutoff_time = cutoff_time #store cutoff time per query set q_sel_B = Q_set_B.filter(followed_by=user) q_sel_B.cutoff_time = cutoff_time #store cutoff time per query set elif feed.feed_type == 'q_ask': q_ask_A = Q_set_A.filter(author=user) q_ask_A.cutoff_time = cutoff_time q_ask_B = Q_set_B.filter(author=user) q_ask_B.cutoff_time = cutoff_time elif feed.feed_type == 'q_ans': q_ans_A = Q_set_A.filter(answers__author=user)[:settings.MAX_ALERTS_PER_EMAIL] q_ans_A.cutoff_time = cutoff_time q_ans_B = Q_set_B.filter(answers__author=user)[:settings.MAX_ALERTS_PER_EMAIL] q_ans_B.cutoff_time = cutoff_time elif feed.feed_type == 'q_all': if user.tag_filter_setting == 'ignored': ignored_tags = Tag.objects.filter(user_selections__reason='bad', \ user_selections__user=user) q_all_A = Q_set_A.exclude( tags__in=ignored_tags )[:settings.MAX_ALERTS_PER_EMAIL] q_all_B = Q_set_B.exclude( tags__in=ignored_tags )[:settings.MAX_ALERTS_PER_EMAIL] else: selected_tags = Tag.objects.filter(user_selections__reason='good', \ user_selections__user=user) q_all_A = Q_set_A.filter( tags__in=selected_tags ) q_all_B = Q_set_B.filter( tags__in=selected_tags ) q_all_A.cutoff_time = cutoff_time q_all_B.cutoff_time = cutoff_time #build list in this order q_list = OrderedDict() extend_question_list(q_sel_A, q_list) extend_question_list(q_sel_B, q_list) if user.tag_filter_setting == 'interesting': extend_question_list(q_all_A, q_list) extend_question_list(q_all_B, q_list) extend_question_list(q_ask_A, q_list, limit=True) extend_question_list(q_ask_B, q_list, limit=True) extend_question_list(q_ans_A, q_list, limit=True) extend_question_list(q_ans_B, q_list, limit=True) if user.tag_filter_setting == 'ignored': extend_question_list(q_all_A, q_list, limit=True) extend_question_list(q_all_B, q_list, limit=True) ctype = ContentType.objects.get_for_model(Question) EMAIL_UPDATE_ACTIVITY = const.TYPE_ACTIVITY_QUESTION_EMAIL_UPDATE_SENT for q, meta_data in q_list.items(): #this loop edits meta_data for each question #so that user will receive counts on new edits new answers, etc #maybe not so important actually?? #keeps email activity per question per user try: update_info = Activity.objects.get( user=user, content_type=ctype, object_id=q.id, activity_type=EMAIL_UPDATE_ACTIVITY ) emailed_at = update_info.active_at except Activity.DoesNotExist: update_info = Activity(user=user, content_object=q, activity_type=EMAIL_UPDATE_ACTIVITY) emailed_at = datetime.datetime(1970,1,1)#long time ago except Activity.MultipleObjectsReturned: raise Exception('server error - multiple question email activities found per user-question pair') cutoff_time = meta_data['cutoff_time']#cutoff time for the question #wait some more time before emailing about this question if emailed_at > cutoff_time: #here we are maybe losing opportunity to record the finding #of yet unseen version of a question meta_data['skip'] = True continue #collect info on all sorts of news that happened after #the most recent emailing to the user about this question q_rev = QuestionRevision.objects.filter(question=q,\ revised_at__gt=emailed_at) q_rev = q_rev.exclude(author=user) #now update all sorts of metadata per question meta_data['q_rev'] = len(q_rev) if len(q_rev) > 0 and q.added_at == q_rev[0].revised_at: meta_data['q_rev'] = 0 meta_data['new_q'] = True else: meta_data['new_q'] = False new_ans = Answer.objects.filter(question=q,\ added_at__gt=emailed_at) new_ans = new_ans.exclude(author=user) meta_data['new_ans'] = len(new_ans) ans_rev = AnswerRevision.objects.filter(answer__question=q,\ revised_at__gt=emailed_at) ans_rev = ans_rev.exclude(author=user) meta_data['ans_rev'] = len(ans_rev) if len(q_rev)+len(new_ans)+len(ans_rev) == 0: meta_data['skip'] = True else: meta_data['skip'] = False update_info.active_at = now update_info.save() #save question email update activity #q_list is actually a ordered dictionary #print 'user %s gets %d' % (user.username, len(q_list.keys())) #todo: sort question list by update time return q_list
def get_updated_questions_for_user(self,user): q_sel = None q_ask = None q_ans = None q_all = None now = datetime.datetime.now() Q_set1 = Question.objects.exclude( last_activity_by=user, ).exclude( last_activity_at__lt=user.date_joined ).filter( Q(viewed__who=user,viewed__when__lt=F('last_activity_at')) | \ ~Q(viewed__who=user) ).exclude( deleted=True ).exclude( closed=True ) user_feeds = EmailFeedSetting.objects.filter(subscriber=user).exclude(frequency='n') for feed in user_feeds: cutoff_time = now - EmailFeedSetting.DELTA_TABLE[feed.frequency] if feed.reported_at == None or feed.reported_at <= cutoff_time: Q_set = Q_set1.exclude(last_activity_at__gt=cutoff_time)#report these excluded later feed.reported_at = now feed.save()#may not actually report anything, depending on filters below if feed.feed_type == 'q_sel': q_sel = Q_set.filter(followed_by=user) q_sel.cutoff_time = cutoff_time #store cutoff time per query set elif feed.feed_type == 'q_ask': q_ask = Q_set.filter(author=user) q_ask.cutoff_time = cutoff_time elif feed.feed_type == 'q_ans': q_ans = Q_set.filter(answers__author=user) q_ans.cutoff_time = cutoff_time elif feed.feed_type == 'q_all': if user.tag_filter_setting == 'ignored': ignored_tags = Tag.objects.filter(user_selections__reason='bad',user_selections__user=user) q_all = Q_set.exclude( tags__in=ignored_tags ) else: selected_tags = Tag.objects.filter(user_selections__reason='good',user_selections__user=user) q_all = Q_set.filter( tags__in=selected_tags ) q_all.cutoff_time = cutoff_time #build list in this order q_list = OrderedDict() def extend_question_list(src, dst): """src is a query set with questions or an empty list dst - is an ordered dictionary """ if src is None: return #will not do anything if subscription of this type is not used cutoff_time = src.cutoff_time for q in src: if q in dst: if cutoff_time < dst[q]['cutoff_time']: dst[q]['cutoff_time'] = cutoff_time else: #initialise a questions metadata dictionary to use for email reporting dst[q] = {'cutoff_time':cutoff_time} extend_question_list(q_sel, q_list) extend_question_list(q_ask, q_list) extend_question_list(q_ans, q_list) extend_question_list(q_all, q_list) ctype = ContentType.objects.get_for_model(Question) EMAIL_UPDATE_ACTIVITY = const.TYPE_ACTIVITY_QUESTION_EMAIL_UPDATE_SENT for q, meta_data in q_list.items(): #todo use Activity, but first start keeping more Activity records #act = Activity.objects.filter(content_type=ctype, object_id=q.id) #because currently activity is not fully recorded to through #revision records to see what kind modifications were done on #the questions and answers try: update_info = Activity.objects.get(content_type=ctype, object_id=q.id, activity_type=EMAIL_UPDATE_ACTIVITY) emailed_at = update_info.active_at except Activity.DoesNotExist: update_info = Activity(user=user, content_object=q, activity_type=EMAIL_UPDATE_ACTIVITY) emailed_at = datetime.datetime(1970,1,1)#long time ago except Activity.MultipleObjectsReturned: raise Exception('server error - multiple question email activities found per user-question pair') q_rev = QuestionRevision.objects.filter(question=q,\ revised_at__lt=cutoff_time,\ revised_at__gt=emailed_at) q_rev = q_rev.exclude(author=user) meta_data['q_rev'] = len(q_rev) if len(q_rev) > 0 and q.added_at == q_rev[0].revised_at: meta_data['q_rev'] = 0 meta_data['new_q'] = True else: meta_data['new_q'] = False new_ans = Answer.objects.filter(question=q,\ added_at__lt=cutoff_time,\ added_at__gt=emailed_at) new_ans = new_ans.exclude(author=user) meta_data['new_ans'] = len(new_ans) ans_rev = AnswerRevision.objects.filter(answer__question=q,\ revised_at__lt=cutoff_time,\ revised_at__gt=emailed_at) ans_rev = ans_rev.exclude(author=user) meta_data['ans_rev'] = len(ans_rev) if len(q_rev) == 0 and len(new_ans) == 0 and len(ans_rev) == 0: meta_data['nothing_new'] = True else: meta_data['nothing_new'] = False update_info.active_at = now update_info.save() #save question email update activity return q_list