From e1b783ead98ba0c557237c7cd23af59d71306f4b Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Mon, 8 May 2023 15:03:10 -0300 Subject: [PATCH 001/101] chore: Update requirements.txt for Django 3.0 --- requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index d3989d3f2..b46adf3dd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ # -*- conf-mode -*- -setuptools>=51.1.0 # Require this first, to prevent later errors +setuptools>=51.1.0,<67.5.0 # Require this first, to prevent later errors # argon2-cffi>=21.3.0 # For the Argon2 password hasher option beautifulsoup4>=4.11.1 # Only used in tests @@ -9,7 +9,7 @@ celery>=5.2.6 coverage>=4.5.4,<5.0 # Coverage 5.x moves from a json database to SQLite. Moving to 5.x will require substantial rewrites in ietf.utils.test_runner and ietf.release.views decorator>=5.1.1 defusedxml>=0.7.1 # for TastyPie when using xml; not a declared dependency -Django>=2.2.28,<3.0 +Django<3.1 django-analytical>=3.1.0 django-bootstrap5>=21.3 django-celery-beat>=2.3.0 @@ -46,7 +46,7 @@ mypy>=0.782,<0.790 # Version requirements determined by django-stubs. mysqlclient>=2.1.0 oic>=1.3 # Used only by tests Pillow>=9.1.0 -psycopg2<2.9 +psycopg2>=2.9 pyang>=2.5.3 pyflakes>=2.4.0 pyopenssl>=22.0.0 # Used by urllib3.contrib, which is used by PyQuery but not marked as a dependency From 9fda268853b013e80ca7b5baf532377e095f1653 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Mon, 8 May 2023 15:19:33 -0300 Subject: [PATCH 002/101] fix: Replace `available_attrs` helper (dropped by Django 3.0) --- ietf/ietfauth/utils.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ietf/ietfauth/utils.py b/ietf/ietfauth/utils.py index 029342e7f..702b0199d 100644 --- a/ietf/ietfauth/utils.py +++ b/ietf/ietfauth/utils.py @@ -7,7 +7,7 @@ import oidc_provider.lib.claims -from functools import wraps +from functools import wraps, WRAPPER_ASSIGNMENTS from django.conf import settings from django.contrib.auth import REDIRECT_FIELD_NAME @@ -15,7 +15,6 @@ from django.core.exceptions import PermissionDenied from django.db.models import Q from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404 -from django.utils.decorators import available_attrs from django.utils.http import urlquote import debug # pyflakes:ignore @@ -113,7 +112,7 @@ def passes_test_decorator(test_func, message): error. The test function should be on the form fn(user) -> true/false.""" def decorate(view_func): - @wraps(view_func, assigned=available_attrs(view_func)) + @wraps(view_func, assigned=WRAPPER_ASSIGNMENTS) def inner(request, *args, **kwargs): if not request.user.is_authenticated: return HttpResponseRedirect('%s?%s=%s' % (settings.LOGIN_URL, REDIRECT_FIELD_NAME, urlquote(request.get_full_path()))) From 6d4d09542f6d5dcc627f9af849ff932f7f29624e Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Mon, 8 May 2023 22:55:15 -0300 Subject: [PATCH 003/101] fix: Replace obsolete `curry()` with `functools.partialmethod()` --- ietf/meeting/views.py | 10 +++++----- ietf/secr/telechat/views.py | 7 ++++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py index 28beaa113..87b48a7a2 100644 --- a/ietf/meeting/views.py +++ b/ietf/meeting/views.py @@ -17,6 +17,7 @@ import tempfile from calendar import timegm from collections import OrderedDict, Counter, deque, defaultdict, namedtuple +from functools import partialmethod from urllib.parse import parse_qs, unquote, urlencode, urlsplit, urlunsplit from tempfile import mkstemp from wsgiref.handlers import format_date_time @@ -38,7 +39,6 @@ from django.template import TemplateDoesNotExist from django.template.loader import render_to_string from django.utils import timezone from django.utils.encoding import force_str -from django.utils.functional import curry from django.utils.text import slugify from django.views.decorators.cache import cache_page from django.views.decorators.csrf import ensure_csrf_cookie, csrf_exempt @@ -3210,8 +3210,8 @@ def interim_request(request): if meeting_type in ('single', 'multi-day'): meeting = form.save(date=get_earliest_session_date(formset)) - # need to use curry here to pass custom variable to form init - SessionFormset.form.__init__ = curry( + # need to use partialmethod here to pass custom variable to form init + SessionFormset.form.__init__ = partialmethod( InterimSessionModelForm.__init__, user=request.user, group=group, @@ -3233,7 +3233,7 @@ def interim_request(request): # subsequently dealt with individually elif meeting_type == 'series': series = [] - SessionFormset.form.__init__ = curry( + SessionFormset.form.__init__ = partialmethod( InterimSessionModelForm.__init__, user=request.user, group=group, @@ -3453,7 +3453,7 @@ def interim_request_edit(request, number): group = Group.objects.get(pk=form.data['group']) is_approved = is_interim_meeting_approved(meeting) - SessionFormset.form.__init__ = curry( + SessionFormset.form.__init__ = partialmethod( InterimSessionModelForm.__init__, user=request.user, group=group, diff --git a/ietf/secr/telechat/views.py b/ietf/secr/telechat/views.py index 4dd4fba4e..f13a082f2 100644 --- a/ietf/secr/telechat/views.py +++ b/ietf/secr/telechat/views.py @@ -4,10 +4,11 @@ import datetime +from functools import partialmethod + from django.contrib import messages from django.forms.formsets import formset_factory from django.shortcuts import render, get_object_or_404, redirect -from django.utils.functional import curry import debug # pyflakes:ignore @@ -215,7 +216,7 @@ def doc_detail(request, date, name): initial_state = {'state':doc.get_state(state_type).pk, 'substate':tag} - # need to use curry here to pass custom variable to form init + # need to use partialmethod here to pass custom variable to form init if doc.active_ballot(): ballot_type = doc.active_ballot().ballot_type elif doc.type.slug == 'draft': @@ -223,7 +224,7 @@ def doc_detail(request, date, name): else: ballot_type = BallotType.objects.get(doc_type=doc.type) BallotFormset = formset_factory(BallotForm, extra=0) - BallotFormset.form.__init__ = curry(BallotForm.__init__, ballot_type=ballot_type) + BallotFormset.form.__init__ = partialmethod(BallotForm.__init__, ballot_type=ballot_type) agenda = agenda_data(date=date) header = get_section_header(doc, agenda) From 9fde845719c6f68732f949a3fba6a6600ccebbd9 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Tue, 9 May 2023 11:05:43 -0300 Subject: [PATCH 004/101] chore: Revert psycopg2 dependency Try again with Django 3.2 --- requirements.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index b46adf3dd..05d7e9d16 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,7 +16,6 @@ django-celery-beat>=2.3.0 django-csp>=3.7 django-cors-headers>=3.11.0 django-debug-toolbar>=3.2.4 -django-form-utils>=1.0.3 # Only one use, in the liaisons app. Last release was in 2015. django-markup>=1.5 # Limited use - need to reconcile against direct use of markdown django-oidc-provider>=0.7 django-password-strength>=1.2.1 @@ -46,7 +45,7 @@ mypy>=0.782,<0.790 # Version requirements determined by django-stubs. mysqlclient>=2.1.0 oic>=1.3 # Used only by tests Pillow>=9.1.0 -psycopg2>=2.9 +psycopg2<2.9 pyang>=2.5.3 pyflakes>=2.4.0 pyopenssl>=22.0.0 # Used by urllib3.contrib, which is used by PyQuery but not marked as a dependency From 2cf2a3dee69bbc15dcfac30c8bbaaf7929418389 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Tue, 9 May 2023 11:09:41 -0300 Subject: [PATCH 005/101] chore: Remove django-cookie-delete-with-all-settings.patch Patch no longer applies. Needed until Django 4.2.1, so bring it back if we deploy something earlier than that to production. --- ietf/settings.py | 4 +- ...ango-cookie-delete-with-all-settings.patch | 46 ------------------- 2 files changed, 1 insertion(+), 49 deletions(-) delete mode 100644 patch/django-cookie-delete-with-all-settings.patch diff --git a/ietf/settings.py b/ietf/settings.py index 00e7f292f..cbbc6d418 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -76,7 +76,7 @@ MANAGERS = ADMINS DATABASES = { 'default': { 'NAME': 'datatracker', - 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'ENGINE': 'django.db.backends.postgresql', 'USER': 'ietf', #'PASSWORD': 'somepassword', }, @@ -430,7 +430,6 @@ INSTALLED_APPS = [ 'corsheaders', 'django_markup', 'django_password_strength', - 'form_utils', 'oidc_provider', 'simple_history', 'tastypie', @@ -1123,7 +1122,6 @@ CHECKS_LIBRARY_PATCHES_TO_APPLY = [ 'patch/fix-jwkest-jwt-logging.patch', 'patch/fix-django-password-strength-kwargs.patch', 'patch/add-django-http-cookie-value-none.patch', - 'patch/django-cookie-delete-with-all-settings.patch', 'patch/tastypie-django22-fielderror-response.patch', ] if DEBUG: diff --git a/patch/django-cookie-delete-with-all-settings.patch b/patch/django-cookie-delete-with-all-settings.patch deleted file mode 100644 index 830f031d7..000000000 --- a/patch/django-cookie-delete-with-all-settings.patch +++ /dev/null @@ -1,46 +0,0 @@ ---- django/contrib/messages/storage/cookie.py.orig 2020-08-13 11:10:36.719177122 +0200 -+++ django/contrib/messages/storage/cookie.py 2020-08-13 11:45:23.503463150 +0200 -@@ -92,6 +92,8 @@ - response.delete_cookie( - self.cookie_name, - domain=settings.SESSION_COOKIE_DOMAIN, -+ secure=settings.SESSION_COOKIE_SECURE or None, -+ httponly=settings.SESSION_COOKIE_HTTPONLY or None, - samesite=settings.SESSION_COOKIE_SAMESITE, - ) - ---- django/http/response.py.orig 2020-08-13 11:16:04.060627793 +0200 -+++ django/http/response.py 2020-08-13 11:54:03.482476973 +0200 -@@ -210,12 +210,18 @@ - value = signing.get_cookie_signer(salt=key + salt).sign(value) - return self.set_cookie(key, value, **kwargs) - -- def delete_cookie(self, key, path='/', domain=None, samesite=None): -+ def delete_cookie(self, key, path='/', domain=None, secure=False, httponly=False, samesite=None): - # Most browsers ignore the Set-Cookie header if the cookie name starts - # with __Host- or __Secure- and the cookie doesn't use the secure flag. -- secure = key.startswith(('__Secure-', '__Host-')) -+ if key in self.cookies: -+ domain = self.cookies[key].get('domain', domain) -+ secure = self.cookies[key].get('secure', secure) -+ httponly = self.cookies[key].get('httponly', httponly) -+ samesite = self.cookies[key].get('samesite', samesite) -+ else: -+ secure = secure or key.startswith(('__Secure-', '__Host-')) - self.set_cookie( -- key, max_age=0, path=path, domain=domain, secure=secure, -+ key, max_age=0, path=path, domain=domain, secure=secure, httponly=httponly, - expires='Thu, 01 Jan 1970 00:00:00 GMT', samesite=samesite, - ) - ---- django/contrib/sessions/middleware.py.orig 2020-08-13 12:12:12.401898114 +0200 -+++ django/contrib/sessions/middleware.py 2020-08-13 12:14:52.690520659 +0200 -@@ -39,6 +39,8 @@ - settings.SESSION_COOKIE_NAME, - path=settings.SESSION_COOKIE_PATH, - domain=settings.SESSION_COOKIE_DOMAIN, -+ secure=settings.SESSION_COOKIE_SECURE or None, -+ httponly=settings.SESSION_COOKIE_HTTPONLY or None, - samesite=settings.SESSION_COOKIE_SAMESITE, - ) - else: From 07e26dd52e08ff6dfcfc0a8d39d729f8e576b631 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Tue, 9 May 2023 15:21:50 -0300 Subject: [PATCH 006/101] refactor: Replace deprecated force_text with force_str --- ietf/api/serializer.py | 2 +- ietf/doc/mails.py | 10 +++++----- ietf/doc/models.py | 6 +++--- ietf/doc/templatetags/ietf_filters.py | 5 ++--- ietf/doc/utils_charter.py | 4 ++-- ietf/doc/views_charter.py | 4 ++-- ietf/doc/views_status_change.py | 4 ++-- ietf/group/admin.py | 4 ++-- ietf/ipr/feeds.py | 4 ++-- ietf/ipr/mail.py | 4 ++-- ietf/liaisons/forms.py | 2 +- ietf/nomcom/templatetags/nomcom_tags.py | 4 ++-- ietf/nomcom/views.py | 4 ++-- ietf/person/factories.py | 4 ++-- ietf/submit/mail.py | 4 ++-- ietf/submit/tests.py | 8 ++++---- ietf/sync/rfceditor.py | 4 ++-- ietf/utils/accesstoken.py | 4 ++-- ietf/utils/admin.py | 4 ++-- ietf/utils/mail.py | 10 +++++----- ietf/utils/management/commands/loadrelated.py | 4 ++-- ietf/utils/management/commands/mergedata.py | 4 ++-- 22 files changed, 51 insertions(+), 52 deletions(-) diff --git a/ietf/api/serializer.py b/ietf/api/serializer.py index 9d6cf1ebb..8340fa8be 100644 --- a/ietf/api/serializer.py +++ b/ietf/api/serializer.py @@ -221,7 +221,7 @@ class JsonExportMixin(object): # obj = None # # if obj is None: -# raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_text(self.model._meta.verbose_name), 'key': escape(object_id)}) +# raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_str(self.model._meta.verbose_name), 'key': escape(object_id)}) # # content_type = 'application/json' # return HttpResponse(serialize([ obj ], sort_keys=True, indent=3)[2:-2], content_type=content_type) diff --git a/ietf/doc/mails.py b/ietf/doc/mails.py index ddb2843cc..8f5d0eb67 100644 --- a/ietf/doc/mails.py +++ b/ietf/doc/mails.py @@ -11,7 +11,7 @@ from django.utils.html import strip_tags from django.conf import settings from django.urls import reverse as urlreverse from django.utils import timezone -from django.utils.encoding import force_text +from django.utils.encoding import force_str import debug # pyflakes:ignore from ietf.doc.templatetags.mail_filters import std_level_prompt @@ -175,7 +175,7 @@ def generate_ballot_writeup(request, doc): e.doc = doc e.rev = doc.rev e.desc = "Ballot writeup was generated" - e.text = force_text(render_to_string("doc/mail/ballot_writeup.txt", {'iana': iana, 'doc': doc })) + e.text = force_str(render_to_string("doc/mail/ballot_writeup.txt", {'iana': iana, 'doc': doc })) # caller is responsible for saving, if necessary return e @@ -187,7 +187,7 @@ def generate_ballot_rfceditornote(request, doc): e.doc = doc e.rev = doc.rev e.desc = "RFC Editor Note for ballot was generated" - e.text = force_text(render_to_string("doc/mail/ballot_rfceditornote.txt")) + e.text = force_str(render_to_string("doc/mail/ballot_rfceditornote.txt")) e.save() return e @@ -232,7 +232,7 @@ def generate_last_call_announcement(request, doc): e.doc = doc e.rev = doc.rev e.desc = "Last call announcement was generated" - e.text = force_text(mail) + e.text = force_str(mail) # caller is responsible for saving, if necessary return e @@ -252,7 +252,7 @@ def generate_approval_mail(request, doc): e.doc = doc e.rev = doc.rev e.desc = "Ballot approval text was generated" - e.text = force_text(mail) + e.text = force_str(mail) # caller is responsible for saving, if necessary return e diff --git a/ietf/doc/models.py b/ietf/doc/models.py index ee56c0dc3..2181490c0 100644 --- a/ietf/doc/models.py +++ b/ietf/doc/models.py @@ -23,7 +23,7 @@ from django.urls import reverse as urlreverse from django.contrib.contenttypes.models import ContentType from django.conf import settings from django.utils import timezone -from django.utils.encoding import force_text +from django.utils.encoding import force_str from django.utils.html import mark_safe # type:ignore from django.contrib.staticfiles import finders @@ -1131,7 +1131,7 @@ class DocHistory(DocumentInfo): name = models.CharField(max_length=255) def __str__(self): - return force_text(self.doc.name) + return force_str(self.doc.name) def get_related_session(self): return self.doc.get_related_session() @@ -1193,7 +1193,7 @@ class DocAlias(models.Model): return self.docs.first() def __str__(self): - return u"%s-->%s" % (self.name, ','.join([force_text(d.name) for d in self.docs.all() if isinstance(d, Document) ])) + return u"%s-->%s" % (self.name, ','.join([force_str(d.name) for d in self.docs.all() if isinstance(d, Document) ])) document_link = admin_link("document") class Meta: verbose_name = "document alias" diff --git a/ietf/doc/templatetags/ietf_filters.py b/ietf/doc/templatetags/ietf_filters.py index 332e5ca15..1c5836328 100644 --- a/ietf/doc/templatetags/ietf_filters.py +++ b/ietf/doc/templatetags/ietf_filters.py @@ -13,8 +13,7 @@ from django.utils.html import escape from django.template.defaultfilters import truncatewords_html, linebreaksbr, stringfilter, striptags from django.utils.safestring import mark_safe, SafeData from django.utils.html import strip_tags -from django.utils.encoding import force_text -from django.utils.encoding import force_str # pyflakes:ignore force_str is used in the doctests +from django.utils.encoding import force_str from django.urls import reverse as urlreverse from django.core.cache import cache from django.core.exceptions import ValidationError @@ -132,7 +131,7 @@ register.filter('fill', fill) @register.filter def prettystdname(string, space=" "): from ietf.doc.utils import prettify_std_name - return prettify_std_name(force_text(string or ""), space) + return prettify_std_name(force_str(string or ""), space) @register.filter def rfceditor_info_url(rfcnum : str): diff --git a/ietf/doc/utils_charter.py b/ietf/doc/utils_charter.py index d14684d42..84dc70c8c 100644 --- a/ietf/doc/utils_charter.py +++ b/ietf/doc/utils_charter.py @@ -12,7 +12,7 @@ from django.conf import settings from django.urls import reverse as urlreverse from django.template.loader import render_to_string from django.utils import timezone -from django.utils.encoding import smart_text, force_text +from django.utils.encoding import smart_text, force_str import debug # pyflakes:ignore @@ -153,7 +153,7 @@ def generate_ballot_writeup(request, doc): e.doc = doc e.rev = doc.rev, e.desc = "Ballot writeup was generated" - e.text = force_text(render_to_string("doc/charter/ballot_writeup.txt")) + e.text = force_str(render_to_string("doc/charter/ballot_writeup.txt")) # caller is responsible for saving, if necessary return e diff --git a/ietf/doc/views_charter.py b/ietf/doc/views_charter.py index c49710895..d3173291d 100644 --- a/ietf/doc/views_charter.py +++ b/ietf/doc/views_charter.py @@ -17,7 +17,7 @@ from django.conf import settings from django.contrib import messages from django.contrib.auth.decorators import login_required from django.utils import timezone -from django.utils.encoding import force_text +from django.utils.encoding import force_str from django.utils.html import escape import debug # pyflakes:ignore @@ -821,7 +821,7 @@ def charter_with_milestones_txt(request, name, rev): try: with io.open(os.path.join(settings.CHARTER_PATH, filename), 'r') as f: - charter_text = force_text(f.read(), errors='ignore') + charter_text = force_str(f.read(), errors='ignore') except IOError: charter_text = "Error reading charter text %s" % filename diff --git a/ietf/doc/views_status_change.py b/ietf/doc/views_status_change.py index 99f82d435..9a878bc54 100644 --- a/ietf/doc/views_status_change.py +++ b/ietf/doc/views_status_change.py @@ -15,7 +15,7 @@ from django.http import Http404, HttpResponseRedirect from django.urls import reverse from django.template.loader import render_to_string from django.conf import settings -from django.utils.encoding import force_text +from django.utils.encoding import force_str from django.utils.html import escape import debug # pyflakes:ignore @@ -665,7 +665,7 @@ def generate_last_call_text(request, doc): e.doc = doc e.rev = doc.rev e.desc = 'Last call announcement was generated' - e.text = force_text(new_text) + e.text = force_str(new_text) e.save() return e diff --git a/ietf/group/admin.py b/ietf/group/admin.py index 0773a4ce2..d6cbf5f1b 100644 --- a/ietf/group/admin.py +++ b/ietf/group/admin.py @@ -14,7 +14,7 @@ from django.contrib.admin.utils import unquote from django.core.management import load_command_class from django.http import Http404 from django.shortcuts import render -from django.utils.encoding import force_text +from django.utils.encoding import force_str from django.utils.html import escape from django.utils.translation import ugettext as _ @@ -152,7 +152,7 @@ class GroupAdmin(admin.ModelAdmin): permission_denied(request, "You don't have edit permissions for this change.") if obj is None: - raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_text(opts.verbose_name), 'key': escape(object_id)}) + raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_str(opts.verbose_name), 'key': escape(object_id)}) return self.send_reminder(request, sdo=obj) diff --git a/ietf/ipr/feeds.py b/ietf/ipr/feeds.py index b5f4c4e6a..4979c649b 100644 --- a/ietf/ipr/feeds.py +++ b/ietf/ipr/feeds.py @@ -6,7 +6,7 @@ from django.contrib.syndication.views import Feed from django.utils.feedgenerator import Atom1Feed from django.urls import reverse_lazy from django.utils.safestring import mark_safe -from django.utils.encoding import force_text +from django.utils.encoding import force_str from ietf.ipr.models import IprDisclosureBase @@ -25,7 +25,7 @@ class LatestIprDisclosuresFeed(Feed): return mark_safe(item.title) def item_description(self, item): - return force_text(item.title) + return force_str(item.title) def item_pubdate(self, item): return item.time diff --git a/ietf/ipr/mail.py b/ietf/ipr/mail.py index f1d8039db..842426d82 100644 --- a/ietf/ipr/mail.py +++ b/ietf/ipr/mail.py @@ -12,7 +12,7 @@ from email import message_from_bytes from email.utils import parsedate_tz from django.template.loader import render_to_string -from django.utils.encoding import force_text, force_bytes +from django.utils.encoding import force_str, force_bytes import debug # pyflakes:ignore @@ -102,7 +102,7 @@ def get_reply_to(): address with "plus addressing" using a random string. Guaranteed to be unique""" local,domain = get_base_ipr_request_address().split('@') while True: - rand = force_text(base64.urlsafe_b64encode(os.urandom(12))) + rand = force_str(base64.urlsafe_b64encode(os.urandom(12))) address = "{}+{}@{}".format(local,rand,domain) q = Message.objects.filter(reply_to=address) if not q: diff --git a/ietf/liaisons/forms.py b/ietf/liaisons/forms.py index fa1f550d0..3dfe6e406 100644 --- a/ietf/liaisons/forms.py +++ b/ietf/liaisons/forms.py @@ -132,7 +132,7 @@ class AddCommentForm(forms.Form): # def render(self): # output = [] # for widget in self: -# output.append(format_html(force_text(widget))) +# output.append(format_html(force_str(widget))) # return mark_safe('\n'.join(output)) diff --git a/ietf/nomcom/templatetags/nomcom_tags.py b/ietf/nomcom/templatetags/nomcom_tags.py index 05a2c2e8b..c751383fb 100644 --- a/ietf/nomcom/templatetags/nomcom_tags.py +++ b/ietf/nomcom/templatetags/nomcom_tags.py @@ -6,7 +6,7 @@ import re from django import template from django.conf import settings from django.template.defaultfilters import linebreaksbr, force_escape -from django.utils.encoding import force_text, DjangoUnicodeDecodeError +from django.utils.encoding import force_str, DjangoUnicodeDecodeError from django.utils.safestring import mark_safe import debug # pyflakes:ignore @@ -68,7 +68,7 @@ def decrypt(string, request, year, plain=False): code, out, error = pipe(command % (settings.OPENSSL_COMMAND, encrypted_file.name), key) try: - out = force_text(out) + out = force_str(out) except DjangoUnicodeDecodeError: pass if code != 0: diff --git a/ietf/nomcom/views.py b/ietf/nomcom/views.py index d862b3a40..51c165bce 100644 --- a/ietf/nomcom/views.py +++ b/ietf/nomcom/views.py @@ -18,7 +18,7 @@ from django.http import Http404, HttpResponseRedirect, HttpResponse from django.shortcuts import render, get_object_or_404, redirect from django.template.loader import render_to_string from django.urls import reverse -from django.utils.encoding import force_bytes, force_text +from django.utils.encoding import force_bytes, force_str from ietf.dbtemplate.models import DBTemplate @@ -684,7 +684,7 @@ def private_questionnaire(request, year): if form.is_valid(): form.save() messages.success(request, 'The questionnaire response has been registered.') - questionnaire_response = force_text(form.cleaned_data['comment_text']) + questionnaire_response = force_str(form.cleaned_data['comment_text']) form = QuestionnaireForm(nomcom=nomcom, user=request.user) else: form = QuestionnaireForm(nomcom=nomcom, user=request.user) diff --git a/ietf/person/factories.py b/ietf/person/factories.py index 8e80932c9..4761a3f4e 100644 --- a/ietf/person/factories.py +++ b/ietf/person/factories.py @@ -16,7 +16,7 @@ from unicodedata import normalize from django.conf import settings from django.contrib.auth.models import User from django.utils.text import slugify -from django.utils.encoding import force_text +from django.utils.encoding import force_str import debug # pyflakes:ignore @@ -68,7 +68,7 @@ class PersonFactory(factory.django.DjangoModelFactory): # Some i18n names, e.g., "शिला के.सी." have a dot at the end that is also part of the ASCII, e.g., "Shilaa Kesii." # That trailing dot breaks extract_authors(). Avoid this issue by stripping the dot from the ASCII. # Some others have a trailing semicolon (e.g., "உயிரோவியம் தங்கராஐ;") - strip those, too. - ascii = factory.LazyAttribute(lambda p: force_text(unidecode_name(p.name)).rstrip(".;")) + ascii = factory.LazyAttribute(lambda p: force_str(unidecode_name(p.name)).rstrip(".;")) class Params: with_bio = factory.Trait(biography = "\n\n".join(fake.paragraphs())) # type: ignore diff --git a/ietf/submit/mail.py b/ietf/submit/mail.py index 93f97026c..1953ad81c 100644 --- a/ietf/submit/mail.py +++ b/ietf/submit/mail.py @@ -13,7 +13,7 @@ from django.urls import reverse as urlreverse from django.core.exceptions import ValidationError from django.contrib.sites.models import Site from django.template.loader import render_to_string -from django.utils.encoding import force_text, force_str +from django.utils.encoding import force_str import debug # pyflakes:ignore @@ -202,7 +202,7 @@ def get_reply_to(): address with "plus addressing" using a random string. Guaranteed to be unique""" local,domain = get_base_submission_message_address().split('@') while True: - rand = force_text(base64.urlsafe_b64encode(os.urandom(12))) + rand = force_str(base64.urlsafe_b64encode(os.urandom(12))) address = "{}+{}@{}".format(local,rand,domain) q = Message.objects.filter(reply_to=address) if not q: diff --git a/ietf/submit/tests.py b/ietf/submit/tests.py index 50a58494d..0a40b0016 100644 --- a/ietf/submit/tests.py +++ b/ietf/submit/tests.py @@ -23,7 +23,7 @@ from django.test import override_settings from django.test.client import RequestFactory from django.urls import reverse as urlreverse from django.utils import timezone -from django.utils.encoding import force_str, force_text +from django.utils.encoding import force_str import debug # pyflakes:ignore from ietf.submit.utils import (expirable_submissions, expire_submission, find_submission_filenames, @@ -701,10 +701,10 @@ class SubmitTests(BaseSubmitTestCase): self.assertTrue("New Version Notification" in outbox[-2]["Subject"]) self.assertTrue(name in get_payload_text(outbox[-2])) interesting_address = {'ietf':'mars', 'irtf':'irtf-chair', 'iab':'iab-chair', 'ise':'rfc-ise'}[draft.stream_id] - self.assertTrue(interesting_address in force_text(outbox[-2].as_string())) + self.assertTrue(interesting_address in force_str(outbox[-2].as_string())) if draft.stream_id == 'ietf': - self.assertTrue(draft.ad.role_email("ad").address in force_text(outbox[-2].as_string())) - self.assertTrue(ballot_position.balloter.role_email("ad").address in force_text(outbox[-2].as_string())) + self.assertTrue(draft.ad.role_email("ad").address in force_str(outbox[-2].as_string())) + self.assertTrue(ballot_position.balloter.role_email("ad").address in force_str(outbox[-2].as_string())) self.assertTrue("New Version Notification" in outbox[-1]["Subject"]) self.assertTrue(name in get_payload_text(outbox[-1])) r = self.client.get(urlreverse('ietf.doc.views_search.recent_drafts')) diff --git a/ietf/sync/rfceditor.py b/ietf/sync/rfceditor.py index 59356dd48..784e7a2f0 100644 --- a/ietf/sync/rfceditor.py +++ b/ietf/sync/rfceditor.py @@ -12,7 +12,7 @@ from xml.dom import pulldom, Node from django.conf import settings from django.utils import timezone -from django.utils.encoding import smart_bytes, force_str, force_text +from django.utils.encoding import smart_bytes, force_str import debug # pyflakes:ignore @@ -583,7 +583,7 @@ def post_approved_draft(url, name): if r.status_code != 200: raise RuntimeError("Status code is not 200 OK (it's %s)." % r.status_code) - if force_text(r.text) != "OK": + if force_str(r.text) != "OK": raise RuntimeError('Response is not "OK" (it\'s "%s").' % r.text) except Exception as e: diff --git a/ietf/utils/accesstoken.py b/ietf/utils/accesstoken.py index b2a93f77d..243d3f24d 100644 --- a/ietf/utils/accesstoken.py +++ b/ietf/utils/accesstoken.py @@ -5,7 +5,7 @@ import time, random, hashlib from django.conf import settings -from django.utils.encoding import force_bytes, force_text +from django.utils.encoding import force_bytes, force_str def generate_random_key(max_length=32): @@ -18,4 +18,4 @@ def generate_access_token(key, max_length=32): # we hash it with the private key to make sure only we can # generate and use the final token - so storing the key in the # database is safe - return force_text(hashlib.sha256(force_bytes(settings.SECRET_KEY) + force_bytes(key)).hexdigest()[:max_length]) + return force_str(hashlib.sha256(force_bytes(settings.SECRET_KEY) + force_bytes(key)).hexdigest()[:max_length]) diff --git a/ietf/utils/admin.py b/ietf/utils/admin.py index 3e562c2bc..fa1ebb708 100644 --- a/ietf/utils/admin.py +++ b/ietf/utils/admin.py @@ -3,7 +3,7 @@ from django.contrib import admin -from django.utils.encoding import force_text +from django.utils.encoding import force_str from ietf.utils.models import VersionInfo @@ -14,7 +14,7 @@ def name(obj): if callable(obj.name): name = obj.name() else: - name = force_text(obj.name) + name = force_str(obj.name) if name: return name return str(obj) diff --git a/ietf/utils/mail.py b/ietf/utils/mail.py index b2b9f0b9d..e747c7477 100644 --- a/ietf/utils/mail.py +++ b/ietf/utils/mail.py @@ -27,7 +27,7 @@ from django.core.validators import validate_email from django.template.loader import render_to_string from django.template import Context,RequestContext from django.utils import timezone -from django.utils.encoding import force_text, force_str, force_bytes +from django.utils.encoding import force_str, force_bytes import debug # pyflakes:ignore @@ -137,7 +137,7 @@ def send_smtp(msg, bcc=None): server.quit() except smtplib.SMTPServerDisconnected: pass - subj = force_text(msg.get('Subject', '[no subject]')) + subj = force_str(msg.get('Subject', '[no subject]')) tau = time.time() - mark log("sent email (%.3fs) from '%s' to %s id %s subject '%s'" % (tau, frm, to, msg.get('Message-ID', ''), subj)) @@ -166,7 +166,7 @@ def copy_email(msg, to, toUser=False, originalBcc=None): # Overwrite the From: header, so that the copy from a development or # test server doesn't look like spam. new['From'] = settings.DEFAULT_FROM_EMAIL - new['Subject'] = '[Django %s] %s' % (settings.SERVER_MODE, force_text(msg.get('Subject', '[no subject]'))) + new['Subject'] = '[Django %s] %s' % (settings.SERVER_MODE, force_str(msg.get('Subject', '[no subject]'))) new['To'] = to send_smtp(new) @@ -325,7 +325,7 @@ def show_that_mail_was_sent(request,leadline,msg,bcc): from ietf.ietfauth.utils import has_role if has_role(request.user,['Area Director','Secretariat','IANA','RFC Editor','ISE','IAD','IRTF Chair','WG Chair','RG Chair','WG Secretary','RG Secretary']): info = "%s at %s %s\n" % (leadline,timezone.now().strftime("%Y-%m-%d %H:%M:%S"),settings.TIME_ZONE) - info += "Subject: %s\n" % force_text(msg.get('Subject','[no subject]')) + info += "Subject: %s\n" % force_str(msg.get('Subject','[no subject]')) info += "To: %s\n" % msg.get('To','[no to]') if msg.get('Cc'): info += "Cc: %s\n" % msg.get('Cc') @@ -336,7 +336,7 @@ def show_that_mail_was_sent(request,leadline,msg,bcc): def save_as_message(request, msg, bcc): by = ((request and request.user and not request.user.is_anonymous and request.user.person) or ietf.person.models.Person.objects.get(name="(System)")) - headers, body = force_text(str(msg)).split('\n\n', 1) + headers, body = force_str(str(msg)).split('\n\n', 1) kwargs = {'by': by, 'body': body, 'content_type': msg.get_content_type(), 'bcc': bcc or '' } for (arg, field) in [ ('cc', 'Cc'), diff --git a/ietf/utils/management/commands/loadrelated.py b/ietf/utils/management/commands/loadrelated.py index e3d84990c..da9d00d5d 100644 --- a/ietf/utils/management/commands/loadrelated.py +++ b/ietf/utils/management/commands/loadrelated.py @@ -18,7 +18,7 @@ from django.core.exceptions import ObjectDoesNotExist from django.core import serializers from django.db import DEFAULT_DB_ALIAS, DatabaseError, IntegrityError, connections from django.db.models.signals import post_save -from django.utils.encoding import force_text +from django.utils.encoding import force_str import django.core.management.commands.loaddata as loaddata import debug # pyflakes:ignore @@ -91,7 +91,7 @@ class Command(loaddata.Command): obj.save(using=self.using) self.loaded_object_count += 1 except (DatabaseError, IntegrityError, ObjectDoesNotExist, AttributeError) as e: - error_msg = force_text(e) + error_msg = force_str(e) if "Duplicate entry" in error_msg: pass else: diff --git a/ietf/utils/management/commands/mergedata.py b/ietf/utils/management/commands/mergedata.py index 861973482..e73014c78 100644 --- a/ietf/utils/management/commands/mergedata.py +++ b/ietf/utils/management/commands/mergedata.py @@ -15,7 +15,7 @@ from django.core.management.base import CommandError from django.core.management.commands.loaddata import Command as LoadCommand, humanize from django.db import DatabaseError, IntegrityError, router, transaction from django.db.models import ManyToManyField -from django.utils.encoding import force_text +from django.utils.encoding import force_str from ietf.utils.models import ForeignKey @@ -234,7 +234,7 @@ class Command(LoadCommand): 'object_name': obj.object._meta.object_name, 'pk': obj.object.pk, 'data': obj_to_dict(obj.object), - 'error_msg': force_text(e) + 'error_msg': force_str(e) },) raise if objects and show_progress: From da168395fcb4bb75d7944093a9ef5b8bbd8af34a Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Tue, 9 May 2023 15:23:33 -0300 Subject: [PATCH 007/101] refactor: Replace deprecated smart_text with smart_str --- ietf/api/serializer.py | 12 ++++++------ ietf/doc/utils_charter.py | 4 ++-- ietf/meeting/utils.py | 4 ++-- ietf/utils/test_data.py | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ietf/api/serializer.py b/ietf/api/serializer.py index 8340fa8be..e4253bfb6 100644 --- a/ietf/api/serializer.py +++ b/ietf/api/serializer.py @@ -9,7 +9,7 @@ from django.core.cache import cache from django.core.exceptions import ObjectDoesNotExist, FieldError from django.core.serializers.json import Serializer from django.http import HttpResponse -from django.utils.encoding import smart_text +from django.utils.encoding import smart_str from django.db.models import Field from django.db.models.query import QuerySet from django.db.models.signals import post_save, post_delete, m2m_changed @@ -121,7 +121,7 @@ class AdminJsonSerializer(Serializer): for name in expansions: try: field = getattr(obj, name) - #self._current["_"+name] = smart_text(field) + #self._current["_"+name] = smart_str(field) if not isinstance(field, Field): options = self.options.copy() options["expand"] = [ v[len(name)+2:] for v in options["expand"] if v.startswith(name+"__") ] @@ -188,10 +188,10 @@ class AdminJsonSerializer(Serializer): related = related.natural_key() elif field.remote_field.field_name == related._meta.pk.name: # Related to remote object via primary key - related = smart_text(related._get_pk_val(), strings_only=True) + related = smart_str(related._get_pk_val(), strings_only=True) else: # Related to remote object via other field - related = smart_text(getattr(related, field.remote_field.field_name), strings_only=True) + related = smart_str(getattr(related, field.remote_field.field_name), strings_only=True) self._current[field.name] = related def handle_m2m_field(self, obj, field): @@ -201,7 +201,7 @@ class AdminJsonSerializer(Serializer): elif self.use_natural_keys and hasattr(field.remote_field.to, 'natural_key'): m2m_value = lambda value: value.natural_key() else: - m2m_value = lambda value: smart_text(value._get_pk_val(), strings_only=True) + m2m_value = lambda value: smart_str(value._get_pk_val(), strings_only=True) self._current[field.name] = [m2m_value(related) for related in getattr(obj, field.name).iterator()] @@ -264,6 +264,6 @@ class JsonExportMixin(object): qd = dict( ( k, json.loads(v)[0] ) for k,v in items ) except (FieldError, ValueError) as e: return HttpResponse(json.dumps({"error": str(e)}, sort_keys=True, indent=3), content_type=content_type) - text = json.dumps({smart_text(self.model._meta): qd}, sort_keys=True, indent=3) + text = json.dumps({smart_str(self.model._meta): qd}, sort_keys=True, indent=3) return HttpResponse(text, content_type=content_type) diff --git a/ietf/doc/utils_charter.py b/ietf/doc/utils_charter.py index 84dc70c8c..2e85b3cc1 100644 --- a/ietf/doc/utils_charter.py +++ b/ietf/doc/utils_charter.py @@ -12,7 +12,7 @@ from django.conf import settings from django.urls import reverse as urlreverse from django.template.loader import render_to_string from django.utils import timezone -from django.utils.encoding import smart_text, force_str +from django.utils.encoding import smart_str, force_str import debug # pyflakes:ignore @@ -197,7 +197,7 @@ def derive_new_work_text(review_text,group): 'Reply_to':'<iesg@ietf.org>'}) if not addrs.cc: del m['Cc'] - return smart_text(m.as_string()) + return smart_str(m.as_string()) def default_review_text(group, charter, by): now = timezone.now() diff --git a/ietf/meeting/utils.py b/ietf/meeting/utils.py index 1f4896c88..a99f29463 100644 --- a/ietf/meeting/utils.py +++ b/ietf/meeting/utils.py @@ -15,7 +15,7 @@ from django.conf import settings from django.contrib import messages from django.template.loader import render_to_string from django.utils import timezone -from django.utils.encoding import smart_text +from django.utils.encoding import smart_str import debug # pyflakes:ignore @@ -699,7 +699,7 @@ def handle_upload_file(file, filename, meeting, subdir, request=None, encoding=N ) else: try: - text = smart_text(text) + text = smart_str(text) except UnicodeDecodeError as e: return "Failure trying to save '%s'. Hint: Try to upload as UTF-8: %s..." % (filename, str(e)[:120]) # Whole file sanitization; add back what's missing from a complete diff --git a/ietf/utils/test_data.py b/ietf/utils/test_data.py index 3e3324211..1e7097625 100644 --- a/ietf/utils/test_data.py +++ b/ietf/utils/test_data.py @@ -7,7 +7,7 @@ import datetime from django.conf import settings from django.contrib.auth.models import User from django.utils import timezone -from django.utils.encoding import smart_text +from django.utils.encoding import smart_str import debug # pyflakes:ignore @@ -38,7 +38,7 @@ def create_person(group, role_name, name=None, username=None, email_address=None user = User.objects.create(username=username,is_staff=is_staff,is_superuser=is_superuser) user.set_password(password) user.save() - person = Person.objects.create(name=name, ascii=unidecode_name(smart_text(name)), user=user) + person = Person.objects.create(name=name, ascii=unidecode_name(smart_str(name)), user=user) email = Email.objects.create(address=email_address, person=person, origin=user.username) Role.objects.create(group=group, name_id=role_name, person=person, email=email) return person From 31fd43184cb9b3db59ac979fcdf5e8226f27d263 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Tue, 9 May 2023 15:39:12 -0300 Subject: [PATCH 008/101] chore: Tweak add-django-http-cookie-value-none.patch for Django 3.0 --- patch/add-django-http-cookie-value-none.patch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patch/add-django-http-cookie-value-none.patch b/patch/add-django-http-cookie-value-none.patch index ab75235f2..54d0f74d9 100644 --- a/patch/add-django-http-cookie-value-none.patch +++ b/patch/add-django-http-cookie-value-none.patch @@ -1,6 +1,6 @@ --- django/http/response.py.orig 2020-07-08 14:34:42.776562458 +0200 +++ django/http/response.py 2020-07-08 14:35:56.454687322 +0200 -@@ -197,8 +197,8 @@ +@@ -196,8 +196,8 @@ if httponly: self.cookies[key]['httponly'] = True if samesite: From ed571ae50b16d3e6cb6f109058ad0a46e2eaf790 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Wed, 10 May 2023 12:40:37 -0300 Subject: [PATCH 009/101] chore: Rename DB engine to drop the deprecated "_psycopg2" suffix --- dev/deploy-to-container/settings_local.py | 2 +- dev/diff/settings_local.py | 2 +- dev/tests/settings_local.py | 2 +- docker/configs/settings_postgresqldb.py | 2 +- ietf/settings_postgrestest.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dev/deploy-to-container/settings_local.py b/dev/deploy-to-container/settings_local.py index d9bf00f22..60981ba56 100644 --- a/dev/deploy-to-container/settings_local.py +++ b/dev/deploy-to-container/settings_local.py @@ -10,7 +10,7 @@ DATABASES = { 'HOST': '__DBHOST__', 'PORT': 5432, 'NAME': 'datatracker', - 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'ENGINE': 'django.db.backends.postgresql', 'USER': 'django', 'PASSWORD': 'RkTkDPFnKpko', }, diff --git a/dev/diff/settings_local.py b/dev/diff/settings_local.py index cd3923d50..593ccadd7 100644 --- a/dev/diff/settings_local.py +++ b/dev/diff/settings_local.py @@ -10,7 +10,7 @@ DATABASES = { 'HOST': '__DBHOST__', 'PORT': 5432, 'NAME': 'datatracker', - 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'ENGINE': 'django.db.backends.postgresql', 'USER': 'django', 'PASSWORD': 'RkTkDPFnKpko', }, diff --git a/dev/tests/settings_local.py b/dev/tests/settings_local.py index fdc60a849..0cd761c0a 100644 --- a/dev/tests/settings_local.py +++ b/dev/tests/settings_local.py @@ -10,7 +10,7 @@ DATABASES = { 'HOST': 'db', 'PORT': 5432, 'NAME': 'datatracker', - 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'ENGINE': 'django.db.backends.postgresql', 'USER': 'django', 'PASSWORD': 'RkTkDPFnKpko', }, diff --git a/docker/configs/settings_postgresqldb.py b/docker/configs/settings_postgresqldb.py index fe0c827ff..05d19b9a8 100644 --- a/docker/configs/settings_postgresqldb.py +++ b/docker/configs/settings_postgresqldb.py @@ -3,7 +3,7 @@ DATABASES = { 'HOST': 'db', 'PORT': 5432, 'NAME': 'datatracker', - 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'ENGINE': 'django.db.backends.postgresql', 'USER': 'django', 'PASSWORD': 'RkTkDPFnKpko', }, diff --git a/ietf/settings_postgrestest.py b/ietf/settings_postgrestest.py index 450fd9180..13bbc9239 100755 --- a/ietf/settings_postgrestest.py +++ b/ietf/settings_postgrestest.py @@ -39,7 +39,7 @@ DATABASES = { 'HOST': 'db', 'PORT': '5432', 'NAME': 'test.db', - 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'ENGINE': 'django.db.backends.postgresql', 'USER': 'django', 'PASSWORD': 'RkTkDPFnKpko', }, From 69eb6340fd568b59d937a9bc50c386288ae8af91 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Wed, 10 May 2023 15:41:36 -0300 Subject: [PATCH 010/101] test: Do not misuse django.conf.settings for HTML validation params --- ietf/utils/test_runner.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/ietf/utils/test_runner.py b/ietf/utils/test_runner.py index 7453b40a4..281b22724 100644 --- a/ietf/utils/test_runner.py +++ b/ietf/utils/test_runner.py @@ -95,7 +95,7 @@ old_create = None template_coverage_collection = None code_coverage_collection = None url_coverage_collection = None - +validation_settings = {"validate_html": None, "validate_html_harder": None, "show_logging": False} def start_vnu_server(port=8888): "Start a vnu validation server on the indicated port" @@ -282,7 +282,7 @@ class ValidatingTemplates(DjangoTemplates): def __init__(self, params): super().__init__(params) - if not settings.validate_html: + if not validation_settings["validate_html"]: return self.validation_cache = set() self.cwd = str(pathlib.Path.cwd()) @@ -298,7 +298,7 @@ class ValidatingTemplate(Template): def render(self, context=None, request=None): content = super().render(context, request) - if not settings.validate_html: + if not validation_settings["validate_html"]: return content if not self.origin.name.endswith("html"): @@ -310,7 +310,7 @@ class ValidatingTemplate(Template): return content fingerprint = hash(content) + sys.maxsize + 1 # make hash positive - if not settings.validate_html_harder and fingerprint in self.backend.validation_cache: + if not validation_settings["validate_html_harder"] and fingerprint in self.backend.validation_cache: # already validated this HTML fragment, skip it # as an optimization, make page a bit smaller by not returning HTML for the menus # FIXME: figure out why this still includes base/menu.html @@ -326,7 +326,7 @@ class ValidatingTemplate(Template): # don't validate each template by itself, causes too much overhead # instead, save a batch of them and then validate them all in one go # this delays error detection a bit, but is MUCH faster - settings.validate_html.batches[kind].append( + validation_settings["validate_html"].batches[kind].append( (self.origin.name, content, fingerprint) ) return content @@ -726,9 +726,10 @@ class IetfTestRunner(DiscoverRunner): self.html_report = html_report self.permit_mixed_migrations = permit_mixed_migrations self.show_logging = show_logging - settings.validate_html = self if validate_html else None - settings.validate_html_harder = self if validate_html and validate_html_harder else None - settings.show_logging = show_logging + global validation_settings + validation_settings["validate_html"] = self if validate_html else None + validation_settings["validate_html_harder"] = self if validate_html and validate_html_harder else None + validation_settings["show_logging"] = show_logging # self.root_dir = os.path.dirname(settings.BASE_DIR) self.coverage_file = os.path.join(self.root_dir, settings.TEST_COVERAGE_MAIN_FILE) @@ -843,7 +844,7 @@ class IetfTestRunner(DiscoverRunner): s[1] = tuple(s[1]) # random.setstate() won't accept a list in lieu of a tuple factory.random.set_random_state(s) - if not settings.validate_html: + if not validation_settings["validate_html"]: print(" Not validating any generated HTML; " "please do so at least once before committing changes") else: @@ -912,7 +913,7 @@ class IetfTestRunner(DiscoverRunner): self.config_file[kind].flush() pathlib.Path(self.config_file[kind].name).chmod(0o644) - if not settings.validate_html_harder: + if not validation_settings["validate_html_harder"]: print("") self.vnu = None else: @@ -941,7 +942,7 @@ class IetfTestRunner(DiscoverRunner): with open(self.coverage_file, "w") as file: json.dump(self.coverage_master, file, indent=2, sort_keys=True) - if settings.validate_html: + if validation_settings["validate_html"]: for kind in self.batches: if len(self.batches[kind]): print(f" WARNING: not all templates of kind '{kind}' were validated") @@ -1007,7 +1008,7 @@ class IetfTestRunner(DiscoverRunner): + "\n" ) - if settings.validate_html_harder and kind != "frag": + if validation_settings["validate_html_harder"] and kind != "frag": files = [ os.path.join(d, f) for d, dirs, files in os.walk(tmppath) @@ -1084,7 +1085,7 @@ class IetfTestRunner(DiscoverRunner): self.test_apps, self.test_paths = self.get_test_paths(test_labels) - if settings.validate_html: + if validation_settings["validate_html"]: extra_tests += [ TemplateValidationTests( test_runner=self, From 57026bbb5f69b7e4cd81daa93d17061e031b533d Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Wed, 10 May 2023 15:55:25 -0300 Subject: [PATCH 011/101] Revert "chore: Remove django-cookie-delete-with-all-settings.patch" This reverts commit 2cf2a3dee69bbc15dcfac30c8bbaaf7929418389. --- ietf/settings.py | 4 +- ...ango-cookie-delete-with-all-settings.patch | 46 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 patch/django-cookie-delete-with-all-settings.patch diff --git a/ietf/settings.py b/ietf/settings.py index cbbc6d418..00e7f292f 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -76,7 +76,7 @@ MANAGERS = ADMINS DATABASES = { 'default': { 'NAME': 'datatracker', - 'ENGINE': 'django.db.backends.postgresql', + 'ENGINE': 'django.db.backends.postgresql_psycopg2', 'USER': 'ietf', #'PASSWORD': 'somepassword', }, @@ -430,6 +430,7 @@ INSTALLED_APPS = [ 'corsheaders', 'django_markup', 'django_password_strength', + 'form_utils', 'oidc_provider', 'simple_history', 'tastypie', @@ -1122,6 +1123,7 @@ CHECKS_LIBRARY_PATCHES_TO_APPLY = [ 'patch/fix-jwkest-jwt-logging.patch', 'patch/fix-django-password-strength-kwargs.patch', 'patch/add-django-http-cookie-value-none.patch', + 'patch/django-cookie-delete-with-all-settings.patch', 'patch/tastypie-django22-fielderror-response.patch', ] if DEBUG: diff --git a/patch/django-cookie-delete-with-all-settings.patch b/patch/django-cookie-delete-with-all-settings.patch new file mode 100644 index 000000000..830f031d7 --- /dev/null +++ b/patch/django-cookie-delete-with-all-settings.patch @@ -0,0 +1,46 @@ +--- django/contrib/messages/storage/cookie.py.orig 2020-08-13 11:10:36.719177122 +0200 ++++ django/contrib/messages/storage/cookie.py 2020-08-13 11:45:23.503463150 +0200 +@@ -92,6 +92,8 @@ + response.delete_cookie( + self.cookie_name, + domain=settings.SESSION_COOKIE_DOMAIN, ++ secure=settings.SESSION_COOKIE_SECURE or None, ++ httponly=settings.SESSION_COOKIE_HTTPONLY or None, + samesite=settings.SESSION_COOKIE_SAMESITE, + ) + +--- django/http/response.py.orig 2020-08-13 11:16:04.060627793 +0200 ++++ django/http/response.py 2020-08-13 11:54:03.482476973 +0200 +@@ -210,12 +210,18 @@ + value = signing.get_cookie_signer(salt=key + salt).sign(value) + return self.set_cookie(key, value, **kwargs) + +- def delete_cookie(self, key, path='/', domain=None, samesite=None): ++ def delete_cookie(self, key, path='/', domain=None, secure=False, httponly=False, samesite=None): + # Most browsers ignore the Set-Cookie header if the cookie name starts + # with __Host- or __Secure- and the cookie doesn't use the secure flag. +- secure = key.startswith(('__Secure-', '__Host-')) ++ if key in self.cookies: ++ domain = self.cookies[key].get('domain', domain) ++ secure = self.cookies[key].get('secure', secure) ++ httponly = self.cookies[key].get('httponly', httponly) ++ samesite = self.cookies[key].get('samesite', samesite) ++ else: ++ secure = secure or key.startswith(('__Secure-', '__Host-')) + self.set_cookie( +- key, max_age=0, path=path, domain=domain, secure=secure, ++ key, max_age=0, path=path, domain=domain, secure=secure, httponly=httponly, + expires='Thu, 01 Jan 1970 00:00:00 GMT', samesite=samesite, + ) + +--- django/contrib/sessions/middleware.py.orig 2020-08-13 12:12:12.401898114 +0200 ++++ django/contrib/sessions/middleware.py 2020-08-13 12:14:52.690520659 +0200 +@@ -39,6 +39,8 @@ + settings.SESSION_COOKIE_NAME, + path=settings.SESSION_COOKIE_PATH, + domain=settings.SESSION_COOKIE_DOMAIN, ++ secure=settings.SESSION_COOKIE_SECURE or None, ++ httponly=settings.SESSION_COOKIE_HTTPONLY or None, + samesite=settings.SESSION_COOKIE_SAMESITE, + ) + else: From 21ac8c067d5ad9587ef4fe88e6e611bf05cd5808 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Wed, 10 May 2023 16:50:11 -0300 Subject: [PATCH 012/101] chore: Fix cookie-delete patch to work with Django 3.0 --- ...ango-cookie-delete-with-all-settings.patch | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/patch/django-cookie-delete-with-all-settings.patch b/patch/django-cookie-delete-with-all-settings.patch index 830f031d7..eb9d0a6c6 100644 --- a/patch/django-cookie-delete-with-all-settings.patch +++ b/patch/django-cookie-delete-with-all-settings.patch @@ -11,7 +11,7 @@ --- django/http/response.py.orig 2020-08-13 11:16:04.060627793 +0200 +++ django/http/response.py 2020-08-13 11:54:03.482476973 +0200 -@@ -210,12 +210,18 @@ +@@ -209,12 +209,18 @@ value = signing.get_cookie_signer(salt=key + salt).sign(value) return self.set_cookie(key, value, **kwargs) @@ -35,12 +35,12 @@ --- django/contrib/sessions/middleware.py.orig 2020-08-13 12:12:12.401898114 +0200 +++ django/contrib/sessions/middleware.py 2020-08-13 12:14:52.690520659 +0200 -@@ -39,6 +39,8 @@ - settings.SESSION_COOKIE_NAME, - path=settings.SESSION_COOKIE_PATH, - domain=settings.SESSION_COOKIE_DOMAIN, -+ secure=settings.SESSION_COOKIE_SECURE or None, -+ httponly=settings.SESSION_COOKIE_HTTPONLY or None, - samesite=settings.SESSION_COOKIE_SAMESITE, - ) - else: +@@ -38,6 +38,8 @@ + settings.SESSION_COOKIE_NAME, + path=settings.SESSION_COOKIE_PATH, + domain=settings.SESSION_COOKIE_DOMAIN, ++ secure=settings.SESSION_COOKIE_SECURE or None, ++ httponly=settings.SESSION_COOKIE_HTTPONLY or None, + samesite=settings.SESSION_COOKIE_SAMESITE, + ) + patch_vary_headers(response, ('Cookie',)) From a75dbd4f40d9fdf27292a8ee06e4cff1ccd00e68 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Wed, 10 May 2023 16:50:42 -0300 Subject: [PATCH 013/101] chore: Remove accidentally reverted removal of form_utils app --- ietf/settings.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ietf/settings.py b/ietf/settings.py index 00e7f292f..207619f7e 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -430,7 +430,6 @@ INSTALLED_APPS = [ 'corsheaders', 'django_markup', 'django_password_strength', - 'form_utils', 'oidc_provider', 'simple_history', 'tastypie', From 85d0934ba0b57934414c263d1ee6c4afc7c01a98 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Wed, 10 May 2023 17:03:31 -0300 Subject: [PATCH 014/101] refactor: Refactor LiaisonForm without BetterModelForm --- ietf/liaisons/forms.py | 10 +------ ietf/templates/liaisons/edit.html | 45 +++++++++++++++++++++---------- 2 files changed, 32 insertions(+), 23 deletions(-) diff --git a/ietf/liaisons/forms.py b/ietf/liaisons/forms.py index 3dfe6e406..01aa935dc 100644 --- a/ietf/liaisons/forms.py +++ b/ietf/liaisons/forms.py @@ -9,7 +9,6 @@ import operator from typing import Union # pyflakes:ignore from email.utils import parseaddr -from form_utils.forms import BetterModelForm from django import forms from django.conf import settings @@ -213,7 +212,7 @@ class CustomModelMultipleChoiceField(forms.ModelMultipleChoiceField): return super(CustomModelMultipleChoiceField, self).prepare_value(value) -class LiaisonModelForm(BetterModelForm): +class LiaisonModelForm(forms.ModelForm): '''Specify fields which require a custom widget or that are not part of the model. ''' from_groups = forms.ModelMultipleChoiceField(queryset=Group.objects.all(),label='Groups',required=False) @@ -238,13 +237,6 @@ class LiaisonModelForm(BetterModelForm): class Meta: model = LiaisonStatement exclude = ('attachments','state','from_name','to_name') - fieldsets = [('From', {'fields': ['from_groups','from_contact', 'response_contacts'], 'legend': ''}), - ('To', {'fields': ['to_groups','to_contacts'], 'legend': ''}), - ('Other email addresses', {'fields': ['technical_contacts','action_holder_contacts','cc_contacts'], 'legend': ''}), - ('Purpose', {'fields':['purpose', 'deadline'], 'legend': ''}), - ('Reference', {'fields': ['other_identifiers','related_to'], 'legend': ''}), - ('Liaison Statement', {'fields': ['title', 'submitted_date', 'body', 'attachments'], 'legend': ''}), - ('Add attachment', {'fields': ['attach_title', 'attach_file', 'attach_button'], 'legend': ''})] def __init__(self, user, *args, **kwargs): super(LiaisonModelForm, self).__init__(*args, **kwargs) diff --git a/ietf/templates/liaisons/edit.html b/ietf/templates/liaisons/edit.html index 2c7b4b65b..30cc4b00e 100644 --- a/ietf/templates/liaisons/edit.html +++ b/ietf/templates/liaisons/edit.html @@ -47,21 +47,38 @@ method="post" enctype="multipart/form-data" data-edit-form="{{ form.edit }}" - data-ajax-info-url="{% url "ietf.liaisons.views.ajax_get_liaison_info" %}"> + data-ajax-info-url="{% url 'ietf.liaisons.views.ajax_get_liaison_info' %}"> {% csrf_token %} - {% for fieldset in form.fieldsets %} - <h2>{{ fieldset.name }}</h2> - {% for field in fieldset %} - {% if field.id_for_label != "id_attachments" %} - {% bootstrap_field field layout="horizontal" %} - {% else %} - <div class="row mb-3"> - <p class="col-md-2 fw-bold col-form-label">{{ field.label }}</p> - <div class="col-md-10">{{ field }}</div> - </div> - {% endif %} - {% endfor %} - {% endfor %} + <h2>From</h2> + {% bootstrap_field form.from_groups layout="horizontal" %} + {% bootstrap_field form.from_contact layout="horizontal" %} + {% bootstrap_field form.response_contacts layout="horizontal" %} + <h2>To</h2> + {% bootstrap_field form.to_groups layout="horizontal" %} + {% bootstrap_field form.to_contacts layout="horizontal" %} + <h2>Other email addresses</h2> + {% bootstrap_field form.technical_contacts layout="horizontal" %} + {% bootstrap_field form.action_holder_contacts layout="horizontal" %} + {% bootstrap_field form.cc_contacts layout="horizontal" %} + <h2>Purpose</h2> + {% bootstrap_field form.purpose layout="horizontal" %} + {% bootstrap_field form.deadline layout="horizontal" %} + <h2>Reference</h2> + {% bootstrap_field form.other_identifiers layout="horizontal" %} + {% bootstrap_field form.related_to layout="horizontal" %} + <h2>Liaison Statement</h2> + {% bootstrap_field form.title layout="horizontal" %} + {% bootstrap_field form.submitted_date layout="horizontal" %} + {% bootstrap_field form.body layout="horizontal" %} + <div class="row mb-3"> + <p class="col-md-2 fw-bold col-form-label">{{ form.attachments.label }}</p> + <div class="col-md-10">{{ form.attachments }}</div> + </div> + <h2>Add attachment</h2> + {% bootstrap_field form.attach_title layout="horizontal" %} + {% bootstrap_field form.attach_file layout="horizontal" %} + {% bootstrap_field form.attach_button layout="horizontal" %} + <a class="btn btn-danger float-end" href="{% if liaison %}{% url 'ietf.liaisons.views.liaison_detail' object_id=liaison.pk %}{% else %}{% url 'ietf.liaisons.views.liaison_list' %}{% endif %}"> Cancel From 163479bc06aafefdd69ba63b16cfd0b21dcabe9f Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Wed, 10 May 2023 17:12:34 -0300 Subject: [PATCH 015/101] refactor: Replace obsolete `staticfiles` template lib with `static` --- ietf/secr/templates/announcement/confirm.html | 2 +- ietf/secr/templates/areas/list.html | 2 +- ietf/secr/templates/areas/people.html | 2 +- ietf/secr/templates/areas/view.html | 2 +- ietf/secr/templates/base_secr.html | 2 +- ietf/secr/templates/base_secr_bootstrap.html | 2 +- ietf/secr/templates/base_site.html | 2 +- ietf/secr/templates/base_site_bootstrap.html | 2 +- ietf/secr/templates/confirm_cancel.html | 2 +- ietf/secr/templates/confirm_delete.html | 2 +- ietf/secr/templates/meetings/add.html | 2 +- ietf/secr/templates/meetings/base_rooms_times.html | 2 +- ietf/secr/templates/meetings/blue_sheet.html | 2 +- ietf/secr/templates/meetings/edit_meeting.html | 2 +- ietf/secr/templates/meetings/main.html | 2 +- ietf/secr/templates/meetings/notifications.html | 2 +- ietf/secr/templates/meetings/regular_session_edit.html | 2 +- ietf/secr/templates/meetings/view.html | 2 +- ietf/secr/templates/rolodex/add.html | 2 +- ietf/secr/templates/rolodex/edit.html | 2 +- ietf/secr/templates/rolodex/search.html | 2 +- ietf/secr/templates/sreq/confirm.html | 2 +- ietf/secr/templates/sreq/edit.html | 2 +- ietf/secr/templates/sreq/locked.html | 2 +- ietf/secr/templates/sreq/main.html | 2 +- ietf/secr/templates/sreq/new.html | 2 +- ietf/secr/templates/sreq/tool_status.html | 2 +- ietf/secr/templates/sreq/view.html | 2 +- ietf/secr/templates/telechat/base_telechat.html | 2 +- .../meeting/edit_meeting_timeslots_and_misc_sessions.html | 2 +- ietf/templates/meeting/new_meeting_schedule.html | 2 +- ietf/templates/meeting/previously_approved_slides.html | 2 +- 32 files changed, 32 insertions(+), 32 deletions(-) diff --git a/ietf/secr/templates/announcement/confirm.html b/ietf/secr/templates/announcement/confirm.html index 7ad745a09..ddf2a6de6 100644 --- a/ietf/secr/templates/announcement/confirm.html +++ b/ietf/secr/templates/announcement/confirm.html @@ -1,5 +1,5 @@ {% extends "base_site.html" %} -{% load staticfiles %} +{% load static %} {% block title %}Announcement{% endblock %} diff --git a/ietf/secr/templates/areas/list.html b/ietf/secr/templates/areas/list.html index a0ed1ae4a..0d9946efc 100644 --- a/ietf/secr/templates/areas/list.html +++ b/ietf/secr/templates/areas/list.html @@ -1,5 +1,5 @@ {% extends "base_site.html" %} -{% load staticfiles %} +{% load static %} {% block title %}Areas{% endblock %} {% block extrahead %}{{ block.super }} diff --git a/ietf/secr/templates/areas/people.html b/ietf/secr/templates/areas/people.html index e84dc1a79..168089f52 100644 --- a/ietf/secr/templates/areas/people.html +++ b/ietf/secr/templates/areas/people.html @@ -1,5 +1,5 @@ {% extends "base_site.html" %} -{% load staticfiles %} +{% load static %} {% block title %}Areas - People{% endblock %} {% block extrahead %}{{ block.super }} diff --git a/ietf/secr/templates/areas/view.html b/ietf/secr/templates/areas/view.html index e3ecac70a..2bcb9619c 100644 --- a/ietf/secr/templates/areas/view.html +++ b/ietf/secr/templates/areas/view.html @@ -1,5 +1,5 @@ {% extends "base_site.html" %} -{% load staticfiles %} +{% load static %} {% block title %}Areas - View{% endblock %} {% block extrahead %}{{ block.super }} diff --git a/ietf/secr/templates/base_secr.html b/ietf/secr/templates/base_secr.html index 47b893f04..18d77e47b 100644 --- a/ietf/secr/templates/base_secr.html +++ b/ietf/secr/templates/base_secr.html @@ -1,5 +1,5 @@ <!DOCTYPE html> -{% load staticfiles %} +{% load static %} <html lang="en"> <head> <meta charset="utf-8"> diff --git a/ietf/secr/templates/base_secr_bootstrap.html b/ietf/secr/templates/base_secr_bootstrap.html index 2eee566a1..a32634684 100644 --- a/ietf/secr/templates/base_secr_bootstrap.html +++ b/ietf/secr/templates/base_secr_bootstrap.html @@ -1,5 +1,5 @@ <!DOCTYPE html> -{% load staticfiles %} +{% load static %} <html lang="en"> <head> <meta charset="utf-8"> diff --git a/ietf/secr/templates/base_site.html b/ietf/secr/templates/base_site.html index d369a40ec..5e3ddc62d 100644 --- a/ietf/secr/templates/base_site.html +++ b/ietf/secr/templates/base_site.html @@ -1,7 +1,7 @@ {% extends "base_secr.html" %} {% load i18n %} {% load ietf_filters %} -{% load staticfiles %} +{% load static %} {% block title %}{{ title }}{% if user|has_role:"Secretariat" %} Secretariat Dashboard {% else %} IETF Dashboard {% endif %}{% endblock %} diff --git a/ietf/secr/templates/base_site_bootstrap.html b/ietf/secr/templates/base_site_bootstrap.html index c1c2fdac6..1653b26b8 100644 --- a/ietf/secr/templates/base_site_bootstrap.html +++ b/ietf/secr/templates/base_site_bootstrap.html @@ -1,7 +1,7 @@ {% extends "base_secr_bootstrap.html" %} {% load i18n %} {% load ietf_filters %} -{% load staticfiles %} +{% load static %} {% block title %}{{ title }}{% if user|has_role:"Secretariat" %} Secretariat Dashboard {% else %} WG Chair Dashboard {% endif %}{% endblock %} diff --git a/ietf/secr/templates/confirm_cancel.html b/ietf/secr/templates/confirm_cancel.html index 6bae631a7..541c82863 100644 --- a/ietf/secr/templates/confirm_cancel.html +++ b/ietf/secr/templates/confirm_cancel.html @@ -1,5 +1,5 @@ {% extends "base_site.html" %} -{% load staticfiles %} +{% load static %} {% block title %}Confirm Cancel{% endblock %} diff --git a/ietf/secr/templates/confirm_delete.html b/ietf/secr/templates/confirm_delete.html index ccfc7b1c2..3f8fd19c8 100644 --- a/ietf/secr/templates/confirm_delete.html +++ b/ietf/secr/templates/confirm_delete.html @@ -1,5 +1,5 @@ {% extends "base_site.html" %} -{% load staticfiles %} +{% load static %} {% block title %}Confirm Delete{% endblock %} diff --git a/ietf/secr/templates/meetings/add.html b/ietf/secr/templates/meetings/add.html index 5a7825526..b2cc2617d 100644 --- a/ietf/secr/templates/meetings/add.html +++ b/ietf/secr/templates/meetings/add.html @@ -1,5 +1,5 @@ {% extends "base_site.html" %} -{% load staticfiles %} +{% load static %} {% block title %}Meetings - Add{% endblock %} diff --git a/ietf/secr/templates/meetings/base_rooms_times.html b/ietf/secr/templates/meetings/base_rooms_times.html index f856b1f62..263418fab 100644 --- a/ietf/secr/templates/meetings/base_rooms_times.html +++ b/ietf/secr/templates/meetings/base_rooms_times.html @@ -1,5 +1,5 @@ {% extends "base_site_bootstrap.html" %} -{% load staticfiles %} +{% load static %} {% block title %}Meetings{% endblock %} diff --git a/ietf/secr/templates/meetings/blue_sheet.html b/ietf/secr/templates/meetings/blue_sheet.html index d67efd9f6..9bda80f2e 100644 --- a/ietf/secr/templates/meetings/blue_sheet.html +++ b/ietf/secr/templates/meetings/blue_sheet.html @@ -1,5 +1,5 @@ {% extends "base_site.html" %} -{% load staticfiles %} +{% load static %} {% block title %}Meetings - Blue Sheet{% endblock %} diff --git a/ietf/secr/templates/meetings/edit_meeting.html b/ietf/secr/templates/meetings/edit_meeting.html index 773536e65..474373dbe 100644 --- a/ietf/secr/templates/meetings/edit_meeting.html +++ b/ietf/secr/templates/meetings/edit_meeting.html @@ -1,5 +1,5 @@ {% extends "base_site.html" %} -{% load staticfiles %} +{% load static %} {% block title %}Meetings - Edit{% endblock %} diff --git a/ietf/secr/templates/meetings/main.html b/ietf/secr/templates/meetings/main.html index 90c380289..ff110dd97 100755 --- a/ietf/secr/templates/meetings/main.html +++ b/ietf/secr/templates/meetings/main.html @@ -1,5 +1,5 @@ {% extends "base_site.html" %} -{% load staticfiles %} +{% load static %} {% block title %}Meetings{% endblock %} diff --git a/ietf/secr/templates/meetings/notifications.html b/ietf/secr/templates/meetings/notifications.html index bf7099577..dbe66ff28 100644 --- a/ietf/secr/templates/meetings/notifications.html +++ b/ietf/secr/templates/meetings/notifications.html @@ -1,5 +1,5 @@ {% extends "base_site.html" %} -{% load staticfiles %} +{% load static %} {% block title %}Meetings{% endblock %} diff --git a/ietf/secr/templates/meetings/regular_session_edit.html b/ietf/secr/templates/meetings/regular_session_edit.html index fbfba4f96..9993858be 100644 --- a/ietf/secr/templates/meetings/regular_session_edit.html +++ b/ietf/secr/templates/meetings/regular_session_edit.html @@ -1,5 +1,5 @@ {% extends "base_site.html" %} -{% load staticfiles tz %} +{% load static tz %} {% block title %}Meetings{% endblock %} diff --git a/ietf/secr/templates/meetings/view.html b/ietf/secr/templates/meetings/view.html index d552d38dc..d54346dae 100644 --- a/ietf/secr/templates/meetings/view.html +++ b/ietf/secr/templates/meetings/view.html @@ -1,5 +1,5 @@ {% extends "base_site.html" %} -{% load staticfiles %} +{% load static %} {% block title %}Meetings{% endblock %} diff --git a/ietf/secr/templates/rolodex/add.html b/ietf/secr/templates/rolodex/add.html index 272b844fa..5adb738f2 100644 --- a/ietf/secr/templates/rolodex/add.html +++ b/ietf/secr/templates/rolodex/add.html @@ -1,5 +1,5 @@ {% extends "base_site.html" %} -{% load staticfiles %} +{% load static %} {% block title %}Rolodex - Add{% endblock %} diff --git a/ietf/secr/templates/rolodex/edit.html b/ietf/secr/templates/rolodex/edit.html index 28a125f10..ed4c0f97e 100644 --- a/ietf/secr/templates/rolodex/edit.html +++ b/ietf/secr/templates/rolodex/edit.html @@ -1,5 +1,5 @@ {% extends "base_site.html" %} -{% load staticfiles %} +{% load static %} {% block title %}Rolodex - Edit{% endblock %} diff --git a/ietf/secr/templates/rolodex/search.html b/ietf/secr/templates/rolodex/search.html index 8994cfabd..065b0463f 100644 --- a/ietf/secr/templates/rolodex/search.html +++ b/ietf/secr/templates/rolodex/search.html @@ -1,5 +1,5 @@ {% extends "base_site.html" %} -{% load staticfiles %} +{% load static %} {% block title %}Rolodex - Search{% endblock %} diff --git a/ietf/secr/templates/sreq/confirm.html b/ietf/secr/templates/sreq/confirm.html index 4215c89c3..025375af3 100755 --- a/ietf/secr/templates/sreq/confirm.html +++ b/ietf/secr/templates/sreq/confirm.html @@ -1,5 +1,5 @@ {% extends "base_site.html" %} -{% load staticfiles %} +{% load static %} {% block title %}Sessions - Confirm{% endblock %} diff --git a/ietf/secr/templates/sreq/edit.html b/ietf/secr/templates/sreq/edit.html index b0bfbc1e0..f6e62104b 100755 --- a/ietf/secr/templates/sreq/edit.html +++ b/ietf/secr/templates/sreq/edit.html @@ -1,5 +1,5 @@ {% extends "base_site.html" %} -{% load staticfiles %} +{% load static %} {% block title %}Sessions - Edit{% endblock %} {% block extrahead %}{{ block.super }} diff --git a/ietf/secr/templates/sreq/locked.html b/ietf/secr/templates/sreq/locked.html index 5f619f37c..c27cf578e 100755 --- a/ietf/secr/templates/sreq/locked.html +++ b/ietf/secr/templates/sreq/locked.html @@ -1,5 +1,5 @@ {% extends "base_site.html" %} -{% load staticfiles %} +{% load static %} {% block title %}Sessions{% endblock %} diff --git a/ietf/secr/templates/sreq/main.html b/ietf/secr/templates/sreq/main.html index bdb33bb77..a6695cd4f 100755 --- a/ietf/secr/templates/sreq/main.html +++ b/ietf/secr/templates/sreq/main.html @@ -1,6 +1,6 @@ {% extends "base_site.html" %} {% load ietf_filters %} -{% load staticfiles %} +{% load static %} {% block title %}Sessions{% endblock %} diff --git a/ietf/secr/templates/sreq/new.html b/ietf/secr/templates/sreq/new.html index 2c6afb557..3f46e6f89 100755 --- a/ietf/secr/templates/sreq/new.html +++ b/ietf/secr/templates/sreq/new.html @@ -1,5 +1,5 @@ {% extends "base_site.html" %} -{% load staticfiles %} +{% load static %} {% block title %}Sessions- New{% endblock %} diff --git a/ietf/secr/templates/sreq/tool_status.html b/ietf/secr/templates/sreq/tool_status.html index cf5131c22..b91e73a12 100755 --- a/ietf/secr/templates/sreq/tool_status.html +++ b/ietf/secr/templates/sreq/tool_status.html @@ -1,5 +1,5 @@ {% extends "base_site.html" %} -{% load staticfiles %} +{% load static %} {% block title %}Sessions{% endblock %} diff --git a/ietf/secr/templates/sreq/view.html b/ietf/secr/templates/sreq/view.html index c7ae2d27b..9a0a3b01c 100644 --- a/ietf/secr/templates/sreq/view.html +++ b/ietf/secr/templates/sreq/view.html @@ -1,5 +1,5 @@ {% extends "base_site.html" %} -{% load staticfiles %} +{% load static %} {% block title %}Sessions - View{% endblock %} diff --git a/ietf/secr/templates/telechat/base_telechat.html b/ietf/secr/templates/telechat/base_telechat.html index 73d42ea71..1c8feaff6 100644 --- a/ietf/secr/templates/telechat/base_telechat.html +++ b/ietf/secr/templates/telechat/base_telechat.html @@ -1,5 +1,5 @@ {% extends "base_site.html" %} -{% load staticfiles %} +{% load static %} {% block title %}Telechat{% endblock %} diff --git a/ietf/templates/meeting/edit_meeting_timeslots_and_misc_sessions.html b/ietf/templates/meeting/edit_meeting_timeslots_and_misc_sessions.html index 214845035..5e51ab5a8 100644 --- a/ietf/templates/meeting/edit_meeting_timeslots_and_misc_sessions.html +++ b/ietf/templates/meeting/edit_meeting_timeslots_and_misc_sessions.html @@ -1,7 +1,7 @@ {% extends "base.html" %} {# Copyright The IETF Trust 2015-2020, All Rights Reserved #} {% load origin %} -{% load staticfiles %} +{% load static %} {% load ietf_filters %} {% load django_bootstrap5 %} {% block title %}{{ schedule.name }}: IETF {{ meeting.number }} meeting agenda{% endblock %} diff --git a/ietf/templates/meeting/new_meeting_schedule.html b/ietf/templates/meeting/new_meeting_schedule.html index c93d72b2a..d1b5263c5 100644 --- a/ietf/templates/meeting/new_meeting_schedule.html +++ b/ietf/templates/meeting/new_meeting_schedule.html @@ -1,7 +1,7 @@ {% extends "base.html" %} {# Copyright The IETF Trust 2015-2020, All Rights Reserved #} {% load origin %} -{% load staticfiles %} +{% load static %} {% load ietf_filters %} {% load django_bootstrap5 %} {% block content %} diff --git a/ietf/templates/meeting/previously_approved_slides.html b/ietf/templates/meeting/previously_approved_slides.html index 060be438e..25a4c9786 100644 --- a/ietf/templates/meeting/previously_approved_slides.html +++ b/ietf/templates/meeting/previously_approved_slides.html @@ -1,6 +1,6 @@ {% extends "base.html" %} {# Copyright The IETF Trust 2020, All Rights Reserved #} -{% load origin staticfiles django_bootstrap5 %} +{% load origin static django_bootstrap5 %} {% block title %} Approved Slides for {{ submission.session.meeting }} : {{ submission.session.group.acronym }} {% endblock %} From 4f443cc44538da4fbee1243d0558569c8f36806e Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Wed, 10 May 2023 17:16:20 -0300 Subject: [PATCH 016/101] refactor: Explicitly allow name=None for a couple of views --- ietf/doc/views_status_change.py | 2 +- ietf/meeting/views.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ietf/doc/views_status_change.py b/ietf/doc/views_status_change.py index 9a878bc54..ec914eebe 100644 --- a/ietf/doc/views_status_change.py +++ b/ietf/doc/views_status_change.py @@ -531,7 +531,7 @@ def rfc_status_changes(request): ) @role_required("Area Director","Secretariat") -def start_rfc_status_change(request,name): +def start_rfc_status_change(request, name=None): """Start the RFC status change review process, setting the initial shepherding AD, and possibly putting the review on a telechat.""" if name: diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py index 87b48a7a2..873bdad75 100644 --- a/ietf/meeting/views.py +++ b/ietf/meeting/views.py @@ -2730,7 +2730,7 @@ def upload_session_agenda(request, session_id, num): }) -def upload_session_slides(request, session_id, num, name): +def upload_session_slides(request, session_id, num, name=None): # num is redundant, but we're dragging it along an artifact of where we are in the current URL structure session = get_object_or_404(Session,pk=session_id) if not session.can_manage_materials(request.user): From 1015cf83f83f3c6e7df542290f814131c6ae1812 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Wed, 10 May 2023 18:47:36 -0300 Subject: [PATCH 017/101] fix: Finish refactoring LiaisonModelForm subclasses without BetterModelForm --- ietf/liaisons/forms.py | 8 -------- ietf/templates/liaisons/edit.html | 7 ++++++- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/ietf/liaisons/forms.py b/ietf/liaisons/forms.py index 01aa935dc..2c2811375 100644 --- a/ietf/liaisons/forms.py +++ b/ietf/liaisons/forms.py @@ -468,14 +468,6 @@ class OutgoingLiaisonForm(LiaisonModelForm): class Meta: model = LiaisonStatement exclude = ('attachments','state','from_name','to_name','action_holder_contacts') - # add approved field, no action_holder_contacts - fieldsets = [('From', {'fields': ['from_groups','from_contact','response_contacts','approved'], 'legend': ''}), - ('To', {'fields': ['to_groups','to_contacts'], 'legend': ''}), - ('Other email addresses', {'fields': ['technical_contacts','cc_contacts'], 'legend': ''}), - ('Purpose', {'fields':['purpose', 'deadline'], 'legend': ''}), - ('Reference', {'fields': ['other_identifiers','related_to'], 'legend': ''}), - ('Liaison Statement', {'fields': ['title', 'submitted_date', 'body', 'attachments'], 'legend': ''}), - ('Add attachment', {'fields': ['attach_title', 'attach_file', 'attach_button'], 'legend': ''})] def is_approved(self): return self.cleaned_data['approved'] diff --git a/ietf/templates/liaisons/edit.html b/ietf/templates/liaisons/edit.html index 30cc4b00e..c8023e50c 100644 --- a/ietf/templates/liaisons/edit.html +++ b/ietf/templates/liaisons/edit.html @@ -53,12 +53,17 @@ {% bootstrap_field form.from_groups layout="horizontal" %} {% bootstrap_field form.from_contact layout="horizontal" %} {% bootstrap_field form.response_contacts layout="horizontal" %} + {% if form.approved %} + {% bootstrap_field form.approved layout="horizontal" %} + {% endif %} <h2>To</h2> {% bootstrap_field form.to_groups layout="horizontal" %} {% bootstrap_field form.to_contacts layout="horizontal" %} <h2>Other email addresses</h2> {% bootstrap_field form.technical_contacts layout="horizontal" %} - {% bootstrap_field form.action_holder_contacts layout="horizontal" %} + {% if form.action_holder_contacts %} + {% bootstrap_field form.action_holder_contacts layout="horizontal" %} + {% endif %} {% bootstrap_field form.cc_contacts layout="horizontal" %} <h2>Purpose</h2> {% bootstrap_field form.purpose layout="horizontal" %} From e6259a521874b68a3b4c33d0a1b80aa0933b7c9f Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Wed, 10 May 2023 18:52:52 -0300 Subject: [PATCH 018/101] chore: Remove filter on staticfiles DeprecationWarning --- ietf/settings.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ietf/settings.py b/ietf/settings.py index 207619f7e..5af6c7530 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -16,7 +16,6 @@ warnings.simplefilter("always", DeprecationWarning) warnings.filterwarnings("ignore", message="'urllib3\[secure\]' extra is deprecated") warnings.filterwarnings("ignore", message="The logout\(\) view is superseded by") warnings.filterwarnings("ignore", message="Report.file_reporters will no longer be available in Coverage.py 4.2", module="coverage.report") -warnings.filterwarnings("ignore", message="{% load staticfiles %} is deprecated") warnings.filterwarnings("ignore", message="Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated", module="bleach") warnings.filterwarnings("ignore", message="HTTPResponse.getheader\(\) is deprecated", module='selenium.webdriver') try: From 406ba7bf0bb8778b569c84dad04277b2d740cb26 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Wed, 10 May 2023 19:18:30 -0300 Subject: [PATCH 019/101] style: Apply Black style to active_group_types() view --- ietf/group/views.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/ietf/group/views.py b/ietf/group/views.py index 4d83a6b4c..1749912ea 100644 --- a/ietf/group/views.py +++ b/ietf/group/views.py @@ -300,8 +300,26 @@ def active_groups(request, group_type=None): raise Http404 def active_group_types(request): - grouptypes = GroupTypeName.objects.filter(slug__in=['wg','rg','ag','rag','team','dir','review','area','program','iabasg','adm']).filter(group__state='active').annotate(group_count=Count('group')) - return render(request, 'group/active_groups.html', {'grouptypes':grouptypes}) + grouptypes = ( + GroupTypeName.objects.filter( + slug__in=[ + "wg", + "rg", + "ag", + "rag", + "team", + "dir", + "review", + "area", + "program", + "iabasg", + "adm", + ] + ) + .filter(group__state="active") + .annotate(group_count=Count("group")) + ) + return render(request, "group/active_groups.html", {"grouptypes": grouptypes}) def active_dirs(request): dirs = Group.objects.filter(type__in=['dir', 'review'], state="active").order_by("name") From eee145ee6728cc15f5b75711c4b435a1e1d58452 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Wed, 10 May 2023 19:19:39 -0300 Subject: [PATCH 020/101] fix: Explicitly order GroupTypeNames in active_group_types Relying on Meta.ordering to order querysets involving GROUP BY queries is deprecated and will be dropped in Django 3.1. --- ietf/group/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ietf/group/views.py b/ietf/group/views.py index 1749912ea..95bf4c5e9 100644 --- a/ietf/group/views.py +++ b/ietf/group/views.py @@ -317,6 +317,7 @@ def active_group_types(request): ] ) .filter(group__state="active") + .order_by('order', 'name') # default ordering ignored for "GROUP BY" queries, make it explicit .annotate(group_count=Count("group")) ) return render(request, "group/active_groups.html", {"grouptypes": grouptypes}) From 0d070597b482cafa8db814e9c8e74bf5f2a11c34 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Wed, 10 May 2023 19:21:47 -0300 Subject: [PATCH 021/101] style: Double backslashes in strings Not sure why, but if I change the DeprecationWarning filter to "error", I get a SyntaxError from the \[ because it is an invalid escape sequence. Not sure why that change triggers it, but "\[" and "\\[" are the same, so this is a no-op. --- ietf/settings.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ietf/settings.py b/ietf/settings.py index 5af6c7530..c08918c7e 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -13,11 +13,11 @@ import warnings from typing import Any, Dict, List, Tuple # pyflakes:ignore warnings.simplefilter("always", DeprecationWarning) -warnings.filterwarnings("ignore", message="'urllib3\[secure\]' extra is deprecated") -warnings.filterwarnings("ignore", message="The logout\(\) view is superseded by") +warnings.filterwarnings("ignore", message="'urllib3\\[secure\\]' extra is deprecated") +warnings.filterwarnings("ignore", message="The logout\\(\\) view is superseded by") warnings.filterwarnings("ignore", message="Report.file_reporters will no longer be available in Coverage.py 4.2", module="coverage.report") warnings.filterwarnings("ignore", message="Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated", module="bleach") -warnings.filterwarnings("ignore", message="HTTPResponse.getheader\(\) is deprecated", module='selenium.webdriver') +warnings.filterwarnings("ignore", message="HTTPResponse.getheader\\(\\) is deprecated", module='selenium.webdriver') try: import syslog syslog.openlog(str("datatracker"), syslog.LOG_PID, syslog.LOG_USER) From f8a9efc5a4ec2f3cdad00d304ba606674568aca4 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Thu, 11 May 2023 11:39:12 -0400 Subject: [PATCH 022/101] ci: run tests on feat/django4 PRs (#5604) --- .github/workflows/ci-run-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci-run-tests.yml b/.github/workflows/ci-run-tests.yml index caabc7511..c5d765018 100644 --- a/.github/workflows/ci-run-tests.yml +++ b/.github/workflows/ci-run-tests.yml @@ -4,6 +4,7 @@ on: pull_request: branches: - 'main' + - 'feat/django4' paths: - 'client/**' - 'ietf/**' From 1419a0e7c81d72283c55861d0e16e28d7b11c7db Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 12 May 2023 13:18:15 -0300 Subject: [PATCH 023/101] test: Remove ScheduleEditTests.testUnschedule (#5607) * test: Remove ScheduleEditTests.testUnschedule Has been disabled under Django 2. Simple refactoring does not make it functional under Django 3. Probably because we know that Selenium does not handle HTML5 drag-and-drop well. Discarding until we move to a better JS testing framework. * test: Remove unused imports --- ietf/meeting/tests_js.py | 38 -------------------------------------- 1 file changed, 38 deletions(-) diff --git a/ietf/meeting/tests_js.py b/ietf/meeting/tests_js.py index 327d526cc..2e7a5de6c 100644 --- a/ietf/meeting/tests_js.py +++ b/ietf/meeting/tests_js.py @@ -7,9 +7,7 @@ import datetime import shutil import os import re -from unittest import skipIf -import django from django.utils import timezone from django.utils.text import slugify from django.db.models import F @@ -880,42 +878,6 @@ class EditMeetingScheduleTests(IetfSeleniumTestCase): self.assertNotIn('would-violate-hint', session_elements[4].get_attribute('class'), 'Constraint violation should not be indicated on non-conflicting session') -@ifSeleniumEnabled -@skipIf(django.VERSION[0]==2, "Skipping test with race conditions under Django 2") -class ScheduleEditTests(IetfSeleniumTestCase): - def testUnschedule(self): - - meeting = make_meeting_test_data() - - self.assertEqual(SchedTimeSessAssignment.objects.filter(session__meeting=meeting, session__group__acronym='mars', schedule__name='test-schedule').count(),1) - - - ss = list(SchedTimeSessAssignment.objects.filter(session__meeting__number=72,session__group__acronym='mars',schedule__name='test-schedule')) # pyflakes:ignore - - self.login() - url = self.absreverse('ietf.meeting.views.edit_meeting_schedule',kwargs=dict(num='72',name='test-schedule',owner='plain@example.com')) - self.driver.get(url) - - # driver.get() will wait for scripts to finish, but not ajax - # requests. Wait for completion of the permissions check: - read_only_note = self.driver.find_element(By.ID, 'read_only') - WebDriverWait(self.driver, 10).until(expected_conditions.invisibility_of_element(read_only_note), "Read-only schedule") - - s1 = Session.objects.filter(group__acronym='mars', meeting=meeting).first() - selector = "#session_{}".format(s1.pk) - WebDriverWait(self.driver, 30).until(expected_conditions.presence_of_element_located((By.CSS_SELECTOR, selector)), "Did not find %s"%selector) - - self.assertEqual(self.driver.find_elements(By.CSS_SELECTOR, "#sortable-list #session_{}".format(s1.pk)), []) - - element = self.driver.find_element(By.ID, 'session_{}'.format(s1.pk)) - target = self.driver.find_element(By.ID, 'sortable-list') - ActionChains(self.driver).drag_and_drop(element,target).perform() - - self.assertTrue(self.driver.find_elements(By.CSS_SELECTOR, "#sortable-list #session_{}".format(s1.pk))) - - time.sleep(0.1) # The API that modifies the database runs async - - self.assertEqual(SchedTimeSessAssignment.objects.filter(session__meeting__number=72,session__group__acronym='mars',schedule__name='test-schedule').count(),0) @ifSeleniumEnabled class SlideReorderTests(IetfSeleniumTestCase): From b2534fdf32e554b4624a54a6ac0cf30272ba78d9 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Thu, 11 May 2023 17:20:45 -0300 Subject: [PATCH 024/101] chore: Update requirements.txt for Django 3.1 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 1804a737c..e0fe30473 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ celery>=5.2.6 coverage>=4.5.4,<5.0 # Coverage 5.x moves from a json database to SQLite. Moving to 5.x will require substantial rewrites in ietf.utils.test_runner and ietf.release.views decorator>=5.1.1 defusedxml>=0.7.1 # for TastyPie when using xml; not a declared dependency -Django<3.1 +Django<3.2 django-analytical>=3.1.0 django-bootstrap5>=21.3 django-celery-beat>=2.3.0 From c4f99d0b1d3b59f46413be9fa126bf0ed95370db Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 12 May 2023 10:49:58 -0300 Subject: [PATCH 025/101] chore: Update django-stubs and mypy requirements for Django 3.1 --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index e0fe30473..cfabe0ebe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,7 +21,7 @@ django-oidc-provider>=0.7,<0.8 # 0.8 dropped Django 2 support django-password-strength>=1.2.1 django-referrer-policy>=1.0 django-simple-history>=3.0.0 -django-stubs==1.6.0 # The django-stubs version used determines the the mypy version indicated below +django-stubs==1.8.0 # The django-stubs version used determines the the mypy version indicated below django-tastypie==0.14.3 # Version must be locked in sync with version of Django django-vite>=2.0.2 django-webtest>=1.9.10 # Only used in tests @@ -41,7 +41,7 @@ logging_tree>=1.9 # Used only by the showloggers management command lxml>=4.8.0,<5 markdown>=3.3.6 mock>=4.0.3 # Used only by tests, of course -mypy>=0.782,<0.790 # Version requirements determined by django-stubs. +mypy==0.812 # Version requirements determined by django-stubs. oic>=1.3 # Used only by tests Pillow>=9.1.0 psycopg2<2.9 From ccb7d666ce0c1d70dbbcad6a148b5437bbf951b4 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 12 May 2023 10:50:27 -0300 Subject: [PATCH 026/101] chore: Remove add-django-http-cookie-value-none.patch Fixed upstream --- ietf/settings.py | 1 - patch/add-django-http-cookie-value-none.patch | 13 ------------- 2 files changed, 14 deletions(-) delete mode 100644 patch/add-django-http-cookie-value-none.patch diff --git a/ietf/settings.py b/ietf/settings.py index c08918c7e..0aca062b7 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -1120,7 +1120,6 @@ CHECKS_LIBRARY_PATCHES_TO_APPLY = [ 'patch/fix-oidc-access-token-post.patch', 'patch/fix-jwkest-jwt-logging.patch', 'patch/fix-django-password-strength-kwargs.patch', - 'patch/add-django-http-cookie-value-none.patch', 'patch/django-cookie-delete-with-all-settings.patch', 'patch/tastypie-django22-fielderror-response.patch', ] diff --git a/patch/add-django-http-cookie-value-none.patch b/patch/add-django-http-cookie-value-none.patch deleted file mode 100644 index 54d0f74d9..000000000 --- a/patch/add-django-http-cookie-value-none.patch +++ /dev/null @@ -1,13 +0,0 @@ ---- django/http/response.py.orig 2020-07-08 14:34:42.776562458 +0200 -+++ django/http/response.py 2020-07-08 14:35:56.454687322 +0200 -@@ -196,8 +196,8 @@ - if httponly: - self.cookies[key]['httponly'] = True - if samesite: -- if samesite.lower() not in ('lax', 'strict'): -- raise ValueError('samesite must be "lax" or "strict".') -+ if samesite.lower() not in ('lax', 'strict', 'none'): -+ raise ValueError('samesite must be "lax", "strict", or "none", not "%s".' % samesite) - self.cookies[key]['samesite'] = samesite - - def setdefault(self, key, value): From addc96713414537899a5bd512789e96e9269f91e Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 12 May 2023 11:35:11 -0300 Subject: [PATCH 027/101] chore: Update django-cookie-delete-with-all-settings.patch --- ...ango-cookie-delete-with-all-settings.patch | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/patch/django-cookie-delete-with-all-settings.patch b/patch/django-cookie-delete-with-all-settings.patch index eb9d0a6c6..01dee277d 100644 --- a/patch/django-cookie-delete-with-all-settings.patch +++ b/patch/django-cookie-delete-with-all-settings.patch @@ -1,6 +1,6 @@ --- django/contrib/messages/storage/cookie.py.orig 2020-08-13 11:10:36.719177122 +0200 +++ django/contrib/messages/storage/cookie.py 2020-08-13 11:45:23.503463150 +0200 -@@ -92,6 +92,8 @@ +@@ -95,6 +95,8 @@ response.delete_cookie( self.cookie_name, domain=settings.SESSION_COOKIE_DOMAIN, @@ -11,22 +11,30 @@ --- django/http/response.py.orig 2020-08-13 11:16:04.060627793 +0200 +++ django/http/response.py 2020-08-13 11:54:03.482476973 +0200 -@@ -209,12 +209,18 @@ +@@ -210,12 +210,18 @@ value = signing.get_cookie_signer(salt=key + salt).sign(value) return self.set_cookie(key, value, **kwargs) - def delete_cookie(self, key, path='/', domain=None, samesite=None): + def delete_cookie(self, key, path='/', domain=None, secure=False, httponly=False, samesite=None): - # Most browsers ignore the Set-Cookie header if the cookie name starts - # with __Host- or __Secure- and the cookie doesn't use the secure flag. -- secure = key.startswith(('__Secure-', '__Host-')) + # Browsers can ignore the Set-Cookie header if the cookie doesn't use + # the secure flag and: + # - the cookie name starts with "__Host-" or "__Secure-", or + # - the samesite is "none". +- secure = ( +- key.startswith(('__Secure-', '__Host-')) or +- (samesite and samesite.lower() == 'none') +- ) + if key in self.cookies: + domain = self.cookies[key].get('domain', domain) + secure = self.cookies[key].get('secure', secure) + httponly = self.cookies[key].get('httponly', httponly) + samesite = self.cookies[key].get('samesite', samesite) + else: -+ secure = secure or key.startswith(('__Secure-', '__Host-')) ++ secure = secure or ( ++ key.startswith(('__Secure-', '__Host-')) or ++ (samesite and samesite.lower() == 'none') ++ ) self.set_cookie( - key, max_age=0, path=path, domain=domain, secure=secure, + key, max_age=0, path=path, domain=domain, secure=secure, httponly=httponly, @@ -35,7 +43,7 @@ --- django/contrib/sessions/middleware.py.orig 2020-08-13 12:12:12.401898114 +0200 +++ django/contrib/sessions/middleware.py 2020-08-13 12:14:52.690520659 +0200 -@@ -38,6 +38,8 @@ +@@ -42,6 +42,8 @@ settings.SESSION_COOKIE_NAME, path=settings.SESSION_COOKIE_PATH, domain=settings.SESSION_COOKIE_DOMAIN, From d0cb46d320063e8e1999a0857c5b16499a3a3f77 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 12 May 2023 15:39:57 -0300 Subject: [PATCH 028/101] fix: Use TruncDate instead of QuerySet.extra() --- ietf/doc/views_stats.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ietf/doc/views_stats.py b/ietf/doc/views_stats.py index 7d56e8569..ab71efce7 100644 --- a/ietf/doc/views_stats.py +++ b/ietf/doc/views_stats.py @@ -7,6 +7,7 @@ from django.conf import settings from django.core.cache import cache from django.urls import reverse as urlreverse from django.db.models.aggregates import Count +from django.db.models.functions import TruncDate from django.http import JsonResponse, HttpResponseBadRequest from django.shortcuts import render from django.views.decorators.cache import cache_page @@ -40,15 +41,12 @@ def model_to_timeline_data(model, field='time', **kwargs): assert field in [ f.name for f in model._meta.get_fields() ] objects = ( model.objects.filter(**kwargs) + .annotate(date=TruncDate(field)) .order_by('date') - .extra(select={'date': 'date(%s.%s)'% (model._meta.db_table, field) }) .values('date') .annotate(count=Count('id'))) if objects.exists(): obj_list = list(objects) - # This is needed for sqlite, when we're running tests: - if type(obj_list[0]['date']) != datetime.date: - obj_list = [ {'date': dt(e['date']), 'count': e['count']} for e in obj_list ] today = date_today(datetime.timezone.utc) if not obj_list[-1]['date'] == today: obj_list += [ {'date': today, 'count': 0} ] From 00118f7807d3003690ed68d2ebee3d30fbb9ec77 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 12 May 2023 16:26:08 -0300 Subject: [PATCH 029/101] chore: Update requirements.txt for Django 3.2 --- requirements.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index cfabe0ebe..ea7ef7fb3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ celery>=5.2.6 coverage>=4.5.4,<5.0 # Coverage 5.x moves from a json database to SQLite. Moving to 5.x will require substantial rewrites in ietf.utils.test_runner and ietf.release.views decorator>=5.1.1 defusedxml>=0.7.1 # for TastyPie when using xml; not a declared dependency -Django<3.2 +Django<4 django-analytical>=3.1.0 django-bootstrap5>=21.3 django-celery-beat>=2.3.0 @@ -21,7 +21,7 @@ django-oidc-provider>=0.7,<0.8 # 0.8 dropped Django 2 support django-password-strength>=1.2.1 django-referrer-policy>=1.0 django-simple-history>=3.0.0 -django-stubs==1.8.0 # The django-stubs version used determines the the mypy version indicated below +django-stubs>=4.2.0 # The django-stubs version used determines the the mypy version indicated below django-tastypie==0.14.3 # Version must be locked in sync with version of Django django-vite>=2.0.2 django-webtest>=1.9.10 # Only used in tests @@ -41,10 +41,10 @@ logging_tree>=1.9 # Used only by the showloggers management command lxml>=4.8.0,<5 markdown>=3.3.6 mock>=4.0.3 # Used only by tests, of course -mypy==0.812 # Version requirements determined by django-stubs. +mypy<1.3 # Version requirements determined by django-stubs. oic>=1.3 # Used only by tests Pillow>=9.1.0 -psycopg2<2.9 +psycopg2>=2.9.6 pyang>=2.5.3 pyflakes>=2.4.0 pyopenssl>=22.0.0 # Used by urllib3.contrib, which is used by PyQuery but not marked as a dependency From aa4c04126c386202d780ee6df07e1214b340daf3 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 12 May 2023 16:33:56 -0300 Subject: [PATCH 030/101] chore: Use re_path() instead of url(), its deprecated synonym, --- ietf/secr/urls.py | 18 +++++++++--------- ietf/utils/urls.py | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/ietf/secr/urls.py b/ietf/secr/urls.py index 5a3df23d0..8e80af62c 100644 --- a/ietf/secr/urls.py +++ b/ietf/secr/urls.py @@ -1,13 +1,13 @@ -from django.conf.urls import url, include +from django.conf.urls import re_path, include from django.views.generic import TemplateView urlpatterns = [ - url(r'^$', TemplateView.as_view(template_name='main.html')), - url(r'^announcement/', include('ietf.secr.announcement.urls')), - url(r'^areas/', include('ietf.secr.areas.urls')), - url(r'^console/', include('ietf.secr.console.urls')), - url(r'^meetings/', include('ietf.secr.meetings.urls')), - url(r'^rolodex/', include('ietf.secr.rolodex.urls')), - url(r'^sreq/', include('ietf.secr.sreq.urls')), - url(r'^telechat/', include('ietf.secr.telechat.urls')), + re_path(r'^$', TemplateView.as_view(template_name='main.html')), + re_path(r'^announcement/', include('ietf.secr.announcement.urls')), + re_path(r'^areas/', include('ietf.secr.areas.urls')), + re_path(r'^console/', include('ietf.secr.console.urls')), + re_path(r'^meetings/', include('ietf.secr.meetings.urls')), + re_path(r'^rolodex/', include('ietf.secr.rolodex.urls')), + re_path(r'^sreq/', include('ietf.secr.sreq.urls')), + re_path(r'^telechat/', include('ietf.secr.telechat.urls')), ] diff --git a/ietf/utils/urls.py b/ietf/utils/urls.py index 7f46d9c43..9be48e16b 100644 --- a/ietf/utils/urls.py +++ b/ietf/utils/urls.py @@ -6,7 +6,7 @@ import debug # pyflakes:ignore from inspect import isclass -from django.conf.urls import url as django_url +from django.conf.urls import re_path from django.views.generic import View from django.utils.encoding import force_str @@ -42,5 +42,5 @@ def url(regex, view, kwargs=None, name=None): #debug.show('branch') #debug.show('name') pass - return django_url(regex, view, kwargs=kwargs, name=name) + return re_path(regex, view, kwargs=kwargs, name=name) From 87fdfaa713b673dca5806e21aabeb67120b1e5e8 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 12 May 2023 16:38:15 -0300 Subject: [PATCH 031/101] chore: Update django-tastypie requirement --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ea7ef7fb3..4fd7db4d4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,7 +22,7 @@ django-password-strength>=1.2.1 django-referrer-policy>=1.0 django-simple-history>=3.0.0 django-stubs>=4.2.0 # The django-stubs version used determines the the mypy version indicated below -django-tastypie==0.14.3 # Version must be locked in sync with version of Django +django-tastypie>=0.14.5 # Version must be locked in sync with version of Django django-vite>=2.0.2 django-webtest>=1.9.10 # Only used in tests django-widget-tweaks>=1.4.12 From 828071a582896844ee9bc905fb7e031271d08c5a Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 12 May 2023 16:56:38 -0300 Subject: [PATCH 032/101] chore: Unpin oidc-provider, update its patch --- patch/change-oidc-provider-field-sizes-228.patch | 8 ++++---- requirements.txt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/patch/change-oidc-provider-field-sizes-228.patch b/patch/change-oidc-provider-field-sizes-228.patch index 8203f163b..849e2960a 100644 --- a/patch/change-oidc-provider-field-sizes-228.patch +++ b/patch/change-oidc-provider-field-sizes-228.patch @@ -281,7 +281,7 @@ diff -ur oidc_provider.orig/migrations/0021_refresh_token_not_unique.py oidc_pro diff -ur oidc_provider.orig/models.py oidc_provider/models.py --- oidc_provider.orig/models.py 2018-09-14 21:34:52.000000000 +0200 +++ oidc_provider/models.py 2020-06-07 13:34:26.830716635 +0200 -@@ -67,8 +67,8 @@ +@@ -66,8 +66,8 @@ verbose_name=_(u'Client Type'), help_text=_(u'<b>Confidential</b> clients are capable of maintaining the confidentiality' u' of their credentials. <b>Public</b> clients are incapable.')) @@ -292,7 +292,7 @@ diff -ur oidc_provider.orig/models.py oidc_provider/models.py response_types = models.ManyToManyField(ResponseType) jwt_alg = models.CharField( max_length=10, -@@ -78,15 +78,15 @@ +@@ -77,15 +77,15 @@ help_text=_(u'Algorithm used to encode ID Tokens.')) date_created = models.DateField(auto_now_add=True, verbose_name=_(u'Date Created')) website_url = models.CharField( @@ -311,7 +311,7 @@ diff -ur oidc_provider.orig/models.py oidc_provider/models.py logo = models.FileField( blank=True, default='', upload_to='oidc_provider/clients', verbose_name=_(u'Logo Image')) reuse_consent = models.BooleanField( -@@ -186,12 +186,12 @@ +@@ -185,12 +185,12 @@ user = models.ForeignKey( settings.AUTH_USER_MODEL, verbose_name=_(u'User'), on_delete=models.CASCADE) @@ -328,7 +328,7 @@ diff -ur oidc_provider.orig/models.py oidc_provider/models.py class Meta: verbose_name = _(u'Authorization Code') -@@ -205,8 +205,8 @@ +@@ -204,8 +204,8 @@ user = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, verbose_name=_(u'User'), on_delete=models.CASCADE) diff --git a/requirements.txt b/requirements.txt index 4fd7db4d4..70c970a44 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,7 +17,7 @@ django-csp>=3.7 django-cors-headers>=3.11.0 django-debug-toolbar>=3.2.4 django-markup>=1.5 # Limited use - need to reconcile against direct use of markdown -django-oidc-provider>=0.7,<0.8 # 0.8 dropped Django 2 support +django-oidc-provider>=0.8 # 0.8 dropped Django 2 support django-password-strength>=1.2.1 django-referrer-policy>=1.0 django-simple-history>=3.0.0 From ebd28cd78361384d9c143964f82123088d9a16f9 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 12 May 2023 17:19:41 -0300 Subject: [PATCH 033/101] chore: Update fix-oidc-access-token-post.patch --- patch/fix-oidc-access-token-post.patch | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/patch/fix-oidc-access-token-post.patch b/patch/fix-oidc-access-token-post.patch index 4234fdf61..00271633e 100644 --- a/patch/fix-oidc-access-token-post.patch +++ b/patch/fix-oidc-access-token-post.patch @@ -11,12 +11,10 @@ diff -ur oidc_provider.orig/lib/utils/common.py oidc_provider/lib/utils/common.p --- oidc_provider.orig/lib/utils/oauth2.py 2020-05-22 15:09:21.009044320 +0200 +++ oidc_provider/lib/utils/oauth2.py 2020-06-05 17:05:23.271285858 +0200 -@@ -21,10 +21,14 @@ - """ +@@ -22,9 +22,13 @@ auth_header = request.META.get('HTTP_AUTHORIZATION', '') -- if re.compile('^[Bb]earer\s{1}.+$').match(auth_header): -+ if re.compile(r'^[Bb]earer\s{1}.+$').match(auth_header): + if re.compile(r'^[Bb]earer\s{1}.+$').match(auth_header): access_token = auth_header.split()[1] - else: + elif request.method == 'GET': @@ -27,13 +25,4 @@ diff -ur oidc_provider.orig/lib/utils/common.py oidc_provider/lib/utils/common.p + access_token = '' return access_token - -@@ -39,7 +43,7 @@ - """ - auth_header = request.META.get('HTTP_AUTHORIZATION', '') - -- if re.compile('^Basic\s{1}.+$').match(auth_header): -+ if re.compile(r'^Basic\s{1}.+$').match(auth_header): - b64_user_pass = auth_header.split()[1] - try: - user_pass = b64decode(b64_user_pass).decode('utf-8').split(':') + From 09276150b5a7f0cd06cec68e6291852d88ff879b Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 12 May 2023 17:25:20 -0300 Subject: [PATCH 034/101] chore: Update tastypie-django22-fielderror-response.patch --- patch/tastypie-django22-fielderror-response.patch | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/patch/tastypie-django22-fielderror-response.patch b/patch/tastypie-django22-fielderror-response.patch index bb4decd75..ffb152d31 100644 --- a/patch/tastypie-django22-fielderror-response.patch +++ b/patch/tastypie-django22-fielderror-response.patch @@ -1,15 +1,15 @@ --- tastypie/resources.py.orig 2020-08-24 13:14:25.463166100 +0200 +++ tastypie/resources.py 2020-08-24 13:15:55.133759224 +0200 -@@ -15,7 +15,7 @@ +@@ -12,7 +12,7 @@ ObjectDoesNotExist, MultipleObjectsReturned, ValidationError, FieldDoesNotExist ) from django.core.signals import got_request_exception -from django.core.exceptions import ImproperlyConfigured +from django.core.exceptions import ImproperlyConfigured, FieldError from django.db.models.fields.related import ForeignKey - try: - from django.contrib.gis.db.models.fields import GeometryField -@@ -2207,6 +2207,8 @@ + from django.urls.conf import re_path + from tastypie.utils.timezone import make_naive_utc +@@ -2198,6 +2198,8 @@ return self.authorized_read_list(objects, bundle) except ValueError: raise BadRequest("Invalid resource lookup data provided (mismatched type).") @@ -20,7 +20,7 @@ """ --- tastypie/paginator.py.orig 2020-08-25 15:24:46.391588425 +0200 +++ tastypie/paginator.py 2020-08-25 15:24:53.591797122 +0200 -@@ -128,6 +128,8 @@ +@@ -124,6 +124,8 @@ except (AttributeError, TypeError): # If it's not a QuerySet (or it's ilk), fallback to ``len``. return len(self.objects) From 4cfe1c17d020fab93ea21d7856967eb5fc56b2da Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 12 May 2023 17:28:01 -0300 Subject: [PATCH 035/101] chore: Update django-cookie-delete-with-all-settings.patch --- patch/django-cookie-delete-with-all-settings.patch | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/patch/django-cookie-delete-with-all-settings.patch b/patch/django-cookie-delete-with-all-settings.patch index 01dee277d..bf4e45c2e 100644 --- a/patch/django-cookie-delete-with-all-settings.patch +++ b/patch/django-cookie-delete-with-all-settings.patch @@ -1,6 +1,6 @@ --- django/contrib/messages/storage/cookie.py.orig 2020-08-13 11:10:36.719177122 +0200 +++ django/contrib/messages/storage/cookie.py 2020-08-13 11:45:23.503463150 +0200 -@@ -95,6 +95,8 @@ +@@ -108,6 +108,8 @@ response.delete_cookie( self.cookie_name, domain=settings.SESSION_COOKIE_DOMAIN, @@ -11,7 +11,7 @@ --- django/http/response.py.orig 2020-08-13 11:16:04.060627793 +0200 +++ django/http/response.py 2020-08-13 11:54:03.482476973 +0200 -@@ -210,12 +210,18 @@ +@@ -243,12 +243,18 @@ value = signing.get_cookie_signer(salt=key + salt).sign(value) return self.set_cookie(key, value, **kwargs) @@ -43,7 +43,7 @@ --- django/contrib/sessions/middleware.py.orig 2020-08-13 12:12:12.401898114 +0200 +++ django/contrib/sessions/middleware.py 2020-08-13 12:14:52.690520659 +0200 -@@ -42,6 +42,8 @@ +@@ -40,6 +40,8 @@ settings.SESSION_COOKIE_NAME, path=settings.SESSION_COOKIE_PATH, domain=settings.SESSION_COOKIE_DOMAIN, From 9d21196adcc0d4d7074a1324ed14704abcd2af18 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 12 May 2023 18:03:28 -0300 Subject: [PATCH 036/101] chore: Add DEFAULT_AUTO_FIELD to settings.py --- ietf/settings.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ietf/settings.py b/ietf/settings.py index 0aca062b7..6756e6f91 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -12,6 +12,8 @@ import datetime import warnings from typing import Any, Dict, List, Tuple # pyflakes:ignore +import django.db.models + warnings.simplefilter("always", DeprecationWarning) warnings.filterwarnings("ignore", message="'urllib3\\[secure\\]' extra is deprecated") warnings.filterwarnings("ignore", message="The logout\\(\\) view is superseded by") @@ -102,6 +104,11 @@ USE_I18N = False USE_TZ = True +# Default primary key field type to use for models that don’t have a field with primary_key=True. +# In the future (relative to 4.2), the default will become 'django.db.models.BigAutoField.' +DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' + + if SERVER_MODE == 'production': MEDIA_ROOT = '/a/www/www6s/lib/dt/media/' MEDIA_URL = 'https://www.ietf.org/lib/dt/media/' From 075aed7e9a1a2bb472eaff483a0c557ddb1e4daf Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 12 May 2023 18:26:56 -0300 Subject: [PATCH 037/101] refactor: Replace deprecated request.is_ajax() --- ietf/community/views.py | 5 +++-- ietf/doc/views_review.py | 3 ++- ietf/utils/http.py | 10 ++++++++++ 3 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 ietf/utils/http.py diff --git a/ietf/community/views.py b/ietf/community/views.py index b0646424a..054bed302 100644 --- a/ietf/community/views.py +++ b/ietf/community/views.py @@ -22,6 +22,7 @@ from ietf.community.utils import docs_tracked_by_community_list, docs_matching_c from ietf.community.utils import states_of_significant_change, reset_name_contains_index_for_rule from ietf.doc.models import DocEvent, Document from ietf.doc.utils_search import prepare_document_table +from ietf.utils.http import is_ajax from ietf.utils.response import permission_denied def view_list(request, username=None): @@ -142,7 +143,7 @@ def track_document(request, name, username=None, acronym=None): if not doc in clist.added_docs.all(): clist.added_docs.add(doc) - if request.is_ajax(): + if is_ajax(request): return HttpResponse(json.dumps({ 'success': True }), content_type='application/json') else: return HttpResponseRedirect(clist.get_absolute_url()) @@ -162,7 +163,7 @@ def untrack_document(request, name, username=None, acronym=None): if clist.pk is not None: clist.added_docs.remove(doc) - if request.is_ajax(): + if is_ajax(request): return HttpResponse(json.dumps({ 'success': True }), content_type='application/json') else: return HttpResponseRedirect(clist.get_absolute_url()) diff --git a/ietf/doc/views_review.py b/ietf/doc/views_review.py index a13e9eb08..2d09c928b 100644 --- a/ietf/doc/views_review.py +++ b/ietf/doc/views_review.py @@ -53,6 +53,7 @@ from ietf.utils.textupload import get_cleaned_text_file_content from ietf.utils.mail import send_mail_message from ietf.mailtrigger.utils import gather_address_lists from ietf.utils.fields import MultiEmailField +from ietf.utils.http import is_ajax from ietf.utils.response import permission_denied from ietf.utils.timezone import date_today, DEADLINE_TZINFO @@ -1090,7 +1091,7 @@ def _generate_ajax_or_redirect_response(request, doc): redirect_url = request.GET.get('next') url_is_safe = is_safe_url(url=redirect_url, allowed_hosts=request.get_host(), require_https=request.is_secure()) - if request.is_ajax(): + if is_ajax(request): return HttpResponse(json.dumps({'success': True}), content_type='application/json') elif url_is_safe: return HttpResponseRedirect(redirect_url) diff --git a/ietf/utils/http.py b/ietf/utils/http.py new file mode 100644 index 000000000..6e6409e31 --- /dev/null +++ b/ietf/utils/http.py @@ -0,0 +1,10 @@ +# Copyright The IETF Trust 2023, All Rights Reserved +# -*- coding: utf-8 -*- + +def is_ajax(request): + """Checks whether a request was an AJAX call + + See https://docs.djangoproject.com/en/3.1/releases/3.1/#id2 - this implements the + exact reproduction of the deprecated method suggested there. + """ + return request.headers.get("x-requested-with") == "XMLHttpRequest" From b5d9e9b14c97af3e5480e85eb4065a689202001f Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 12 May 2023 18:27:51 -0300 Subject: [PATCH 038/101] refactor: Replace deprecated django.utils.http.urlquote --- ietf/ietfauth/utils.py | 2 +- ietf/nomcom/decorators.py | 3 ++- ietf/secr/utils/decorators.py | 2 +- ietf/sync/iana.py | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/ietf/ietfauth/utils.py b/ietf/ietfauth/utils.py index 702b0199d..52f582ca8 100644 --- a/ietf/ietfauth/utils.py +++ b/ietf/ietfauth/utils.py @@ -8,6 +8,7 @@ import oidc_provider.lib.claims from functools import wraps, WRAPPER_ASSIGNMENTS +from urllib.parse import quote as urlquote from django.conf import settings from django.contrib.auth import REDIRECT_FIELD_NAME @@ -15,7 +16,6 @@ from django.core.exceptions import PermissionDenied from django.db.models import Q from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404 -from django.utils.http import urlquote import debug # pyflakes:ignore diff --git a/ietf/nomcom/decorators.py b/ietf/nomcom/decorators.py index a002f7c7e..43250bd30 100644 --- a/ietf/nomcom/decorators.py +++ b/ietf/nomcom/decorators.py @@ -3,10 +3,11 @@ import functools +from urllib.parse import quote as urlquote from django.urls import reverse from django.http import HttpResponseRedirect -from django.utils.http import urlquote + def nomcom_private_key_required(view_func): diff --git a/ietf/secr/utils/decorators.py b/ietf/secr/utils/decorators.py index f635bc7ec..5887c3c9c 100644 --- a/ietf/secr/utils/decorators.py +++ b/ietf/secr/utils/decorators.py @@ -1,12 +1,12 @@ # Copyright The IETF Trust 2013-2020, All Rights Reserved from functools import wraps +from urllib.parse import quote as urlquote from django.conf import settings from django.contrib.auth import REDIRECT_FIELD_NAME from django.core.exceptions import ObjectDoesNotExist from django.http import HttpResponseRedirect from django.shortcuts import render, get_object_or_404 -from django.utils.http import urlquote from ietf.ietfauth.utils import has_role from ietf.doc.models import Document diff --git a/ietf/sync/iana.py b/ietf/sync/iana.py index 9993d492a..dc61f9159 100644 --- a/ietf/sync/iana.py +++ b/ietf/sync/iana.py @@ -10,11 +10,11 @@ import re import requests from email.utils import parsedate_to_datetime +from urllib.parse import quote as urlquote from django.conf import settings from django.utils import timezone from django.utils.encoding import smart_bytes, force_str -from django.utils.http import urlquote import debug # pyflakes:ignore From bee7e113606823bbe2995d2b298105d4523f9f11 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 12 May 2023 18:29:39 -0300 Subject: [PATCH 039/101] chore: Remove accidental import from settings.py --- ietf/settings.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/ietf/settings.py b/ietf/settings.py index 6756e6f91..4e145e16d 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -12,8 +12,6 @@ import datetime import warnings from typing import Any, Dict, List, Tuple # pyflakes:ignore -import django.db.models - warnings.simplefilter("always", DeprecationWarning) warnings.filterwarnings("ignore", message="'urllib3\\[secure\\]' extra is deprecated") warnings.filterwarnings("ignore", message="The logout\\(\\) view is superseded by") From 32ed1b7c4a4e90c570f51201bf04e2bd1a94ecf4 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 12 May 2023 18:33:15 -0300 Subject: [PATCH 040/101] refactor: Replace deprecated {% ifequal %} with {% if %} --- ietf/templates/doc/document_referenced_by.html | 4 ++-- ietf/templates/doc/document_references.html | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ietf/templates/doc/document_referenced_by.html b/ietf/templates/doc/document_referenced_by.html index 30d536d79..3557ecf56 100644 --- a/ietf/templates/doc/document_referenced_by.html +++ b/ietf/templates/doc/document_referenced_by.html @@ -64,7 +64,7 @@ </a> </td> <td> - {% ifequal ref.source.get_state.slug 'rfc' %} + {% if ref.source.get_state.slug == 'rfc' %} {% with ref.source.std_level as lvl %} {% if lvl %}{{ lvl }}{% endif %} {% endwith %} @@ -72,7 +72,7 @@ {% with ref.source.intended_std_level as lvl %} {% if lvl %}{{ lvl }}{% endif %} {% endwith %} - {% endifequal %} + {% end %} </td> <td>{{ ref.relationship.name }}</td> <td>{{ ref.is_downref|default:'' }}</td> diff --git a/ietf/templates/doc/document_references.html b/ietf/templates/doc/document_references.html index 4578d6b8c..d9134be6f 100644 --- a/ietf/templates/doc/document_references.html +++ b/ietf/templates/doc/document_references.html @@ -51,7 +51,7 @@ </a> </td> <td> - {% ifequal ref.target.document.get_state.slug 'rfc' %} + {% if ref.target.document.get_state.slug == 'rfc' %} {% with ref.target.document.std_level as lvl %} {% if lvl %}{{ lvl }}{% endif %} {% endwith %} @@ -59,7 +59,7 @@ {% with ref.target.document.intended_std_level as lvl %} {% if lvl %}{{ lvl }}{% endif %} {% endwith %} - {% endifequal %} + {% endif %} </td> <td>{{ ref.relationship.name }}</td> <td>{{ ref.is_downref|default:'' }}</td> From 21004864b0f9bc87b4700675da848dce46357f08 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 12 May 2023 18:35:12 -0300 Subject: [PATCH 041/101] refactor: Replace deprecated is_safe_url with new name --- ietf/doc/views_review.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ietf/doc/views_review.py b/ietf/doc/views_review.py index 2d09c928b..96229ccca 100644 --- a/ietf/doc/views_review.py +++ b/ietf/doc/views_review.py @@ -11,7 +11,7 @@ import requests import email.utils from django.utils import timezone -from django.utils.http import is_safe_url +from django.utils.http import url_has_allowed_host_and_scheme from simple_history.utils import update_change_reason @@ -1089,7 +1089,7 @@ def review_wishes_remove(request, name): def _generate_ajax_or_redirect_response(request, doc): redirect_url = request.GET.get('next') - url_is_safe = is_safe_url(url=redirect_url, allowed_hosts=request.get_host(), + url_is_safe = url_has_allowed_host_and_scheme(url=redirect_url, allowed_hosts=request.get_host(), require_https=request.is_secure()) if is_ajax(request): return HttpResponse(json.dumps({'success': True}), content_type='application/json') From 79b749fcdfa83a5411f27fd1d3e0655b64d3432b Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 12 May 2023 18:36:05 -0300 Subject: [PATCH 042/101] style: Restyle method using Black --- ietf/doc/views_review.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/ietf/doc/views_review.py b/ietf/doc/views_review.py index 96229ccca..fa6e3a7ff 100644 --- a/ietf/doc/views_review.py +++ b/ietf/doc/views_review.py @@ -1088,11 +1088,16 @@ def review_wishes_remove(request, name): def _generate_ajax_or_redirect_response(request, doc): - redirect_url = request.GET.get('next') - url_is_safe = url_has_allowed_host_and_scheme(url=redirect_url, allowed_hosts=request.get_host(), - require_https=request.is_secure()) + redirect_url = request.GET.get("next") + url_is_safe = url_has_allowed_host_and_scheme( + url=redirect_url, + allowed_hosts=request.get_host(), + require_https=request.is_secure(), + ) if is_ajax(request): - return HttpResponse(json.dumps({'success': True}), content_type='application/json') + return HttpResponse( + json.dumps({"success": True}), content_type="application/json" + ) elif url_is_safe: return HttpResponseRedirect(redirect_url) else: From 872bdef06b9c87c0cfa97f5930e01206e92ef8aa Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 12 May 2023 18:37:01 -0300 Subject: [PATCH 043/101] refactor: Use gettext instead of deprecated ugettext --- ietf/group/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ietf/group/admin.py b/ietf/group/admin.py index d6cbf5f1b..afaa87c0b 100644 --- a/ietf/group/admin.py +++ b/ietf/group/admin.py @@ -16,7 +16,7 @@ from django.http import Http404 from django.shortcuts import render from django.utils.encoding import force_str from django.utils.html import escape -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext as _ from ietf.group.models import (Group, GroupFeatures, GroupHistory, GroupEvent, GroupURL, GroupMilestone, GroupMilestoneHistory, GroupStateTransitions, Role, RoleHistory, ChangeStateGroupEvent, From 00f3f01e7ea4c443522be06efa7b50f99f4dd05c Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 12 May 2023 18:52:35 -0300 Subject: [PATCH 044/101] fix: {% endif %}, not {% end %} --- ietf/templates/doc/document_referenced_by.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ietf/templates/doc/document_referenced_by.html b/ietf/templates/doc/document_referenced_by.html index 3557ecf56..958108bdd 100644 --- a/ietf/templates/doc/document_referenced_by.html +++ b/ietf/templates/doc/document_referenced_by.html @@ -72,7 +72,7 @@ {% with ref.source.intended_std_level as lvl %} {% if lvl %}{{ lvl }}{% endif %} {% endwith %} - {% end %} + {% endif %} </td> <td>{{ ref.relationship.name }}</td> <td>{{ ref.is_downref|default:'' }}</td> From 587bc4d730202981ab6a0469a0a09de89e24372d Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 12 May 2023 20:00:14 -0300 Subject: [PATCH 045/101] test: Remove outdated mypy test exceptions --- mypy.ini | 11 ----------- requirements.txt | 7 +++++++ 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/mypy.ini b/mypy.ini index 825bf0316..19df7ec9b 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,7 +1,5 @@ [mypy] -#mypy_path = ./stubs/ - ignore_missing_imports = True plugins = @@ -9,12 +7,3 @@ plugins = [mypy.plugins.django-stubs] django_settings_module = ietf.settings - -[mypy-ietf.group.migrations.0004_add_group_feature_fields] -ignore_errors = True - -[mypy-ietf.group.migrations.0002_groupfeatures_historicalgroupfeatures] -ignore_errors = True - -[mypy-ietf.doc.migrations.0001_initial] -ignore_errors = True diff --git a/requirements.txt b/requirements.txt index 70c970a44..0b97607f6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,9 +5,11 @@ argon2-cffi>=21.3.0 # For the Argon2 password hasher option beautifulsoup4>=4.11.1 # Only used in tests bibtexparser>=1.2.0 # Only used in tests bleach>=6 +types-bleach>=6 celery>=5.2.6 coverage>=4.5.4,<5.0 # Coverage 5.x moves from a json database to SQLite. Moving to 5.x will require substantial rewrites in ietf.utils.test_runner and ietf.release.views decorator>=5.1.1 +types-decorator>=5.1.1 defusedxml>=0.7.1 # for TastyPie when using xml; not a declared dependency Django<4 django-analytical>=3.1.0 @@ -28,6 +30,7 @@ django-webtest>=1.9.10 # Only used in tests django-widget-tweaks>=1.4.12 djlint>=1.0.0 # To auto-indent templates via "djlint --profile django --reformat" docutils>=0.18.1 # Used only by dbtemplates for RestructuredText +types-docutils>=0.18.1 factory-boy>=3.2.1 github3.py>=3.2.0 gunicorn>=20.1.0 @@ -40,7 +43,9 @@ jwcrypto>=1.2 # for signed notifications - this is aspirational, and is not r logging_tree>=1.9 # Used only by the showloggers management command lxml>=4.8.0,<5 markdown>=3.3.6 +types-markdown>=3.3.6 mock>=4.0.3 # Used only by tests, of course +types-mock>=4.0.3 mypy<1.3 # Version requirements determined by django-stubs. oic>=1.3 # Used only by tests Pillow>=9.1.0 @@ -50,11 +55,13 @@ pyflakes>=2.4.0 pyopenssl>=22.0.0 # Used by urllib3.contrib, which is used by PyQuery but not marked as a dependency pyquery>=1.4.3 python-dateutil>=2.8.2 +types-python-dateutil>=2.8.2 python-magic==0.4.18 # Versions beyond the yanked .19 and .20 introduce form failures python-memcached>=1.59 # for django.core.cache.backends.memcached python-mimeparse>=1.6 # from TastyPie pytz==2022.2.1 # Pinned as changes need to be vetted for their effect on Meeting fields requests>=2.27.1 +types-requests>=2.27.1 requests-mock>=1.9.3 rfc2html>=2.0.3 scout-apm>=2.24.2 From 7ad74c99e446a6c29e7f1e19d1ad9fc737c171a6 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 12 May 2023 20:29:11 -0300 Subject: [PATCH 046/101] refactor: import from django.urls instead of django.conf.urls --- ietf/api/urls.py | 2 +- ietf/doc/urls.py | 4 ++-- ietf/group/urls.py | 2 +- ietf/meeting/urls.py | 4 ++-- ietf/secr/urls.py | 2 +- ietf/urls.py | 3 +-- ietf/utils/urls.py | 4 ++-- 7 files changed, 10 insertions(+), 11 deletions(-) diff --git a/ietf/api/urls.py b/ietf/api/urls.py index 5185b9f88..7ee55cf70 100644 --- a/ietf/api/urls.py +++ b/ietf/api/urls.py @@ -1,7 +1,7 @@ # Copyright The IETF Trust 2017, All Rights Reserved from django.conf import settings -from django.conf.urls import include +from django.urls import include from django.views.generic import TemplateView from ietf import api diff --git a/ietf/doc/urls.py b/ietf/doc/urls.py index edfb89f38..f4b672804 100644 --- a/ietf/doc/urls.py +++ b/ietf/doc/urls.py @@ -33,9 +33,9 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -from django.conf.urls import include -from django.views.generic import RedirectView from django.conf import settings +from django.urls import include +from django.views.generic import RedirectView from ietf.doc import views_search, views_draft, views_ballot, views_status_change, views_doc, views_downref, views_stats, views_help, views_bofreq from ietf.utils.urls import url diff --git a/ietf/group/urls.py b/ietf/group/urls.py index 713a0b7ee..0e4f7ef2f 100644 --- a/ietf/group/urls.py +++ b/ietf/group/urls.py @@ -1,7 +1,7 @@ # Copyright The IETF Trust 2013-2020, All Rights Reserved from django.conf import settings -from django.conf.urls import include +from django.urls import include from django.views.generic import RedirectView from ietf.community import views as community_views diff --git a/ietf/meeting/urls.py b/ietf/meeting/urls.py index 8284f8fcf..d7a623899 100644 --- a/ietf/meeting/urls.py +++ b/ietf/meeting/urls.py @@ -1,8 +1,8 @@ # Copyright The IETF Trust 2007-2020, All Rights Reserved -from django.conf.urls import include -from django.views.generic import RedirectView from django.conf import settings +from django.urls import include +from django.views.generic import RedirectView from ietf.meeting import views, views_proceedings from ietf.utils.urls import url diff --git a/ietf/secr/urls.py b/ietf/secr/urls.py index 8e80af62c..e5d3c19c3 100644 --- a/ietf/secr/urls.py +++ b/ietf/secr/urls.py @@ -1,4 +1,4 @@ -from django.conf.urls import re_path, include +from django.urls import re_path, include from django.views.generic import TemplateView urlpatterns = [ diff --git a/ietf/urls.py b/ietf/urls.py index 972e06cb3..8eb08b8d0 100644 --- a/ietf/urls.py +++ b/ietf/urls.py @@ -1,15 +1,14 @@ # Copyright The IETF Trust 2007-2022, All Rights Reserved from django.conf import settings -from django.conf.urls import include from django.conf.urls.static import static as static_url from django.contrib import admin from django.contrib.sitemaps import views as sitemap_views from django.contrib.staticfiles.urls import staticfiles_urlpatterns +from django.urls import include, path from django.views import static as static_view from django.views.generic import TemplateView from django.views.defaults import server_error -from django.urls import path import debug # pyflakes:ignore diff --git a/ietf/utils/urls.py b/ietf/utils/urls.py index 9be48e16b..9c26da724 100644 --- a/ietf/utils/urls.py +++ b/ietf/utils/urls.py @@ -6,9 +6,9 @@ import debug # pyflakes:ignore from inspect import isclass -from django.conf.urls import re_path -from django.views.generic import View +from django.urls import re_path from django.utils.encoding import force_str +from django.views.generic import View def url(regex, view, kwargs=None, name=None): if callable(view) and hasattr(view, '__name__'): From 68eb68538233b1427fdd8179430c60639c04aed0 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Sat, 13 May 2023 10:05:33 -0300 Subject: [PATCH 047/101] test: Use django_stubs_ext.QuerySetAny for isinstance() checks mypy complains if QuerySet is used because it uses a parameterized generic type. --- ietf/api/serializer.py | 5 +++-- ietf/liaisons/forms.py | 4 ++-- ietf/liaisons/widgets.py | 7 ++++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/ietf/api/serializer.py b/ietf/api/serializer.py index e4253bfb6..27f194c5b 100644 --- a/ietf/api/serializer.py +++ b/ietf/api/serializer.py @@ -11,9 +11,10 @@ from django.core.serializers.json import Serializer from django.http import HttpResponse from django.utils.encoding import smart_str from django.db.models import Field -from django.db.models.query import QuerySet from django.db.models.signals import post_save, post_delete, m2m_changed +from django_stubs_ext import QuerySetAny + import debug # pyflakes:ignore @@ -145,7 +146,7 @@ class AdminJsonSerializer(Serializer): field_value = None else: field_value = field - if isinstance(field_value, QuerySet) or isinstance(field_value, list): + if isinstance(field_value, QuerySetAny) or isinstance(field_value, list): self._current[name] = dict([ (rel.pk, self.expand_related(rel, name)) for rel in field_value ]) else: if hasattr(field_value, "_meta"): diff --git a/ietf/liaisons/forms.py b/ietf/liaisons/forms.py index 2c2811375..b41351b94 100644 --- a/ietf/liaisons/forms.py +++ b/ietf/liaisons/forms.py @@ -13,11 +13,11 @@ from email.utils import parseaddr from django import forms from django.conf import settings from django.core.exceptions import ObjectDoesNotExist, ValidationError -from django.db.models.query import QuerySet from django.forms.utils import ErrorList from django.db.models import Q #from django.forms.widgets import RadioFieldRenderer from django.core.validators import validate_email +from django_stubs_ext import QuerySetAny import debug # pyflakes:ignore @@ -203,7 +203,7 @@ class SearchLiaisonForm(forms.Form): class CustomModelMultipleChoiceField(forms.ModelMultipleChoiceField): '''If value is a QuerySet, return it as is (for use in widget.render)''' def prepare_value(self, value): - if isinstance(value, QuerySet): + if isinstance(value, QuerySetAny): return value if (hasattr(value, '__iter__') and not isinstance(value, str) and diff --git a/ietf/liaisons/widgets.py b/ietf/liaisons/widgets.py index d6e2fe936..74368e83f 100644 --- a/ietf/liaisons/widgets.py +++ b/ietf/liaisons/widgets.py @@ -3,11 +3,12 @@ from django.urls import reverse as urlreverse -from django.db.models.query import QuerySet from django.forms.widgets import Widget from django.utils.safestring import mark_safe from django.utils.html import conditional_escape +from django_stubs_ext import QuerySetAny + class ButtonWidget(Widget): def __init__(self, *args, **kwargs): @@ -34,7 +35,7 @@ class ShowAttachmentsWidget(Widget): html = '<div id="id_%s">' % name html += '<span class="d-none showAttachmentsEmpty form-control widget">No files attached</span>' html += '<div class="attachedFiles form-control widget">' - if value and isinstance(value, QuerySet): + if value and isinstance(value, QuerySetAny): for attachment in value: html += '<a class="initialAttach" href="%s">%s</a> ' % (conditional_escape(attachment.document.get_href()), conditional_escape(attachment.document.title)) html += '<a class="btn btn-primary btn-sm" href="{}">Edit</a> '.format(urlreverse("ietf.liaisons.views.liaison_edit_attachment", kwargs={'object_id':attachment.statement.pk,'doc_id':attachment.document.pk})) @@ -43,4 +44,4 @@ class ShowAttachmentsWidget(Widget): else: html += 'No files attached' html += '</div></div>' - return mark_safe(html) \ No newline at end of file + return mark_safe(html) From c840d53cb2bb8a569a556fe5c9105d29d54de788 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Mon, 15 May 2023 11:04:29 -0300 Subject: [PATCH 048/101] test: Suppress mypy error on import of _lazy_re_compile() --- ietf/utils/validators.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ietf/utils/validators.py b/ietf/utils/validators.py index ec7366fd1..9642a2877 100644 --- a/ietf/utils/validators.py +++ b/ietf/utils/validators.py @@ -11,10 +11,11 @@ from urllib.parse import urlparse, urlsplit, urlunsplit from django.apps import apps from django.conf import settings from django.core.exceptions import ObjectDoesNotExist, ValidationError -from django.core.validators import RegexValidator, URLValidator, EmailValidator, _lazy_re_compile, BaseValidator +from django.core.validators import RegexValidator, URLValidator, EmailValidator, BaseValidator from django.template.defaultfilters import filesizeformat from django.utils.deconstruct import deconstructible from django.utils.ipv6 import is_valid_ipv6_address +from django.utils.regex_helper import _lazy_re_compile # type: ignore from django.utils.translation import gettext_lazy as _ import debug # pyflakes:ignore From cbb946455ff1314619cb607b0da7819fab184d25 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Mon, 15 May 2023 11:25:38 -0300 Subject: [PATCH 049/101] test: Remove unused assignment that caused a mypy error --- ietf/doc/views_stats.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ietf/doc/views_stats.py b/ietf/doc/views_stats.py index ab71efce7..34f670d5c 100644 --- a/ietf/doc/views_stats.py +++ b/ietf/doc/views_stats.py @@ -25,7 +25,6 @@ from ietf.utils.timezone import date_today epochday = datetime.datetime.utcfromtimestamp(0).date().toordinal() -column_chart_conf = settings.CHART_TYPE_COLUMN_OPTIONS def dt(s): "Convert the date string returned by sqlite's date() to a datetime.date" From 22bf50892293a40ee89c4354b8c85f504341180f Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Mon, 15 May 2023 12:20:02 -0300 Subject: [PATCH 050/101] test: Suppress notices from mypy involving factory types --- ietf/meeting/tests_js.py | 10 +++++----- ietf/meeting/tests_views.py | 2 +- .../management/commands/generate_name_fixture.py | 2 +- ietf/submit/tests.py | 12 ++++++------ 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/ietf/meeting/tests_js.py b/ietf/meeting/tests_js.py index 2e7a5de6c..abb2fb777 100644 --- a/ietf/meeting/tests_js.py +++ b/ietf/meeting/tests_js.py @@ -1493,7 +1493,7 @@ class EditTimeslotsTests(IetfSeleniumTestCase): """Test the timeslot editor""" def setUp(self): super().setUp() - self.meeting: Meeting = MeetingFactory( + self.meeting: Meeting = MeetingFactory( # type: ignore[annotation-unchecked] type_id='ietf', number=120, date=date_today() + datetime.timedelta(days=10), @@ -1570,13 +1570,13 @@ class EditTimeslotsTests(IetfSeleniumTestCase): delete_time = delete_time_local.astimezone(datetime.timezone.utc) duration = datetime.timedelta(minutes=60) - delete: [TimeSlot] = TimeSlotFactory.create_batch( + delete: [TimeSlot] = TimeSlotFactory.create_batch( # type: ignore[annotation-unchecked] 2, meeting=self.meeting, time=delete_time_local, duration=duration, ) - keep: [TimeSlot] = [ + keep: [TimeSlot] = [ # type: ignore[annotation-unchecked] TimeSlotFactory( meeting=self.meeting, time=keep_time, @@ -1613,14 +1613,14 @@ class EditTimeslotsTests(IetfSeleniumTestCase): hours = [10, 12] other_days = [self.meeting.get_meeting_date(d) for d in range(1, 3)] - delete: [TimeSlot] = [ + delete: [TimeSlot] = [ # type: ignore[annotation-unchecked] TimeSlotFactory( meeting=self.meeting, time=datetime_from_date(delete_day, self.meeting.tz()).replace(hour=hour), ) for hour in hours ] - keep: [TimeSlot] = [ + keep: [TimeSlot] = [ # type: ignore[annotation-unchecked] TimeSlotFactory( meeting=self.meeting, time=datetime_from_date(day, self.meeting.tz()).replace(hour=hour), diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py index 449ad03a3..a1bd2bf58 100644 --- a/ietf/meeting/tests_views.py +++ b/ietf/meeting/tests_views.py @@ -2357,7 +2357,7 @@ class EditTimeslotsTests(TestCase): def test_invalid_edit_timeslot(self): meeting = self.create_bare_meeting() - ts: TimeSlot = TimeSlotFactory(meeting=meeting, name='slot') # n.b., colon indicates type hinting + ts: TimeSlot = TimeSlotFactory(meeting=meeting, name='slot') # type: ignore[annotation-unchecked] self.login() r = self.client.post( self.edit_timeslot_url(ts), diff --git a/ietf/name/management/commands/generate_name_fixture.py b/ietf/name/management/commands/generate_name_fixture.py index 02dc08faf..bbf33e600 100644 --- a/ietf/name/management/commands/generate_name_fixture.py +++ b/ietf/name/management/commands/generate_name_fixture.py @@ -67,7 +67,7 @@ class Command(BaseCommand): pprint(connection.queries) raise - objects = [] # type: List[object] + objects: List[object] = [] # type: ignore[annotation-unchecked] model_objects = {} import ietf.name.models diff --git a/ietf/submit/tests.py b/ietf/submit/tests.py index 28fa36b75..84cb9db7c 100644 --- a/ietf/submit/tests.py +++ b/ietf/submit/tests.py @@ -1204,15 +1204,15 @@ class SubmitTests(BaseSubmitTestCase): Unlike some other tests in this module, does not confirm draft if this would be required. """ - orig_draft = DocumentFactory( + orig_draft: Document = DocumentFactory( # type: ignore[annotation-unchecked] type_id='draft', group=GroupFactory(type_id=group_type) if group_type else None, stream_id=stream_type, - ) # type: Document + ) name = orig_draft.name group = orig_draft.group new_rev = '%02d' % (int(orig_draft.rev) + 1) - author = PersonFactory() # type: Person + author: Person = PersonFactory() # type: ignore[annotation-unchecked] DocumentAuthor.objects.create(person=author, document=orig_draft) orig_draft.docextresource_set.create(name_id='faq', value='https://faq.example.com/') orig_draft.docextresource_set.create(name_id='wiki', value='https://wiki.example.com', display_name='Test Wiki') @@ -1982,7 +1982,7 @@ class SubmitTests(BaseSubmitTestCase): group = GroupFactory() # someone to be notified of resource suggestion when permission not granted RoleFactory(group=group, person=PersonFactory(), name_id='chair') - submission = SubmissionFactory(state_id='grp-appr', group=group) # type: Submission + submission: Submission = SubmissionFactory(state_id='grp-appr', group=group) # type: ignore[annotation-unchecked] SubmissionExtResourceFactory(submission=submission) # use secretary user to ensure we have permission to approve @@ -2000,7 +2000,7 @@ class SubmitTests(BaseSubmitTestCase): group = GroupFactory() # someone to be notified of resource suggestion when permission not granted RoleFactory(group=group, person=PersonFactory(), name_id='chair') - submission = SubmissionFactory(state_id=state, group=group) # type: Submission + submission: Submission = SubmissionFactory(state_id=state, group=group) # type: ignore[annotation-unchecked] SubmissionExtResourceFactory(submission=submission) url = urlreverse( @@ -2052,7 +2052,7 @@ class SubmitTests(BaseSubmitTestCase): def test_forcepost_with_extresources(self): # state needs to be one that has 'posted' as a next state - submission = SubmissionFactory(state_id='grp-appr') # type: Submission + submission: Submission = SubmissionFactory(state_id='grp-appr') # type: ignore[annotation-unchecked] SubmissionExtResourceFactory(submission=submission) url = urlreverse( From 0319f35e0fbb2e858ae608a05a008a29b41750f1 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Mon, 15 May 2023 13:01:59 -0300 Subject: [PATCH 051/101] test: Use Django 3.2 HttpResponse.headers API --- ietf/doc/tests.py | 2 +- ietf/meeting/tests_views.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ietf/doc/tests.py b/ietf/doc/tests.py index 65859bf4b..47c4e146c 100644 --- a/ietf/doc/tests.py +++ b/ietf/doc/tests.py @@ -1779,7 +1779,7 @@ class DocTestCase(TestCase): self.client.login(username='ad', password='ad+password') r = self.client.post(urlreverse('ietf.doc.views_status_change.change_state',kwargs=dict(name=doc.name)),dict(new_state=iesgeval_pk)) self.assertEqual(r.status_code, 302) - r = self.client.get(r._headers["location"][1]) + r = self.client.get(r.headers["location"]) self.assertContains(r, ">IESG Evaluation<") self.assertEqual(len(outbox), 2) self.assertIn('iesg-secretary',outbox[0]['To']) diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py index a1bd2bf58..22d77f9d2 100644 --- a/ietf/meeting/tests_views.py +++ b/ietf/meeting/tests_views.py @@ -556,7 +556,7 @@ class MeetingTests(BaseMeetingTestCase): self.assertContains(r, "1. More work items underway") - cont_disp = r._headers.get('content-disposition', ('Content-Disposition', ''))[1] + cont_disp = r.headers.get('content-disposition', ('Content-Disposition', ''))[1] cont_disp = re.split('; ?', cont_disp) cont_disp_settings = dict( e.split('=', 1) for e in cont_disp if '=' in e ) filename = cont_disp_settings.get('filename', '').strip('"') From 329fa26ee0290ea90d996412b18a1d0c4b5e3e4d Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Mon, 15 May 2023 15:29:00 -0300 Subject: [PATCH 052/101] chore: Remove abandoned django-password-strength package --- ietf/settings.py | 2 -- .../fix-django-password-strength-kwargs.patch | 36 ------------------- requirements.txt | 1 - 3 files changed, 39 deletions(-) delete mode 100644 patch/fix-django-password-strength-kwargs.patch diff --git a/ietf/settings.py b/ietf/settings.py index 4e145e16d..e0c6820a8 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -433,7 +433,6 @@ INSTALLED_APPS = [ 'django_celery_beat', 'corsheaders', 'django_markup', - 'django_password_strength', 'oidc_provider', 'simple_history', 'tastypie', @@ -1124,7 +1123,6 @@ CHECKS_LIBRARY_PATCHES_TO_APPLY = [ 'patch/change-oidc-provider-field-sizes-228.patch', 'patch/fix-oidc-access-token-post.patch', 'patch/fix-jwkest-jwt-logging.patch', - 'patch/fix-django-password-strength-kwargs.patch', 'patch/django-cookie-delete-with-all-settings.patch', 'patch/tastypie-django22-fielderror-response.patch', ] diff --git a/patch/fix-django-password-strength-kwargs.patch b/patch/fix-django-password-strength-kwargs.patch deleted file mode 100644 index 9f24ce932..000000000 --- a/patch/fix-django-password-strength-kwargs.patch +++ /dev/null @@ -1,36 +0,0 @@ ---- django_password_strength/widgets.py.orig 2020-06-24 16:07:28.479533134 +0200 -+++ django_password_strength/widgets.py 2020-06-24 16:08:09.540714290 +0200 -@@ -8,7 +8,7 @@ - Form widget to show the user how strong his/her password is. - """ - -- def render(self, name, value, attrs=None): -+ def render(self, name, value, **kwargs): - strength_markup = """ - <div style="margin-top: 10px;"> - <div class="progress" style="margin-bottom: 10px;"> -@@ -30,7 +30,7 @@ - except KeyError: - self.attrs['class'] = 'password_strength' - -- return mark_safe( super(PasswordInput, self).render(name, value, attrs) + strength_markup ) -+ return mark_safe( super(PasswordInput, self).render(name, value, **kwargs) + strength_markup ) - - class Media: - js = ( -@@ -48,7 +48,7 @@ - super(PasswordConfirmationInput, self).__init__(attrs, render_value) - self.confirm_with=confirm_with - -- def render(self, name, value, attrs=None): -+ def render(self, name, value, **kwargs): - if self.confirm_with: - self.attrs['data-confirm-with'] = 'id_%s' % self.confirm_with - -@@ -68,4 +68,4 @@ - except KeyError: - self.attrs['class'] = 'password_confirmation' - -- return mark_safe( super(PasswordInput, self).render(name, value, attrs) + confirmation_markup ) -+ return mark_safe( super(PasswordInput, self).render(name, value, **kwargs) + confirmation_markup ) - diff --git a/requirements.txt b/requirements.txt index 0b97607f6..48491aa2a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,7 +20,6 @@ django-cors-headers>=3.11.0 django-debug-toolbar>=3.2.4 django-markup>=1.5 # Limited use - need to reconcile against direct use of markdown django-oidc-provider>=0.8 # 0.8 dropped Django 2 support -django-password-strength>=1.2.1 django-referrer-policy>=1.0 django-simple-history>=3.0.0 django-stubs>=4.2.0 # The django-stubs version used determines the the mypy version indicated below From b714bfb083e43b8e44b6b90104a9e6ae64c5367d Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Mon, 15 May 2023 17:55:11 -0300 Subject: [PATCH 053/101] chore: Put widgets from django-password-strength into ietfauth --- ietf/ietfauth/forms.py | 4 +- ietf/ietfauth/widgets.py | 114 ++++++++++++++++++ .../registration/change_password.html | 3 +- .../registration/confirm_account.html | 3 +- 4 files changed, 118 insertions(+), 6 deletions(-) create mode 100644 ietf/ietfauth/widgets.py diff --git a/ietf/ietfauth/forms.py b/ietf/ietfauth/forms.py index ce9f58f49..9b8ee22e0 100644 --- a/ietf/ietfauth/forms.py +++ b/ietf/ietfauth/forms.py @@ -11,14 +11,14 @@ from django.core.exceptions import ValidationError from django.db import models from django.contrib.auth.models import User -from django_password_strength.widgets import PasswordStrengthInput, PasswordConfirmationInput - import debug # pyflakes:ignore from ietf.person.models import Person, Email from ietf.mailinglists.models import Allowlisted from ietf.utils.text import isascii +from .widgets import PasswordStrengthInput, PasswordConfirmationInput + class RegistrationForm(forms.Form): email = forms.EmailField(label="Your email (lowercase)") diff --git a/ietf/ietfauth/widgets.py b/ietf/ietfauth/widgets.py new file mode 100644 index 000000000..6b01a67bd --- /dev/null +++ b/ietf/ietfauth/widgets.py @@ -0,0 +1,114 @@ +from django.forms import PasswordInput +from django.utils.safestring import mark_safe +from django.utils.translation import gettext as _ + +# The PasswordStrengthInput and PasswordConfirmationInput widgets come from the +# django-password-strength project, https://pypi.org/project/django-password-strength/ +# +# Original license: +# +# Copyright © 2015 A.J. May and individual contributors. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +# following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +# disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +# following disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +# products derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +class PasswordStrengthInput(PasswordInput): + """ + Form widget to show the user how strong his/her password is. + """ + + def render(self, name, value, attrs=None, renderer=None): + strength_markup = """ + <div style="margin-top: 10px;"> + <div class="progress" style="margin-bottom: 10px;"> + <div class="progress-bar progress-bar-warning password_strength_bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="5" style="width: 0%%"></div> + </div> + <p class="text-muted password_strength_info hidden"> + <span class="label label-danger"> + %s + </span> + <span style="margin-left:5px;"> + %s + </span> + </p> + </div> + """ % ( + _("Warning"), + _( + 'This password would take <em class="password_strength_time"></em> to crack.' + ), + ) + + try: + self.attrs["class"] = "%s password_strength".strip() % self.attrs["class"] + except KeyError: + self.attrs["class"] = "password_strength" + + return mark_safe( + super(PasswordInput, self).render(name, value, attrs, renderer) + + strength_markup + ) + + class Media: + js = ( + "ietf/js/zxcvbn.js", + "ietf/js/password_strength.js", + ) + + +class PasswordConfirmationInput(PasswordInput): + """ + Form widget to confirm the users password by letting him/her type it again. + """ + + def __init__(self, confirm_with=None, attrs=None, render_value=False): + super(PasswordConfirmationInput, self).__init__(attrs, render_value) + self.confirm_with = confirm_with + + def render(self, name, value, attrs=None, renderer=None): + if self.confirm_with: + self.attrs["data-confirm-with"] = "id_%s" % self.confirm_with + + confirmation_markup = """ + <div style="margin-top: 10px;" class="hidden password_strength_info"> + <p class="text-muted"> + <span class="label label-danger"> + %s + </span> + <span style="margin-left:5px;">%s</span> + </p> + </div> + """ % ( + _("Warning"), + _("Your passwords don't match."), + ) + + try: + self.attrs["class"] = ( + "%s password_confirmation".strip() % self.attrs["class"] + ) + except KeyError: + self.attrs["class"] = "password_confirmation" + + return mark_safe( + super(PasswordInput, self).render(name, value, attrs, renderer) + + confirmation_markup + ) diff --git a/ietf/templates/registration/change_password.html b/ietf/templates/registration/change_password.html index 1df189031..21c102bd0 100644 --- a/ietf/templates/registration/change_password.html +++ b/ietf/templates/registration/change_password.html @@ -6,8 +6,7 @@ {% block title %}Change password{% endblock %} {% block js %} {{ block.super }} - <script src="{% static 'ietf/js/zxcvbn.js' %}"></script> - <script src="{% static 'ietf/js/password_strength.js' %}"></script> + {{ form.media.js }} {% endblock %} {% block content %} {% origin %} diff --git a/ietf/templates/registration/confirm_account.html b/ietf/templates/registration/confirm_account.html index b419b5f34..d6639d8e7 100644 --- a/ietf/templates/registration/confirm_account.html +++ b/ietf/templates/registration/confirm_account.html @@ -6,8 +6,7 @@ {% block title %}Complete account creation{% endblock %} {% block js %} {{ block.super }} - <script src="{% static 'ietf/js/zxcvbn.js' %}"></script> - <script src="{% static 'ietf/js/password_strength.js' %}"></script> + {{ form.media.js }} {% endblock %} {% block content %} {% origin %} From 869562e914207ebad97050a47a510bff34b56118 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Tue, 16 May 2023 11:23:35 -0300 Subject: [PATCH 054/101] chore: Update requirements.txt to Django 4.0 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 48491aa2a..1c39cf4ec 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ coverage>=4.5.4,<5.0 # Coverage 5.x moves from a json database to SQLite. Mo decorator>=5.1.1 types-decorator>=5.1.1 defusedxml>=0.7.1 # for TastyPie when using xml; not a declared dependency -Django<4 +Django<4.1 django-analytical>=3.1.0 django-bootstrap5>=21.3 django-celery-beat>=2.3.0 From 374c1a40beea646ad3d4f66ba7d5b9ef88a82e4d Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Tue, 16 May 2023 11:26:30 -0300 Subject: [PATCH 055/101] chore: Use new format for CSRF_TRUSTED_ORIGINS setting --- ietf/settings.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ietf/settings.py b/ietf/settings.py index e0c6820a8..6f8673d7b 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -319,7 +319,14 @@ UTILS_LOGGER_LEVELS: Dict[str, str] = { X_FRAME_OPTIONS = 'SAMEORIGIN' -CSRF_TRUSTED_ORIGINS = ['ietf.org', '*.ietf.org', 'meetecho.com', '*.meetecho.com', 'gather.town', '*.gather.town', ] +CSRF_TRUSTED_ORIGINS = [ + "https://ietf.org", + "https://*.ietf.org", + 'https://meetecho.com', + 'https://*.meetecho.com', + 'https://gather.town', + 'https://*.gather.town', +] CSRF_COOKIE_SAMESITE = 'None' CSRF_COOKIE_SECURE = True From 76fa01b8170fc3e3145930dc831edb334c65d8b0 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Tue, 16 May 2023 13:21:12 -0300 Subject: [PATCH 056/101] chore: Suppress deprecation warning for oidc_provider AppConfig --- ietf/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ietf/settings.py b/ietf/settings.py index 6f8673d7b..81091e1f3 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -13,6 +13,7 @@ import warnings from typing import Any, Dict, List, Tuple # pyflakes:ignore warnings.simplefilter("always", DeprecationWarning) +warnings.filterwarnings("ignore", message="'oidc_provider' defines default_app_config") # hopefully only need until Django 4.1 or 4.2 warnings.filterwarnings("ignore", message="'urllib3\\[secure\\]' extra is deprecated") warnings.filterwarnings("ignore", message="The logout\\(\\) view is superseded by") warnings.filterwarnings("ignore", message="Report.file_reporters will no longer be available in Coverage.py 4.2", module="coverage.report") From e7ae72bce5c12f3a8864de92df145caf1d0ddffc Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Tue, 16 May 2023 13:58:06 -0300 Subject: [PATCH 057/101] chore: Update django-cookie-delete-with-all-settings.patch --- ...ango-cookie-delete-with-all-settings.patch | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/patch/django-cookie-delete-with-all-settings.patch b/patch/django-cookie-delete-with-all-settings.patch index bf4e45c2e..9b327928d 100644 --- a/patch/django-cookie-delete-with-all-settings.patch +++ b/patch/django-cookie-delete-with-all-settings.patch @@ -1,6 +1,6 @@ --- django/contrib/messages/storage/cookie.py.orig 2020-08-13 11:10:36.719177122 +0200 +++ django/contrib/messages/storage/cookie.py 2020-08-13 11:45:23.503463150 +0200 -@@ -108,6 +108,8 @@ +@@ -109,6 +109,8 @@ response.delete_cookie( self.cookie_name, domain=settings.SESSION_COOKIE_DOMAIN, @@ -11,39 +11,42 @@ --- django/http/response.py.orig 2020-08-13 11:16:04.060627793 +0200 +++ django/http/response.py 2020-08-13 11:54:03.482476973 +0200 -@@ -243,12 +243,18 @@ +@@ -261,20 +261,28 @@ value = signing.get_cookie_signer(salt=key + salt).sign(value) return self.set_cookie(key, value, **kwargs) - -- def delete_cookie(self, key, path='/', domain=None, samesite=None): -+ def delete_cookie(self, key, path='/', domain=None, secure=False, httponly=False, samesite=None): + +- def delete_cookie(self, key, path="/", domain=None, samesite=None): ++ def delete_cookie(self, key, path="/", domain=None, secure=False, httponly=False, samesite=None): # Browsers can ignore the Set-Cookie header if the cookie doesn't use # the secure flag and: # - the cookie name starts with "__Host-" or "__Secure-", or # - the samesite is "none". -- secure = ( -- key.startswith(('__Secure-', '__Host-')) or -- (samesite and samesite.lower() == 'none') +- secure = key.startswith(("__Secure-", "__Host-")) or ( +- samesite and samesite.lower() == "none" - ) + if key in self.cookies: -+ domain = self.cookies[key].get('domain', domain) -+ secure = self.cookies[key].get('secure', secure) -+ httponly = self.cookies[key].get('httponly', httponly) -+ samesite = self.cookies[key].get('samesite', samesite) ++ domain = self.cookies[key].get("domain", domain) ++ secure = self.cookies[key].get("secure", secure) ++ httponly = self.cookies[key].get("httponly", httponly) ++ samesite = self.cookies[key].get("samesite", samesite) + else: + secure = secure or ( -+ key.startswith(('__Secure-', '__Host-')) or -+ (samesite and samesite.lower() == 'none') ++ key.startswith(("__Secure-", "__Host-")) or ++ (samesite and samesite.lower() == "none") + ) self.set_cookie( -- key, max_age=0, path=path, domain=domain, secure=secure, -+ key, max_age=0, path=path, domain=domain, secure=secure, httponly=httponly, - expires='Thu, 01 Jan 1970 00:00:00 GMT', samesite=samesite, + key, + max_age=0, + path=path, + domain=domain, + secure=secure, ++ httponly=httponly, + expires="Thu, 01 Jan 1970 00:00:00 GMT", + samesite=samesite, ) - --- django/contrib/sessions/middleware.py.orig 2020-08-13 12:12:12.401898114 +0200 +++ django/contrib/sessions/middleware.py 2020-08-13 12:14:52.690520659 +0200 -@@ -40,6 +40,8 @@ +@@ -38,6 +38,8 @@ settings.SESSION_COOKIE_NAME, path=settings.SESSION_COOKIE_PATH, domain=settings.SESSION_COOKIE_DOMAIN, @@ -51,4 +54,4 @@ + httponly=settings.SESSION_COOKIE_HTTPONLY or None, samesite=settings.SESSION_COOKIE_SAMESITE, ) - patch_vary_headers(response, ('Cookie',)) + patch_vary_headers(response, ("Cookie",)) From 88452a2db1213bc92ab97a525a1456c615a7080a Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Tue, 16 May 2023 16:20:51 -0300 Subject: [PATCH 058/101] chore: Add USE_DEPRECATED_PYTZ to settings.py --- ietf/settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ietf/settings.py b/ietf/settings.py index 81091e1f3..ca7b1e179 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -102,6 +102,8 @@ SITE_ID = 1 USE_I18N = False USE_TZ = True +USE_DEPRECATED_PYTZ = True # supported until Django 5 + # Default primary key field type to use for models that don’t have a field with primary_key=True. # In the future (relative to 4.2), the default will become 'django.db.models.BigAutoField.' From 223c679942d8809d3d6ba27e310ff3da997fe8e5 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Tue, 16 May 2023 17:48:44 -0300 Subject: [PATCH 059/101] chore: Add no-op migration to satisfy Django bookkeeping changes --- ...y_ad_alter_dochistory_shepherd_and_more.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 ietf/doc/migrations/0004_alter_dochistory_ad_alter_dochistory_shepherd_and_more.py diff --git a/ietf/doc/migrations/0004_alter_dochistory_ad_alter_dochistory_shepherd_and_more.py b/ietf/doc/migrations/0004_alter_dochistory_ad_alter_dochistory_shepherd_and_more.py new file mode 100644 index 000000000..adc0e6962 --- /dev/null +++ b/ietf/doc/migrations/0004_alter_dochistory_ad_alter_dochistory_shepherd_and_more.py @@ -0,0 +1,36 @@ +# Generated by Django 4.0.10 on 2023-05-16 20:36 + +from django.db import migrations +import django.db.models.deletion +import ietf.utils.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('person', '0001_initial'), + ('doc', '0003_remove_document_info_order'), + ] + + operations = [ + migrations.AlterField( + model_name='dochistory', + name='ad', + field=ietf.utils.models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='ad_%(class)s_set', to='person.person', verbose_name='area director'), + ), + migrations.AlterField( + model_name='dochistory', + name='shepherd', + field=ietf.utils.models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='shepherd_%(class)s_set', to='person.email'), + ), + migrations.AlterField( + model_name='document', + name='ad', + field=ietf.utils.models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='ad_%(class)s_set', to='person.person', verbose_name='area director'), + ), + migrations.AlterField( + model_name='document', + name='shepherd', + field=ietf.utils.models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='shepherd_%(class)s_set', to='person.email'), + ), + ] From 8cf609bfa93ccf5f3094467083e4452de74cffc1 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Wed, 17 May 2023 09:45:07 -0300 Subject: [PATCH 060/101] refactor: Implement require_api_key with functools.wraps The @decorator mechanism does not seem to work with @method_decorator in Django 4.0, have not tracked down why. --- ietf/utils/decorators.py | 95 +++++++++++++++++++++------------------- ietf/utils/urls.py | 4 +- 2 files changed, 52 insertions(+), 47 deletions(-) diff --git a/ietf/utils/decorators.py b/ietf/utils/decorators.py index d37d255a3..db919bd4b 100644 --- a/ietf/utils/decorators.py +++ b/ietf/utils/decorators.py @@ -5,6 +5,7 @@ import datetime from decorator import decorator, decorate +from functools import wraps from django.conf import settings from django.contrib.auth import login @@ -39,52 +40,54 @@ def person_required(f, request, *args, **kwargs): return render(request, 'registration/missing_person.html') return f(request, *args, **kwargs) -@decorator -def require_api_key(f, request, *args, **kwargs): - - def err(code, text): - return HttpResponse(text, status=code, content_type='text/plain') - # Check method and get hash - if request.method == 'POST': - hash = request.POST.get('apikey') - elif request.method == 'GET': - hash = request.GET.get('apikey') - else: - return err(405, "Method not allowed") - if not hash: - return err(400, "Missing apikey parameter") - # Check hash - key = PersonalApiKey.validate_key(force_bytes(hash)) - if not key: - return err(403, "Invalid apikey") - # Check endpoint - urlpath = request.META.get('PATH_INFO') - if not (urlpath and urlpath == key.endpoint): - return err(400, "Apikey endpoint mismatch") - # Check time since regular login - person = key.person - last_login = person.user.last_login - if not person.user.is_staff: - time_limit = (timezone.now() - datetime.timedelta(days=settings.UTILS_APIKEY_GUI_LOGIN_LIMIT_DAYS)) - if last_login == None or last_login < time_limit: - return err(400, "Too long since last regular login") - # Log in - login(request, person.user) - # restore the user.last_login field, so it reflects only gui logins - person.user.last_login = last_login - person.user.save() - # Update stats - key.count += 1 - key.latest = timezone.now() - key.save() - PersonApiKeyEvent.objects.create(person=person, type='apikey_login', key=key, desc="Logged in with key ID %s, endpoint %s" % (key.id, key.endpoint)) - # Execute decorated function - try: - ret = f(request, *args, **kwargs) - except AttributeError as e: - log.log("Bad API call: args: %s, kwargs: %s, exception: %s" % (args, kwargs, e)) - return err(400, "Bad or missing parameters") - return ret + +def require_api_key(f): + @wraps(f) + def _wrapper(request, *args, **kwargs): + def err(code, text): + return HttpResponse(text, status=code, content_type='text/plain') + # Check method and get hash + if request.method == 'POST': + hash = request.POST.get('apikey') + elif request.method == 'GET': + hash = request.GET.get('apikey') + else: + return err(405, "Method not allowed") + if not hash: + return err(400, "Missing apikey parameter") + # Check hash + key = PersonalApiKey.validate_key(force_bytes(hash)) + if not key: + return err(403, "Invalid apikey") + # Check endpoint + urlpath = request.META.get('PATH_INFO') + if not (urlpath and urlpath == key.endpoint): + return err(400, "Apikey endpoint mismatch") + # Check time since regular login + person = key.person + last_login = person.user.last_login + if not person.user.is_staff: + time_limit = (timezone.now() - datetime.timedelta(days=settings.UTILS_APIKEY_GUI_LOGIN_LIMIT_DAYS)) + if last_login == None or last_login < time_limit: + return err(400, "Too long since last regular login") + # Log in + login(request, person.user) + # restore the user.last_login field, so it reflects only gui logins + person.user.last_login = last_login + person.user.save() + # Update stats + key.count += 1 + key.latest = timezone.now() + key.save() + PersonApiKeyEvent.objects.create(person=person, type='apikey_login', key=key, desc="Logged in with key ID %s, endpoint %s" % (key.id, key.endpoint)) + # Execute decorated function + try: + ret = f(request, *args, **kwargs) + except AttributeError as e: + log.log("Bad API call: args: %s, kwargs: %s, exception: %s" % (args, kwargs, e)) + return err(400, "Bad or missing parameters") + return ret + return _wrapper def _memoize(func, self, *args, **kwargs): diff --git a/ietf/utils/urls.py b/ietf/utils/urls.py index 9c26da724..6abda9b97 100644 --- a/ietf/utils/urls.py +++ b/ietf/utils/urls.py @@ -11,7 +11,9 @@ from django.utils.encoding import force_str from django.views.generic import View def url(regex, view, kwargs=None, name=None): - if callable(view) and hasattr(view, '__name__'): + if hasattr(view, "view_class"): + view_name = "%s.%s" % (view.__module__, view.view_class.__name__) + elif callable(view) and hasattr(view, '__name__'): view_name = "%s.%s" % (view.__module__, view.__name__) else: view_name = regex From f85978fe24386c9b03cb169c9b2161b1fb36fb96 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Wed, 17 May 2023 12:52:34 -0300 Subject: [PATCH 061/101] chore: Disable L10N localization --- ietf/settings.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ietf/settings.py b/ietf/settings.py index ca7b1e179..3fd08ffa4 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -101,6 +101,10 @@ SITE_ID = 1 # to load the internationalization machinery. USE_I18N = False +# Django 4.0 changed the default setting of USE_L10N to True. The setting +# is deprecated and will be removed in Django 5.0. +USE_L10N = False + USE_TZ = True USE_DEPRECATED_PYTZ = True # supported until Django 5 From d519bca12ce69e4e6478a75fca3dd04d1ef657b8 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Wed, 17 May 2023 15:59:28 -0300 Subject: [PATCH 062/101] test: Fix ignore_pattern so Redirect/TemplateViews are ignored again --- ietf/utils/test_runner.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ietf/utils/test_runner.py b/ietf/utils/test_runner.py index 281b22724..b901c97b2 100644 --- a/ietf/utils/test_runner.py +++ b/ietf/utils/test_runner.py @@ -74,7 +74,7 @@ from django.urls import URLResolver # type: ignore from django.template.backends.django import DjangoTemplates from django.template.backends.django import Template # type: ignore[attr-defined] from django.utils import timezone -# from django.utils.safestring import mark_safe +from django.views.generic import RedirectView, TemplateView import debug # pyflakes:ignore debug.debug = True @@ -550,8 +550,10 @@ class CoverageTest(unittest.TestCase): return (regex in ("^_test500/$", "^accounts/testemail/$") or regex.startswith("^admin/") or re.search('^api/v1/[^/]+/[^/]+/', regex) - or getattr(pattern.callback, "__name__", "") == "RedirectView" - or getattr(pattern.callback, "__name__", "") == "TemplateView" + or ( + hasattr(pattern.callback, "view_class") + and isinstance(pattern.callback.view_class, (RedirectView, TemplateView)) + ) or pattern.callback == django.views.static.serve) patterns = [(regex, re.compile(regex, re.U), obj) for regex, obj in url_patterns From cbabb864c259d4253d258c8284c7c70d292600fe Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Wed, 17 May 2023 16:09:56 -0300 Subject: [PATCH 063/101] test: Use issubclass, not isinstance, to ID view_class --- ietf/utils/test_runner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ietf/utils/test_runner.py b/ietf/utils/test_runner.py index b901c97b2..8905eab90 100644 --- a/ietf/utils/test_runner.py +++ b/ietf/utils/test_runner.py @@ -552,7 +552,7 @@ class CoverageTest(unittest.TestCase): or re.search('^api/v1/[^/]+/[^/]+/', regex) or ( hasattr(pattern.callback, "view_class") - and isinstance(pattern.callback.view_class, (RedirectView, TemplateView)) + and issubclass(pattern.callback.view_class, (RedirectView, TemplateView)) ) or pattern.callback == django.views.static.serve) From e7cc2878364fcfd94faac9acbfed36e10410fa55 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Wed, 17 May 2023 17:42:12 -0300 Subject: [PATCH 064/101] chore: Update requirements.txt for Django 4.1 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 1c39cf4ec..e05b31200 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ coverage>=4.5.4,<5.0 # Coverage 5.x moves from a json database to SQLite. Mo decorator>=5.1.1 types-decorator>=5.1.1 defusedxml>=0.7.1 # for TastyPie when using xml; not a declared dependency -Django<4.1 +Django<4.2 django-analytical>=3.1.0 django-bootstrap5>=21.3 django-celery-beat>=2.3.0 From c26c9c71e4b9f0c4e62cf6929455b5db84b08f44 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Wed, 17 May 2023 17:42:51 -0300 Subject: [PATCH 065/101] chore: Switch to PyMemcacheCache backend --- ietf/settings.py | 4 ++-- requirements.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ietf/settings.py b/ietf/settings.py index 3fd08ffa4..e7011abf9 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -716,13 +716,13 @@ CACHE_MIDDLEWARE_KEY_PREFIX = '' # This setting is possibly overridden further down, after the import of settings_local CACHES = { 'default': { - 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', + 'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache', 'LOCATION': '127.0.0.1:11211', 'VERSION': __version__, 'KEY_PREFIX': 'ietf:dt', }, 'sessions': { - 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', + 'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache', 'LOCATION': '127.0.0.1:11211', # No release-specific VERSION setting. 'KEY_PREFIX': 'ietf:dt', diff --git a/requirements.txt b/requirements.txt index e05b31200..75aca6574 100644 --- a/requirements.txt +++ b/requirements.txt @@ -56,7 +56,7 @@ pyquery>=1.4.3 python-dateutil>=2.8.2 types-python-dateutil>=2.8.2 python-magic==0.4.18 # Versions beyond the yanked .19 and .20 introduce form failures -python-memcached>=1.59 # for django.core.cache.backends.memcached +pymemcache>=4.0.0 # for django.core.cache.backends.memcached.PyMemcacheCache python-mimeparse>=1.6 # from TastyPie pytz==2022.2.1 # Pinned as changes need to be vetted for their effect on Meeting fields requests>=2.27.1 From 3c2def34f91c430beb7846b8b20395b39ebacdbb Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Wed, 17 May 2023 17:55:02 -0300 Subject: [PATCH 066/101] chore: Update django-cookie-delete-with-all-settings.patch --- patch/django-cookie-delete-with-all-settings.patch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patch/django-cookie-delete-with-all-settings.patch b/patch/django-cookie-delete-with-all-settings.patch index 9b327928d..8c194ae33 100644 --- a/patch/django-cookie-delete-with-all-settings.patch +++ b/patch/django-cookie-delete-with-all-settings.patch @@ -11,7 +11,7 @@ --- django/http/response.py.orig 2020-08-13 11:16:04.060627793 +0200 +++ django/http/response.py 2020-08-13 11:54:03.482476973 +0200 -@@ -261,20 +261,28 @@ +@@ -279,20 +279,28 @@ value = signing.get_cookie_signer(salt=key + salt).sign(value) return self.set_cookie(key, value, **kwargs) From 47e2b0b0278d7242a7e8b9ae4ab31b13056e90e1 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Wed, 17 May 2023 19:53:02 -0300 Subject: [PATCH 067/101] fix: Prevent use of FK relation before review request is saved --- ietf/review/policies.py | 9 ++++++--- ietf/review/utils.py | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/ietf/review/policies.py b/ietf/review/policies.py index e834891f7..fe6519a5e 100644 --- a/ietf/review/policies.py +++ b/ietf/review/policies.py @@ -183,9 +183,12 @@ class AbstractReviewerQueuePolicy: role__group=review_req.team ).exclude( person_id__in=rejecting_reviewer_ids ) - one_assignment = (review_req.reviewassignment_set - .exclude(state__slug__in=('rejected', 'no-response')) - .first()) + one_assignment = None + if review_req.pk is not None: + # cannot use reviewassignment_set relation until review_req has been created + one_assignment = (review_req.reviewassignment_set + .exclude(state__slug__in=('rejected', 'no-response')) + .first()) if one_assignment: field.initial = one_assignment.reviewer_id diff --git a/ietf/review/utils.py b/ietf/review/utils.py index 1e9a237b5..2b9979c95 100644 --- a/ietf/review/utils.py +++ b/ietf/review/utils.py @@ -382,7 +382,8 @@ def assign_review_request_to_reviewer(request, review_req, reviewer, add_skip=Fa # with a different view on a ReviewAssignment. log.assertion('reviewer is not None') - if review_req.reviewassignment_set.filter(reviewer=reviewer).exists(): + # cannot reference reviewassignment_set relation until pk exists + if review_req.pk is not None and review_req.reviewassignment_set.filter(reviewer=reviewer).exists(): return # Note that assigning a review no longer unassigns other reviews From 55fb50217947f742e7981bd94a7ac247c0aa3287 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Thu, 18 May 2023 10:42:01 -0300 Subject: [PATCH 068/101] test: Iterate over template.nodelist in apply_template_test --- ietf/utils/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ietf/utils/tests.py b/ietf/utils/tests.py index 69c16be6d..fe715b14d 100644 --- a/ietf/utils/tests.py +++ b/ietf/utils/tests.py @@ -209,7 +209,7 @@ class TemplateChecksTestCase(TestCase): errors = [] for path, template in self.templates.items(): origin = str(template.origin).replace(settings.BASE_DIR, '') - for node in template: + for node in template.nodelist: for child in node.get_nodes_by_type(node_type): errors += func(child, origin, *args, **kwargs) if errors: From d81a092574d1ecddb0901609d3883ff06b8ea322 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Thu, 18 May 2023 11:13:00 -0300 Subject: [PATCH 069/101] chore: Update requirements.txt for Django 4.2 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 75aca6574..972095fb4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ coverage>=4.5.4,<5.0 # Coverage 5.x moves from a json database to SQLite. Mo decorator>=5.1.1 types-decorator>=5.1.1 defusedxml>=0.7.1 # for TastyPie when using xml; not a declared dependency -Django<4.2 +Django>4.2,<5 django-analytical>=3.1.0 django-bootstrap5>=21.3 django-celery-beat>=2.3.0 From 171a5bec73151735deef14f40f155f62015dd257 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Thu, 18 May 2023 12:11:58 -0300 Subject: [PATCH 070/101] chore: Update setuptools version and suppress warnings pkg_resources warning is caused by a few packages (django-simple-history. django-widget-tweaks, etc). The datetime_safe warning is in tastypie, as indicated. The others are deprecated settings we already have tickets for. --- ietf/settings.py | 5 ++++- requirements.txt | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/ietf/settings.py b/ietf/settings.py index e7011abf9..c1786615c 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -13,7 +13,10 @@ import warnings from typing import Any, Dict, List, Tuple # pyflakes:ignore warnings.simplefilter("always", DeprecationWarning) -warnings.filterwarnings("ignore", message="'oidc_provider' defines default_app_config") # hopefully only need until Django 4.1 or 4.2 +warnings.filterwarnings("ignore", message="pkg_resources is deprecated as an API") +warnings.filterwarnings("ignore", module="tastypie", message="The django.utils.datetime_safe module is deprecated.") +warnings.filterwarnings("ignore", message="The USE_DEPRECATED_PYTZ setting,") # https://github.com/ietf-tools/datatracker/issues/5635 +warnings.filterwarnings("ignore", message="The USE_L10N setting is deprecated.") # https://github.com/ietf-tools/datatracker/issues/5648 warnings.filterwarnings("ignore", message="'urllib3\\[secure\\]' extra is deprecated") warnings.filterwarnings("ignore", message="The logout\\(\\) view is superseded by") warnings.filterwarnings("ignore", message="Report.file_reporters will no longer be available in Coverage.py 4.2", module="coverage.report") diff --git a/requirements.txt b/requirements.txt index 972095fb4..81f72ce60 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ # -*- conf-mode -*- -setuptools>=51.1.0,<67.5.0 # Require this first, to prevent later errors +setuptools>=51.1.0 # Require this first, to prevent later errors # argon2-cffi>=21.3.0 # For the Argon2 password hasher option beautifulsoup4>=4.11.1 # Only used in tests @@ -45,7 +45,7 @@ markdown>=3.3.6 types-markdown>=3.3.6 mock>=4.0.3 # Used only by tests, of course types-mock>=4.0.3 -mypy<1.3 # Version requirements determined by django-stubs. +mypy~=1.2.0 # Version requirements determined by django-stubs. oic>=1.3 # Used only by tests Pillow>=9.1.0 psycopg2>=2.9.6 From 65ea426793746a5293a3f0a97cc4a58df3b8473b Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Thu, 18 May 2023 13:15:58 -0300 Subject: [PATCH 071/101] fix: Add changed fields to update_fields in Model.save() methods --- ietf/nomcom/models.py | 4 +++- ietf/stats/models.py | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ietf/nomcom/models.py b/ietf/nomcom/models.py index f2f9c7b31..28116354c 100644 --- a/ietf/nomcom/models.py +++ b/ietf/nomcom/models.py @@ -187,9 +187,11 @@ class NomineePosition(models.Model): ordering = ['nominee'] def save(self, **kwargs): + update_fields = kwargs.pop("update_fields", None) if not self.pk and not self.state_id: self.state = NomineePositionStateName.objects.get(slug='pending') - super(NomineePosition, self).save(**kwargs) + update_fields = {"slug"}.union(update_fields or set()) + super().save(update_fields=update_fields, **kwargs) def __str__(self): return "%s - %s - %s" % (self.nominee, self.state, self.position) diff --git a/ietf/stats/models.py b/ietf/stats/models.py index 422c5b78a..0871804b0 100644 --- a/ietf/stats/models.py +++ b/ietf/stats/models.py @@ -24,7 +24,8 @@ class AffiliationAlias(models.Model): def save(self, *args, **kwargs): self.alias = self.alias.lower() - super(AffiliationAlias, self).save(*args, **kwargs) + update_fields = {"alias"}.union(kwargs.pop("update_fields", set())) + super(AffiliationAlias, self).save(update_fields=update_fields, *args, **kwargs) class Meta: verbose_name_plural = "affiliation aliases" From f56dfd66eb3ea86dd437ad44c9bd042688b8153b Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Thu, 18 May 2023 16:57:01 -0300 Subject: [PATCH 072/101] chore: Update django-cookie-delete-with-all-settings.patch --- patch/django-cookie-delete-with-all-settings.patch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patch/django-cookie-delete-with-all-settings.patch b/patch/django-cookie-delete-with-all-settings.patch index 8c194ae33..fb8bbbe4f 100644 --- a/patch/django-cookie-delete-with-all-settings.patch +++ b/patch/django-cookie-delete-with-all-settings.patch @@ -11,7 +11,7 @@ --- django/http/response.py.orig 2020-08-13 11:16:04.060627793 +0200 +++ django/http/response.py 2020-08-13 11:54:03.482476973 +0200 -@@ -279,20 +279,28 @@ +@@ -282,20 +282,28 @@ value = signing.get_cookie_signer(salt=key + salt).sign(value) return self.set_cookie(key, value, **kwargs) From 6df5d4c67f8febc0ba41a3721b1c748b57992f7b Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Thu, 18 May 2023 17:11:51 -0300 Subject: [PATCH 073/101] chore: Suppress CICharField deprecation warning --- ietf/settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ietf/settings.py b/ietf/settings.py index c1786615c..0d369d92b 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -17,6 +17,7 @@ warnings.filterwarnings("ignore", message="pkg_resources is deprecated as an API warnings.filterwarnings("ignore", module="tastypie", message="The django.utils.datetime_safe module is deprecated.") warnings.filterwarnings("ignore", message="The USE_DEPRECATED_PYTZ setting,") # https://github.com/ietf-tools/datatracker/issues/5635 warnings.filterwarnings("ignore", message="The USE_L10N setting is deprecated.") # https://github.com/ietf-tools/datatracker/issues/5648 +warnings.filterwarnings("ignore", message="\\(fields.W905\\) django.contrib.postgres.fields.CICharField is deprecated.") warnings.filterwarnings("ignore", message="'urllib3\\[secure\\]' extra is deprecated") warnings.filterwarnings("ignore", message="The logout\\(\\) view is superseded by") warnings.filterwarnings("ignore", message="Report.file_reporters will no longer be available in Coverage.py 4.2", module="coverage.report") @@ -1134,6 +1135,7 @@ ACCOUNT_REQUEST_EMAIL = 'account-request@ietf.org' SILENCED_SYSTEM_CHECKS = [ "fields.W342", # Setting unique=True on a ForeignKey has the same effect as using a OneToOneField. + "fields.W905", # django.contrib.postgres.fields.CICharField is deprecated. (see https://github.com/ietf-tools/datatracker/issues/5660) ] CHECKS_LIBRARY_PATCHES_TO_APPLY = [ From c71f44fdb2b18c1f152a12f376d52fa103711882 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Thu, 18 May 2023 17:15:25 -0300 Subject: [PATCH 074/101] chore: Suppress deprecation warning for oidc_provider --- ietf/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ietf/settings.py b/ietf/settings.py index 0d369d92b..90d48d212 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -15,6 +15,7 @@ from typing import Any, Dict, List, Tuple # pyflakes:ignore warnings.simplefilter("always", DeprecationWarning) warnings.filterwarnings("ignore", message="pkg_resources is deprecated as an API") warnings.filterwarnings("ignore", module="tastypie", message="The django.utils.datetime_safe module is deprecated.") +warnings.filterwarnings("ignore", module="oidc_provider", message="The django.utils.timezone.utc alias is deprecated.") warnings.filterwarnings("ignore", message="The USE_DEPRECATED_PYTZ setting,") # https://github.com/ietf-tools/datatracker/issues/5635 warnings.filterwarnings("ignore", message="The USE_L10N setting is deprecated.") # https://github.com/ietf-tools/datatracker/issues/5648 warnings.filterwarnings("ignore", message="\\(fields.W905\\) django.contrib.postgres.fields.CICharField is deprecated.") From 39a854fa1a57c8e1a1cee8244368536644a6f39d Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Thu, 18 May 2023 18:36:38 -0300 Subject: [PATCH 075/101] fix: Use arbitrary date in the past instead of datetime.min --- ietf/doc/views_search.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ietf/doc/views_search.py b/ietf/doc/views_search.py index 0eca0007e..5bd1c8517 100644 --- a/ietf/doc/views_search.py +++ b/ietf/doc/views_search.py @@ -765,7 +765,9 @@ def drafts_in_iesg_process(request): if s.slug == "lc": for d in docs: e = d.latest_event(LastCallDocEvent, type="sent_last_call") - d.lc_expires = e.expires if e else datetime.datetime.min + # If we don't have an event, use an arbitrary date in the past (but not datetime.datetime.min, + # which causes problems with timezone conversions) + d.lc_expires = e.expires if e else datetime.datetime(1950, 1, 1) docs = list(docs) docs.sort(key=lambda d: d.lc_expires) From 25b99764605cbfeabed9dd9ba8058b5f7d5ed53d Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Thu, 18 May 2023 18:37:25 -0300 Subject: [PATCH 076/101] chore: Switch to JSONSerializer PickleSerializer is deprecated --- ietf/settings.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ietf/settings.py b/ietf/settings.py index 90d48d212..c8454d3cc 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -348,11 +348,7 @@ SESSION_COOKIE_SAMESITE = 'None' SESSION_COOKIE_SECURE = True SESSION_EXPIRE_AT_BROWSER_CLOSE = False -# We want to use the JSON serialisation, as it's safer -- but there is /secr/ -# code which stashes objects in the session that can't be JSON serialized. -# Switch when that code is rewritten. -#SESSION_SERIALIZER = "django.contrib.sessions.serializers.JSONSerializer" -SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer' +SESSION_SERIALIZER = "django.contrib.sessions.serializers.JSONSerializer" SESSION_ENGINE = "django.contrib.sessions.backends.cache" SESSION_SAVE_EVERY_REQUEST = True SESSION_CACHE_ALIAS = 'sessions' From 19abdfe5e70a98e0c489b9b1a98acd0354ae7b97 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 19 May 2023 11:09:05 -0300 Subject: [PATCH 077/101] refactor: Inject tests without using deprecated extra_tests --- ietf/utils/test_runner.py | 89 ++++++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 38 deletions(-) diff --git a/ietf/utils/test_runner.py b/ietf/utils/test_runner.py index 8905eab90..ea07a4c2a 100644 --- a/ietf/utils/test_runner.py +++ b/ietf/utils/test_runner.py @@ -742,6 +742,11 @@ class IetfTestRunner(DiscoverRunner): "as the collection of test coverage data isn't currently threadsafe.") sys.exit(1) self.check_coverage = False + from ietf.doc.tests import TemplateTagTest # import here to prevent circular imports + # Ensure that the coverage tests come last. Specifically list TemplateTagTest before CoverageTest. If this list + # contains parent classes to later subclasses, the parent classes will determine the ordering, so use the most + # specific classes necessary to get the right ordering: + self.reorder_by = (PyFlakesTestCase, MyPyTest,) + self.reorder_by + (StaticLiveServerTestCase, TemplateTagTest, CoverageTest,) def setup_test_environment(self, **kwargs): global template_coverage_collection @@ -1061,20 +1066,57 @@ class IetfTestRunner(DiscoverRunner): test_paths = [ os.path.join(*app.split('.')) for app in test_apps ] return test_apps, test_paths + # Django 5 will drop the extra_tests mechanism for the test runner. Work around + # by adding a special label to the test suite, then injecting our extra tests + # in load_tests_for_label() + def build_suite(self, test_labels=None, extra_tests=None, **kwargs): + if test_labels is None: + # Base class sets test_labels to ["."] if it was None. The label we're + # adding will interfere with that, so replicate that behavior here. + test_labels = ["."] + test_labels = ("_ietf_extra_tests",) + tuple(test_labels) + return super().build_suite(test_labels, extra_tests, **kwargs) + + def load_tests_for_label(self, label, discover_kwargs): + if label == "_ietf_extra_tests": + return self._extra_tests() or None + return super().load_tests_for_label(label, discover_kwargs) + + def _extra_tests(self): + """Get extra tests that should be added to the test suite""" + tests = [] + if validation_settings["validate_html"]: + tests += [ + TemplateValidationTests( + test_runner=self, + validate_html=self, + methodName='run_template_validation', + ), + ] + if self.check_coverage: + global template_coverage_collection, code_coverage_collection, url_coverage_collection + template_coverage_collection = True + code_coverage_collection = True + url_coverage_collection = True + tests += [ + PyFlakesTestCase(test_runner=self, methodName='pyflakes_test'), + MyPyTest(test_runner=self, methodName='mypy_test'), + #CoverageTest(test_runner=self, methodName='interleaved_migrations_test'), + CoverageTest(test_runner=self, methodName='url_coverage_test'), + CoverageTest(test_runner=self, methodName='template_coverage_test'), + CoverageTest(test_runner=self, methodName='code_coverage_test'), + ] + return tests + def run_tests(self, test_labels, extra_tests=None, **kwargs): - global old_destroy, old_create, test_database_name, template_coverage_collection, code_coverage_collection, url_coverage_collection - from django.db import connection - from ietf.doc.tests import TemplateTagTest - - if extra_tests is None: - extra_tests=[] - # Tests that involve switching back and forth between the real # database and the test database are way too dangerous to run # against the production database if socket.gethostname().split('.')[0] in ['core3', 'ietfa', 'ietfb', 'ietfc', ]: raise EnvironmentError("Refusing to run tests on production server") + from django.db import connection + global old_destroy, old_create old_create = connection.creation.__class__.create_test_db connection.creation.__class__.create_test_db = safe_create_test_db old_destroy = connection.creation.__class__.destroy_test_db @@ -1087,35 +1129,6 @@ class IetfTestRunner(DiscoverRunner): self.test_apps, self.test_paths = self.get_test_paths(test_labels) - if validation_settings["validate_html"]: - extra_tests += [ - TemplateValidationTests( - test_runner=self, - validate_html=self, - methodName='run_template_validation', - ), - ] - - if self.check_coverage: - template_coverage_collection = True - code_coverage_collection = True - url_coverage_collection = True - extra_tests += [ - PyFlakesTestCase(test_runner=self, methodName='pyflakes_test'), - MyPyTest(test_runner=self, methodName='mypy_test'), - #CoverageTest(test_runner=self, methodName='interleaved_migrations_test'), - CoverageTest(test_runner=self, methodName='url_coverage_test'), - CoverageTest(test_runner=self, methodName='template_coverage_test'), - CoverageTest(test_runner=self, methodName='code_coverage_test'), - ] - - # ensure that the coverage tests come last. Specifically list - # TemplateTagTest before CoverageTest. If this list contains - # parent classes to later subclasses, the parent classes will - # determine the ordering, so use the most specific classes - # necessary to get the right ordering: - self.reorder_by = (PyFlakesTestCase, MyPyTest, ) + self.reorder_by + (StaticLiveServerTestCase, TemplateTagTest, CoverageTest, ) - failures = super(IetfTestRunner, self).run_tests(test_labels, extra_tests=extra_tests, **kwargs) if self.check_coverage: @@ -1139,10 +1152,10 @@ class IetfTestRunner(DiscoverRunner): if self.run_full_test_suite: print((" %8s coverage: %6.2f%% (%s: %6.2f%%)" % - (test.capitalize(), test_coverage*100, latest_coverage_version, master_coverage*100, ))) + (test.capitalize(), test_coverage*100, latest_coverage_version, master_coverage*100, ))) else: print((" %8s coverage: %6.2f%%" % - (test.capitalize(), test_coverage*100, ))) + (test.capitalize(), test_coverage*100, ))) print((""" Per-file code and template coverage and per-url-pattern url coverage data From b06fc7acc45e7394cf3ec986d18e1570599cae59 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 19 May 2023 11:28:58 -0300 Subject: [PATCH 078/101] chore: Suppress warning about CryptPasswordHasher --- ietf/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ietf/settings.py b/ietf/settings.py index c8454d3cc..039b069f8 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -18,7 +18,7 @@ warnings.filterwarnings("ignore", module="tastypie", message="The django.utils.d warnings.filterwarnings("ignore", module="oidc_provider", message="The django.utils.timezone.utc alias is deprecated.") warnings.filterwarnings("ignore", message="The USE_DEPRECATED_PYTZ setting,") # https://github.com/ietf-tools/datatracker/issues/5635 warnings.filterwarnings("ignore", message="The USE_L10N setting is deprecated.") # https://github.com/ietf-tools/datatracker/issues/5648 -warnings.filterwarnings("ignore", message="\\(fields.W905\\) django.contrib.postgres.fields.CICharField is deprecated.") +warnings.filterwarnings("ignore", message="django.contrib.auth.hashers.CryptPasswordHasher is deprecated.") # https://github.com/ietf-tools/datatracker/issues/5663 warnings.filterwarnings("ignore", message="'urllib3\\[secure\\]' extra is deprecated") warnings.filterwarnings("ignore", message="The logout\\(\\) view is superseded by") warnings.filterwarnings("ignore", message="Report.file_reporters will no longer be available in Coverage.py 4.2", module="coverage.report") From 2d6681d78ca4809ed139231b1e267dabe8e347f9 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 19 May 2023 11:42:43 -0300 Subject: [PATCH 079/101] refactor: logout via GET is deprecated, use POST --- ietf/templates/base/menu_user.html | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/ietf/templates/base/menu_user.html b/ietf/templates/base/menu_user.html index bb68855b6..9e6bbde56 100644 --- a/ietf/templates/base/menu_user.html +++ b/ietf/templates/base/menu_user.html @@ -32,11 +32,13 @@ {% else %} {% if user.is_authenticated %} <li> - <a class="dropdown-item {% if flavor != 'top' %} text-wrap link-primary{% endif %}" - rel="nofollow" - href="{% url 'django.contrib.auth.views.logout' %}"> - Sign out - </a> + <form id="logout-form" method="post" action="{% url 'django.contrib.auth.views.logout' %}"> + {% csrf_token %} + <button class="dropdown-item {% if flavor != 'top' %} text-wrap link-primary{% endif %}" + type="submit"> + Sign out + </button> + </form> </li> <li> <a class="dropdown-item {% if flavor != 'top' %} text-wrap link-primary{% endif %}" From b6a791511fc9edc68910096777f194b9d6e0b3e9 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 19 May 2023 12:52:26 -0300 Subject: [PATCH 080/101] chore: Use DjangoDivFormRenderer to opt in to new default --- ietf/settings.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ietf/settings.py b/ietf/settings.py index 039b069f8..5eed23097 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -113,6 +113,11 @@ USE_L10N = False USE_TZ = True USE_DEPRECATED_PYTZ = True # supported until Django 5 +# The DjangoDivFormRenderer is a transitional class that opts in to defaulting to the div.html +# template for formsets. This will become the default behavior in Django 5.0. This configuration +# can be removed at that point. +# See https://docs.djangoproject.com/en/4.2/releases/4.1/#forms +FORM_RENDERER = "django.forms.renderers.DjangoDivFormRenderer" # Default primary key field type to use for models that don’t have a field with primary_key=True. # In the future (relative to 4.2), the default will become 'django.db.models.BigAutoField.' From 7ae0576a442ae574fe9b2f4025f20d5d91c777e4 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 19 May 2023 13:03:56 -0300 Subject: [PATCH 081/101] style: Apply Black style to methods in review.utils --- ietf/review/utils.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/ietf/review/utils.py b/ietf/review/utils.py index 2b9979c95..2514f535d 100644 --- a/ietf/review/utils.py +++ b/ietf/review/utils.py @@ -599,7 +599,9 @@ def suggested_review_requests_for_team(team): res.sort(key=lambda r: (r.deadline, r.doc_id), reverse=True) return res -def extract_revision_ordered_review_assignments_for_documents_and_replaced(review_assignment_queryset, names): +def extract_revision_ordered_review_assignments_for_documents_and_replaced( + review_assignment_queryset, names +): """Extracts all review assignments for document names (including replaced ancestors), return them neatly sorted.""" names = set(names) @@ -608,8 +610,13 @@ def extract_revision_ordered_review_assignments_for_documents_and_replaced(revie assignments_for_each_doc = defaultdict(list) replacement_name_set = set(e for l in replaces.values() for e in l) | names - for r in ( review_assignment_queryset.filter(review_request__doc__name__in=replacement_name_set) - .order_by("-reviewed_rev","-assigned_on", "-id").iterator()): + for r in ( + review_assignment_queryset.filter( + review_request__doc__name__in=replacement_name_set + ) + .order_by("-reviewed_rev", "-assigned_on", "-id") + .iterator() + ): assignments_for_each_doc[r.review_request.doc.name].append(r) # now collect in breadth-first order to keep the revision order intact @@ -647,7 +654,10 @@ def extract_revision_ordered_review_assignments_for_documents_and_replaced(revie return res -def extract_revision_ordered_review_requests_for_documents_and_replaced(review_request_queryset, names): + +def extract_revision_ordered_review_requests_for_documents_and_replaced( + review_request_queryset, names +): """Extracts all review requests for document names (including replaced ancestors), return them neatly sorted.""" names = set(names) @@ -655,7 +665,13 @@ def extract_revision_ordered_review_requests_for_documents_and_replaced(review_r replaces = extract_complete_replaces_ancestor_mapping_for_docs(names) requests_for_each_doc = defaultdict(list) - for r in review_request_queryset.filter(doc__name__in=set(e for l in replaces.values() for e in l) | names).order_by("-time", "-id").iterator(): + for r in ( + review_request_queryset.filter( + doc__name__in=set(e for l in replaces.values() for e in l) | names + ) + .order_by("-time", "-id") + .iterator() + ): requests_for_each_doc[r.doc.name].append(r) # now collect in breadth-first order to keep the revision order intact From da8717f0e97ec90492b78e75488e4284f64cee68 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 19 May 2023 13:10:06 -0300 Subject: [PATCH 082/101] chore: Set chunk_size on QuerySet.iterator() --- ietf/review/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ietf/review/utils.py b/ietf/review/utils.py index 2514f535d..31b6b401f 100644 --- a/ietf/review/utils.py +++ b/ietf/review/utils.py @@ -615,7 +615,7 @@ def extract_revision_ordered_review_assignments_for_documents_and_replaced( review_request__doc__name__in=replacement_name_set ) .order_by("-reviewed_rev", "-assigned_on", "-id") - .iterator() + .iterator(chunk_size=2000) # chunk_size not tested, using pre-Django 5 default value ): assignments_for_each_doc[r.review_request.doc.name].append(r) @@ -670,7 +670,7 @@ def extract_revision_ordered_review_requests_for_documents_and_replaced( doc__name__in=set(e for l in replaces.values() for e in l) | names ) .order_by("-time", "-id") - .iterator() + .iterator(chunk_size=2000) # chunk_size not tested, using pre-Django 5 default value ): requests_for_each_doc[r.doc.name].append(r) From fdc074b31323e3c1faedb4dd0dd3a218b757a0ed Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 19 May 2023 13:23:07 -0300 Subject: [PATCH 083/101] test: Use new signature for assertFormError --- ietf/ietfauth/tests.py | 8 ++++---- ietf/meeting/tests_views.py | 18 +++++++++--------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/ietf/ietfauth/tests.py b/ietf/ietfauth/tests.py index 631da9870..97ce2ef59 100644 --- a/ietf/ietfauth/tests.py +++ b/ietf/ietfauth/tests.py @@ -664,7 +664,7 @@ class IetfAuthTests(TestCase): "new_password_confirmation": "foobar", }) self.assertEqual(r.status_code, 200) - self.assertFormError(r, 'form', 'current_password', 'Invalid password') + self.assertFormError(r["form"], 'current_password', 'Invalid password') # mismatching new passwords r = self.client.post(chpw_url, {"current_password": "password", @@ -672,7 +672,7 @@ class IetfAuthTests(TestCase): "new_password_confirmation": "barfoo", }) self.assertEqual(r.status_code, 200) - self.assertFormError(r, 'form', None, "The password confirmation is different than the new password") + self.assertFormError(r["form"], None, "The password confirmation is different than the new password") # correct password change r = self.client.post(chpw_url, {"current_password": "password", @@ -711,7 +711,7 @@ class IetfAuthTests(TestCase): "password": "password", }) self.assertEqual(r.status_code, 200) - self.assertFormError(r, 'form', 'username', + self.assertFormError(r["form"], 'username', "Select a valid choice. fiddlesticks is not one of the available choices.") # wrong password @@ -719,7 +719,7 @@ class IetfAuthTests(TestCase): "password": "foobar", }) self.assertEqual(r.status_code, 200) - self.assertFormError(r, 'form', 'password', 'Invalid password') + self.assertFormError(r["form"], 'password', 'Invalid password') # correct username change r = self.client.post(chun_url, {"username": "othername@example.org", diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py index 22d77f9d2..12655e22e 100644 --- a/ietf/meeting/tests_views.py +++ b/ietf/meeting/tests_views.py @@ -3931,7 +3931,7 @@ class EditTests(TestCase): 'base': meeting.schedule.base_id, }) self.assertEqual(r.status_code, 200) - self.assertFormError(r, 'form', 'name', 'Enter a valid value.') + self.assertFormError(r["form"], 'name', 'Enter a valid value.') self.assertEqual(meeting.schedule_set.count(), orig_schedule_count, 'Schedule should not be created') r = self.client.post(url, { @@ -3941,7 +3941,7 @@ class EditTests(TestCase): 'base': meeting.schedule.base_id, }) self.assertEqual(r.status_code, 200) - self.assertFormError(r, 'form', 'name', 'Enter a valid value.') + self.assertFormError(r["form"], 'name', 'Enter a valid value.') self.assertEqual(meeting.schedule_set.count(), orig_schedule_count, 'Schedule should not be created') # Non-ASCII alphanumeric characters @@ -3952,7 +3952,7 @@ class EditTests(TestCase): 'base': meeting.schedule.base_id, }) self.assertEqual(r.status_code, 200) - self.assertFormError(r, 'form', 'name', 'Enter a valid value.') + self.assertFormError(r["form"], 'name', 'Enter a valid value.') self.assertEqual(meeting.schedule_set.count(), orig_schedule_count, 'Schedule should not be created') def test_edit_session(self): @@ -4037,9 +4037,9 @@ class EditTests(TestCase): self.assertIn(return_url_unofficial, r.content.decode()) r = self.client.post(url, {}) - self.assertFormError(r, 'form', 'confirmed', 'This field is required.') + self.assertFormError(r["form"], 'confirmed', 'This field is required.') r = self.client.post(url_unofficial, {}) - self.assertFormError(r, 'form', 'confirmed', 'This field is required.') + self.assertFormError(r["form"], 'confirmed', 'This field is required.') r = self.client.post(url, {'confirmed': 'on'}) self.assertRedirects(r, return_url) @@ -7973,7 +7973,7 @@ class ProceedingsTests(BaseMeetingTestCase): invalid_file.seek(0) # read the file contents again r = self.client.post(url, {'file': invalid_file, 'external_url': ''}) self.assertEqual(r.status_code, 200) - self.assertFormError(r, 'form', 'file', 'Found an unexpected extension: .png. Expected one of .pdf') + self.assertFormError(r["form"], 'file', 'Found an unexpected extension: .png. Expected one of .pdf') def test_add_proceedings_material_doc_empty(self): """Upload proceedings materials document without specifying a file""" @@ -7986,7 +7986,7 @@ class ProceedingsTests(BaseMeetingTestCase): ) r = self.client.post(url, {'external_url': ''}) self.assertEqual(r.status_code, 200) - self.assertFormError(r, 'form', 'file', 'This field is required') + self.assertFormError(r["form"], 'file', 'This field is required') def test_add_proceedings_material_url(self): """Add a URL as proceedings material""" @@ -8010,7 +8010,7 @@ class ProceedingsTests(BaseMeetingTestCase): ) r = self.client.post(url, {'use_url': 'on', 'external_url': "Ceci n'est pas une URL"}) self.assertEqual(r.status_code, 200) - self.assertFormError(r, 'form', 'external_url', 'Enter a valid URL.') + self.assertFormError(r["form"], 'external_url', 'Enter a valid URL.') def test_add_proceedings_material_url_empty(self): """Add proceedings materials URL without specifying the URL""" @@ -8023,7 +8023,7 @@ class ProceedingsTests(BaseMeetingTestCase): ) r = self.client.post(url, {'use_url': 'on', 'external_url': ''}) self.assertEqual(r.status_code, 200) - self.assertFormError(r, 'form', 'external_url', 'This field is required') + self.assertFormError(r["form"], 'external_url', 'This field is required') @override_settings(MEETING_DOC_HREFS={'procmaterials': '{doc.name}:{doc.rev}'}) def test_replace_proceedings_material(self): From 1eafdca65c26f2bf2da851ab9e14ceffd15dece4 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 19 May 2023 13:23:38 -0300 Subject: [PATCH 084/101] chore: Replace django.utils.timezone.utc with dateutil.timezone.utc --- ietf/api/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ietf/api/tests.py b/ietf/api/tests.py index c910b6f90..2285fa153 100644 --- a/ietf/api/tests.py +++ b/ietf/api/tests.py @@ -691,7 +691,7 @@ class CustomApiTests(TestCase): self.assertEqual(set(missing_fields), set(drop_fields)) def test_api_version(self): - DumpInfo.objects.create(date=timezone.datetime(2022,8,31,7,10,1,tzinfo=timezone.utc), host='testapi.example.com',tz='UTC') + DumpInfo.objects.create(date=timezone.datetime(2022,8,31,7,10,1,tzinfo=datetime.timezone.utc), host='testapi.example.com',tz='UTC') url = urlreverse('ietf.api.views.version') r = self.client.get(url) data = r.json() From 37a65218794ebfc6b96f4f01dca0a1ae86fed5f4 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 19 May 2023 13:37:39 -0300 Subject: [PATCH 085/101] test: Form is r.context["form"], not r["form"] --- ietf/ietfauth/tests.py | 8 ++++---- ietf/meeting/tests_views.py | 18 +++++++++--------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/ietf/ietfauth/tests.py b/ietf/ietfauth/tests.py index 97ce2ef59..3c88bcf07 100644 --- a/ietf/ietfauth/tests.py +++ b/ietf/ietfauth/tests.py @@ -664,7 +664,7 @@ class IetfAuthTests(TestCase): "new_password_confirmation": "foobar", }) self.assertEqual(r.status_code, 200) - self.assertFormError(r["form"], 'current_password', 'Invalid password') + self.assertFormError(r.context["form"], 'current_password', 'Invalid password') # mismatching new passwords r = self.client.post(chpw_url, {"current_password": "password", @@ -672,7 +672,7 @@ class IetfAuthTests(TestCase): "new_password_confirmation": "barfoo", }) self.assertEqual(r.status_code, 200) - self.assertFormError(r["form"], None, "The password confirmation is different than the new password") + self.assertFormError(r.context["form"], None, "The password confirmation is different than the new password") # correct password change r = self.client.post(chpw_url, {"current_password": "password", @@ -711,7 +711,7 @@ class IetfAuthTests(TestCase): "password": "password", }) self.assertEqual(r.status_code, 200) - self.assertFormError(r["form"], 'username', + self.assertFormError(r.context["form"], 'username', "Select a valid choice. fiddlesticks is not one of the available choices.") # wrong password @@ -719,7 +719,7 @@ class IetfAuthTests(TestCase): "password": "foobar", }) self.assertEqual(r.status_code, 200) - self.assertFormError(r["form"], 'password', 'Invalid password') + self.assertFormError(r.context["form"], 'password', 'Invalid password') # correct username change r = self.client.post(chun_url, {"username": "othername@example.org", diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py index 12655e22e..b39987459 100644 --- a/ietf/meeting/tests_views.py +++ b/ietf/meeting/tests_views.py @@ -3931,7 +3931,7 @@ class EditTests(TestCase): 'base': meeting.schedule.base_id, }) self.assertEqual(r.status_code, 200) - self.assertFormError(r["form"], 'name', 'Enter a valid value.') + self.assertFormError(r.context["form"], 'name', 'Enter a valid value.') self.assertEqual(meeting.schedule_set.count(), orig_schedule_count, 'Schedule should not be created') r = self.client.post(url, { @@ -3941,7 +3941,7 @@ class EditTests(TestCase): 'base': meeting.schedule.base_id, }) self.assertEqual(r.status_code, 200) - self.assertFormError(r["form"], 'name', 'Enter a valid value.') + self.assertFormError(r.context["form"], 'name', 'Enter a valid value.') self.assertEqual(meeting.schedule_set.count(), orig_schedule_count, 'Schedule should not be created') # Non-ASCII alphanumeric characters @@ -3952,7 +3952,7 @@ class EditTests(TestCase): 'base': meeting.schedule.base_id, }) self.assertEqual(r.status_code, 200) - self.assertFormError(r["form"], 'name', 'Enter a valid value.') + self.assertFormError(r.context["form"], 'name', 'Enter a valid value.') self.assertEqual(meeting.schedule_set.count(), orig_schedule_count, 'Schedule should not be created') def test_edit_session(self): @@ -4037,9 +4037,9 @@ class EditTests(TestCase): self.assertIn(return_url_unofficial, r.content.decode()) r = self.client.post(url, {}) - self.assertFormError(r["form"], 'confirmed', 'This field is required.') + self.assertFormError(r.context["form"], 'confirmed', 'This field is required.') r = self.client.post(url_unofficial, {}) - self.assertFormError(r["form"], 'confirmed', 'This field is required.') + self.assertFormError(r.context["form"], 'confirmed', 'This field is required.') r = self.client.post(url, {'confirmed': 'on'}) self.assertRedirects(r, return_url) @@ -7973,7 +7973,7 @@ class ProceedingsTests(BaseMeetingTestCase): invalid_file.seek(0) # read the file contents again r = self.client.post(url, {'file': invalid_file, 'external_url': ''}) self.assertEqual(r.status_code, 200) - self.assertFormError(r["form"], 'file', 'Found an unexpected extension: .png. Expected one of .pdf') + self.assertFormError(r.context["form"], 'file', 'Found an unexpected extension: .png. Expected one of .pdf') def test_add_proceedings_material_doc_empty(self): """Upload proceedings materials document without specifying a file""" @@ -7986,7 +7986,7 @@ class ProceedingsTests(BaseMeetingTestCase): ) r = self.client.post(url, {'external_url': ''}) self.assertEqual(r.status_code, 200) - self.assertFormError(r["form"], 'file', 'This field is required') + self.assertFormError(r.context["form"], 'file', 'This field is required') def test_add_proceedings_material_url(self): """Add a URL as proceedings material""" @@ -8010,7 +8010,7 @@ class ProceedingsTests(BaseMeetingTestCase): ) r = self.client.post(url, {'use_url': 'on', 'external_url': "Ceci n'est pas une URL"}) self.assertEqual(r.status_code, 200) - self.assertFormError(r["form"], 'external_url', 'Enter a valid URL.') + self.assertFormError(r.context["form"], 'external_url', 'Enter a valid URL.') def test_add_proceedings_material_url_empty(self): """Add proceedings materials URL without specifying the URL""" @@ -8023,7 +8023,7 @@ class ProceedingsTests(BaseMeetingTestCase): ) r = self.client.post(url, {'use_url': 'on', 'external_url': ''}) self.assertEqual(r.status_code, 200) - self.assertFormError(r["form"], 'external_url', 'This field is required') + self.assertFormError(r.context["form"], 'external_url', 'This field is required') @override_settings(MEETING_DOC_HREFS={'procmaterials': '{doc.name}:{doc.rev}'}) def test_replace_proceedings_material(self): From 102a612857ef2490e95c2bfcca5f8192c3ff3278 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 19 May 2023 14:12:12 -0300 Subject: [PATCH 086/101] test: POST instead of GET for logout tests --- ietf/ietfauth/tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ietf/ietfauth/tests.py b/ietf/ietfauth/tests.py index 3c88bcf07..0e5fcb3c4 100644 --- a/ietf/ietfauth/tests.py +++ b/ietf/ietfauth/tests.py @@ -98,7 +98,7 @@ class IetfAuthTests(TestCase): self.assertEqual(urlsplit(r["Location"])[2], urlreverse(ietf.ietfauth.views.profile)) # try logging out - r = self.client.get(urlreverse('django.contrib.auth.views.logout')) + r = self.client.post(urlreverse('django.contrib.auth.views.logout'), {}) self.assertEqual(r.status_code, 200) self.assertNotContains(r, "accounts/logout") @@ -215,7 +215,7 @@ class IetfAuthTests(TestCase): self.assertContains(r, "Allowlist entry creation successful") # log out - r = self.client.get(urlreverse('django.contrib.auth.views.logout')) + r = self.client.post(urlreverse('django.contrib.auth.views.logout'), {}) self.assertEqual(r.status_code, 200) # register and verify allowlisted email From be25fb954bfd6197d412493703898aa2c7e6069c Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 19 May 2023 14:18:02 -0300 Subject: [PATCH 087/101] test: Ignore menu bar when counting "submit" buttons (the "Sign out" link is now a submit button) --- ietf/meeting/tests_views.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py index b39987459..afa1b646a 100644 --- a/ietf/meeting/tests_views.py +++ b/ietf/meeting/tests_views.py @@ -6518,8 +6518,8 @@ class ImportNotesTests(TestCase): r = self.client.get(url) # try to import the same text self.assertContains(r, "This document is identical", status_code=200) q = PyQuery(r.content) - self.assertEqual(len(q('button:disabled[type="submit"]')), 1) - self.assertEqual(len(q('button:enabled[type="submit"]')), 0) + self.assertEqual(len(q('#content button:disabled[type="submit"]')), 1) + self.assertEqual(len(q('#content button:enabled[type="submit"]')), 0) def test_allows_import_on_existing_bad_unicode(self): """Should not be able to import text identical to the current revision""" @@ -6543,8 +6543,8 @@ class ImportNotesTests(TestCase): r = self.client.get(url) # try to import the same text self.assertNotContains(r, "This document is identical", status_code=200) q = PyQuery(r.content) - self.assertEqual(len(q('button:enabled[type="submit"]')), 1) - self.assertEqual(len(q('button:disabled[type="submit"]')), 0) + self.assertEqual(len(q('#content button:enabled[type="submit"]')), 1) + self.assertEqual(len(q('#content button:disabled[type="submit"]')), 0) def test_handles_missing_previous_revision_file(self): """Should still allow import if the file for the previous revision is missing""" From 93e9f8e8504739952d4cfec4c89814192541b92c Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 19 May 2023 14:36:08 -0300 Subject: [PATCH 088/101] fix: Do not set update_fields when saving new instance --- ietf/doc/tests_draft.py | 4 ++-- ietf/nomcom/models.py | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/ietf/doc/tests_draft.py b/ietf/doc/tests_draft.py index 0ebfd6b8f..5b6dd63b9 100644 --- a/ietf/doc/tests_draft.py +++ b/ietf/doc/tests_draft.py @@ -573,7 +573,7 @@ class ResurrectTests(DraftFileMixin, TestCase): r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) - self.assertEqual(len(q('form [type=submit]')), 1) + self.assertEqual(len(q('#content form [type=submit]')), 1) # request resurrect @@ -609,7 +609,7 @@ class ResurrectTests(DraftFileMixin, TestCase): r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) - self.assertEqual(len(q('form [type=submit]')), 1) + self.assertEqual(len(q('#content form [type=submit]')), 1) # complete resurrect events_before = draft.docevent_set.count() diff --git a/ietf/nomcom/models.py b/ietf/nomcom/models.py index 28116354c..6c1281f9b 100644 --- a/ietf/nomcom/models.py +++ b/ietf/nomcom/models.py @@ -187,11 +187,10 @@ class NomineePosition(models.Model): ordering = ['nominee'] def save(self, **kwargs): - update_fields = kwargs.pop("update_fields", None) if not self.pk and not self.state_id: + # Don't need to set update_fields because the self.pk test means this is a new instance self.state = NomineePositionStateName.objects.get(slug='pending') - update_fields = {"slug"}.union(update_fields or set()) - super().save(update_fields=update_fields, **kwargs) + super().save(**kwargs) def __str__(self): return "%s - %s - %s" % (self.nominee, self.state, self.position) From 36fe6a0206c51de907c2aa14afe70629f4e88d02 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 19 May 2023 14:57:27 -0300 Subject: [PATCH 089/101] fix: Store nomcom private key in session as str bytes are incompatible with JSONSerializer --- ietf/nomcom/factories.py | 2 +- ietf/nomcom/utils.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ietf/nomcom/factories.py b/ietf/nomcom/factories.py index 8ef4e07fa..6fd6819b0 100644 --- a/ietf/nomcom/factories.py +++ b/ietf/nomcom/factories.py @@ -66,7 +66,7 @@ Tdb0MiLc+r/zvx8oXtgDjDUa def provide_private_key_to_test_client(testcase): session = testcase.client.session - session['NOMCOM_PRIVATE_KEY_%s'%testcase.nc.year()] = key + session['NOMCOM_PRIVATE_KEY_%s'%testcase.nc.year()] = key.decode("utf8") session.save() def nomcom_kwargs_for_year(year=None, *args, **kwargs): diff --git a/ietf/nomcom/utils.py b/ietf/nomcom/utils.py index 4c91bfd1d..5cc41f820 100644 --- a/ietf/nomcom/utils.py +++ b/ietf/nomcom/utils.py @@ -183,7 +183,7 @@ def retrieve_nomcom_private_key(request, year): settings.OPENSSL_COMMAND, command_line_safe_secret(settings.NOMCOM_APP_SECRET) ), - private_key + private_key.encode("utf8") ) if code != 0: log("openssl error: %s:\n Error %s: %s" %(command, code, error)) @@ -205,8 +205,8 @@ def store_nomcom_private_key(request, year, private_key): if code != 0: log("openssl error: %s:\n Error %s: %s" %(command, code, error)) if error and error!=b"*** WARNING : deprecated key derivation used.\nUsing -iter or -pbkdf2 would be better.\n": - out = '' - request.session['NOMCOM_PRIVATE_KEY_%s' % year] = out + out = b'' + request.session['NOMCOM_PRIVATE_KEY_%s' % year] = out.decode("utf8") def validate_private_key(key): From bc3dcb6c03dc1e088fb6582dbb8cfdb32580bd0c Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 19 May 2023 15:11:59 -0300 Subject: [PATCH 090/101] test: Fix another test broken by changing "Sign out" to a form --- ietf/nomcom/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ietf/nomcom/tests.py b/ietf/nomcom/tests.py index 8e915b74d..767570fb3 100644 --- a/ietf/nomcom/tests.py +++ b/ietf/nomcom/tests.py @@ -1993,7 +1993,7 @@ class NoPublicKeyTests(TestCase): text_bits = [x.xpath('.//text()') for x in q('.alert-warning')] flat_text_bits = [item for sublist in text_bits for item in sublist] self.assertTrue(any(['not yet' in y for y in flat_text_bits])) - self.assertEqual(bool(q('form:not(.navbar-form)')),expected_form) + self.assertEqual(bool(q('#content form:not(.navbar-form)')),expected_form) self.client.logout() def test_not_yet(self): From 579d187f0c31e81833ffffe259ac5b36a9fc6960 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 19 May 2023 15:20:09 -0300 Subject: [PATCH 091/101] chore: Suppress deprecation warning in oidc_provider --- ietf/ietfauth/tests.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ietf/ietfauth/tests.py b/ietf/ietfauth/tests.py index 0e5fcb3c4..20078a6ef 100644 --- a/ietf/ietfauth/tests.py +++ b/ietf/ietfauth/tests.py @@ -12,6 +12,7 @@ import requests_mock import shutil import time import urllib +import warnings from .factories import OidClientRecordFactory from Cryptodome.PublicKey import RSA @@ -1157,6 +1158,7 @@ class OpenIDConnectTests(TestCase): self.assertEqual(set(userinfo['reg_type'].split()), set(['remote', 'hackathon'])) # Check that ending a session works + warnings.filterwarnings("ignore", "Log out via GET requests is deprecated") # happens in oidc_provider r = client.do_end_session_request(state=params["state"], scope=args['scope']) self.assertEqual(r.status_code, 302) self.assertEqual(r.headers["Location"], urlreverse('ietf.ietfauth.views.login')) From 2eaea55ce8e14e8317372c0cd714ab1bc63e5468 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 19 May 2023 15:25:35 -0300 Subject: [PATCH 092/101] chore: Move log out suppression to settings,py --- ietf/ietfauth/tests.py | 1 - ietf/settings.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/ietf/ietfauth/tests.py b/ietf/ietfauth/tests.py index 20078a6ef..46fc2dc34 100644 --- a/ietf/ietfauth/tests.py +++ b/ietf/ietfauth/tests.py @@ -1158,7 +1158,6 @@ class OpenIDConnectTests(TestCase): self.assertEqual(set(userinfo['reg_type'].split()), set(['remote', 'hackathon'])) # Check that ending a session works - warnings.filterwarnings("ignore", "Log out via GET requests is deprecated") # happens in oidc_provider r = client.do_end_session_request(state=params["state"], scope=args['scope']) self.assertEqual(r.status_code, 302) self.assertEqual(r.headers["Location"], urlreverse('ietf.ietfauth.views.login')) diff --git a/ietf/settings.py b/ietf/settings.py index 5eed23097..5d5340832 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -14,6 +14,7 @@ from typing import Any, Dict, List, Tuple # pyflakes:ignore warnings.simplefilter("always", DeprecationWarning) warnings.filterwarnings("ignore", message="pkg_resources is deprecated as an API") +warnings.filterwarnings("ignore", "Log out via GET requests is deprecated") # happens in oidc_provider warnings.filterwarnings("ignore", module="tastypie", message="The django.utils.datetime_safe module is deprecated.") warnings.filterwarnings("ignore", module="oidc_provider", message="The django.utils.timezone.utc alias is deprecated.") warnings.filterwarnings("ignore", message="The USE_DEPRECATED_PYTZ setting,") # https://github.com/ietf-tools/datatracker/issues/5635 From 2a29be5d6a7e6be9d79c67d7ff1210e93b09ad29 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 19 May 2023 16:23:42 -0300 Subject: [PATCH 093/101] test: Remove unused import --- ietf/ietfauth/tests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ietf/ietfauth/tests.py b/ietf/ietfauth/tests.py index 46fc2dc34..0e5fcb3c4 100644 --- a/ietf/ietfauth/tests.py +++ b/ietf/ietfauth/tests.py @@ -12,7 +12,6 @@ import requests_mock import shutil import time import urllib -import warnings from .factories import OidClientRecordFactory from Cryptodome.PublicKey import RSA From 58182fd7f600f2a89ecb73e4efb96df75cca64a8 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Fri, 19 May 2023 17:54:24 -0300 Subject: [PATCH 094/101] test: Fix selectors in selenium tests --- ietf/doc/tests_js.py | 4 ++-- ietf/utils/jstest.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ietf/doc/tests_js.py b/ietf/doc/tests_js.py index 02daaae90..ac63c0995 100644 --- a/ietf/doc/tests_js.py +++ b/ietf/doc/tests_js.py @@ -118,7 +118,7 @@ class EditAuthorsTests(IetfSeleniumTestCase): # Must provide a "basis" (change reason) self.driver.find_element(By.ID, 'id_basis').send_keys('change testing') # Now click the 'submit' button and check that the update was accepted. - submit_button = self.driver.find_element(By.CSS_SELECTOR, 'button[type="submit"]') + submit_button = self.driver.find_element(By.CSS_SELECTOR, '#content button[type="submit"]') self.driver.execute_script("arguments[0].click();", submit_button) # FIXME: no idea why this fails: # self.scroll_to_element(submit_button) # submit_button.click() @@ -132,4 +132,4 @@ class EditAuthorsTests(IetfSeleniumTestCase): self.assertEqual( list(draft.documentauthor_set.values_list('person', flat=True)), [first_auth.person.pk] + [auth.pk for auth in authors] - ) \ No newline at end of file + ) diff --git a/ietf/utils/jstest.py b/ietf/utils/jstest.py index 722b40581..a901df66f 100644 --- a/ietf/utils/jstest.py +++ b/ietf/utils/jstest.py @@ -81,7 +81,7 @@ class IetfSeleniumTestCase(IetfLiveServerTestCase): self.driver.get(url) self.driver.find_element(By.NAME, 'username').send_keys(username) self.driver.find_element(By.NAME, 'password').send_keys(password) - self.driver.find_element(By.XPATH, '//button[@type="submit"]').click() + self.driver.find_element(By.XPATH, '//*[@id="content"]//button[@type="submit"]').click() def scroll_to_element(self, element): """Scroll an element into view""" @@ -108,4 +108,4 @@ class presence_of_element_child_by_css_selector: def __call__(self, driver): child = self.element.find_element(By.CSS_SELECTOR, self.child_selector) - return child if child is not None else False \ No newline at end of file + return child if child is not None else False From 5f0e1a524b9faf6871584ed20ccb18ba610a286d Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Tue, 23 May 2023 11:30:14 -0300 Subject: [PATCH 095/101] chore: Display errors if nomcom private key encoding fails --- ietf/nomcom/templatetags/nomcom_tags.py | 6 ++++-- ietf/nomcom/utils.py | 14 ++++++++++++++ ietf/nomcom/views.py | 12 ++++++++++-- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/ietf/nomcom/templatetags/nomcom_tags.py b/ietf/nomcom/templatetags/nomcom_tags.py index c751383fb..38dfc61b9 100644 --- a/ietf/nomcom/templatetags/nomcom_tags.py +++ b/ietf/nomcom/templatetags/nomcom_tags.py @@ -55,8 +55,10 @@ def formatted_email(address): @register.simple_tag def decrypt(string, request, year, plain=False): - key = retrieve_nomcom_private_key(request, year) - + try: + key = retrieve_nomcom_private_key(request, year) + except UnicodeError: + return f"-*- Encrypted text [Error retrieving private key, contact the secretariat ({settings.SECRETARIAT_SUPPORT_EMAIL})]" if not key: return '-*- Encrypted text [No private key provided] -*-' diff --git a/ietf/nomcom/utils.py b/ietf/nomcom/utils.py index 5cc41f820..f00baa934 100644 --- a/ietf/nomcom/utils.py +++ b/ietf/nomcom/utils.py @@ -172,6 +172,12 @@ def command_line_safe_secret(secret): return base64.encodebytes(secret).decode('utf-8').rstrip() def retrieve_nomcom_private_key(request, year): + """Retrieve decrypted nomcom private key from the session store + + Retrieves encrypted, ascii-armored private key from the session store, encodes + as utf8 bytes, then decrypts. Raises UnicodeError if the value in the session + store cannot be encoded as utf8. + """ private_key = request.session.get('NOMCOM_PRIVATE_KEY_%s' % year, None) if not private_key: @@ -183,6 +189,7 @@ def retrieve_nomcom_private_key(request, year): settings.OPENSSL_COMMAND, command_line_safe_secret(settings.NOMCOM_APP_SECRET) ), + # The openssl command expects ascii-armored input, so utf8 encoding should be valid private_key.encode("utf8") ) if code != 0: @@ -191,6 +198,12 @@ def retrieve_nomcom_private_key(request, year): def store_nomcom_private_key(request, year, private_key): + """Put encrypted nomcom private key in the session store + + Encrypts the private key using openssl, then decodes the ascii-armored output + as utf8 and adds to the session store. Raises UnicodeError if the openssl's + output cannot be decoded as utf8. + """ if not private_key: request.session['NOMCOM_PRIVATE_KEY_%s' % year] = '' else: @@ -206,6 +219,7 @@ def store_nomcom_private_key(request, year, private_key): log("openssl error: %s:\n Error %s: %s" %(command, code, error)) if error and error!=b"*** WARNING : deprecated key derivation used.\nUsing -iter or -pbkdf2 would be better.\n": out = b'' + # The openssl command output in 'out' is an ascii-armored value, so should be utf8-decodable request.session['NOMCOM_PRIVATE_KEY_%s' % year] = out.decode("utf8") diff --git a/ietf/nomcom/views.py b/ietf/nomcom/views.py index 51c165bce..77a3c3b76 100644 --- a/ietf/nomcom/views.py +++ b/ietf/nomcom/views.py @@ -158,8 +158,16 @@ def private_key(request, year): if request.method == 'POST': form = PrivateKeyForm(data=request.POST) if form.is_valid(): - store_nomcom_private_key(request, year, force_bytes(form.cleaned_data.get('key', ''))) - return HttpResponseRedirect(back_url) + try: + store_nomcom_private_key(request, year, force_bytes(form.cleaned_data.get('key', ''))) + except UnicodeError: + form.add_error( + None, + "An internal error occurred while adding your private key to your session." + f"Please contact the secretariat for assistance ({settings.SECRETARIAT_SUPPORT_EMAIL})" + ) + else: + return HttpResponseRedirect(back_url) else: form = PrivateKeyForm() From ceb41e6106f3208841d5541208e557ef8081e9fc Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Tue, 23 May 2023 11:30:35 -0300 Subject: [PATCH 096/101] test: Check that error is displayed on decode failure --- ietf/nomcom/tests.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ietf/nomcom/tests.py b/ietf/nomcom/tests.py index 767570fb3..edb7d71c4 100644 --- a/ietf/nomcom/tests.py +++ b/ietf/nomcom/tests.py @@ -4,6 +4,7 @@ import datetime import io +import mock import random import shutil @@ -1577,6 +1578,16 @@ class NewActiveNomComTests(TestCase): login_testing_unauthorized(self,self.chair.user.username,url) response = self.client.get(url) self.assertEqual(response.status_code,200) + # Check that we get an error if there's an encoding problem talking to openssl + # "\xc3\x28" is an invalid utf8 string + with mock.patch("ietf.nomcom.utils.pipe", return_value=(0, b"\xc3\x28", None)): + response = self.client.post(url, {'key': force_str(key)}) + self.assertFormError( + response.context["form"], + None, + "An internal error occurred while adding your private key to your session." + f"Please contact the secretariat for assistance ({settings.SECRETARIAT_SUPPORT_EMAIL})", + ) response = self.client.post(url,{'key': force_str(key)}) self.assertEqual(response.status_code,302) From 4012a213c53d2cc99f319a549d53d198fad09d45 Mon Sep 17 00:00:00 2001 From: Robert Sparks <rjsparks@nostrum.com> Date: Tue, 23 May 2023 11:25:28 -0500 Subject: [PATCH 097/101] feat!: Version 11 based on Django 4 --- .github/workflows/build.yml | 6 +++--- ietf/__init__.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a2ac8bd7a..8abf25b6c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -91,10 +91,10 @@ jobs: echo "pkg_version=$NEXT_VERSION" >> $GITHUB_OUTPUT echo "::notice::Release $NEXT_VERSION created using branch $GITHUB_REF_NAME" else - echo "Using TEST mode: 10.0.0-dev.$GITHUB_RUN_NUMBER" + echo "Using TEST mode: 11.0.0-dev.$GITHUB_RUN_NUMBER" echo "should_deploy=false" >> $GITHUB_OUTPUT - echo "pkg_version=10.0.0-dev.$GITHUB_RUN_NUMBER" >> $GITHUB_OUTPUT - echo "::notice::Non-production build 10.0.0-dev.$GITHUB_RUN_NUMBER created using branch $GITHUB_REF_NAME" + echo "pkg_version=11.0.0-dev.$GITHUB_RUN_NUMBER" >> $GITHUB_OUTPUT + echo "::notice::Non-production build 11.0.0-dev.$GITHUB_RUN_NUMBER created using branch $GITHUB_REF_NAME" fi # ----------------------------------------------------------------- diff --git a/ietf/__init__.py b/ietf/__init__.py index b0dad6c65..59f9802de 100644 --- a/ietf/__init__.py +++ b/ietf/__init__.py @@ -6,7 +6,7 @@ from . import checks # pyflakes:ignore # Version must stay in single quotes for automatic CI replace # Don't add patch number here: -__version__ = '10.0.0-dev' +__version__ = '11.0.0-dev' # Release hash must stay in single quotes for automatic CI replace __release_hash__ = '' From 301535932366654c543515d213d02447dbfb4b4a Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Wed, 31 May 2023 11:05:56 -0300 Subject: [PATCH 098/101] test: Use QuerySetAny alias for QuerySet type checks --- ietf/doc/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ietf/doc/utils.py b/ietf/doc/utils.py index b2e65066a..6c9409475 100644 --- a/ietf/doc/utils.py +++ b/ietf/doc/utils.py @@ -17,7 +17,6 @@ from zoneinfo import ZoneInfo from django.conf import settings from django.contrib import messages -from django.db.models import QuerySet from django.forms import ValidationError from django.http import Http404 from django.template.loader import render_to_string @@ -25,6 +24,7 @@ from django.utils import timezone from django.utils.html import escape from django.urls import reverse as urlreverse +from django_stubs_ext import QuerySetAny import debug # pyflakes:ignore from ietf.community.models import CommunityList @@ -345,7 +345,7 @@ def augment_events_with_revision(doc, events): """Take a set of events for doc and add a .rev attribute with the revision they refer to by checking NewRevisionDocEvents.""" - if isinstance(events, QuerySet): + if isinstance(events, QuerySetAny): qs = events.filter(newrevisiondocevent__isnull=False) else: qs = NewRevisionDocEvent.objects.filter(doc=doc) @@ -353,7 +353,7 @@ def augment_events_with_revision(doc, events): if doc.type_id == "draft" and doc.get_state_slug() == "rfc": # add fake "RFC" revision - if isinstance(events, QuerySet): + if isinstance(events, QuerySetAny): e = events.filter(type="published_rfc").order_by('time').last() else: e = doc.latest_event(type="published_rfc") From e121b5dd5045a37068cdb7c493703f4d2a949849 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Wed, 31 May 2023 12:36:21 -0300 Subject: [PATCH 099/101] fix: Return NomComs, not Groups, from active_nomcoms filter --- ietf/group/templatetags/group_filters.py | 17 ++++++++--------- ietf/templates/base/menu.html | 10 +++++----- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/ietf/group/templatetags/group_filters.py b/ietf/group/templatetags/group_filters.py index e7fb4a181..632567ca3 100644 --- a/ietf/group/templatetags/group_filters.py +++ b/ietf/group/templatetags/group_filters.py @@ -2,7 +2,7 @@ from django import template import debug # pyflakes:ignore -from ietf.group.models import Group +from ietf.nomcom.models import NomCom register = template.Library() @@ -19,14 +19,13 @@ def active_nomcoms(user): if not (user and hasattr(user, "is_authenticated") and user.is_authenticated): return [] - groups = [] - - groups.extend(Group.objects.filter( - role__person__user=user, - type_id='nomcom', - state__slug='active').distinct().select_related("type")) - - return groups + return list( + NomCom.objects.filter( + group__role__person__user=user, + group__type_id='nomcom', # just in case... + group__state__slug='active', + ) + ) @register.inclusion_tag('person/person_link.html') def role_person_link(role, **kwargs): diff --git a/ietf/templates/base/menu.html b/ietf/templates/base/menu.html index 691f1972d..d97980805 100644 --- a/ietf/templates/base/menu.html +++ b/ietf/templates/base/menu.html @@ -171,7 +171,7 @@ </li> {% endfor %} {% endif %} - {% if user|active_nomcoms %} + {% with user|active_nomcoms as nomcoms %}{% if nomcoms %} {% if flavor == 'top' %} <li><hr class="dropdown-divider"> </li> @@ -179,15 +179,15 @@ <li {% if flavor == 'top' %}class="dropdown-header"{% else %}class="nav-item fw-bolder"{% endif %}> NomComs </li> - {% for g in user|active_nomcoms %} + {% for nomcom in nomcoms %} <li> <a class="dropdown-item {% if flavor != 'top' %}text-wrap link-primary{% endif %}" - href="{% url "ietf.nomcom.views.private_index" g.nomcom_set.first.year %}"> - {{ g.acronym|capfirst }} + href="{% url "ietf.nomcom.views.private_index" nomcom.year %}"> + {{ nomcom|capfirst }} </a> </li> {% endfor %} - {% endif %} + {% endif %}{% endwith %} {% endif %} {% if flavor == 'top' %} <li><hr class="dropdown-divider"> From 8cd09ab3be79b0942fdfe2b0e9075aba5828af4f Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Wed, 31 May 2023 14:13:46 -0300 Subject: [PATCH 100/101] Revert "fix: Return NomComs, not Groups, from active_nomcoms filter" This reverts commit e121b5dd5045a37068cdb7c493703f4d2a949849. --- ietf/group/templatetags/group_filters.py | 17 +++++++++-------- ietf/templates/base/menu.html | 10 +++++----- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/ietf/group/templatetags/group_filters.py b/ietf/group/templatetags/group_filters.py index 632567ca3..e7fb4a181 100644 --- a/ietf/group/templatetags/group_filters.py +++ b/ietf/group/templatetags/group_filters.py @@ -2,7 +2,7 @@ from django import template import debug # pyflakes:ignore -from ietf.nomcom.models import NomCom +from ietf.group.models import Group register = template.Library() @@ -19,13 +19,14 @@ def active_nomcoms(user): if not (user and hasattr(user, "is_authenticated") and user.is_authenticated): return [] - return list( - NomCom.objects.filter( - group__role__person__user=user, - group__type_id='nomcom', # just in case... - group__state__slug='active', - ) - ) + groups = [] + + groups.extend(Group.objects.filter( + role__person__user=user, + type_id='nomcom', + state__slug='active').distinct().select_related("type")) + + return groups @register.inclusion_tag('person/person_link.html') def role_person_link(role, **kwargs): diff --git a/ietf/templates/base/menu.html b/ietf/templates/base/menu.html index d97980805..691f1972d 100644 --- a/ietf/templates/base/menu.html +++ b/ietf/templates/base/menu.html @@ -171,7 +171,7 @@ </li> {% endfor %} {% endif %} - {% with user|active_nomcoms as nomcoms %}{% if nomcoms %} + {% if user|active_nomcoms %} {% if flavor == 'top' %} <li><hr class="dropdown-divider"> </li> @@ -179,15 +179,15 @@ <li {% if flavor == 'top' %}class="dropdown-header"{% else %}class="nav-item fw-bolder"{% endif %}> NomComs </li> - {% for nomcom in nomcoms %} + {% for g in user|active_nomcoms %} <li> <a class="dropdown-item {% if flavor != 'top' %}text-wrap link-primary{% endif %}" - href="{% url "ietf.nomcom.views.private_index" nomcom.year %}"> - {{ nomcom|capfirst }} + href="{% url "ietf.nomcom.views.private_index" g.nomcom_set.first.year %}"> + {{ g.acronym|capfirst }} </a> </li> {% endfor %} - {% endif %}{% endwith %} + {% endif %} {% endif %} {% if flavor == 'top' %} <li><hr class="dropdown-divider"> From 2d661a1a5f08e5429d17ed979a9099faf3af51aa Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@staff.ietf.org> Date: Thu, 1 Jun 2023 17:12:15 -0300 Subject: [PATCH 101/101] chore: Use permissive cross-origin-opener-policy setting --- ietf/settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ietf/settings.py b/ietf/settings.py index 6039064fa..7d07f1220 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -539,6 +539,8 @@ SECURE_HSTS_SECONDS = 3600 #SECURE_REDIRECT_EXEMPT #SECURE_SSL_HOST #SECURE_SSL_REDIRECT = True +# Relax the COOP policy to allow Meetecho authentication pop-up +SECURE_CROSS_ORIGIN_OPENER_POLICY = "unsafe-none" # Override this in your settings_local with the IP addresses relevant for you: INTERNAL_IPS = (