Have companies delete your personal data

Compare changes

Choose any two refs to compare.

+62 -26
+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 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 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 = [ 58 "display_message_date", 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"] 66 can_delete = False 67 extra = 0 68 ordering = ["sequence"] 69 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, 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", 81 "message_type", 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 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 = [ 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 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] 355 except (AttributeError, TypeError): 356 - logger.error(f"Couldn't parse local portion of email addresses {to_addresses}. Aborting") 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 364 365 if not drs.exists(): 366 - logger.warning(f"Found no matching DeletionRequest for incoming email {message} given UUIDs {uuids}. Aborting") 367 return 368 if drs.count() > 1: 369 - logger.error(f"Found multiple matching DeletionRequests for incoming email {message} given UUIDs {uuids}. Aborting") 370 return 371 372 ConversationItem.objects.create( 373 - deletion_request = drs.first(), 374 - message = message, 375 - direction = MessageDirection.INCOMING.value 376 ) 377 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.