Exemple #1
0
    def perform_mutate(cls, form, info):
        request = info.context
        form_data = RhFormInfo.get_dict_from_request(request)
        if form_data is None:
            cls.log(info, "User has not completed the rental history form, aborting mutation.")
            return cls.make_error("You haven't completed all the previous steps yet.")

        rhr = models.RentalHistoryRequest(**form_data)
        rhr.set_user(request.user)
        rhr.full_clean()
        rhr.save()
        slack.sendmsg_async(get_slack_notify_text(rhr), is_safe=True)

        first_name: str = form_data["first_name"]
        last_name: str = form_data["last_name"]
        email_dhcr.send_email_to_dhcr(
            first_name,
            last_name,
            form_data["address"],
            BOROUGH_CHOICES.get_label(form_data["borough"]),
            form_data["zipcode"],
            form_data["apartment_number"]
        )
        trigger_followup_campaign_async(
            f"{first_name} {last_name}",
            form_data["phone_number"],
            "RH"
        )
        RhFormInfo.clear_from_request(request)
        return cls.mutation_success()
Exemple #2
0
def get_answers_and_documents_and_notify(token_id: str) -> None:
    '''
    Attempt to generate a user's HP Action packet, and send related notifications
    to interested parties.
    '''

    token = UploadToken.objects.find_unexpired(token_id)
    assert token is not None
    user = token.user
    hdinfo = user_to_hpactionvars(user)
    docs = get_answers_and_documents(token, hdinfo)
    if docs is not None:
        airtable.sync.sync_user(user)
        user.send_sms_async(
            f"{get_site_name()} here! Follow this link to your completed "
            f"HP Action legal forms. You will need to print these "
            f"papers before bringing them to court! "
            f"{absolute_reverse('hpaction:latest_pdf')}",
        )
        user.trigger_followup_campaign_async("HP")
        slack.sendmsg_async(
            f"{slack.hyperlink(text=user.first_name, href=user.admin_url)} "
            f"has generated HP Action legal forms!",
            is_safe=True
        )
Exemple #3
0
def get_answers_and_documents_and_notify(token_id: str) -> None:
    """
    Attempt to generate a user's HP Action packet, and send related notifications
    to interested parties.
    """

    token = UploadToken.objects.find_unexpired(token_id)
    assert token is not None
    user = token.user
    kind: str = token.kind
    hdinfo = user_to_hpactionvars(user, kind)
    docs = get_answers_and_documents(token, hdinfo)
    if docs is not None:
        airtable.sync.sync_user(user)
        if kind != HP_ACTION_CHOICES.EMERGENCY:
            user.send_sms_async(
                f"{get_site_name()} here! Follow this link to your completed "
                f"HP Action legal forms. You will need to print these "
                f"papers before bringing them to court! "
                f"{absolute_reverse('hpaction:latest_pdf', kwargs={'kind': docs.kind})}",
            )
            user.trigger_followup_campaign_async("HP")
        label = HP_ACTION_CHOICES.get_label(kind)
        slack.sendmsg_async(
            f"{slack.hyperlink(text=user.best_first_name, href=user.admin_url)} "
            f"has generated {label} legal forms!",
            is_safe=True,
        )
Exemple #4
0
 def perform_mutate(cls, form, info: ResolveInfo):
     request = info.context
     user = request.user
     recipients = [f.cleaned_data['email'] for f in form.formsets['recipients']]
     cls.send_email(user.pk, recipients)
     slack.sendmsg_async(get_slack_notify_text(user, cls.attachment_name, len(recipients)),
                         is_safe=True)
     return cls(errors=[], recipients=recipients)
Exemple #5
0
 def handle(self, *args, **options):
     link = get_site_hyperlink()
     try:
         self.run_check()
     except Exception:
         self.stdout.write('Health check FAILED! Traceback follows.')
         slack.sendmsg_async(f'Health check for {link} FAILED!',
                             is_safe=True)
         raise
def send_declaration(decl: SubmittedHardshipDeclaration):
    """
    Send the given declaration using whatever information is populated
    in the user's landlord details: that is, if we have the landlord's
    email, then send an email of the declaration, and if we have
    the landlord's mailing address, then send a physical copy
    of the declaration.

    This will also send a copy of the declaration to the user's
    housing court, and to the user themselves.

    If any part of the sending fails, this function can be called
    again and it won't send multiple copies of the declaration.
    """

    pdf_bytes = render_declaration(decl)
    user = decl.user
    ld = user.landlord_details

    if ld.email:
        email_declaration_to_landlord(decl, pdf_bytes)

    if ld.address_lines_for_mailing:
        send_declaration_via_lob(decl, pdf_bytes)

    send_declaration_to_housing_court(decl, pdf_bytes)

    if user.email:
        send_declaration_to_user(decl, pdf_bytes)

    if decl.fully_processed_at is None:
        ef_site_origin = get_site_origin(
            get_site_of_type(SITE_CHOICES.EVICTIONFREE))
        user.chain_sms_async([
            _("%(name)s, you can download a PDF of your completed declaration form by "
              "logging back into your account: %(url)s.") % {
                  "name": user.best_first_name,
                  "url": f"{ef_site_origin}/{user.locale}/login",
              },
            _("For more information about New York’s eviction protections and your "
              "rights as a tenant, visit %(url)s. To get involved in organizing and the "
              "fight to #StopEvictions and #CancelRent, follow us on Twitter at "
              "@RTCNYC and @housing4allNY.") % {
                  "url": "http://bit.ly/EvictionProtectionsNY",
              },
        ])

        slack.sendmsg_async(
            f"{slack.hyperlink(text=user.best_first_name, href=user.admin_url)} "
            f"has sent a hardship declaration! :meowparty:",
            is_safe=True,
        )

        decl.fully_processed_at = timezone.now()
        decl.save()
Exemple #7
0
    def create_serving_papers(self, request, userid):
        self._ensure_lob_integration()
        sender = self._get_serving_papers_sender(userid)
        go_back_href = reverse("admin:hpaction_hpuser_change",
                               args=(sender.pk, ))
        ld = sender.landlord_details

        if request.method == "POST":
            form = ServingPapersForm(request.POST, request.FILES)
            if form.is_valid():
                papers = form.save(commit=False)
                papers.uploaded_by = request.user
                papers.sender = sender
                self._send_papers(papers)
                messages.success(
                    request,
                    "The recipient has been served! See below for more details."
                )
                slack.sendmsg_async(
                    f"{slack.escape(request.user.best_first_name)} has served "
                    f"{slack.hyperlink(text=sender.best_first_name, href=sender.admin_url)}'s "
                    "landlord!",
                    is_safe=True,
                )
                return HttpResponseRedirect(go_back_href)
        else:
            form = ServingPapersForm(initial={
                "name": ld.name,
                **ld.get_address_as_dict(),
            })

        # This makes it easier to manually test the form.
        form.use_required_attribute = False

        # http://www.dmertl.com/blog/?p=116
        fieldsets = [(None, {"fields": form.base_fields})]
        adminform = AdminForm(form, fieldsets, {})

        ctx = {
            **self.site.each_context(request),
            "sender": sender,
            "form": form,
            "adminform": adminform,
            "go_back_href": go_back_href,
        }

        return TemplateResponse(request,
                                "hpaction/admin/create-serving-papers.html",
                                ctx)
Exemple #8
0
 def perform_mutate(cls, form: forms.LetterRequestForm, info: ResolveInfo):
     request = info.context
     lr = form.save()
     if lr.mail_choice == 'WE_WILL_MAIL':
         sync_user_with_airtable(request.user)
         lr.user.send_sms_async(
             f"{get_site_name()} here - we've received your request and will "
             f"update you once the letter has been sent. "
             f"Please allow for 1-2 business days to process.", )
     slack.sendmsg_async(
         f"{slack.hyperlink(text=lr.user.first_name, href=lr.user.admin_url)} "
         f"has completed a letter of complaint with the mail choice "
         f"*{slack.escape(models.LOC_MAILING_CHOICES.get_label(lr.mail_choice))}*!",
         is_safe=True)
     return cls.mutation_success()
def send_letter(letter: models.Letter):
    """
    Send the given letter using whatever information is populated
    in their landlord details: that is, if we have the landlord's
    email, then send an email of the letter, and if we have
    the landlord's mailing address, then send a physical copy
    of the letter.

    If any part of the sending fails, this function can be called
    again and it won't send multiple copies of the letter.
    """

    pdf_bytes = render_multilingual_letter(letter)
    user = letter.user
    ld = user.landlord_details

    if ld.email:
        email_letter_to_landlord(letter, pdf_bytes)

    if ld.address_lines_for_mailing:
        send_letter_via_lob(
            letter,
            pdf_bytes,
            sms_text=USER_CONFIRMATION_TEXT,
            letter_description="No rent letter",
        )

    if user.email:
        email_react_rendered_content_with_attachment(
            SITE_CHOICES.NORENT,
            user,
            NORENT_EMAIL_TO_USER_URL,
            is_html_email=True,
            recipients=[user.email],
            attachment=norent_pdf_response(pdf_bytes),
            # Use the user's preferred locale, since they will be the one
            # reading it.
            locale=user.locale,
        )

    slack.sendmsg_async(
        f"{slack.hyperlink(text=user.best_first_name, href=user.admin_url)} "
        f"has sent a no rent letter!",
        is_safe=True,
    )

    letter.fully_processed_at = timezone.now()
    letter.save()
Exemple #10
0
    def perform_mutate(cls, form, info):
        request = info.context
        scf = get_scaffolding(request)
        if not scaffolding_has_rental_history_request_info(scf):
            cls.log(
                info,
                "User has not completed the rental history form, aborting mutation."
            )
            return cls.make_error(
                "You haven't completed all the previous steps yet.")

        rhr = models.RentalHistoryRequest(
            first_name=scf.first_name,
            last_name=scf.last_name,
            apartment_number=scf.apt_number,
            phone_number=scf.phone_number,
            address=scf.street,
            address_verified=scf.address_verified,
            borough=scf.borough,
            zipcode=scf.zip_code,
        )
        rhr.set_user(request.user)
        rhr.full_clean()
        rhr.save()
        slack.sendmsg_async(get_slack_notify_text(rhr), is_safe=True)

        email = react_render_email(
            SITE_CHOICES.JUSTFIX,
            project.locales.DEFAULT,
            "rh/email-to-dhcr.txt",
            session=request.session,
        )
        email_dhcr.send_email_to_dhcr(email.subject, email.body)
        trigger_followup_campaign_async(
            f"{scf.first_name} {scf.last_name}",
            scf.phone_number,
            "RH",
            locale=translation.get_language_from_request(request,
                                                         check_path=True),
        )

        # Note that we used to purge the scaffolding information here, but lots
        # of users go on to create an account after this, and we don't want them
        # to have to re-enter all their information, so we'll keep it around.

        return cls.mutation_success()
Exemple #11
0
def update_envelope_status(de: DocusignEnvelope, event: str) -> None:
    """
    Update the given DocuSign envelope model based on the given
    event that just occured.
    """

    # The actual value of 'event' doesn't seem to be documented anywhere on
    # DocuSign's developer docs, except for the SOAP API documentation, which
    # looks semantically equivalent to the REST API but with camel-cased
    # event names instead of snake-cased ones, and with 'On' prepended to the
    # event names:
    #
    #   https://developers.docusign.com/esign-soap-api/reference/administrative-group/embedded-callback-event-codes
    #
    # Through experimentation this seems to be some of the options:
    #
    #   * 'signing_complete' - User completed signing flow successfully.
    #   * 'viewing_complete' - User viewed the forms. This can be the case if
    #     the user previously signed or declined the forms and now wants to
    #     look at them again.
    #   * 'cancel' - User decided to "finish later". We can create a new recipient
    #     view URL for the same envelope ID and they will be taken to the
    #     point at which they left off (e.g. if they signed in only one of 3
    #     places before clicking "finish later", then that will be the state
    #     they return to).
    #   * 'decline' - User chose "decline to sign".
    #   * 'ttl_expired' - Used if the recipient view URL is visited more than
    #     once.  This should only happen rarely, if ever, because DocuSign
    #     immediately redirects from the super-long recipient view URL to
    #     a shorter, reloadable URL immediately.

    if event == "signing_complete":
        de.status = HP_DOCUSIGN_STATUS_CHOICES.SIGNED
        user = de.docs.user
        slack.sendmsg_async(
            f"{slack.hyperlink(text=user.best_first_name, href=user.admin_url)} "
            f"has signed their Emergency HP Action documents! ❤️",
            is_safe=True,
        )
        user.trigger_followup_campaign_async("EHP")
        send_service_instructions_email(user)
        de.save()
    elif event == "decline":
        de.status = HP_DOCUSIGN_STATUS_CHOICES.DECLINED
        de.save()
Exemple #12
0
    def perform_mutate(cls, form, info: ResolveInfo):
        request = info.context
        user = request.user
        if not user.email:
            return cls.make_error("You have no email address!")

        if not user.is_email_verified:
            return cls.make_error("Your email address is not verified!")

        docs = HPActionDocuments.objects.get_latest_for_user(
            user, HP_ACTION_CHOICES.EMERGENCY)

        if not docs:
            return cls.make_error("You have no HP Action documents to sign!")

        api_client = docusign.core.create_default_api_client()
        de = DocusignEnvelope.objects.filter(docs=docs).first()

        if de is None:
            envelope_definition = hpadocusign.create_envelope_definition_for_hpa(
                docs)
            envelope_id = hpadocusign.create_envelope_for_hpa(
                envelope_definition=envelope_definition, api_client=api_client)
            slack.sendmsg_async(
                f"{slack.hyperlink(text=user.best_first_name, href=user.admin_url)} "
                f"has started the Emergency HP Action signing ceremony! 🔥",
                is_safe=True,
            )
            de = DocusignEnvelope(id=envelope_id, docs=docs)
            de.save()

        return_url = hpadocusign.create_callback_url_for_signing_flow(
            request,
            envelope_id=de.id,
            next_url=absolutify_url(form.cleaned_data["next_url"]),
        )

        url = hpadocusign.create_recipient_view_for_hpa(
            user=user,
            envelope_id=de.id,
            api_client=api_client,
            return_url=return_url,
        )

        return cls(errors=[], redirect_url=url)
Exemple #13
0
def verify_email(request: HttpRequest) -> HttpResponse:
    code = request.GET.get("code", "")
    result, user = verify_code(code)
    if not user:
        if result not in [ev.VERIFY_EXPIRED, ev.VERIFY_INVALID_CODE]:
            logger.warning(f"Unusual verification result: {result}")
        return HttpResponse(
            f"Unfortunately, an error occurred and we were unable "
            f"to verify your account.")
    if result == ev.VERIFY_OK:
        slack.sendmsg_async(
            f"{slack.hyperlink(text=user.best_first_name, href=user.admin_url)} "
            f"has verified their email address!",
            is_safe=True,
        )
    return HttpResponse(
        f"Thank you for verifying your email address! You may now "
        f"close this web page.")
Exemple #14
0
    def perform_mutate(cls, form: forms.OnboardingStep4Form,
                       info: ResolveInfo):
        request = info.context
        phone_number = form.cleaned_data['phone_number']
        password = form.cleaned_data['password'] or None
        prev_steps = cls.__extract_all_step_session_data(request)
        if prev_steps is None:
            cls.log(
                info,
                "User has not completed previous steps, aborting mutation.")
            return cls.make_error(
                "You haven't completed all the previous steps yet.")
        with transaction.atomic():
            user = JustfixUser.objects.create_user(
                username=JustfixUser.objects.generate_random_username(),
                first_name=prev_steps['first_name'],
                last_name=prev_steps['last_name'],
                phone_number=phone_number,
                password=password,
            )

            oi = OnboardingInfo(user=user,
                                **pick_model_fields(OnboardingInfo,
                                                    **prev_steps,
                                                    **form.cleaned_data))
            oi.full_clean()
            oi.save()

        user.send_sms_async(
            f"Welcome to {get_site_name()}, {user.first_name}! "
            f"We'll be sending you notifications from this phone number.", )
        slack.sendmsg_async(
            f"{slack.hyperlink(text=user.first_name, href=user.admin_url)} "
            f"from {slack.escape(oi.borough_label)} has signed up!",
            is_safe=True)

        user.backend = settings.AUTHENTICATION_BACKENDS[0]
        login(request, user)

        for step in SESSION_STEPS:
            step.clear_from_request(request)

        return cls.mutation_success()
Exemple #15
0
    def mail_via_lob(self, request, letterid):
        from .admin import get_lob_nomail_reason

        letter = get_object_or_404(models.LetterRequest, pk=letterid)
        user = letter.user
        lob_nomail_reason = get_lob_nomail_reason(letter)
        is_post = request.method == "POST"
        ctx = {
            **self.base_letter_context(request, letter),
            "title": "Mail letter of complaint via Lob",
            "lob_nomail_reason": lob_nomail_reason,
            "is_post": is_post,
        }

        if not lob_nomail_reason:
            if is_post:
                verifications = signing.loads(request.POST["signed_verifications"])
                response = self._create_letter(request, letter, verifications)
                letter.lob_letter_object = response
                letter.tracking_number = response["tracking_number"]
                letter.letter_sent_at = timezone.now()
                letter.save()
                self._log_letter_action(request, letter, "Mailed the letter via Lob.", CHANGE)
                airtable.sync.sync_user(user)
                slack.sendmsg_async(
                    f"{slack.escape(request.user.best_first_name)} has sent "
                    f"{slack.hyperlink(text=user.best_first_name, href=user.admin_url)}'s "
                    "letter of complaint!",
                    is_safe=True,
                )
            else:
                ctx.update(
                    {
                        **self._get_mail_confirmation_context(user),
                        "landlord_address_details_url": get_ll_addr_details_url(
                            user.landlord_details
                        ),
                    }
                )

        return TemplateResponse(request, "loc/admin/lob.html", ctx)
Exemple #16
0
def complete_onboarding(request, info, password: Optional[str]) -> JustfixUser:
    with transaction.atomic():
        user = JustfixUser.objects.create_user(
            username=JustfixUser.objects.generate_random_username(),
            first_name=info["first_name"],
            last_name=info["last_name"],
            preferred_first_name=info.get("preferred_first_name", ""),
            email=info["email"],
            phone_number=info["phone_number"],
            password=password,
            locale=translation.get_language_from_request(request,
                                                         check_path=True),
        )

        oi = OnboardingInfo(user=user,
                            **pick_model_fields(OnboardingInfo, **info))
        oi.full_clean()
        oi.save()

    partner = referral.get_partner(request)
    via = ""
    if partner:
        partner.users.add(user)
        via = f", via our partner {partner.name}"

    slack.sendmsg_async(
        f"{slack.hyperlink(text=user.best_first_name, href=user.admin_url)} "
        f"from {slack.escape(oi.city)}, {slack.escape(oi.state)} has signed up for "
        f"{slack.escape(SIGNUP_INTENT_CHOICES.get_label(oi.signup_intent))} in "
        f"{slack.escape(LOCALE_CHOICES.get_label(user.locale))}{via}!",
        is_safe=True,
    )

    user.backend = settings.AUTHENTICATION_BACKENDS[0]
    login(request, user)

    return user