tangled
alpha
login
or
join now
handle.invalid
/
deletemepleaseeu
Have companies delete your personal data
0
fork
atom
overview
issues
pulls
pipelines
Compare changes
Choose any two refs to compare.
base:
main
feat/improve-deletionrequest-admin-layout
bug/linking-incoming-messages
no tags found
compare:
main
feat/improve-deletionrequest-admin-layout
bug/linking-incoming-messages
no tags found
go
+62
-26
2 changed files
expand all
collapse all
unified
split
app
badactors
admin
deletion_request.py
models.py
+38
-16
app/badactors/admin/deletion_request.py
···
5
from django.contrib import admin, messages
6
from django.shortcuts import redirect
7
from django.urls import reverse, path
0
8
from django.utils.html import format_html
9
from django.utils.translation import ngettext
10
···
42
43
class PendingEmailInline(admin.TabularInline):
44
model = PendingEmail
45
-
fields = ["subject", "status", "created_at", "updated_at"]
46
-
readonly_fields = ["subject", "status", "created_at", "updated_at"]
47
extra = 0
48
can_delete = False
49
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
50
def has_add_permission(self, request, obj=None):
51
return False
52
···
55
template = "admin/badactors/deletionrequest/conversationitem_inline.html"
56
model = ConversationItem
57
fields = [
0
58
"display_message_date",
0
59
"direction",
60
-
"display_message_link",
61
"message_type",
62
-
"requires_manual_review",
63
-
"notes",
64
]
65
-
readonly_fields = ["display_message_date", "direction", "display_message_link"]
0
0
0
0
0
66
can_delete = False
67
extra = 0
68
ordering = ["sequence"]
69
0
0
0
0
0
0
0
70
def display_message_date(self, obj):
71
"""Show message timestamp."""
72
if obj.message and obj.message.processed:
73
-
return obj.message.processed.strftime("%Y-%m-%d %H:%M")
74
return "-"
75
76
display_message_date.short_description = "Date"
···
84
85
display_message_link.short_description = "Message"
86
87
-
def formfield_for_dbfield(self, db_field, request, **kwargs):
88
-
"""Customize form fields."""
89
-
if db_field.name == "notes":
90
-
kwargs["widget"] = admin.widgets.AdminTextareaWidget(
91
-
attrs={"rows": 2, "cols": 40}
92
-
)
93
-
return super().formfield_for_dbfield(db_field, request, **kwargs)
94
-
95
96
class ConversationClassificationInline(admin.StackedInline):
97
model = ConversationClassification
···
128
search_fields = ["company__name", "status", "uuid", "user_email_address"]
129
actions = ["send_confirmation_emails_to_users", "send_gdpr_request"]
130
inlines = [
131
-
LetterOfAuthorityInline,
132
PendingEmailInline,
133
ConversationItemInline,
134
ConversationClassificationInline,
0
135
]
136
137
def display_company(self, obj):
···
5
from django.contrib import admin, messages
6
from django.shortcuts import redirect
7
from django.urls import reverse, path
8
+
from django.utils import formats
9
from django.utils.html import format_html
10
from django.utils.translation import ngettext
11
···
43
44
class PendingEmailInline(admin.TabularInline):
45
model = PendingEmail
46
+
fields = ["link", "created_at", "subject", "status", "updated_at"]
47
+
readonly_fields = ["link", "subject", "status", "created_at", "updated_at"]
48
extra = 0
49
can_delete = False
50
51
+
def link(self, obj):
52
+
if obj.pk:
53
+
link = reverse("admin:badactors_pendingemail_change", args=[obj.pk])
54
+
return format_html('<a href="{}">PE-{}</a>', link, obj.pk)
55
+
return "-"
56
+
57
+
link.short_description = "PendingEmail"
58
+
59
+
def get_queryset(self, request):
60
+
qs = super().get_queryset(request)
61
+
return qs.filter(
62
+
status__in=[
63
+
PendingEmail.Status.DRAFT,
64
+
PendingEmail.Status.READY,
65
+
PendingEmail.Status.APPROVED,
66
+
]
67
+
)
68
+
69
def has_add_permission(self, request, obj=None):
70
return False
71
···
74
template = "admin/badactors/deletionrequest/conversationitem_inline.html"
75
model = ConversationItem
76
fields = [
77
+
"display_message_link",
78
"display_message_date",
79
+
"display_message_subject",
80
"direction",
0
81
"message_type",
0
0
82
]
83
+
readonly_fields = [
84
+
"display_message_date",
85
+
"display_message_subject",
86
+
"direction",
87
+
"display_message_link",
88
+
]
89
can_delete = False
90
extra = 0
91
ordering = ["sequence"]
92
93
+
def display_message_subject(self, obj):
94
+
if obj.message and obj.message.subject:
95
+
return obj.message.subject
96
+
return "-"
97
+
98
+
display_message_subject.short_description = "Subject"
99
+
100
def display_message_date(self, obj):
101
"""Show message timestamp."""
102
if obj.message and obj.message.processed:
103
+
return formats.date_format(obj.message.processed, "DATETIME_FORMAT")
104
return "-"
105
106
display_message_date.short_description = "Date"
···
114
115
display_message_link.short_description = "Message"
116
0
0
0
0
0
0
0
0
117
118
class ConversationClassificationInline(admin.StackedInline):
119
model = ConversationClassification
···
150
search_fields = ["company__name", "status", "uuid", "user_email_address"]
151
actions = ["send_confirmation_emails_to_users", "send_gdpr_request"]
152
inlines = [
0
153
PendingEmailInline,
154
ConversationItemInline,
155
ConversationClassificationInline,
156
+
LetterOfAuthorityInline,
157
]
158
159
def display_company(self, obj):
+24
-10
app/badactors/models.py
···
347
def __str__(self):
348
return f"ConversationItem #{self.sequence} for {self.deletion_request}"
349
0
350
@receiver(message_received)
351
def add_incoming_message_to_dr(sender, message: Message, **kwargs):
352
to_addresses = message.to_addresses
353
try:
354
-
uuids = [address.split("@")[0] for address in to_addresses if "@deletemeplease.eu" in address]
0
0
0
0
355
except (AttributeError, TypeError):
356
-
logger.error(f"Couldn't parse local portion of email addresses {to_addresses}. Aborting")
0
0
357
return
358
359
try:
360
drs = DeletionRequest.objects.filter(uuid__in=uuids)
361
-
except ValidationError:
362
-
logger.warning(f"Found no matching DeletionRequest for incoming email {message} given UUIDs {uuids}. Aborting")
363
-
return
0
0
364
365
if not drs.exists():
366
-
logger.warning(f"Found no matching DeletionRequest for incoming email {message} given UUIDs {uuids}. Aborting")
0
0
367
return
368
if drs.count() > 1:
369
-
logger.error(f"Found multiple matching DeletionRequests for incoming email {message} given UUIDs {uuids}. Aborting")
0
0
370
return
371
372
ConversationItem.objects.create(
373
-
deletion_request = drs.first(),
374
-
message = message,
375
-
direction = MessageDirection.INCOMING.value
376
)
377
0
378
class DeletionRequest(models.Model):
379
"""
380
Model to store information about deletion requests.
···
347
def __str__(self):
348
return f"ConversationItem #{self.sequence} for {self.deletion_request}"
349
350
+
351
@receiver(message_received)
352
def add_incoming_message_to_dr(sender, message: Message, **kwargs):
353
to_addresses = message.to_addresses
354
try:
355
+
uuids = [
356
+
address.split("@")[0]
357
+
for address in to_addresses
358
+
if "@deletemeplease.eu" in address
359
+
]
360
except (AttributeError, TypeError):
361
+
logger.error(
362
+
f"Couldn't parse local portion of email addresses {to_addresses}. Aborting"
363
+
)
364
return
365
366
try:
367
drs = DeletionRequest.objects.filter(uuid__in=uuids)
368
+
except ValidationError:
369
+
logger.warning(
370
+
f"Found no matching DeletionRequest for incoming email {message} given UUIDs {uuids}. Aborting"
371
+
)
372
+
return
373
374
if not drs.exists():
375
+
logger.warning(
376
+
f"Found no matching DeletionRequest for incoming email {message} given UUIDs {uuids}. Aborting"
377
+
)
378
return
379
if drs.count() > 1:
380
+
logger.error(
381
+
f"Found multiple matching DeletionRequests for incoming email {message} given UUIDs {uuids}. Aborting"
382
+
)
383
return
384
385
ConversationItem.objects.create(
386
+
deletion_request=drs.first(),
387
+
message=message,
388
+
direction=MessageDirection.INCOMING.value,
389
)
390
391
+
392
class DeletionRequest(models.Model):
393
"""
394
Model to store information about deletion requests.