fix: Fix incorrect "GDPR" features/terminology. Fixes #4521. (#4587)

* fix: remove help/personal-information and the prompt-for-consent email management command.

* fix: remove gdpr treatment except for consent checkbox. Rename Submit.

* fix: drom the consent column from Person and Person.History

* fix: remove the consent boolean. Reorganize the account info form.

* chore: reorder migrations
This commit is contained in:
Robert Sparks 2022-10-18 13:27:19 -05:00 committed by GitHub
parent 66533827e1
commit 9b4903e2e6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 76 additions and 380 deletions

View file

@ -19,9 +19,3 @@ class HelpPageTests(TestCase):
for name in names:
if not '-' in name:
self.assertIn(name, content)
def test_personal_information_help(self):
r = self.client.get('/help/personal-information')
self.assertContains(r, 'personal information')
self.assertContains(r, 'GDPR')

View file

@ -1,6 +1,4 @@
# Copyright The IETF Trust 2013-2018, All Rights Reserved
from django.views.generic import TemplateView
# Copyright The IETF Trust 2013-2022, All Rights Reserved
from ietf.help import views
from ietf.utils.urls import url
@ -9,6 +7,5 @@ urlpatterns = [
url(r'^state/(?P<doc>[-\w]+)/(?P<type>[-\w]+)/?$', views.state),
url(r'^state/(?P<doc>[-\w]+)/?$', views.state),
url(r'^state/?$', views.state_index),
url(r'^personal-information/?$', TemplateView.as_view(template_name='help/personal-information.html'), name='personal-information'),
]

View file

@ -103,10 +103,7 @@ def get_person_form(*args, **kwargs):
class PersonForm(forms.ModelForm):
class Meta:
model = Person
exclude = exclude_list
widgets = {
'consent': forms.widgets.CheckboxInput,
}
exclude = exclude_list
def __init__(self, *args, **kwargs):
super(PersonForm, self).__init__(*args, **kwargs)
@ -120,10 +117,6 @@ def get_person_form(*args, **kwargs):
self.fields['pronouns_selectable'] = forms.MultipleChoiceField(label='Pronouns', choices = [(option, option) for option in ["he/him", "she/her", "they/them"]], widget=forms.CheckboxSelectMultiple, required=False)
for f in ['name', 'ascii', 'ascii_short', 'biography', 'photo', 'photo_thumb', 'pronouns_selectable']:
if f in self.fields:
self.fields[f].label = mark_safe(self.fields[f].label + ' <a href="#pi" aria-label="!"><i class="bi bi-exclamation-circle"></i></a>')
self.unidecoded_ascii = False
if self.data and not self.data.get("ascii", "").strip():
@ -155,19 +148,6 @@ def get_person_form(*args, **kwargs):
prevent_system_name(name)
return ascii_cleaner(name)
def clean_consent(self):
consent = self.cleaned_data.get('consent')
require_consent = (
self.cleaned_data.get('name') != person.name_from_draft
or self.cleaned_data.get('ascii') != person.name_from_draft
or self.cleaned_data.get('biography')
or self.cleaned_data.get('pronouns_selectable')
or self.cleaned_data.get('pronouns_freetext')
)
if consent == False and require_consent:
raise forms.ValidationError("In order to modify your profile with data that require consent, you must permit the IETF to use the uploaded data.")
return consent
def clean(self):
if self.cleaned_data.get("pronouns_selectable") and self.cleaned_data.get("pronouns_freetext"):
self.add_error("pronouns_freetext", "Either select from the pronoun checkboxes or provide a custom value, but not both")

View file

@ -252,7 +252,6 @@ class IetfAuthTests(TestCase):
"pronouns_freetext": "foo/bar",
"affiliation": "Test Org",
"active_emails": email_address,
"consent": True,
}
# edit details - faulty ASCII
@ -1011,4 +1010,4 @@ class UtilsTests(TestCase):
"""has_role is False if role_names is empty"""
role = RoleFactory(name_id='secr', group__acronym='secretariat')
self.assertTrue(has_role(role.person.user, ['Secretariat']), 'Test is broken')
self.assertFalse(has_role(role.person.user, []), 'has_role() should return False when role_name is empty')
self.assertFalse(has_role(role.person.user, []), 'has_role() should return False when role_name is empty')

View file

@ -54,7 +54,6 @@ from django.contrib.auth.views import LoginView
from django.contrib.sites.models import Site
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.urls import reverse as urlreverse
from django.utils.safestring import mark_safe
from django.http import Http404, HttpResponseRedirect, HttpResponseForbidden
from django.shortcuts import render, redirect, get_object_or_404
from django.utils.encoding import force_bytes
@ -699,7 +698,6 @@ def login(request, extra_context=None):
which is not recognized as a valid password hash.
"""
require_consent = []
if request.method == "POST":
form = AuthenticationForm(request, data=request.POST)
username = form.data.get('username')
@ -721,11 +719,6 @@ def login(request, extra_context=None):
user = u2
#
if user:
try:
if user.person and not user.person.consent:
require_consent = user.person.needs_consent()
except ObjectDoesNotExist:
pass
try:
identify_hasher(user.password)
except ValueError:
@ -742,18 +735,6 @@ def login(request, extra_context=None):
except Person.DoesNotExist:
logout(request)
response = render(request, 'registration/missing_person.html')
if require_consent:
messages.warning(request, mark_safe(f'''
You have personal information associated with your account which is not
derived from draft submissions or other ietf work, namely: %s. Please go
to your <a href="{urlreverse("ietf.ietfauth.views.profile")}">account profile</a> and review your
personal information, then scoll to the bottom and check the 'confirm'
checkbox and submit the form, in order to to indicate that that the
provided personal information may be used and displayed within the IETF
datatracker.
''' % ', '.join(require_consent)))
return response
@login_required

View file

@ -36,7 +36,7 @@ class PersonAdmin(simple_history.admin.SimpleHistoryAdmin):
prefix, first, middle, last, suffix = name_parts(obj.name)
return "%s %s" % (first, last)
list_display = ["name", "short", "plain_name", "time", "user", ]
fields = ("user", "time", "name", "plain", "name_from_draft", "ascii", "ascii_short", "pronouns_selectable", "pronouns_freetext", "biography", "photo", "photo_thumb", "consent",)
fields = ("user", "time", "name", "plain", "name_from_draft", "ascii", "ascii_short", "pronouns_selectable", "pronouns_freetext", "biography", "photo", "photo_thumb",)
readonly_fields = ("name_from_draft", )
search_fields = ["name", "ascii"]
raw_id_fields = ["user"]

View file

@ -0,0 +1,21 @@
# Copyright The IETF Trust 2022, All Rights Reserved
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('person', '0025_chat_and_polls_apikey'),
]
operations = [
migrations.RemoveField(
model_name='historicalperson',
name='consent',
),
migrations.RemoveField(
model_name='person',
name='consent',
),
]

View file

@ -0,0 +1,18 @@
# Copyright The IETF Trust 2022, All Rights Reserved
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('person', '0026_drop_consent'),
]
operations = [
migrations.AlterField(
model_name='personevent',
name='type',
field=models.CharField(choices=[('apikey_login', 'API key login'), ('email_address_deactivated', 'Email address deactivated')], max_length=50),
),
]

View file

@ -1,4 +1,4 @@
# Generated by Django 2.2.28 on 2022-10-17 10:18
# Copyright The IETF Trust 2022, All Rights Reserved
from django.db import migrations, models
import ietf.person.models
@ -7,7 +7,7 @@ import ietf.person.models
class Migration(migrations.Migration):
dependencies = [
('person', '0025_chat_and_polls_apikey'),
('person', '0027_personevent_drop_consent'),
]
operations = [

View file

@ -13,7 +13,7 @@ from urllib.parse import urljoin
from django.conf import settings
from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.core.exceptions import ValidationError
from django.core.validators import validate_email
from django.db import models
from django.template.loader import render_to_string
@ -58,7 +58,6 @@ class Person(models.Model):
photo = models.ImageField(storage=NoLocationMigrationFileSystemStorage(), upload_to=settings.PHOTOS_DIRNAME, blank=True, default=None)
photo_thumb = models.ImageField(storage=NoLocationMigrationFileSystemStorage(), upload_to=settings.PHOTOS_DIRNAME, blank=True, default=None)
name_from_draft = models.CharField("Full Name (from submission)", null=True, max_length=255, editable=False, help_text="Name as found in a draft submission.")
consent = models.BooleanField("I hereby give my consent to the use of the personal details I have provided (photo, bio, name, pronouns, email) within the IETF Datatracker", null=True, default=None)
def __str__(self):
return self.plain_name()
@ -194,32 +193,6 @@ class Person(models.Model):
from ietf.doc.models import Document
return Document.objects.filter(documentauthor__person=self, type='draft', states__slug__in=['repl', 'expired', 'auth-rm', 'ietf-rm']).distinct().order_by('-time')
def needs_consent(self):
"""
Returns an empty list or a list of fields which holds information that
requires consent to be given.
"""
needs_consent = []
if self.name != self.name_from_draft:
needs_consent.append("full name")
if self.ascii != self.name_from_draft:
needs_consent.append("ascii name")
if self.biography and not (self.role_set.exists() or self.rolehistory_set.exists()):
needs_consent.append("biography")
if self.user_id:
needs_consent.append("login")
try:
if self.user.communitylist_set.exists():
needs_consent.append("draft notification subscription(s)")
except ObjectDoesNotExist:
pass
for email in self.email_set.all():
if not email.origin.split(':')[0] in ['author', 'role', 'reviewer', 'liaison', 'shepherd', ]:
needs_consent.append("email address(es)")
break
if self.pronouns_freetext or self.pronouns_selectable:
needs_consent.append("pronouns")
return needs_consent
def save(self, *args, **kwargs):
created = not self.pk
@ -428,7 +401,6 @@ class PersonalApiKey(models.Model):
PERSON_EVENT_CHOICES = [
("apikey_login", "API key login"),
("gdpr_notice_email", "GDPR consent request email sent"),
("email_address_deactivated", "Email address deactivated"),
]

View file

@ -40,7 +40,7 @@ def merge_persons(request, source, target, file=sys.stdout, verbose=False):
dedupe_aliases(target)
# copy other attributes
for field in ('ascii','ascii_short', 'biography', 'photo', 'photo_thumb', 'name_from_draft', 'consent'):
for field in ('ascii','ascii_short', 'biography', 'photo', 'photo_thumb', 'name_from_draft'):
if getattr(source,field) and not getattr(target,field):
setattr(target,field,getattr(source,field))
target.save()

View file

@ -104,12 +104,6 @@
</a>
</li>
{% endif %}
<li>
<a class="dropdown-item {% if flavor != 'top' %} text-wrap link-primary{% endif %}"
href="{% url 'personal-information' %}">
Handling of personal information
</a>
</li>
{% endif %}
{% if not request.user.is_authenticated %}
<li>

View file

@ -1,86 +0,0 @@
{# Copyright The IETF Trust 2018-2018, All Rights Reserved #}
{% extends "base.html" %}
{% load origin %}
{% block title %}
Personal Information in the Datatracker
{% endblock %}
{% block content %}
{% origin %}
<h1>Personal Information in the Datatracker</h1>
<p>
<a href="{% url 'ietf.doc.views_doc.document_html' name='rfc3935' %}">RFC 3935, "A Mission Statement for the IETF"</a>
lays out
the goal and the mission of the IETF as follows
</p>
<pre>
The goal of the IETF is to make the Internet work better.
The mission of the IETF is to produce high quality, relevant
technical and engineering documents that influence the way people
design, use, and manage the Internet in such a way as to make the
Internet work better. These documents include protocol standards,
best current practices, and informational documents of various kinds.
</pre>
{% comment %}
The following text has been reviewed by legal counsel (Thomas Zych)
on Thu, 26 Apr 2018 17:24:03 -0400
*** DO NOT CHANGE WITHOUT LEGAL REVIEW ***
{% endcomment %}
<p>
In order to fulfill its mission, the IETF provides various ways to distribute
and discuss Internet-Drafts, and otherwise process them until there is
consensus that they are ready for publication.
</p>
<p>
This makes the information in the content of the draft documents, as well
as contributions related to the draft documents and their processing as
laid out in the
<a href="https://www.ietf.org/about/note-well/">"Note Well"</a>
statement, of legitimate interest to the IETF when it pursues
its mission; not only in general terms, but specifically under Article
6(1)&nbsp;f) of
<a href="https://eur-lex.europa.eu/legal-content/EN/TXT/HTML/?uri=CELEX:32016R0679#d1e1888-1-1">
EU's General Data Protection Regulation
</a>.
</p>
<p>
The datatracker treats all personal information derived from draft documents and
documents published as RFC, as well as personal information derived from
processing draft documents through the IETF procedures, in accordance with
applicable law, including the GDPR. This includes author names,
email addresses and other address information provided in draft documents or as
contact information for IETF roles such as Working Group chairs, secretaries,
Area Directors and other roles.
</p>
<p>
There is however additional personal information held in the datatracker that
is used for other purposes. This information requires the consent of the
individuals whose information is stored and processed which IETF secures
through various means, and the person it relates to may at any time request
its correction or removal. This includes:
</p>
<ul>
<li>Personal photograph or other likeness;</li>
<li>Personal biography;</li>
<li>Personal email addresses not derived from IETF contributions; and</li>
<li>Personal account login information</li>
<li>Personal notification subscriptions</li>
</ul>
<p>
Most of this information can be edited on the individual's
<a href="{% url 'ietf.ietfauth.views.profile' %}">Account Info</a>
page by the individual
after logging in to the account. If the datatracker holds such
information about a person, and they don't have an account, a request to
the
<a href="mailto:{{ settings.SECRETARIAT_ACTION_EMAIL }}">IETF secretariat</a>
to change
or remove the information will be honoured to the extent feasible and
legally permitted.
</p>
<p>
Please also see the IETF's overall
<a href="https://www.ietf.org/privacy-statement/">Statement concerning personal data.</a>
</p>
{% endblock %}

View file

@ -1,10 +1,10 @@
{# Copyright The IETF Trust 2015, All Rights Reserved #}
{% extends "base.html" %}
{% load origin textfilters %}
{% block title %}Profile update successful{% endblock %}
{% block title %}Account information update successful{% endblock %}
{% block content %}
{% origin %}
<h1>Profile update successful</h1>
<h1>Account information update successful</h1>
<p class="my-3">
Your account has been updated to reflect the changes you submitted.
</p>
@ -14,5 +14,5 @@
</p>
{% endfor %}
<a class="btn btn-primary"
href="{% url "ietf.ietfauth.views.profile" %}">Edit profile</a>
href="{% url "ietf.ietfauth.views.profile" %}">Edit account information</a>
{% endblock %}

View file

@ -3,14 +3,10 @@
{% load origin %}
{% load widget_tweaks django_bootstrap5 textfilters ietf_filters %}
{% load person_filters %}
{% block title %}Profile for {{ user }}{% endblock %}
{% block title %}Your account{% endblock %}
{% block content %}
{% origin %}
<h1>Profile information for {{ user.person.name }}</h1>
<p class="my-3">
The information you provide below is used to generate your
<a href="{% url 'ietf.person.views.profile' email_or_name=user.person.email %}">public datatracker profile page</a>
</p>
<h1>Your account</h1>
<form class="form-horizontal" method="post">
{% csrf_token %}
{% bootstrap_form_errors person_form %}
@ -20,9 +16,6 @@
<div class="row mb-3">
<label class="col-sm-2 col-form-label fw-bold">
User name
<a href="#pi" aria-label="!">
<i class="bi bi-exclamation-circle"></i>
</a>
</label>
<div class="col-sm-10">
<p class="form-control-plaintext">
@ -34,9 +27,6 @@
<div class="row mb-3">
<label class="col-sm-2 col-form-label fw-bold">
Password
<a href="#pi" aria-label="!">
<i class="bi bi-exclamation-circle"></i>
</a>
</label>
<div class="col-sm-10">
<a class="btn btn-primary" href="{% url 'ietf.ietfauth.views.change_password' %}">Change password</a>
@ -46,9 +36,6 @@
<div class="row mb-3">
<label class="col-sm-2 col-form-label fw-bold">
Photo
<a href="#pi" aria-label="!">
<i class="bi bi-exclamation-circle"></i>
</a>
</label>
<div class="col-sm-10">{% include "person/photo.html" with person=person %}</div>
</div>
@ -93,12 +80,20 @@
</p>
{% endif %}
</div>
<hr>
<div class="row mb-3">
<label class="col-sm-2 col-form-label fw-bold">
Published information
</label>
<p class="col-sm-10">
All of the data below {% if person.photo %}and your photo {% endif %}
may be openly published in Datatracker, Meetecho and other IT systems.
</p>
</div>
{% bootstrap_form person_form layout="horizontal" exclude="ascii_short" %}
<div class="row mb-3">
<label class="col-sm-2 col-form-label fw-bold">
External resources
<a href="#pi" aria-label="!">
<i class="bi bi-exclamation-circle"></i>
</a>
</label>
<div class="col-sm-10">
{% if person.personextresource_set.all %}
@ -121,6 +116,14 @@
</div>
<div class="row mb-3">
<label class="col-sm-2 col-form-label fw-bold">Email addresses</label>
<p class="col-sm-10">
You can optionally link more email addresses to your account,
which will then ensure that all I-Ds you have published using
different email addresses are all shown against your account.
</p>
</div>
<div class="row mb-3">
<div class="col-sm-2"></div>
<div class="col-sm-10 emails">
<table class="table table-sm">
<thead>
@ -152,11 +155,6 @@
{% if email.active %}checked{% endif %}>
</td>
<td>
{% if email.origin == person.user.username or email.origin == '' %}
<a href="#pi" aria-label="!">
<i class="bi bi-exclamation-circle"></i>
</a>
{% endif %}
{{ email|linkify }}
</td>
<td>{{ email.origin|default:'(unknown)'|urlize_ietf_docs }}</td>
@ -172,56 +170,23 @@
{% endfor %}
<div class="new-emails"></div>
<div>
<button type="button" class="btn btn-primary add-email mb-5">Add new email address</button>
<button type="button" class="btn btn-primary add-email mt-3 mb-5">Add another email address</button>
</div>
{% for role in roles %}
{% bootstrap_field role.email_form.email show_label=False %}
{% endfor %}
</div>
</div>
{% bootstrap_form person_form layout="horizontal" exclude="ascii_short" %}
{% bootstrap_button button_type="submit" content="Submit" extra_classes="offset-sm-2" %}
{% bootstrap_button button_type="submit" content="Apply changes" extra_classes="offset-sm-2" %}
</form>
<div class="form-text mt-5">
<p id="pi">
<b><i class="bi bi-exclamation-circle"></i> Personal information requiring consent</b>
</p>
<p>
Personal information in the datatracker which is derived from your contributions
to the IETF standards development process is covered by the EU General Data Protection
Regulation's
<a href="https://eur-lex.europa.eu/legal-content/EN/TXT/HTML/?uri=CELEX:32016R0679#d1e1888-1-1">
Article 6(1)&nbsp;(f)
</a>
covering IETF's Legitimate Interest due to the IETF's mission of developing standards
for the internet. See also the page on
<a href="{% url 'personal-information' %}">
handling
of personal information
</a>.
</p>
<p>
Personal information which is <i>not</i> derived from your contributions is covered by the EU
<a href="https://eur-lex.europa.eu/legal-content/EN/TXT/HTML/?uri=CELEX:32016R0679#d1e1888-1-1">
GDPR Article 6(1)&nbsp;(a)
</a>
regarding consent. All such information is visible on this page and shown with the
<i class="bi bi-exclamation-circle"></i> symbol next to it, or listed on your
<a href="{% url 'ietf.community.views.view_list' user.username %}">notification subscription page</a>.
Most of this
information can be edited or removed on these pages. There are some exceptions, such
as photos, which currently require an email to
<a href="mailto:{{ settings.SECRETARIAT_SUPPORT_EMAIL }}">the Secretariat</a>
if you wish to update or remove the information.
</p>
<p>
All the information the datatracker has that is coupled to this account and visible
on this page or otherwise related to your work on ietf documents, is also available
to you as a
<a href="{% url 'ietf.api.views.PersonalInformationExportView' %}">JSON blob</a>
when
you are logged in. It contains both the Legitimate Interest information and the information
that requires Consent.
you are logged in.
</p>
</div>
{% endblock %}
@ -261,10 +226,10 @@
.closest("form")
.find(".new-emails");
$('<input class="form-control mb-3" name="new_email" placeholder="You will get a confirmation challenge. To add an address that cannot confirm, contact the secretariat.">')
$('<input class="form-control mb-1" name="new_email" placeholder="You will get a confirmation challenge. To add an address that cannot be confirmed this way, contact the secretariat.">')
.appendTo(container)
.trigger("focus");
$('<div class="float-end text-muted small">Remember to submit the form for the new email challenge to be sent.</div>')
$('<div class="float-end text-muted small mb-3">Remember to submit the form for the new email challenge to be sent.</div>')
.appendTo(container);
})
});

View file

@ -1,36 +0,0 @@
{% load ietf_filters %}{% filter wordwrap:78 %}
Dear {{ person.plain_name }},
We write to address certain personal information stored in your IETF
datatracker profile. Going forward, we will maintain and use your personal
information only with your consent.
If you do nothing in response to this email, the information in your profile
that requires consent ({{ fields|safe }}) will be deleted {{ days }} days from
the date of this email, that is, on {{ date }}. If you later wish to create a
new login, you can do so at
{{ settings.IDTRACKER_BASE_URL }}{% url 'ietf.ietfauth.views.create_account' %}.
If you would like us to continue to maintain and process the information that
requires your consent, please go to
{{ settings.IDTRACKER_BASE_URL }}{% url 'ietf.ietfauth.views.profile' %},
and review and edit the information as desired and confirm your consent to our
continued maintenance and use of your information by checking the 'Consent'
checkbox found at the bottom of the page, and then submit the form.
For information on how personal information is handled in the datatracker, please see
{{ settings.IDTRACKER_BASE_URL }}{% url 'personal-information' %}.
In case you prefer to not follow any email links, due to phishing
considerations, please just go to the datatracker and use the menu
entries to log in or create an account. The links above are provided
for your convenience, but it works just as well to go the datracker
manually and do what's needed.
Please note that you cannot give consent simply by replying to this email;
you must log in to your account and do so there.
Thank You,
The IETF Secretariat
{% endfilter %}

View file

@ -1,103 +0,0 @@
# Copyright The IETF Trust 2018-2020, All Rights Reserved
# -*- coding: utf-8 -*-
import datetime
import time
from django.conf import settings
from django.core.management.base import BaseCommand, CommandError
import debug # pyflakes:ignore
from ietf.person.models import Person, PersonEvent
from ietf.utils.mail import send_mail
class Command(BaseCommand):
help = ("""
Send GDPR consent request emails to persons who have not indicated consent
to having their personal information stored. Each send is logged as a
PersonEvent.
By default email sending happens at a rate of 1 message per second; the
rate can be adjusted with the -r option. At the start of a run, an estimate
is given of how many persons to send to, and how long the run will take.
By default, emails are not sent out if there is less than 6 days since the
previous consent request email. The interval can be adjusted with the -m
option. One effect of this is that it is possible to break of a run and
re-start it with for instance a different rate, without having duplicate
messages go out to persons that were handled in the interrupted run.
""")
def add_arguments(self, parser):
parser.add_argument('-n', '--dry-run', action='store_true', default=False,
help="Don't send email, just list recipients")
parser.add_argument('-d', '--date', help="Date of deletion (mentioned in message)")
parser.add_argument('-m', '--minimum-interval', type=int, default=6,
help="Minimum interval between re-sending email messages, default: %(default)s days")
parser.add_argument('-r', '--rate', type=float, default=1.0,
help='Rate of sending mail, default: %(default)s/s')
parser.add_argument('-R', '--reminder', action='store_true', default=False,
help='Preface the subject with "Reminder:"')
parser.add_argument('user', nargs='*')
def handle(self, *args, **options):
# Don't send copies of the whole bulk mailing to the debug mailbox
if settings.SERVER_MODE == 'production':
settings.EMAIL_COPY_TO = "Email Debug Copy <outbound@ietf.org>"
#
event_type = 'gdpr_notice_email'
# Arguments
# --date
if 'date' in options and options['date'] != None:
try:
date = datetime.datetime.strptime(options['date'], "%Y-%m-%d").date()
except ValueError as e:
raise CommandError('%s' % e)
else:
date = datetime.date.today() + datetime.timedelta(days=30)
days = (date - datetime.date.today()).days
if days <= 1:
raise CommandError('date must be more than 1 day in the future')
# --rate
delay = 1.0/options['rate']
# --minimum_interval
minimum_interval = options['minimum_interval']
latest_previous = datetime.datetime.now() - datetime.timedelta(days=minimum_interval)
# user
self.stdout.write('Querying the database for matching person records ...')
if 'user' in options and options['user']:
persons = Person.objects.filter(user__username__in=options['user'])
else:
exclude = PersonEvent.objects.filter(time__gt=latest_previous, type=event_type)
persons = Person.objects.exclude(consent=True).exclude(personevent__in=exclude)
# Report the size of the run
runtime = persons.count() * delay
self.stdout.write('Sending to %d users; estimated time a bit more than %d:%02d hours' % (persons.count(), runtime//3600, runtime%3600//60))
subject='Personal Information in the IETF Datatracker'
if options['reminder']:
subject = "Reminder: " + subject
for person in persons:
fields = ', '.join(person.needs_consent())
if fields and person.email_set.exists():
if options['dry_run']:
print(("%-32s %-32s %-32s %-32s %s" % (person.email(), person.name_from_draft or '', person.name, person.ascii, fields)).encode('utf8'))
else:
to = [ e.address for e in person.email_set.filter(active=True) ] # pyflakes:ignore
if not to:
to = [ e.address for e in person.email_set.all() ] # pyflakes:ignore
self.stdout.write("Sendimg email to %s" % to)
send_mail(None, to, "<gdprnoreply@ietf.org>",
subject=subject,
template='utils/personal_information_notice.txt',
context={
'date': date, 'days': days, 'fields': fields,
'person': person, 'settings': settings,
},
)
PersonEvent.objects.create(person=person, type='gdpr_notice_email',
desc="Sent GDPR notice email to %s with confirmation deadline %s" % (to, date))
time.sleep(delay)