Add manual data minimisation functionality

parent c0062b4c
......@@ -3,7 +3,7 @@ This module registers admin pages for the models
"""
import csv
import datetime
from django.contrib import admin
from django.contrib import admin, messages
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User
from django.db.models import Q
......@@ -11,7 +11,8 @@ from django.http import HttpResponse
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from members.models import EmailChange
from members import services
from members.models import EmailChange, Member
from . import forms, models
......@@ -93,7 +94,7 @@ class UserAdmin(BaseUserAdmin):
add_form = forms.UserCreationForm
actions = ['address_csv_export', 'student_number_csv_export',
'email_csv_export']
'email_csv_export', 'minimise_data']
inlines = (ProfileInline, MembershipInline,)
list_filter = (MembershipTypeListFilter,
......@@ -161,6 +162,25 @@ class UserAdmin(BaseUserAdmin):
student_number_csv_export.short_description = _('Download student number '
'label for selected users')
def minimise_data(self, request, queryset):
processed = len(services.execute_data_minimisation(
members=Member.objects.filter(pk__in=queryset)))
if processed == 0:
self.message_user(
request,
_('Data minimisation could not be executed '
'for the selected user(s).'),
messages.ERROR
)
else:
self.message_user(
request,
_('Data minimisation was executed '
'for {} user(s).').format(processed),
messages.SUCCESS
)
minimise_data.short_description = _('Minimise data for the selected users')
@admin.register(models.Member)
class MemberAdmin(UserAdmin):
......
from datetime import date
from django.db.models import Q
from django.db.models import Q, Count
from django.utils import timezone
from django.utils.translation import gettext
......@@ -164,17 +164,21 @@ def process_email_change(change_request):
emails.send_email_change_completion_message(change_request)
def execute_data_minimisation(dry_run=False):
def execute_data_minimisation(dry_run=False, members=None):
"""
Clean the profiles of members/users of whom the last membership ended
at least 31 days ago
:param dry_run: does not really remove data if True
:param members: queryset of members to process, optional
:return: list of processed members
"""
members = (Member.objects
.exclude(Q(membership__until__isnull=True) |
Q(membership__until__gt=timezone.now().date()))
if not members:
members = Member.objects
members = (members.annotate(membership_count=Count('membership'))
.exclude((Q(membership__until__isnull=True) |
Q(membership__until__gt=timezone.now().date())) &
Q(membership_count__gt=0))
.distinct()
.prefetch_related('membership_set', 'profile'))
deletion_period = timezone.now().date() - timezone.timedelta(days=31)
......
......@@ -224,77 +224,107 @@ class DataMinimisationTest(TestCase):
@classmethod
def setUpTestData(cls):
cls.member = Member.objects.create(
cls.m1 = Member.objects.create(
username='test1',
first_name='Test1',
last_name='Example',
email='test1@example.org'
)
Profile.objects.create(
user=cls.member,
user=cls.m1,
language='nl',
student_number='s1234567'
)
cls.membership = Membership.objects.create(
user=cls.member,
cls.s1 = Membership.objects.create(
user=cls.m1,
type=Membership.MEMBER,
since=timezone.now().replace(year=2017, month=9, day=1),
until=timezone.now().replace(year=2018, month=9, day=1)
)
cls.m2 = Member.objects.create(
username='test2',
first_name='Test2',
last_name='Example',
email='test2@example.org'
)
Profile.objects.create(
user=cls.m2,
language='nl',
student_number='s7654321'
)
cls.s2 = Membership.objects.create(
user=cls.m2,
type=Membership.MEMBER,
since=timezone.now().replace(year=2017, month=9, day=1),
until=timezone.now().replace(year=2018, month=9, day=1)
)
def test_removes_after_31_days(self):
processed = services.execute_data_minimisation(True)
self.assertEqual(len(processed), 1)
self.assertEqual(processed[0], self.member)
def test_removes_after_31_days_or_no_membership(self):
with self.subTest('Deletes after 31 days'):
processed = services.execute_data_minimisation(True)
self.assertEqual(len(processed), 2)
self.assertEqual(processed[0], self.m1)
self.membership.until = timezone.now().replace(
year=2018, month=11, day=1)
self.membership.save()
processed = services.execute_data_minimisation(True)
self.assertEqual(len(processed), 0)
with self.subTest('Deletes after 31 days'):
self.s1.until = timezone.now().replace(
year=2018, month=11, day=1)
self.s1.save()
processed = services.execute_data_minimisation(True)
self.assertEqual(len(processed), 1)
with self.subTest('Deletes when no memberships'):
self.s1.delete()
processed = services.execute_data_minimisation(True)
self.assertEqual(len(processed), 2)
def test_dry_run(self):
with self.subTest('With dry_run=True'):
services.execute_data_minimisation(True)
self.member.refresh_from_db()
self.assertEqual(self.member.profile.student_number, 's1234567')
self.m1.refresh_from_db()
self.assertEqual(self.m1.profile.student_number, 's1234567')
with self.subTest('With dry_run=False'):
services.execute_data_minimisation(False)
self.member.refresh_from_db()
self.assertIsNone(self.member.profile.student_number)
self.m1.refresh_from_db()
self.assertIsNone(self.m1.profile.student_number)
def test_provided_queryset(self):
processed = services.execute_data_minimisation(True,
members=Member.objects)
self.assertEqual(len(processed), 2)
self.assertEqual(processed[0], self.m1)
def test_does_not_affect_current_members(self):
with self.subTest('Membership ends in future'):
self.membership.until = timezone.now().replace(
self.s1.until = timezone.now().replace(
year=2019, month=9, day=1)
self.membership.save()
self.s1.save()
processed = services.execute_data_minimisation(True)
self.assertEqual(len(processed), 0)
self.assertEqual(len(processed), 1)
with self.subTest('Never ending membership'):
self.membership.until = None
self.membership.save()
self.s1.until = None
self.s1.save()
processed = services.execute_data_minimisation(True)
self.assertEqual(len(processed), 0)
self.membership.until = timezone.now().replace(
self.assertEqual(len(processed), 1)
self.s1.until = timezone.now().replace(
year=2018, month=9, day=1)
self.membership.save()
self.s1.save()
with self.subTest('Newer year membership after expired one'):
m = Membership.objects.create(
user=self.member,
user=self.m1,
type=Membership.MEMBER,
since=timezone.now().replace(year=2018, month=9, day=10),
until=timezone.now().replace(year=2019, month=8, day=31),
)
processed = services.execute_data_minimisation(True)
self.assertEqual(len(processed), 0)
self.assertEqual(len(processed), 1)
m.delete()
with self.subTest('Newer study membership after expired one'):
m = Membership.objects.create(
user=self.member,
user=self.m1,
type=Membership.MEMBER,
since=timezone.now().replace(year=2018, month=9, day=10),
until=None
)
processed = services.execute_data_minimisation(True)
self.assertEqual(len(processed), 0)
self.assertEqual(len(processed), 1)
m.delete()
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment