def test_watch_solution_and_replies(self): """User subscribes to solution and replies: page doesn't break""" self.client.login(username="******", password="******") user = User.objects.get(username="******") QuestionReplyEvent.notify(user, self.question) QuestionSolvedEvent.notify(user, self.question) response = get(self.client, "questions.answers", args=[self.question.id]) eq_(200, response.status_code)
def test_no_notification_on_update(self): """Saving an existing question does not watch it.""" q = Question.objects.get(pk=1) assert not QuestionReplyEvent.is_notifying(q.creator, q) q.save() assert not QuestionReplyEvent.is_notifying(q.creator, q)
def test_watch_solution_and_replies(self): """User subscribes to solution and replies: page doesn't break""" self.client.login(username='******', password='******') user = User.objects.get(username='******') QuestionReplyEvent.notify(user, self.question) QuestionSolvedEvent.notify(user, self.question) response = get(self.client, 'questions.answers', args=[self.question.id]) eq_(200, response.status_code)
def unsubscribe_watch(request, watch_id, secret): """Stop watching a question, for anonymous users.""" watch = get_object_or_404(Watch, pk=watch_id) question = watch.content_object success = False if watch.secret == secret and isinstance(question, Question): user_or_email = watch.user or watch.email QuestionReplyEvent.stop_notifying(user_or_email, question) QuestionSolvedEvent.stop_notifying(user_or_email, question) success = True return jingo.render(request, "questions/unsubscribe_watch.html", {"question": question, "success": success})
def unsubscribe_watch(request, watch_id, secret): """Stop watching a question, for anonymous users.""" watch = get_object_or_404(Watch, pk=watch_id) question = watch.content_object success = False if watch.secret == secret and isinstance(question, Question): user_or_email = watch.user or watch.email QuestionReplyEvent.stop_notifying(user_or_email, question) QuestionSolvedEvent.stop_notifying(user_or_email, question) success = True return jingo.render(request, 'questions/unsubscribe_watch.html', {'question': question, 'success': success})
def save(self, no_update=False, *args, **kwargs): """Override save method to take care of updated.""" new = not self.id if not new and not no_update: self.updated = datetime.now() super(Question, self).save(*args, **kwargs) if new: # Avoid circular import, events.py imports Question from questions.events import QuestionReplyEvent # Authors should automatically watch their own questions. QuestionReplyEvent.notify(self.creator, self)
def _answers_data(request, question_id, form=None, watch_form=None, answer_preview=None): """Return a map of the minimal info necessary to draw an answers page.""" question = get_object_or_404(Question, pk=question_id) answers_ = question.answers.all() if not request.MOBILE: answers_ = paginate(request, answers_, per_page=constants.ANSWERS_PER_PAGE) feed_urls = ((reverse('questions.answers.feed', kwargs={'question_id': question_id}), AnswersFeed().title(question)),) frequencies = dict(FREQUENCY_CHOICES) is_watching_question = ( request.user.is_authenticated() and ( QuestionReplyEvent.is_notifying(request.user, question) or QuestionSolvedEvent.is_notifying(request.user, question))) return {'question': question, 'answers': answers_, 'form': form or AnswerForm(), 'answer_preview': answer_preview, 'watch_form': watch_form or _init_watch_form(request, 'reply'), 'feeds': feed_urls, 'frequencies': frequencies, 'is_watching_question': is_watching_question, 'can_tag': request.user.has_perm('questions.tag_question'), 'can_create_tags': request.user.has_perm('taggit.add_tag')}
def _answers_data(request, question_id, form=None, watch_form=None, answer_preview=None): """Return a map of the minimal info necessary to draw an answers page.""" question = get_object_or_404(Question, pk=question_id) answers_ = paginate(request, question.answers.all(), per_page=constants.ANSWERS_PER_PAGE) vocab = [t.name for t in Tag.objects.all()] # TODO: Fetch only name. feed_urls = ( (reverse("questions.answers.feed", kwargs={"question_id": question_id}), AnswersFeed().title(question)), ) frequencies = dict(FREQUENCY_CHOICES) is_watching_question = request.user.is_authenticated() and ( QuestionReplyEvent.is_notifying(request.user, question) or QuestionSolvedEvent.is_notifying(request.user, question) ) return { "question": question, "answers": answers_, "form": form or AnswerForm(), "answer_preview": answer_preview, "watch_form": watch_form or _init_watch_form(request, "reply"), "feeds": feed_urls, "tag_vocab": json.dumps(vocab), "frequencies": frequencies, "is_watching_question": is_watching_question, "can_tag": request.user.has_perm("questions.tag_question"), "can_create_tags": request.user.has_perm("taggit.add_tag"), }
def save(self, no_update=False, *args, **kwargs): """Override save method to take care of updated.""" new = not self.id if not new: self.clear_cached_html() if not no_update: self.updated = datetime.now() super(Question, self).save(*args, **kwargs) if new: # Avoid circular import, events.py imports Question from questions.events import QuestionReplyEvent # Authors should automatically watch their own questions. QuestionReplyEvent.notify(self.creator, self)
def _answers_data(request, question_id, form=None, watch_form=None, answer_preview=None): """Return a map of the minimal info necessary to draw an answers page.""" question = get_object_or_404(Question, pk=question_id) answers_ = paginate(request, question.answers.all(), per_page=constants.ANSWERS_PER_PAGE) vocab = [t.name for t in Tag.objects.all()] # TODO: Fetch only name. feed_urls = ((reverse('questions.answers.feed', kwargs={'question_id': question_id}), AnswersFeed().title(question)),) frequencies = dict(FREQUENCY_CHOICES) is_watching_question = ( request.user.is_authenticated() and ( QuestionReplyEvent.is_notifying(request.user, question) or QuestionSolvedEvent.is_notifying(request.user, question))) return {'question': question, 'answers': answers_, 'form': form or AnswerForm(), 'answer_preview': answer_preview, 'watch_form': watch_form or _init_watch_form(request, 'reply'), 'feeds': feed_urls, 'tag_vocab': json.dumps(vocab), 'frequencies': frequencies, 'is_watching_question': is_watching_question, 'can_tag': request.user.has_perm('questions.tag_question'), 'can_create_tags': request.user.has_perm('taggit.add_tag')}
def save(self, update=True, no_notify=False, *args, **kwargs): """ Override save method to update question info and take care of updated. """ new = self.id is None if new: page = self.question.num_answers / constants.ANSWERS_PER_PAGE + 1 self.page = page else: self.updated = datetime.now() self.clear_cached_html() super(Answer, self).save(*args, **kwargs) if new: # Occasionally, num_answers seems to get out of sync with the # actual number of answers. This changes it to pull from # uncached on the off chance that fixes it. Plus if we enable # caching of counts, this will continue to work. self.question.num_answers = Answer.uncached.filter( question=self.question).count() self.question.last_answer = self self.question.save(update) self.question.clear_cached_contributors() if not no_notify: # Avoid circular import: events.py imports Question. from questions.events import QuestionReplyEvent QuestionReplyEvent(self).fire(exclude=self.creator)
def save(self, no_update=False, no_notify=False, *args, **kwargs): """ Override save method to update question info and take care of updated. """ new = self.id is None if new: page = self.question.num_answers / constants.ANSWERS_PER_PAGE + 1 self.page = page else: self.updated = datetime.now() super(Answer, self).save(*args, **kwargs) if new: self.question.num_answers = self.question.answers.count() self.question.last_answer = self self.question.save(no_update) if not no_notify: # Avoid circular import, events.py imports Question from questions.events import QuestionReplyEvent QuestionReplyEvent(self).fire(exclude=self.creator)
def test_answer_notification(self, get_current): """Assert that hitting the watch toggle toggles and that proper mails are sent to anonymous users, registered users, and the question asker.""" # TODO: This test is way too monolithic, and the fixtures encode # assumptions that aren't obvious here. Split this test into about 5, # each of which tests just 1 thing. Consider using instantiation # helpers. get_current.return_value.domain = 'testserver' # An arbitrary registered user (pcraciunoiu) watches: question = self._toggle_watch_question('reply', turn_on=True) # An anonymous user watches: QuestionReplyEvent.notify('*****@*****.**', question) # The question asker (jsocol) watches: QuestionReplyEvent.notify(question.creator, question) # Post a reply self.client.login(username='******', password='******') post(self.client, 'questions.reply', {'content': 'an answer'}, args=[question.id]) answer = Answer.uncached.filter().order_by('-id')[0] # Order of emails is not important. attrs_eq(mail.outbox[0], to=['user47963@nowhere'], subject='%s commented on a Firefox question ' "you're watching" % answer.creator.username) starts_with(mail.outbox[0].body, ANSWER_EMAIL.format(answer=answer.id)) attrs_eq(mail.outbox[1], to=[question.creator.email], subject='%s posted an answer to your question "%s"' % (answer.creator.username, question.title)) starts_with(mail.outbox[1].body, ANSWER_EMAIL_TO_ASKER.format(answer=answer.id)) attrs_eq(mail.outbox[2], to=['*****@*****.**'], subject="%s commented on a Firefox question you're watching" % answer.creator.username) starts_with(mail.outbox[2].body, ANSWER_EMAIL_TO_ANONYMOUS.format(answer=answer.id)) self._toggle_watch_question('reply', turn_on=False)
def test_notification_created(self): """Creating a new question auto-watches it for answers.""" u = User.objects.get(pk=118533) q = Question(creator=u, title='foo', content='bar') q.save() assert QuestionReplyEvent.is_notifying(u, q)
def test_unwatch(self): """Unwatch a question.""" # First watch question. self.test_watch_replies_logged_in() # Then unwatch it. self.client.login(username="******", password="******") user = User.objects.get(username="******") post(self.client, "questions.unwatch", args=[self.question.id]) assert not QuestionReplyEvent.is_notifying(user, self.question), "Watch was not destroyed"
def test_watch_replies_logged_in(self): """Watch a question for replies (logged in).""" self.client.login(username='******', password='******') user = User.objects.get(username='******') post(self.client, 'questions.watch', {'event_type': 'reply'}, args=[self.question.id]) assert QuestionReplyEvent.is_notifying(user, self.question), ( 'Watch was not created')
def test_watch_replies_logged_in(self): """Watch a question for replies (logged in).""" self.client.login(username='******', password='******') user = User.objects.get(username='******') post(self.client, 'questions.watch', {'event_type': 'reply'}, args=[self.question.id]) assert QuestionReplyEvent.is_notifying( user, self.question), ('Watch was not created')
def watch_question(request, question_id): """Start watching a question for replies or solution.""" question = get_object_or_404(Question, pk=question_id) form = WatchQuestionForm(request.user, request.POST) # Process the form msg = None if form.is_valid(): user_or_email = (request.user if request.user.is_authenticated() else form.cleaned_data['email']) try: if form.cleaned_data['event_type'] == 'reply': QuestionReplyEvent.notify(user_or_email, question) else: QuestionSolvedEvent.notify(user_or_email, question) except ActivationRequestFailed: msg = _('Could not send a message to that email address.') # Respond to ajax request if request.is_ajax(): if form.is_valid(): msg = msg or (_('You will be notified of updates by email.') if request.user.is_authenticated() else _( 'You should receive an email shortly ' 'to confirm your subscription.')) return HttpResponse(json.dumps({'message': msg})) if request.POST.get('from_vote'): tmpl = 'questions/includes/question_vote_thanks.html' else: tmpl = 'questions/includes/email_subscribe.html' html = jingo.render_to_string(request, tmpl, { 'question': question, 'watch_form': form }) return HttpResponse(json.dumps({'html': html})) # Respond to normal request if form.is_valid() and not msg: return HttpResponseRedirect(question.get_absolute_url()) return answers(request, question.id, watch_form=form, message=msg)
def test_unwatch(self): """Unwatch a question.""" # First watch question. self.test_watch_replies_logged_in() # Then unwatch it. self.client.login(username='******', password='******') user = User.objects.get(username='******') post(self.client, 'questions.unwatch', args=[self.question.id]) assert not QuestionReplyEvent.is_notifying( user, self.question), ('Watch was not destroyed')
def watch_question(request, question_id): """Start watching a question for replies or solution.""" question = get_object_or_404(Question, pk=question_id) form = WatchQuestionForm(request.user, request.POST) # Process the form msg = None if form.is_valid(): user_or_email = (request.user if request.user.is_authenticated() else form.cleaned_data['email']) try: if form.cleaned_data['event_type'] == 'reply': QuestionReplyEvent.notify(user_or_email, question) else: QuestionSolvedEvent.notify(user_or_email, question) except ActivationRequestFailed: msg = _('Could not send message to that email address.') # Respond to ajax request if request.is_ajax(): if form.is_valid(): if not msg: msg = (_('You will be notified of updates by email.') if request.user.is_authenticated() else _('You should receive an email shortly ' 'to confirm your subscription.')) return HttpResponse(json.dumps({'message': msg})) if request.POST.get('from_vote'): tmpl = 'questions/includes/question_vote_thanks.html' else: tmpl = 'questions/includes/email_subscribe.html' html = jingo.render_to_string(request, tmpl, {'question': question, 'watch_form': form}) return HttpResponse(json.dumps({'html': html})) # Respond to normal request # TODO: show failure here if email fails to send. if form.is_valid(): return HttpResponseRedirect(question.get_absolute_url()) return answers(request, question.id, watch_form=form)
def save(self, no_update=False, *args, **kwargs): """Override save method to take care of updated.""" new = not self.id if not new and not no_update: self.updated = datetime.now() # Generate a confirmation_id if necessary if new and not self.confirmation_id: chars = [random.choice(string.ascii_letters) for x in xrange(10)] self.confirmation_id = "".join(chars) super(Question, self).save(*args, **kwargs) if new: # Avoid circular import, events.py imports Question from questions.events import QuestionReplyEvent # Authors should automatically watch their own questions. QuestionReplyEvent.notify(self.creator, self)
def test_autowatch_reply(self, get_current): get_current.return_value.domain = 'testserver' user = User.objects.get(username='******') t1, t2 = Question.objects.filter(is_locked=False)[0:2] assert not QuestionReplyEvent.is_notifying(user, t1) assert not QuestionReplyEvent.is_notifying(user, t2) self.client.login(username='******', password='******') s = Setting.objects.create(user=user, name='questions_watch_after_reply', value='True') data = {'content': 'some content'} post(self.client, 'questions.reply', data, args=[t1.id]) assert QuestionReplyEvent.is_notifying(user, t1) s.value = 'False' s.save() post(self.client, 'questions.reply', data, args=[t2.id]) assert not QuestionReplyEvent.is_notifying(user, t2)
def reply(request, question_id): """Post a new answer to a question.""" question = get_object_or_404(Question, pk=question_id) answer_preview = None if question.is_locked: raise PermissionDenied form = AnswerForm(request.POST) # NOJS: delete images if 'delete_images' in request.POST: for image_id in request.POST.getlist('delete_image'): ImageAttachment.objects.get(pk=image_id).delete() return answers(request, question_id=question_id, form=form) # NOJS: upload image if 'upload_image' in request.POST: upload_imageattachment(request, question) return answers(request, question_id=question_id, form=form) if form.is_valid(): answer = Answer(question=question, creator=request.user, content=form.cleaned_data['content']) if 'preview' in request.POST: answer_preview = answer else: answer.save() ct = ContentType.objects.get_for_model(answer) # Move over to the answer all of the images I added to the # reply form up_images = question.images.filter(creator=request.user) up_images.update(content_type=ct, object_id=answer.id) statsd.incr('questions.answer') if Setting.get_for_user(request.user, 'questions_watch_after_reply'): QuestionReplyEvent.notify(request.user, question) return HttpResponseRedirect(answer.get_absolute_url()) return answers(request, question_id=question_id, form=form, answer_preview=answer_preview)
def test_watch_replies_smtp_error(self, emailmessage_send): """Watch a question for replies and fail to send email.""" emailmessage_send.side_effect = emailmessage_raise_smtp self.client.logout() r = post( self.client, "questions.watch", {"email": "*****@*****.**", "event_type": "reply"}, args=[self.question.id] ) assert not QuestionReplyEvent.is_notifying("*****@*****.**", self.question), "Watch was created" self.assertContains(r, "Could not send a message to that email")
def reply(request, question_id): """Post a new answer to a question.""" question = get_object_or_404(Question, pk=question_id) answer_preview = None if question.is_locked: raise PermissionDenied form = AnswerForm(request.POST) # NOJS: delete images if 'delete_images' in request.POST: for image_id in request.POST.getlist('delete_image'): ImageAttachment.objects.get(pk=image_id).delete() return answers(request, question_id, form) # NOJS: upload image if 'upload_image' in request.POST: upload_imageattachment(request, question) return answers(request, question_id, form) if form.is_valid(): answer = Answer(question=question, creator=request.user, content=form.cleaned_data['content']) if 'preview' in request.POST: answer_preview = answer else: answer.save() ct = ContentType.objects.get_for_model(answer) # Move over to the answer all of the images I added to the # reply form up_images = question.images.filter(creator=request.user) up_images.update(content_type=ct, object_id=answer.id) statsd.incr('questions.answer') if Setting.get_for_user(request.user, 'questions_watch_after_reply'): QuestionReplyEvent.notify(request.user, question) return HttpResponseRedirect(answer.get_absolute_url()) return answers(request, question_id, form, answer_preview=answer_preview)
def test_answer_notification(self, get_current): """Assert that hitting the watch toggle toggles and that proper mails are sent to anonymous users, registered users, and the question asker.""" # TODO: This test is way too monolithic, and the fixtures encode # assumptions that aren't obvious here. Split this test into about 5, # each of which tests just 1 thing. Consider using instantiation # helpers. get_current.return_value.domain = 'testserver' # An arbitrary registered user (pcraciunoiu) watches: question = self._toggle_watch_question('reply', turn_on=True) # An anonymous user watches: QuestionReplyEvent.notify('*****@*****.**', question) # The question asker (jsocol) watches: QuestionReplyEvent.notify(question.creator, question) # Post a reply self.client.login(username='******', password='******') post(self.client, 'questions.reply', {'content': 'an answer'}, args=[question.id]) answer = Answer.uncached.filter().order_by('-id')[0] # Order of emails is not important. attrs_eq(mail.outbox[0], to=['user47963@nowhere'], subject='%s commented on a Firefox question ' "you're watching" % answer.creator.username) starts_with(mail.outbox[0].body, ANSWER_EMAIL.format(answer=answer.id)) attrs_eq(mail.outbox[1], to=[question.creator.email], subject='%s posted an answer to your question "%s"' % (answer.creator.username, question.title)) starts_with(mail.outbox[1].body, ANSWER_EMAIL_TO_ASKER.format( answer=answer.id)) attrs_eq(mail.outbox[2], to=['*****@*****.**'], subject="%s commented on a Firefox question you're watching" % answer.creator.username) starts_with(mail.outbox[2].body, ANSWER_EMAIL_TO_ANONYMOUS.format( answer=answer.id)) self._toggle_watch_question('reply', turn_on=False)
def test_watch_replies_smtp_error(self, emailmessage_send): """Watch a question for replies and fail to send email.""" emailmessage_send.side_effect = emailmessage_raise_smtp self.client.logout() r = post(self.client, 'questions.watch', {'email': '*****@*****.**', 'event_type': 'reply'}, args=[self.question.id]) assert not QuestionReplyEvent.is_notifying( '*****@*****.**', self.question), 'Watch was created' self.assertContains(r, 'Could not send a message to that email')
def watch_question(request, question_id): """Start watching a question for replies or solution.""" question = get_object_or_404(Question, pk=question_id) form = WatchQuestionForm(request.user, request.POST) # Process the form msg = None if form.is_valid(): user_or_email = request.user if request.user.is_authenticated() else form.cleaned_data["email"] try: if form.cleaned_data["event_type"] == "reply": QuestionReplyEvent.notify(user_or_email, question) else: QuestionSolvedEvent.notify(user_or_email, question) statsd.incr("questions.watches.new") except ActivationRequestFailed: msg = _("Could not send a message to that email address.") # Respond to ajax request if request.is_ajax(): if form.is_valid(): msg = msg or ( _("You will be notified of updates by email.") if request.user.is_authenticated() else _("You should receive an email shortly " "to confirm your subscription.") ) return HttpResponse(json.dumps({"message": msg})) if request.POST.get("from_vote"): tmpl = "questions/includes/question_vote_thanks.html" else: tmpl = "questions/includes/email_subscribe.html" html = jingo.render_to_string(request, tmpl, {"question": question, "watch_form": form}) return HttpResponse(json.dumps({"html": html})) if msg: messages.add_message(request, messages.ERROR, msg) return HttpResponseRedirect(question.get_absolute_url())
def test_watch_replies_smtp_error(self, emailmessage_send): """Watch a question for replies and fail to send email.""" emailmessage_send.side_effect = emailmessage_raise_smtp self.client.logout() r = post(self.client, 'questions.watch', { 'email': '*****@*****.**', 'event_type': 'reply' }, args=[self.question.id]) assert not QuestionReplyEvent.is_notifying( '*****@*****.**', self.question), 'Watch was created' self.assertContains(r, 'Could not send a message to that email')
def test_watch_replies(self, get_current): """Watch a question for replies.""" get_current.return_value.domain = "testserver" self.client.logout() post(self.client, "questions.watch", {"email": "*****@*****.**", "event_type": "reply"}, args=[self.question.id]) assert QuestionReplyEvent.is_notifying("*****@*****.**", self.question), "Watch was not created" attrs_eq(mail.outbox[0], to=["*****@*****.**"], subject="Please confirm your email address") assert "questions/confirm/" in mail.outbox[0].body assert "New answers" in mail.outbox[0].body # Now activate the watch. w = Watch.objects.get() get(self.client, "questions.activate_watch", args=[w.id, w.secret]) assert Watch.objects.get().is_active
def test_watch_replies(self, get_current): """Watch a question for replies.""" get_current.return_value.domain = 'testserver' self.client.logout() post(self.client, 'questions.watch', {'email': '*****@*****.**', 'event_type': 'reply'}, args=[self.question.id]) assert QuestionReplyEvent.is_notifying('*****@*****.**', self.question), ( 'Watch was not created') attrs_eq(mail.outbox[0], to=['*****@*****.**'], subject='Please confirm your email address') assert 'questions/confirm/' in mail.outbox[0].body assert 'New answers' in mail.outbox[0].body # Now activate the watch. w = Watch.objects.get() get(self.client, 'questions.activate_watch', args=[w.id, w.secret]) assert Watch.objects.get().is_active
def unwatch_question(request, question_id): """Stop watching a question.""" question = get_object_or_404(Question, pk=question_id) QuestionReplyEvent.stop_notifying(request.user, question) QuestionSolvedEvent.stop_notifying(request.user, question) return HttpResponseRedirect(question.get_absolute_url())
def test_watch_replies_logged_in(self): """Watch a question for replies (logged in).""" self.client.login(username="******", password="******") user = User.objects.get(username="******") post(self.client, "questions.watch", {"event_type": "reply"}, args=[self.question.id]) assert QuestionReplyEvent.is_notifying(user, self.question), "Watch was not created"