Merge branch 'feat/django4' into dev/merge-main-5679

This commit is contained in:
Jennifer Richards 2023-05-23 12:11:17 -03:00 committed by GitHub
commit e973672c25
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
120 changed files with 739 additions and 545 deletions

View file

@ -4,6 +4,7 @@ on:
pull_request:
branches:
- 'main'
- 'feat/django4'
paths:
- 'client/**'
- 'ietf/**'

View file

@ -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',
},

View file

@ -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',
},

View file

@ -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',
},

View file

@ -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',
},

View file

@ -9,11 +9,12 @@ 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
from django_stubs_ext import QuerySetAny
import debug # pyflakes:ignore
@ -121,7 +122,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+"__") ]
@ -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"):
@ -188,10 +189,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 +202,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()]
@ -221,7 +222,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)
@ -264,6 +265,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)

View file

@ -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()

View file

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

View file

@ -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())

View file

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

View file

@ -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'),
),
]

View file

@ -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
@ -1130,7 +1130,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()
@ -1192,7 +1192,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"

View file

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

View file

@ -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'])

View file

@ -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()

View file

@ -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]
)
)

View file

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

View file

@ -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_str, 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
@ -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()

View file

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

View file

@ -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
@ -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
@ -1087,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 = is_safe_url(url=redirect_url, allowed_hosts=request.get_host(),
require_https=request.is_secure())
if request.is_ajax():
return HttpResponse(json.dumps({'success': True}), content_type='application/json')
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"
)
elif url_is_safe:
return HttpResponseRedirect(redirect_url)
else:

View file

@ -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)

View file

@ -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
@ -24,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"
@ -40,15 +40,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} ]

View file

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

View file

@ -14,9 +14,9 @@ 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 _
from django.utils.translation import gettext as _
from ietf.group.models import (Group, GroupFeatures, GroupHistory, GroupEvent, GroupURL, GroupMilestone,
GroupMilestoneHistory, GroupStateTransitions, Role, RoleHistory, ChangeStateGroupEvent,
@ -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)

View file

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

View file

@ -300,8 +300,27 @@ 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")
.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})
def active_dirs(request):
dirs = Group.objects.filter(type__in=['dir', 'review'], state="active").order_by("name")

View file

@ -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)")

View file

@ -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
@ -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",

View file

@ -7,7 +7,8 @@
import oidc_provider.lib.claims
from functools import wraps
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,8 +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.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())))

114
ietf/ietfauth/widgets.py Normal file
View file

@ -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 &copy; 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
)

View file

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

View file

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

View file

@ -9,16 +9,15 @@ 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
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
@ -132,7 +131,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))
@ -204,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
@ -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)
@ -476,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']

View file

@ -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>&nbsp' % (conditional_escape(attachment.document.get_href()), conditional_escape(attachment.document.title))
html += '<a class="btn btn-primary btn-sm" href="{}">Edit</a>&nbsp'.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)
return mark_safe(html)

View file

@ -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):
@ -1531,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),
@ -1608,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,
@ -1651,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),

View file

@ -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('"')
@ -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),
@ -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)
@ -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"""
@ -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):

View file

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

View file

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

View file

@ -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
@ -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):
@ -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,

View file

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

View file

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

View file

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

View file

@ -188,8 +188,9 @@ class NomineePosition(models.Model):
def save(self, **kwargs):
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')
super(NomineePosition, self).save(**kwargs)
super().save(**kwargs)
def __str__(self):
return "%s - %s - %s" % (self.nominee, self.state, self.position)

View file

@ -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
@ -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] -*-'
@ -68,7 +70,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:

View file

@ -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)
@ -1993,7 +2004,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):

View file

@ -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,7 +189,8 @@ def retrieve_nomcom_private_key(request, year):
settings.OPENSSL_COMMAND,
command_line_safe_secret(settings.NOMCOM_APP_SECRET)
),
private_key
# The openssl command expects ascii-armored input, so utf8 encoding should be valid
private_key.encode("utf8")
)
if code != 0:
log("openssl error: %s:\n Error %s: %s" %(command, code, error))
@ -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:
@ -205,8 +218,9 @@ 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''
# 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")
def validate_private_key(key):

View file

@ -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
@ -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()
@ -684,7 +692,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)

View file

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

View file

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

View file

@ -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
@ -598,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)
@ -607,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(chunk_size=2000) # chunk_size not tested, using pre-Django 5 default value
):
assignments_for_each_doc[r.review_request.doc.name].append(r)
# now collect in breadth-first order to keep the revision order intact
@ -646,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)
@ -654,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(chunk_size=2000) # chunk_size not tested, using pre-Django 5 default value
):
requests_for_each_doc[r.doc.name].append(r)
# now collect in breadth-first order to keep the revision order intact

View file

@ -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)

View file

@ -1,5 +1,5 @@
{% extends "base_site.html" %}
{% load staticfiles %}
{% load static %}
{% block title %}Announcement{% endblock %}

View file

@ -1,5 +1,5 @@
{% extends "base_site.html" %}
{% load staticfiles %}
{% load static %}
{% block title %}Areas{% endblock %}
{% block extrahead %}{{ block.super }}

View file

@ -1,5 +1,5 @@
{% extends "base_site.html" %}
{% load staticfiles %}
{% load static %}
{% block title %}Areas - People{% endblock %}
{% block extrahead %}{{ block.super }}

View file

@ -1,5 +1,5 @@
{% extends "base_site.html" %}
{% load staticfiles %}
{% load static %}
{% block title %}Areas - View{% endblock %}
{% block extrahead %}{{ block.super }}

View file

@ -1,5 +1,5 @@
<!DOCTYPE html>
{% load staticfiles %}
{% load static %}
<html lang="en">
<head>
<meta charset="utf-8">

View file

@ -1,5 +1,5 @@
<!DOCTYPE html>
{% load staticfiles %}
{% load static %}
<html lang="en">
<head>
<meta charset="utf-8">

View file

@ -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 %}

View file

@ -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 %}

View file

@ -1,5 +1,5 @@
{% extends "base_site.html" %}
{% load staticfiles %}
{% load static %}
{% block title %}Confirm Cancel{% endblock %}

View file

@ -1,5 +1,5 @@
{% extends "base_site.html" %}
{% load staticfiles %}
{% load static %}
{% block title %}Confirm Delete{% endblock %}

View file

@ -1,5 +1,5 @@
{% extends "base_site.html" %}
{% load staticfiles %}
{% load static %}
{% block title %}Meetings - Add{% endblock %}

View file

@ -1,5 +1,5 @@
{% extends "base_site_bootstrap.html" %}
{% load staticfiles %}
{% load static %}
{% block title %}Meetings{% endblock %}

View file

@ -1,5 +1,5 @@
{% extends "base_site.html" %}
{% load staticfiles %}
{% load static %}
{% block title %}Meetings - Blue Sheet{% endblock %}

View file

@ -1,5 +1,5 @@
{% extends "base_site.html" %}
{% load staticfiles %}
{% load static %}
{% block title %}Meetings - Edit{% endblock %}

View file

@ -1,5 +1,5 @@
{% extends "base_site.html" %}
{% load staticfiles %}
{% load static %}
{% block title %}Meetings{% endblock %}

View file

@ -1,5 +1,5 @@
{% extends "base_site.html" %}
{% load staticfiles %}
{% load static %}
{% block title %}Meetings{% endblock %}

View file

@ -1,5 +1,5 @@
{% extends "base_site.html" %}
{% load staticfiles tz %}
{% load static tz %}
{% block title %}Meetings{% endblock %}

View file

@ -1,5 +1,5 @@
{% extends "base_site.html" %}
{% load staticfiles %}
{% load static %}
{% block title %}Meetings{% endblock %}

View file

@ -1,5 +1,5 @@
{% extends "base_site.html" %}
{% load staticfiles %}
{% load static %}
{% block title %}Rolodex - Add{% endblock %}

View file

@ -1,5 +1,5 @@
{% extends "base_site.html" %}
{% load staticfiles %}
{% load static %}
{% block title %}Rolodex - Edit{% endblock %}

View file

@ -1,5 +1,5 @@
{% extends "base_site.html" %}
{% load staticfiles %}
{% load static %}
{% block title %}Rolodex - Search{% endblock %}

View file

@ -1,5 +1,5 @@
{% extends "base_site.html" %}
{% load staticfiles %}
{% load static %}
{% block title %}Sessions - Confirm{% endblock %}

View file

@ -1,5 +1,5 @@
{% extends "base_site.html" %}
{% load staticfiles %}
{% load static %}
{% block title %}Sessions - Edit{% endblock %}
{% block extrahead %}{{ block.super }}

View file

@ -1,5 +1,5 @@
{% extends "base_site.html" %}
{% load staticfiles %}
{% load static %}
{% block title %}Sessions{% endblock %}

View file

@ -1,6 +1,6 @@
{% extends "base_site.html" %}
{% load ietf_filters %}
{% load staticfiles %}
{% load static %}
{% block title %}Sessions{% endblock %}

View file

@ -1,5 +1,5 @@
{% extends "base_site.html" %}
{% load staticfiles %}
{% load static %}
{% block title %}Sessions- New{% endblock %}

View file

@ -1,5 +1,5 @@
{% extends "base_site.html" %}
{% load staticfiles %}
{% load static %}
{% block title %}Sessions{% endblock %}

View file

@ -1,5 +1,5 @@
{% extends "base_site.html" %}
{% load staticfiles %}
{% load static %}
{% block title %}Sessions - View{% endblock %}

View file

@ -1,5 +1,5 @@
{% extends "base_site.html" %}
{% load staticfiles %}
{% load static %}
{% block title %}Telechat{% endblock %}

View file

@ -1,13 +1,13 @@
from django.conf.urls import url, include
from django.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')),
]

View file

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

View file

@ -13,12 +13,18 @@ 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="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
warnings.filterwarnings("ignore", message="The USE_L10N setting is deprecated.") # https://github.com/ietf-tools/datatracker/issues/5648
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")
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')
warnings.filterwarnings("ignore", message="HTTPResponse.getheader\\(\\) is deprecated", module='selenium.webdriver')
try:
import syslog
syslog.openlog(str("datatracker"), syslog.LOG_PID, syslog.LOG_USER)
@ -101,7 +107,23 @@ 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
# 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 dont 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/'
@ -316,10 +338,10 @@ UTILS_LOGGER_LEVELS: Dict[str, str] = {
X_FRAME_OPTIONS = 'SAMEORIGIN'
CSRF_TRUSTED_ORIGINS = [
'ietf.org',
'*.ietf.org',
'meetecho.com',
'*.meetecho.com',
"https://ietf.org",
"https://*.ietf.org",
'https://meetecho.com',
'https://*.meetecho.com',
]
CSRF_COOKIE_SAMESITE = 'None'
CSRF_COOKIE_SECURE = True
@ -330,11 +352,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'
@ -434,8 +452,6 @@ INSTALLED_APPS = [
'django_celery_beat',
'corsheaders',
'django_markup',
'django_password_strength',
'form_utils',
'oidc_provider',
'simple_history',
'tastypie',
@ -707,13 +723,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',
@ -1122,14 +1138,13 @@ 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 = [
'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/add-django-http-cookie-value-none.patch',
'patch/django-cookie-delete-with-all-settings.patch',
'patch/tastypie-django22-fielderror-response.patch',
]

View file

@ -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',
},

View file

@ -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"

View file

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

View file

@ -24,7 +24,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,
@ -750,10 +750,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'))
@ -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(

View file

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

View file

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

View file

@ -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 %}"

View file

@ -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 %}
{% endif %}
</td>
<td>{{ ref.relationship.name }}</td>
<td>{{ ref.is_downref|default:'' }}</td>

View file

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

View file

@ -47,21 +47,43 @@
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" %}
{% 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" %}
{% 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" %}
{% 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

View file

@ -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 %}

View file

@ -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 %}

View file

@ -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 %}

View file

@ -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 %}

View file

@ -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 %}

View file

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

View file

@ -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])

Some files were not shown because too many files have changed in this diff Show more