Merge pull request #5649 from jennifer-richards/django40

chore: Upgrade to Django 4.0
This commit is contained in:
Jennifer Richards 2023-05-18 15:10:42 -03:00 committed by GitHub
commit cb259e9a2a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 132 additions and 72 deletions

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

@ -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")
@ -100,7 +101,13 @@ 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
# 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.'
@ -319,7 +326,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

View file

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

View file

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

View file

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

View file

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

View file

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