Skip to content

Commit

Permalink
Add admin dashboard tab for LATAC letters (#2386)
Browse files Browse the repository at this point in the history
  • Loading branch information
shakao authored Aug 31, 2022
1 parent 28f2381 commit 360d83a
Show file tree
Hide file tree
Showing 6 changed files with 232 additions and 1 deletion.
163 changes: 163 additions & 0 deletions laletterbuilder/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
from django.contrib import admin
from django.db.models import Q, Count
from django.utils.safestring import mark_safe
from django.urls import reverse

from users.models import JustfixUser
from users.action_progress import LALETTERBUILDER_PROGRESS
from users.admin_user_proxy import UserProxyAdmin
from project.util.admin_util import admin_field, never_has_permission
from loc.admin import LandlordDetailsInline
from project.util.lob_django_util import SendableViaLobAdminMixin
from . import models


class LaIssueInline(admin.StackedInline):
model = models.LaIssue
fields = [
"created_at",
"updated_at",
"value",
]
readonly_fields = fields

has_add_permission = never_has_permission

ordering = ["-created_at"]


@admin.register(models.HabitabilityLetter)
class HabitabilityLetterAdmin(admin.ModelAdmin):
fields = [
"mail_choice",
"email_to_landlord",
"created_at",
"letter_emailed_at",
"letter_sent_at",
"fully_processed_at",
]
readonly_fields = ("created_at",)

has_add_permission = never_has_permission

ordering = ["-created_at"]

inlines = (LaIssueInline,)


class HabitabilityLetterInline(admin.StackedInline, SendableViaLobAdminMixin):
model = models.HabitabilityLetter
fields = [
"mail_choice",
"email_to_landlord",
"created_at",
"letter_emailed_at",
"letter_sent_at",
"fully_processed_at",
"lob_integration",
"edit_link",
]
readonly_fields = fields

has_add_permission = never_has_permission

ordering = ["-created_at"]

def edit_link(self, obj=None):
if obj.pk:
url = reverse(
"admin:%s_%s_change" % (obj._meta.app_label, obj._meta.model_name), args=[obj.pk]
)
return mark_safe(
"""<a href="{url}">{text}</a>""".format(
url=url,
text=("Edit %s") % obj._meta.verbose_name,
)
)
return "(Save and continue editing to link)"


class LaletterbuilderUser(JustfixUser):
class Meta:
proxy = True

verbose_name = "User with LATAC letter"

verbose_name_plural = "Users with LATAC letters"

permissions = [
("view_saje_users", "Can view/download user data on behalf of SAJE"),
("view_rttc_users", "Can view/download user data on behalf of RTTC"),
]


LETTERS_MAILED = "_letters_mailed"

LETTERS_EMAILED = "_letters_emailed"


@admin.register(LaletterbuilderUser)
class LaletterbuilderUserAdmin(UserProxyAdmin):
inlines = (
LandlordDetailsInline,
HabitabilityLetterInline,
)

list_display = UserProxyAdmin.list_display + [
"city",
"letters_mailed",
"letters_emailed",
]

@admin_field(
short_description="State",
admin_order_field="onboarding_info__state",
)
def state(self, obj):
if hasattr(obj, "onboarding_info"):
return obj.onboarding_info.state

@admin_field(
short_description="City",
admin_order_field="onboarding_info__city",
)
def city(self, obj) -> str:
return obj.onboarding_info.city

@admin_field(
short_description="Letter mailed",
admin_order_field=LETTERS_MAILED,
)
def letters_mailed(self, obj) -> bool:
return bool(getattr(obj, LETTERS_MAILED))

@admin_field(
short_description="Letter emailed",
admin_order_field=LETTERS_EMAILED,
)
def letters_emailed(self, obj) -> bool:
return bool(getattr(obj, LETTERS_EMAILED))

def get_queryset(self, request):
queryset = super().get_queryset(request)
queryset = queryset.annotate(
**{
LETTERS_MAILED: (
Count(
"laletterbuilder_letters",
distinct=True,
filter=(Q(norent_letters__letter_sent_at__isnull=False)),
)
),
LETTERS_EMAILED: (
Count(
"laletterbuilder_letters",
distinct=True,
filter=(Q(norent_letters__letter_emailed_at__isnull=False)),
)
),
}
)
return queryset

progress_annotation = LALETTERBUILDER_PROGRESS
37 changes: 37 additions & 0 deletions laletterbuilder/migrations/0008_auto_20220824_1844.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Generated by Django 3.2.13 on 2022-08-24 18:44

from django.db import migrations, models
import users.models


class Migration(migrations.Migration):

dependencies = [
('users', '0011_justfixuser_preferred_first_name'),
('laletterbuilder', '0007_alter_habitabilityletter_email_to_landlord'),
]

operations = [
migrations.CreateModel(
name='LaletterbuilderUser',
fields=[
],
options={
'verbose_name': 'User with LATAC letter',
'verbose_name_plural': 'Users with LATAC letters',
'permissions': [('view_saje_users', 'Can view/download user data on behalf of SAJE'), ('view_rttc_users', 'Can view/download user data on behalf of RTTC')],
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('users.justfixuser',),
managers=[
('objects', users.models.JustfixUserManager()),
],
),
migrations.AlterField(
model_name='laissue',
name='value',
field=models.CharField(choices=[('HEALTH__MOLD__BEDROOM', 'Mold'), ('HEALTH__MOLD__LIVING_ROOM', 'Mold'), ('HEALTH__MOLD__DINING_ROOM', 'Mold'), ('HEALTH__MOLD__BATHROOM', 'Mold'), ('HEALTH__MOLD__KITCHEN', 'Mold'), ('HEALTH__MOLD__HALLWAY', 'Mold'), ('HEALTH__MOLD__COMMON_AREAS', 'Mold'), ('HEALTH__PEELING_PAINT__BEDROOM', 'Peeling paint'), ('HEALTH__PEELING_PAINT__LIVING_ROOM', 'Peeling paint'), ('HEALTH__PEELING_PAINT__DINING_ROOM', 'Peeling paint'), ('HEALTH__PEELING_PAINT__BATHROOM', 'Peeling paint'), ('HEALTH__PEELING_PAINT__KITCHEN', 'Peeling paint'), ('HEALTH__PEELING_PAINT__HALLWAY', 'Peeling paint'), ('HEALTH__PEELING_PAINT__COMMON_AREAS', 'Peeling paint'), ('HEALTH__RODENT_INFESTATION__BEDROOM', 'Rodent infestation'), ('HEALTH__RODENT_INFESTATION__LIVING_ROOM', 'Rodent infestation'), ('HEALTH__RODENT_INFESTATION__DINING_ROOM', 'Rodent infestation'), ('HEALTH__RODENT_INFESTATION__BATHROOM', 'Rodent infestation'), ('HEALTH__RODENT_INFESTATION__KITCHEN', 'Rodent infestation'), ('HEALTH__RODENT_INFESTATION__HALLWAY', 'Rodent infestation'), ('HEALTH__RODENT_INFESTATION__COMMON_AREAS', 'Rodent infestation'), ('HEALTH__BUG_INFESTATION__BEDROOM', 'Bug infestation'), ('HEALTH__BUG_INFESTATION__LIVING_ROOM', 'Bug infestation'), ('HEALTH__BUG_INFESTATION__DINING_ROOM', 'Bug infestation'), ('HEALTH__BUG_INFESTATION__BATHROOM', 'Bug infestation'), ('HEALTH__BUG_INFESTATION__KITCHEN', 'Bug infestation'), ('HEALTH__BUG_INFESTATION__HALLWAY', 'Bug infestation'), ('HEALTH__BUG_INFESTATION__COMMON_AREAS', 'Bug infestation'), ('HEALTH__SMOKE_CO2_DETECTOR__BEDROOM', 'Smoke or carbon monoxide detector'), ('HEALTH__SMOKE_CO2_DETECTOR__LIVING_ROOM', 'Smoke or carbon monoxide detector'), ('HEALTH__SMOKE_CO2_DETECTOR__DINING_ROOM', 'Smoke or carbon monoxide detector'), ('HEALTH__SMOKE_CO2_DETECTOR__BATHROOM', 'Smoke or carbon monoxide detector'), ('HEALTH__SMOKE_CO2_DETECTOR__KITCHEN', 'Smoke or carbon monoxide detector'), ('HEALTH__SMOKE_CO2_DETECTOR__HALLWAY', 'Smoke or carbon monoxide detector'), ('HEALTH__SMOKE_CO2_DETECTOR__COMMON_AREAS', 'Smoke or carbon monoxide detector'), ('HEALTH__TRASH_CANS__BROKEN', 'Trash cans'), ('HEALTH__TRASH_CANS__NOT_ENOUGH', 'Trash cans'), ('HEALTH__TRASH_CANS__OTHER', 'Trash cans'), ('UTILITIES__DEFECTIVE_ELECTRICITY__BEDROOM', 'Defective electricity'), ('UTILITIES__DEFECTIVE_ELECTRICITY__LIVING_ROOM', 'Defective electricity'), ('UTILITIES__DEFECTIVE_ELECTRICITY__DINING_ROOM', 'Defective electricity'), ('UTILITIES__DEFECTIVE_ELECTRICITY__BATHROOM', 'Defective electricity'), ('UTILITIES__DEFECTIVE_ELECTRICITY__KITCHEN', 'Defective electricity'), ('UTILITIES__DEFECTIVE_ELECTRICITY__HALLWAY', 'Defective electricity'), ('UTILITIES__DEFECTIVE_ELECTRICITY__COMMON_AREAS', 'Defective electricity'), ('UTILITIES__SHUT_OFF__WATER', 'Utilities shut off'), ('UTILITIES__SHUT_OFF__GAS', 'Utilities shut off'), ('UTILITIES__SHUT_OFF__ELECTRICITY', 'Utilities shut off'), ('UTILITIES__SHUT_OFF__OTHER', 'Utilities shut off'), ('UTILITIES__WATER_LEAK__BATHROOM', 'Water leak'), ('UTILITIES__WATER_LEAK__KITCHEN', 'Water leak'), ('UTILITIES__WATER_LEAK__LAUNDRY_ROOM', 'Water leak'), ('UTILITIES__WATER_LEAK__COMMON_AREAS', 'Water leak'), ('UTILITIES__DEFECTIVE_PLUMBING__BEDROOM', 'Defective plumbing'), ('UTILITIES__DEFECTIVE_PLUMBING__LIVING_ROOM', 'Defective plumbing'), ('UTILITIES__DEFECTIVE_PLUMBING__DINING_ROOM', 'Defective plumbing'), ('UTILITIES__DEFECTIVE_PLUMBING__BATHROOM', 'Defective plumbing'), ('UTILITIES__DEFECTIVE_PLUMBING__KITCHEN', 'Defective plumbing'), ('UTILITIES__DEFECTIVE_PLUMBING__HALLWAY', 'Defective plumbing'), ('UTILITIES__DEFECTIVE_PLUMBING__COMMON_AREAS', 'Defective plumbing'), ('UTILITIES__NO_HOT_WATER__BATHROOM', 'No hot water'), ('UTILITIES__NO_HOT_WATER__KITCHEN', 'No hot water'), ('UTILITIES__NO_HOT_WATER__LAUNDRY_ROOM', 'No hot water'), ('UTILITIES__NO_HOT_WATER__COMMON_AREAS', 'No hot water'), ('BUILDING_AND_SAFETY__HOLES__BEDROOM', 'Holes'), ('BUILDING_AND_SAFETY__HOLES__LIVING_ROOM', 'Holes'), ('BUILDING_AND_SAFETY__HOLES__DINING_ROOM', 'Holes'), ('BUILDING_AND_SAFETY__HOLES__BATHROOM', 'Holes'), ('BUILDING_AND_SAFETY__HOLES__KITCHEN', 'Holes'), ('BUILDING_AND_SAFETY__HOLES__HALLWAY', 'Holes'), ('BUILDING_AND_SAFETY__HOLES__COMMON_AREAS', 'Holes'), ('BUILDING_AND_SAFETY__BROKEN_WINDOWS__BEDROOM', 'Broken/defective windows'), ('BUILDING_AND_SAFETY__BROKEN_WINDOWS__LIVING_ROOM', 'Broken/defective windows'), ('BUILDING_AND_SAFETY__BROKEN_WINDOWS__DINING_ROOM', 'Broken/defective windows'), ('BUILDING_AND_SAFETY__BROKEN_WINDOWS__BATHROOM', 'Broken/defective windows'), ('BUILDING_AND_SAFETY__BROKEN_WINDOWS__KITCHEN', 'Broken/defective windows'), ('BUILDING_AND_SAFETY__BROKEN_WINDOWS__HALLWAY', 'Broken/defective windows'), ('BUILDING_AND_SAFETY__BROKEN_WINDOWS__COMMON_AREAS', 'Broken/defective windows'), ('BUILDING_AND_SAFETY__INSECURE_LOCKS__FRONT_DOOR', 'Broken/insecure locks'), ('BUILDING_AND_SAFETY__INSECURE_LOCKS__BACK_DOOR', 'Broken/insecure locks'), ('BUILDING_AND_SAFETY__INSECURE_LOCKS__BUILDING_MAIN_ENTRANCE', 'Broken/insecure locks'), ('BUILDING_AND_SAFETY__INSECURE_LOCKS__BUILDING_PARKING_ENTRANCE', 'Broken/insecure locks'), ('BUILDING_AND_SAFETY__FAULTY_DOORS__FRONT_DOOR', 'Faulty doors'), ('BUILDING_AND_SAFETY__FAULTY_DOORS__BACK_DOOR', 'Faulty doors'), ('BUILDING_AND_SAFETY__FAULTY_DOORS__BUILDING_MAIN_ENTRANCE', 'Faulty doors'), ('BUILDING_AND_SAFETY__FAULTY_DOORS__BUILDING_PARKING_ENTRANCE', 'Faulty doors'), ('BUILDING_AND_SAFETY__UNSAFE_STAIRS__BUILDING_MAIN_ENTRANCE', 'Unsafe/broken stairs and/or railing'), ('BUILDING_AND_SAFETY__UNSAFE_STAIRS__BUILDING_EXIT', 'Unsafe/broken stairs and/or railing'), ('BUILDING_AND_SAFETY__UNSAFE_STAIRS__FLOOR_LEVEL', 'Unsafe/broken stairs and/or railing'), ('BUILDING_AND_SAFETY__UNSAFE_STAIRS__OTHER', 'Unsafe/broken stairs and/or railing')], help_text='The issue the letter is reporting.', max_length=100),
),
]
9 changes: 9 additions & 0 deletions laletterbuilder/tests/test_admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from users.tests.test_admin_user_proxy import UserProxyAdminTester
from .factories import HabitabilityLetterFactory


class TestLaletterbuilderUserAdmin(UserProxyAdminTester):
list_view_url = "/admin/laletterbuilder/laletterbuilderuser/"

def create_user(self):
return HabitabilityLetterFactory().user
18 changes: 17 additions & 1 deletion users/action_progress.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from loc.models import AccessDate
from hpaction.models import HP_DOCUSIGN_STATUS_CHOICES, DocusignEnvelope
from norent.models import Letter as NorentLetter
from laletterbuilder.models import HabitabilityLetter


NOT_STARTED = "NOT_STARTED"
Expand Down Expand Up @@ -75,6 +76,21 @@ class ProgressAnnotation(NamedTuple):
),
)

PROGRESS_ANNOTATIONS = [LOC_PROGRESS, EHP_PROGRESS, NORENT_PROGRESS, EVICTIONFREE_PROGRESS]
LALETTERBUILDER_PROGRESS = ProgressAnnotation(
"laletterbuilder_progress",
Case(
When(Exists(HabitabilityLetter.objects.filter(user=OuterRef("pk"))), then=Value(COMPLETE)),
When(onboarding_info__agreed_to_laletterbuilder_terms=True, then=Value(IN_PROGRESS)),
default=Value(NOT_STARTED),
),
)

PROGRESS_ANNOTATIONS = [
LOC_PROGRESS,
EHP_PROGRESS,
NORENT_PROGRESS,
EVICTIONFREE_PROGRESS,
LALETTERBUILDER_PROGRESS,
]

# TODO: Add a progress annotation for LALOC
4 changes: 4 additions & 0 deletions users/admin_user_tabs.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def get_user_tabs() -> List["UserTab"]:
from hpaction.admin import HPUser
from evictionfree.admin import EvictionFreeUser
from norent.admin import NorentUser
from laletterbuilder.admin import LaletterbuilderUser

return [
UserTab(JustfixUser, "General", "General user details"),
Expand All @@ -42,6 +43,9 @@ def get_user_tabs() -> List["UserTab"]:
action_progress.EVICTIONFREE_PROGRESS,
),
UserTab(NorentUser, "NoRent", "NoRent.org details", action_progress.NORENT_PROGRESS),
UserTab(
LaletterbuilderUser, "LATAC", "LA TAC details", action_progress.LALETTERBUILDER_PROGRESS
),
]


Expand Down
2 changes: 2 additions & 0 deletions users/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@
*ModelPermissions("norent", "rentperiod").only(add=True, change=True),
*ModelPermissions("norent", "letter").all,
"norent.change_norentuser",
*ModelPermissions("laletterbuilder", "habitabilityletter").all,
"laletterbuilder.change_laletterbuilderuser",
"rh.view_rentalhistoryrequest",
VIEW_TEXT_MESSAGE_PERMISSION,
*ModelPermissions("evictionfree", "submittedhardshipdeclaration").all,
Expand Down

0 comments on commit 360d83a

Please sign in to comment.