Skip to content

Commit

Permalink
email flows for reputation audits, also some admin QOL
Browse files Browse the repository at this point in the history
  • Loading branch information
daxaxelrod committed Dec 31, 2023
1 parent 5aacbc7 commit c26103d
Show file tree
Hide file tree
Showing 12 changed files with 274 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ export default function RatingDetails({
const [api, contextHolder] = notification.useNotification();

const requestAudit = async () => {
const response = await createReputationAudit(currentUser.id);
if (response?.status === 200) {
try {
const response = await createReputationAudit(currentUser.id);
api.success({
message: "Success",
description:
"Your request has been sent. You will be notified when the audit is complete.",
response.data.message || "Successfully requested audit.",
});
} else {
} catch (error) {
api.error({
message: "Error",
description: "Your request could not be sent.",
Expand Down
12 changes: 10 additions & 2 deletions gatherer/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
class PolicyLineProperty(models.Model):
name = models.CharField(max_length=255)
description = models.TextField(blank=True, null=True)
search_tags = models.TextField(blank=True, null=True, help_text="comma separated")
search_tags = models.TextField(
blank=True, null=True, help_text="comma separated")

image_url = models.URLField(blank=True, null=True)

Expand Down Expand Up @@ -78,6 +79,9 @@ class PropertyLifeExpectancyGuess(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

class Meta:
verbose_name_plural = "Property life expectancy guesses"

def __str__(self):
return f"{self.property_type} ${self.purchase_price} loss rate: {self.yearly_loss_rate_bsp} bsp/year"

Expand All @@ -91,7 +95,8 @@ class PropertyLifeLossGuess(models.Model):
help_text="in basis points, 0 - 10000",
validators=[MinValueValidator(0), MaxValueValidator(10000)],
)
loss_amount = models.IntegerField(help_text="The cost of the loss in cents")
loss_amount = models.IntegerField(
help_text="The cost of the loss in cents")
loss_reason = models.CharField(max_length=1024)
category = models.CharField(
max_length=255, choices=LOSS_REASON_CHOICES, null=True, blank=True
Expand All @@ -100,5 +105,8 @@ class PropertyLifeLossGuess(models.Model):
PropertyLifeExpectancyGuess, on_delete=models.CASCADE, related_name="losses"
)

class Meta:
verbose_name_plural = "Property life loss guesses"

def __str__(self) -> str:
return f"${self.loss_amount} of {self.guess.property_type} lost on {self.loss_date}"
2 changes: 1 addition & 1 deletion open_insure/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"DIRS": [os.path.join(BASE_DIR, "pods/templates")],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
Expand Down
1 change: 1 addition & 0 deletions pods/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ def image_tag(self, obj):


admin.site.register(ReputationDetails)
admin.site.register(ReputationAudit)


admin.site.register(User, UserAdmin)
Expand Down
64 changes: 64 additions & 0 deletions pods/migrations/0027_alter_education_user_reputationaudit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Generated by Django 4.2.6 on 2023-12-31 21:06

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):
dependencies = [
("pods", "0026_regioninfo_user"),
]

operations = [
migrations.AlterField(
model_name="education",
name="user",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="education",
to=settings.AUTH_USER_MODEL,
),
),
migrations.CreateModel(
name="ReputationAudit",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("performed_on", models.DateTimeField(auto_now_add=True)),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
(
"latest_reputation",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="audits",
to="pods.reputationdetails",
),
),
(
"performed_by",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="reputation_audits_performed",
to=settings.AUTH_USER_MODEL,
),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="reputation_audits",
to=settings.AUTH_USER_MODEL,
),
),
],
),
]
36 changes: 36 additions & 0 deletions pods/migrations/0028_alter_reputationdetails_options_and_more.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Generated by Django 4.2.6 on 2023-12-31 21:38

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):
dependencies = [
("pods", "0027_alter_education_user_reputationaudit"),
]

operations = [
migrations.AlterModelOptions(
name="reputationdetails",
options={"verbose_name_plural": "Reputation details"},
),
migrations.AlterField(
model_name="reputationaudit",
name="performed_by",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="reputation_audits_performed",
to=settings.AUTH_USER_MODEL,
),
),
migrations.AlterField(
model_name="reputationaudit",
name="performed_on",
field=models.DateTimeField(
blank=True, help_text="When the audit was performed", null=True
),
),
]
60 changes: 39 additions & 21 deletions pods/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ class Pod(models.Model):
blank=True,
related_name="created_pods",
)
members = models.ManyToManyField(User, related_name="pods", through="UserPod")
members = models.ManyToManyField(
User, related_name="pods", through="UserPod")

# related to security and locking down a pod
max_pod_size = models.IntegerField(
Expand Down Expand Up @@ -67,7 +68,8 @@ def __str__(self):

class UserPod(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
pod = models.ForeignKey(Pod, on_delete=models.SET_NULL, null=True, blank=True)
pod = models.ForeignKey(
Pod, on_delete=models.SET_NULL, null=True, blank=True)
risk_penalty = models.IntegerField(
default=0,
help_text="Base penalty for user who is percieved as more risky to the group, in basis points",
Expand Down Expand Up @@ -172,11 +174,13 @@ class Meta:
def __str__(self):
return f"{self.user} has {self.badge}"


class RegionInfo(models.Model):
city = models.CharField(max_length=100)
state = models.CharField(max_length=100)
country = models.CharField(max_length=100)
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="region_info")
user = models.OneToOneField(
User, on_delete=models.CASCADE, related_name="region_info")


class Institution(models.Model):
Expand All @@ -192,25 +196,32 @@ class Institution(models.Model):
class Meta:
abstract = True


class Experience(Institution):
from_date = models.DateField(null=True, blank=True)
to_date = models.DateField(null=True, blank=True)
description = models.TextField(null=True, blank=True)
position_title = models.CharField(max_length=255, null=True, blank=True)
duration = models.CharField(max_length=255, null=True, blank=True)
location = models.CharField(max_length=255, null=True, blank=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="experiences")
user = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="experiences")


class Education(Institution):
from_date = models.DateField(null=True, blank=True)
to_date = models.DateField(null=True, blank=True)
description = models.TextField(null=True, blank=True)
degree = models.CharField(max_length=255, null=True, blank=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="education")
user = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="education")


class Interest(Institution):
title = models.CharField(max_length=255, null=True, blank=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="interests")
user = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="interests")


class ReputationDetails(models.Model):
user = models.ForeignKey(
Expand Down Expand Up @@ -244,9 +255,9 @@ class ReputationDetails(models.Model):

def __str__(self):
return f"Reputation Details of {self.user.email} (calculated on {self.calculated_on.strftime('%m/%d/%Y, %H:%M')})"
verbose_name = "Reputation Details"
verbose_name_plural = "Reputation Details"

class Meta:
verbose_name_plural = "Reputation details"


class ReputationAudit(models.Model):
Expand All @@ -256,28 +267,35 @@ class ReputationAudit(models.Model):
latest_reputation = models.ForeignKey(
ReputationDetails, on_delete=models.CASCADE, related_name="audits"
)
performed_on = models.DateTimeField(auto_now_add=True)
performed_on = models.DateTimeField(
null=True, blank=True, help_text="When the audit was performed"
)
performed_by = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="reputation_audits_performed"
User,
on_delete=models.CASCADE,
related_name="reputation_audits_performed",
null=True,
blank=True,
)

# maybe a field or two related to email notifications?

created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

class Meta:
constraints = [
models.CheckConstraint(
check=Q(performed_by__is_staff=True) | Q(performed_by__is_superuser=True),
name='admin_or_superuser_constraint',
)
]
def __str__(self):
performed_on_str = ""
if self.performed_on:
performed_on_str = f"(performed on {self.performed_on.strftime('%m/%d/%Y, %H:%M')} by {self.performed_by.email})"
return f"Audit of {self.user.email} {performed_on_str}"

def save(self, *args, **kwargs):
if not self.performed_by.is_staff and not self.performed_by.is_superuser:
raise IntegrityError("Only admins or superusers can perform reputation audits.")
if self.performed_by_id and not self.performed_by.is_staff and not self.performed_by.is_superuser:
raise IntegrityError(
"Only admins or superusers can perform reputation audits.")
super().save(*args, **kwargs)


class EmailSettings(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
updated_at = models.DateTimeField(auto_now=True)
Expand Down
43 changes: 43 additions & 0 deletions pods/reputation/emails.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import logging

from pods.models import ReputationAudit, User


from django.core.mail import EmailMultiAlternatives
from django.conf import settings


from django.template.loader import render_to_string
from django.utils.html import strip_tags


logger = logging.getLogger(__name__)


def notify_user_that_audit_is_complete(
user: User, audit: ReputationAudit
):
profile_url = f"{settings.FRONTEND_URL}/members/{user.id}/"
html_message = render_to_string(
"reputation/audit_completion_notification.html",
{
"user": user,
"audit": audit,
"profile_url": profile_url,
},
)
plain_message = strip_tags(html_message)
from_email = "Open Insure <noreply@openinsure.app>"
to = user.email
subject = f"Audit Completed"

message = EmailMultiAlternatives(
subject,
plain_message,
to=[to],
from_email=from_email,
reply_to=[settings.ADMIN_EMAIL],
)
message.attach_alternative(html_message, "text/html")

message.send()
Loading

0 comments on commit c26103d

Please sign in to comment.