def save(self, user, locale, product, product_config, *args, **kwargs): self.instance.creator = user self.instance.locale = locale self.instance.product = product category_config = product_config["categories"][self.cleaned_data["category"]] if category_config: t = category_config.get("topic") if t: self.instance.topic = Topic.objects.get(slug=t, product=product) question = super(NewQuestionForm, self).save(*args, **kwargs) if self.cleaned_data.get("notifications", False): QuestionReplyEvent.notify(question.creator, question) user_ct = ContentType.objects.get_for_model(user) qst_ct = ContentType.objects.get_for_model(question) # Move over to the question all of the images I added to the reply form up_images = ImageAttachment.objects.filter(creator=user, content_type=user_ct) up_images.update(content_type=qst_ct, object_id=question.id) # User successfully submitted a new question question.add_metadata(**self.cleaned_metadata) if product_config: # TODO: This add_metadata call should be removed once we are # fully IA-driven (sync isn't special case anymore). question.add_metadata(product=product_config["key"]) # The first time a question is saved, automatically apply some tags: question.auto_tag() return question
def setUp(self): p = profile() p.save() self.user = p.user self.client.login(username=self.user.username, password='******') self.question = question(creator=self.user, save=True) QuestionReplyEvent.notify(self.user, self.question)
def reply(request, question_id): """Post a new answer to a question.""" question = get_object_or_404(Question, pk=question_id, is_spam=False) answer_preview = None if not question.allows_new_answer(request.user): raise PermissionDenied form = AnswerForm(request.POST, **{"user": request.user, "question": question}) # 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 question_details(request, question_id=question_id, form=form) # NOJS: upload image if "upload_image" in request.POST: upload_imageattachment(request, request.user) return question_details(request, question_id=question_id, form=form) if form.is_valid() and not request.limited: answer = Answer( question=question, creator=request.user, content=form.cleaned_data["content"], ) if "preview" in request.POST: answer_preview = answer else: if form.cleaned_data.get("is_spam"): _add_to_moderation_queue(request, answer) else: answer.save() ans_ct = ContentType.objects.get_for_model(answer) # Move over to the answer all of the images I added to the # reply form user_ct = ContentType.objects.get_for_model(request.user) up_images = ImageAttachment.objects.filter(creator=request.user, content_type=user_ct) up_images.update(content_type=ans_ct, object_id=answer.id) # Handle needsinfo tag if "needsinfo" in request.POST: question.set_needs_info() elif "clear_needsinfo" in request.POST: question.unset_needs_info() if Setting.get_for_user(request.user, "questions_watch_after_reply"): QuestionReplyEvent.notify(request.user, question) return HttpResponseRedirect(answer.get_absolute_url()) return question_details( request, question_id=question_id, form=form, answer_preview=answer_preview )
def test_notify_arbitrary_reply_to(self): """ Test that notifications to the asker have a correct reply to field. """ watcher = user(save=True) QuestionReplyEvent.notify(watcher, self.question) self.makeAnswer() notification = [m for m in mail.outbox if m.to == [watcher.email]][0] # Headers should be compared case-insensitively. headers = dict((k.lower(), v) for k, v in notification.extra_headers.items()) eq_("*****@*****.**", headers["reply-to"])
def test_notify_anonymous_reply_to(self): """ Test that notifications to the asker have a correct reply to field. """ ANON_EMAIL = "*****@*****.**" QuestionReplyEvent.notify(ANON_EMAIL, self.question) self.makeAnswer() notification = [m for m in mail.outbox if m.to == [ANON_EMAIL]][0] # Headers should be compared case-insensitively. headers = dict((k.lower(), v) for k, v in notification.extra_headers.items()) eq_("*****@*****.**", headers["reply-to"])
def test_notify_arbitrary_reply_to(self): """ Test that notifications to the asker have a correct reply to field. """ watcher = user(save=True) QuestionReplyEvent.notify(watcher, self.question) self.makeAnswer() notification = [m for m in mail.outbox if m.to == [watcher.email]][0] # Headers should be compared case-insensitively. headers = dict((k.lower(), v) for k, v in notification.extra_headers.items()) eq_('*****@*****.**', headers['reply-to'])
def test_notify_anonymous_reply_to(self): """ Test that notifications to the asker have a correct reply to field. """ ANON_EMAIL = '*****@*****.**' QuestionReplyEvent.notify(ANON_EMAIL, self.question) self.makeAnswer() notification = [m for m in mail.outbox if m.to == [ANON_EMAIL]][0] # Headers should be compared case-insensitively. headers = dict((k.lower(), v) for k, v in notification.extra_headers.items()) eq_('*****@*****.**', headers['reply-to'])
def test_notify_arbitrary(self): """Test that arbitrary users are notified of new answers.""" watcher = UserFactory() QuestionReplyEvent.notify(watcher, self.question) self.makeAnswer() # One for the asker's email, and one for the watcher's email. eq_(2, len(mail.outbox)) notification = [m for m in mail.outbox if m.to == [watcher.email]][0] eq_([watcher.email], notification.to) eq_(u'Re: {0}'.format(self.question.title), notification.subject) body = re.sub(r'auth=[a-zA-Z0-9%_-]+', 'auth=AUTH', notification.body) starts_with(body, ANSWER_EMAIL.format(to_user=display_name(watcher), **self.format_args()))
def test_notify_arbitrary(self): """Test that arbitrary users are notified of new answers.""" watcher = user(save=True) QuestionReplyEvent.notify(watcher, self.question) self.makeAnswer() # One for the asker's email, and one for the watcher's email. eq_(2, len(mail.outbox)) notification = [m for m in mail.outbox if m.to == [watcher.email]][0] eq_([watcher.email], notification.to) eq_("Re: {0}".format(self.question.title), notification.subject) body = re.sub(r'auth=[a-zA-Z0-9%_-]+', 'auth=AUTH', notification.body) starts_with(body, ANSWER_EMAIL.format(to_user=display_name(watcher), **self.format_args()))
def test_notify_anonymous(self): """Test that anonymous users are notified of new answers.""" ANON_EMAIL = "*****@*****.**" QuestionReplyEvent.notify(ANON_EMAIL, self.question) self.makeAnswer() # One for the asker's email, and one for the anonymous email. eq_(2, len(mail.outbox)) notification = [m for m in mail.outbox if m.to == [ANON_EMAIL]][0] eq_([ANON_EMAIL], notification.to) eq_("Re: {0}".format(self.question.title), notification.subject) body = re.sub(r"auth=[a-zA-Z0-9%_-]+", "auth=AUTH", notification.body) starts_with(body, ANSWER_EMAIL_TO_ANONYMOUS.format(**self.format_args()))
def test_notify_arbitrary(self): """Test that arbitrary users are notified of new answers.""" watcher = user(save=True) QuestionReplyEvent.notify(watcher, self.question) self.makeAnswer() # One for the asker's email, and one for the watcher's email. eq_(2, len(mail.outbox)) notification = [m for m in mail.outbox if m.to == [watcher.email]][0] eq_([watcher.email], notification.to) eq_("Re: {0}".format(self.question.title), notification.subject) body = re.sub(r"auth=[a-zA-Z0-9%_-]+", "auth=AUTH", notification.body) starts_with(body, ANSWER_EMAIL.format(to_user=watcher.username, **self.format_args()))
def save(self, update=False, *args, **kwargs): """Override save method to take care of updated if requested.""" new = not self.id if not new: self.clear_cached_html() if update: self.updated = datetime.now() super(Question, self).save(*args, **kwargs) if new: # Avoid circular import, events.py imports Question from kitsune.questions.events import QuestionReplyEvent # Authors should automatically watch their own questions. QuestionReplyEvent.notify(self.creator, self)
def test_notify_anonymous(self): """Test that anonymous users are notified of new answers.""" ANON_EMAIL = '*****@*****.**' QuestionReplyEvent.notify(ANON_EMAIL, self.question) self.makeAnswer() # One for the asker's email, and one for the anonymous email. eq_(2, len(mail.outbox)) notification = [m for m in mail.outbox if m.to == [ANON_EMAIL]][0] eq_([ANON_EMAIL], notification.to) eq_("Re: {0}".format(self.question.title), notification.subject) body = re.sub(r'auth=[a-zA-Z0-9%_-]+', 'auth=AUTH', notification.body) starts_with(body, ANSWER_EMAIL_TO_ANONYMOUS .format(**self.format_args()))
def watch_question(request, question_id): """Start watching a question for replies or solution.""" question = get_object_or_404(Question, pk=question_id, is_spam=False) 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 = render_to_string(tmpl, context={ "question": question, "watch_form": form }, request=request) return HttpResponse(json.dumps({"html": html})) if msg: messages.add_message(request, messages.ERROR, msg) return HttpResponseRedirect(question.get_absolute_url())
def test_notify_anonymous(self): """Test that anonymous users are notified of new answers.""" ANON_EMAIL = '*****@*****.**' QuestionReplyEvent.notify(ANON_EMAIL, self.question) self.makeAnswer() # One for the asker's email, and one for the anonymous email. eq_(2, len(mail.outbox)) notification = [m for m in mail.outbox if m.to == [ANON_EMAIL]][0] eq_([ANON_EMAIL], notification.to) eq_("{0} commented on a Firefox question you're watching" .format(self.answer.creator.username), notification.subject) body = re.sub(r'auth=[a-zA-Z0-9%_-]+', 'auth=AUTH', notification.body) starts_with(body, ANSWER_EMAIL_TO_ANONYMOUS .format(**self.format_args()))
def test_notify_unique_auth_tokens(self, email_mock): """Test that arbitrary users get unique auth tokens.""" auth_backend = TokenLoginBackend() auth_re = re.compile(r'auth=([a-zA-Z0-9%_-]+)') watcher = UserFactory() QuestionReplyEvent.notify(watcher, self.question) asker_id = self.question.creator.id self.makeAnswer() def get_auth_token(ctx): return auth_re.search(ctx['answer_url']).group(1).replace('%3D', '=') eq_(email_mock.call_count, 2) all_calls = email_mock.call_args_list for call in all_calls: ctx = call[1]['context_vars'] user = ctx['to_user'] if user.id == asker_id: auth = get_auth_token(ctx) eq_(user, auth_backend.authenticate(auth)) else: assert auth_re.search(ctx['answer_url']) is None
def setUp(self): super(TestAnswerNotifications, self).setUp() self._get_current_mock = mock.patch.object(Site.objects, 'get_current') self._get_current_mock.start().return_value.domain = 'testserver' self.question = QuestionFactory() QuestionReplyEvent.notify(self.question.creator, self.question)
def setUp(self): self.user = UserFactory() self.client.login(username=self.user.username, password='******') self.question = QuestionFactory(creator=self.user) QuestionReplyEvent.notify(self.user, self.question)
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 watches: watcher = user(save=True) q = self._toggle_watch_question('reply', watcher, turn_on=True) # An anonymous user watches: QuestionReplyEvent.notify('*****@*****.**', q) # The question asker watches: QuestionReplyEvent.notify(q.creator, q) # Post a reply replier = user(save=True) self.client.login(username=replier.username, password='******') post(self.client, 'questions.reply', {'content': 'an answer'}, args=[q.id]) a = Answer.uncached.filter().order_by('-id')[0] # Order of emails is not important. eq_(3, len(mail.outbox)) emails_to = [m.to[0] for m in mail.outbox] i = emails_to.index(watcher.email) attrs_eq(mail.outbox[i], to=[watcher.email], subject='%s commented on a Firefox question ' "you're watching" % a.creator.username) body = mail.outbox[i].body body = re.sub(r'auth=[a-zA-Z0-9%_-]+', r'auth=AUTH', body) starts_with(body, ANSWER_EMAIL.format( to_user=watcher.username, title=q.title, content=a.content, replier=replier.username, question_id=q.id, answer_id=a.id)) i = emails_to.index(q.creator.email) attrs_eq(mail.outbox[i], to=[q.creator.email], subject='%s posted an answer to your question "%s"' % (a.creator.username, q.title)) body = mail.outbox[i].body body = re.sub(r'auth=[a-zA-Z0-9%_-]+', r'auth=AUTH', body) starts_with(body, ANSWER_EMAIL_TO_ASKER.format( asker=q.creator.username, title=q.title, content=a.content, replier=replier.username, question_id=q.id, answer_id=a.id)) i = emails_to.index('*****@*****.**') attrs_eq(mail.outbox[i], to=['*****@*****.**'], subject="%s commented on a Firefox question you're watching" % a.creator.username) body = mail.outbox[i].body body = re.sub(r'auth=[a-zA-Z0-9%_-]+', r'auth=AUTH', body) starts_with(body, ANSWER_EMAIL_TO_ANONYMOUS.format( title=q.title, content=a.content, replier=replier.username, question_id=q.id, answer_id=a.id))
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 watches: watcher = user(save=True) q = self._toggle_watch_question('reply', watcher, turn_on=True) # An anonymous user watches: QuestionReplyEvent.notify('*****@*****.**', q) # The question asker watches: QuestionReplyEvent.notify(q.creator, q) # Post a reply replier = user(save=True) self.client.login(username=replier.username, password='******') post(self.client, 'questions.reply', {'content': 'an answer'}, args=[q.id]) a = Answer.uncached.filter().order_by('-id')[0] # Order of emails is not important. eq_(3, len(mail.outbox)) emails_to = [m.to[0] for m in mail.outbox] i = emails_to.index(watcher.email) attrs_eq(mail.outbox[i], to=[watcher.email], subject='%s commented on a Firefox question ' "you're watching" % a.creator.username) starts_with( mail.outbox[i].body, ANSWER_EMAIL.format(to_user=watcher.username, title=q.title, content=a.content, replier=replier.username, question_id=q.id, answer_id=a.id)) i = emails_to.index(q.creator.email) attrs_eq(mail.outbox[i], to=[q.creator.email], subject='%s posted an answer to your question "%s"' % (a.creator.username, q.title)) starts_with( mail.outbox[i].body, ANSWER_EMAIL_TO_ASKER.format(asker=q.creator.username, title=q.title, content=a.content, replier=replier.username, question_id=q.id, answer_id=a.id)) i = emails_to.index('*****@*****.**') attrs_eq(mail.outbox[i], to=['*****@*****.**'], subject="%s commented on a Firefox question you're watching" % a.creator.username) starts_with( mail.outbox[i].body, ANSWER_EMAIL_TO_ANONYMOUS.format(title=q.title, content=a.content, replier=replier.username, question_id=q.id, answer_id=a.id))