From 9b4903e2e6517db534cd475554a2f575d08981ab Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Tue, 18 Oct 2022 13:27:19 -0500 Subject: [PATCH] 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 --- ietf/help/tests_views.py | 6 - ietf/help/urls.py | 5 +- ietf/ietfauth/forms.py | 22 +--- ietf/ietfauth/tests.py | 3 +- ietf/ietfauth/views.py | 19 ---- ietf/person/admin.py | 2 +- ietf/person/migrations/0026_drop_consent.py | 21 ++++ .../0027_personevent_drop_consent.py | 18 +++ ...or.py => 0028_name_character_validator.py} | 4 +- ietf/person/models.py | 30 +---- ietf/person/utils.py | 2 +- ietf/templates/base/menu_user.html | 6 - ietf/templates/help/personal-information.html | 86 --------------- .../registration/confirm_profile_update.html | 6 +- ietf/templates/registration/edit_profile.html | 87 +++++---------- .../utils/personal_information_notice.txt | 36 ------ .../commands/send_gdpr_consent_request.py | 103 ------------------ 17 files changed, 76 insertions(+), 380 deletions(-) create mode 100644 ietf/person/migrations/0026_drop_consent.py create mode 100644 ietf/person/migrations/0027_personevent_drop_consent.py rename ietf/person/migrations/{0026_name_character_validator.py => 0028_name_character_validator.py} (88%) delete mode 100644 ietf/templates/help/personal-information.html delete mode 100644 ietf/templates/utils/personal_information_notice.txt delete mode 100644 ietf/utils/management/commands/send_gdpr_consent_request.py diff --git a/ietf/help/tests_views.py b/ietf/help/tests_views.py index 714dab39e..ee80dad86 100644 --- a/ietf/help/tests_views.py +++ b/ietf/help/tests_views.py @@ -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') diff --git a/ietf/help/urls.py b/ietf/help/urls.py index 9c0bfb157..f1cc625fa 100644 --- a/ietf/help/urls.py +++ b/ietf/help/urls.py @@ -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[-\w]+)/(?P[-\w]+)/?$', views.state), url(r'^state/(?P[-\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'), ] diff --git a/ietf/ietfauth/forms.py b/ietf/ietfauth/forms.py index 851ad02f8..d46ad54db 100644 --- a/ietf/ietfauth/forms.py +++ b/ietf/ietfauth/forms.py @@ -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 + ' ') - 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") diff --git a/ietf/ietfauth/tests.py b/ietf/ietfauth/tests.py index 1651de08d..b6c145e68 100644 --- a/ietf/ietfauth/tests.py +++ b/ietf/ietfauth/tests.py @@ -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') \ No newline at end of file + self.assertFalse(has_role(role.person.user, []), 'has_role() should return False when role_name is empty') diff --git a/ietf/ietfauth/views.py b/ietf/ietfauth/views.py index 8cf21f52c..e405d6620 100644 --- a/ietf/ietfauth/views.py +++ b/ietf/ietfauth/views.py @@ -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 account profile 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 diff --git a/ietf/person/admin.py b/ietf/person/admin.py index 3569c0334..cd8ca2abf 100644 --- a/ietf/person/admin.py +++ b/ietf/person/admin.py @@ -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"] diff --git a/ietf/person/migrations/0026_drop_consent.py b/ietf/person/migrations/0026_drop_consent.py new file mode 100644 index 000000000..2acaad678 --- /dev/null +++ b/ietf/person/migrations/0026_drop_consent.py @@ -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', + ), + ] diff --git a/ietf/person/migrations/0027_personevent_drop_consent.py b/ietf/person/migrations/0027_personevent_drop_consent.py new file mode 100644 index 000000000..e2443596f --- /dev/null +++ b/ietf/person/migrations/0027_personevent_drop_consent.py @@ -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), + ), + ] diff --git a/ietf/person/migrations/0026_name_character_validator.py b/ietf/person/migrations/0028_name_character_validator.py similarity index 88% rename from ietf/person/migrations/0026_name_character_validator.py rename to ietf/person/migrations/0028_name_character_validator.py index 34fb5851f..c7e4a9305 100644 --- a/ietf/person/migrations/0026_name_character_validator.py +++ b/ietf/person/migrations/0028_name_character_validator.py @@ -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 = [ diff --git a/ietf/person/models.py b/ietf/person/models.py index c3c767739..c8982e338 100644 --- a/ietf/person/models.py +++ b/ietf/person/models.py @@ -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"), ] diff --git a/ietf/person/utils.py b/ietf/person/utils.py index 54c2f5dbe..950c19841 100755 --- a/ietf/person/utils.py +++ b/ietf/person/utils.py @@ -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() diff --git a/ietf/templates/base/menu_user.html b/ietf/templates/base/menu_user.html index 1b1e44482..addab232f 100644 --- a/ietf/templates/base/menu_user.html +++ b/ietf/templates/base/menu_user.html @@ -104,12 +104,6 @@ {% endif %} -
  • - - Handling of personal information - -
  • {% endif %} {% if not request.user.is_authenticated %}
  • diff --git a/ietf/templates/help/personal-information.html b/ietf/templates/help/personal-information.html deleted file mode 100644 index d60ee93b6..000000000 --- a/ietf/templates/help/personal-information.html +++ /dev/null @@ -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 %} -

    Personal Information in the Datatracker

    -

    - RFC 3935, "A Mission Statement for the IETF" - lays out - the goal and the mission of the IETF as follows -

    -
    -    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.
    -
    -
    - {% 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 %} -

    - 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. -

    -

    - 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 - "Note Well" - statement, of legitimate interest to the IETF when it pursues - its mission; not only in general terms, but specifically under Article - 6(1) f) of - - EU's General Data Protection Regulation - . -

    -

    - 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. -

    -

    - 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: -

    -
      -
    • Personal photograph or other likeness;
    • -
    • Personal biography;
    • -
    • Personal email addresses not derived from IETF contributions; and
    • -
    • Personal account login information
    • -
    • Personal notification subscriptions
    • -
    -

    - Most of this information can be edited on the individual's - Account Info - 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 - IETF secretariat - to change - or remove the information will be honoured to the extent feasible and - legally permitted. -

    -

    - Please also see the IETF's overall - Statement concerning personal data. -

    -{% endblock %} \ No newline at end of file diff --git a/ietf/templates/registration/confirm_profile_update.html b/ietf/templates/registration/confirm_profile_update.html index af9fc33c9..259b01081 100644 --- a/ietf/templates/registration/confirm_profile_update.html +++ b/ietf/templates/registration/confirm_profile_update.html @@ -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 %} -

    Profile update successful

    +

    Account information update successful

    Your account has been updated to reflect the changes you submitted.

    @@ -14,5 +14,5 @@

    {% endfor %} Edit profile + href="{% url "ietf.ietfauth.views.profile" %}">Edit account information {% endblock %} \ No newline at end of file diff --git a/ietf/templates/registration/edit_profile.html b/ietf/templates/registration/edit_profile.html index bebf8a924..e16f32daa 100644 --- a/ietf/templates/registration/edit_profile.html +++ b/ietf/templates/registration/edit_profile.html @@ -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 %} -

    Profile information for {{ user.person.name }}

    -

    - The information you provide below is used to generate your - public datatracker profile page -

    +

    Your account

    {% csrf_token %} {% bootstrap_form_errors person_form %} @@ -20,9 +16,6 @@

    @@ -34,9 +27,6 @@

    Change password @@ -46,9 +36,6 @@
    {% include "person/photo.html" with person=person %}
    @@ -93,12 +80,20 @@

    {% endif %}
    +
    +
    + +

    + All of the data below {% if person.photo %}and your photo {% endif %} + may be openly published in Datatracker, Meetecho and other IT systems. +

    +
    + {% bootstrap_form person_form layout="horizontal" exclude="ascii_short" %}
    {% if person.personextresource_set.all %} @@ -121,6 +116,14 @@
    +

    + 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. +

    +
    +
    +
    @@ -152,11 +155,6 @@ {% if email.active %}checked{% endif %}> @@ -172,56 +170,23 @@ {% endfor %}
    - +
    {% for role in roles %} {% bootstrap_field role.email_form.email show_label=False %} {% endfor %} - {% 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" %}
    -

    - Personal information requiring consent -

    -

    - 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 - - Article 6(1) (f) - - covering IETF's Legitimate Interest due to the IETF's mission of developing standards - for the internet. See also the page on - - handling - of personal information - . -

    -

    - Personal information which is not derived from your contributions is covered by the EU - - GDPR Article 6(1) (a) - - regarding consent. All such information is visible on this page and shown with the - symbol next to it, or listed on your - notification subscription page. - 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 - the Secretariat - if you wish to update or remove the information. -

    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 JSON blob when - you are logged in. It contains both the Legitimate Interest information and the information - that requires Consent. + you are logged in.

    {% endblock %} @@ -261,10 +226,10 @@ .closest("form") .find(".new-emails"); - $('') + $('') .appendTo(container) .trigger("focus"); - $('
    Remember to submit the form for the new email challenge to be sent.
    ') + $('
    Remember to submit the form for the new email challenge to be sent.
    ') .appendTo(container); }) }); diff --git a/ietf/templates/utils/personal_information_notice.txt b/ietf/templates/utils/personal_information_notice.txt deleted file mode 100644 index 379d8e4ef..000000000 --- a/ietf/templates/utils/personal_information_notice.txt +++ /dev/null @@ -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 %} diff --git a/ietf/utils/management/commands/send_gdpr_consent_request.py b/ietf/utils/management/commands/send_gdpr_consent_request.py deleted file mode 100644 index 2bb08eed1..000000000 --- a/ietf/utils/management/commands/send_gdpr_consent_request.py +++ /dev/null @@ -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 " - # - 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, "", - 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) -
    - {% if email.origin == person.user.username or email.origin == '' %} - - - - {% endif %} {{ email|linkify }} {{ email.origin|default:'(unknown)'|urlize_ietf_docs }}