1
0
mirror of https://github.com/taigaio/taiga-back synced 2025-10-05 15:52:48 +02:00

chore: migrate to django 3.2

This commit is contained in:
David Barragán Merino
2023-02-06 18:15:52 +01:00
committed by David Barragán Merino
parent 04becaf240
commit e7fad0b5cc
202 changed files with 516 additions and 493 deletions

View File

@@ -15,7 +15,7 @@ jobs:
strategy:
matrix:
python-version: [ '3.7', '3.8', '3.9', '3.10' ]
python-version: [ '3.8', '3.9', '3.10', '3.11' ]
services:
postgres:

View File

@@ -2,11 +2,15 @@
## 6.6.0 (unreleased)
- Adapt Dockerfile to the removal of psycopg2-binary
- DOCKER: Added env `POSTGRES_SSLMODE` with default value disabled (thanks to [@ribeiromiranda](https://github.com/ribeiromiranda))
- Add support for Python 3.11
- Remove support for Python 3.7
- Upgrade to Django 3.2 (and upgrade some other dependencies)
- Fix some silent css error in email templates (thanks to [@sarbanha](https://github.com/sarbanha))
- Improves the performance of operations on tags (thanks to [@theriverman](https://github.com/theriverman))
- Remove `write` permission from Trello importer (thanks to [@northben](https://github.com/northben))
- DOCKER: Use python-3.11-slim as base image
- DOCKER: Adapt Dockerfile to the removal of psycopg2-binary
- DOCKER: Added env `POSTGRES_SSLMODE` with default value disabled (thanks to [@ribeiromiranda](https://github.com/ribeiromiranda))
## 6.5.2 (2022-09-26)

View File

@@ -4,7 +4,7 @@
#
# Copyright (c) 2021-present Kaleidos Ventures SL
FROM python:3.7-slim
FROM python:3.11-slim
LABEL maintainer="support@taiga.io"
# Avoid prompting for configuration

View File

@@ -1,4 +1,10 @@
[pytest]
DJANGO_SETTINGS_MODULE = tests.config
filterwarnings =
default::DeprecationWarning
# Django
ignore::django.utils.deprecation.RemovedInDjango40Warning
ignore::django.utils.deprecation.RemovedInDjango41Warning
ignore:'cgi' is deprecated and slated for removal in Python 3.13:DeprecationWarning
ignore:Use setlocale\(\), getencoding\(\) and getlocale\(\) instead:DeprecationWarning
# PyJWT
ignore::jwt.warnings.RemovedInPyjwt3Warning

View File

@@ -1,22 +1,22 @@
#
# This file is autogenerated by pip-compile with python 3.7
# To update, run:
# This file is autogenerated by pip-compile with Python 3.8
# by the following command:
#
# pip-compile requirements-devel.in
#
attrs==21.4.0
attrs==22.2.0
# via
# -c requirements.txt
# pytest
certifi==2021.10.8
certifi==2022.12.7
# via
# -c requirements.txt
# requests
charset-normalizer==2.0.10
charset-normalizer==2.0.12
# via
# -c requirements.txt
# requests
coverage==6.2
coverage==6.5.0
# via
# -r requirements-devel.in
# coveralls
@@ -26,40 +26,31 @@ docopt==0.6.2
# via
# -c requirements.txt
# coveralls
ecdsa==0.17.0
ecdsa==0.18.0
# via python-jose
exceptiongroup==1.1.0
# via pytest
factory-boy==3.2.1
# via -r requirements-devel.in
faker==11.3.0
faker==17.0.0
# via factory-boy
idna==3.3
idna==3.4
# via
# -c requirements.txt
# requests
importlib-metadata==4.10.0
# via
# -c requirements.txt
# pluggy
# pytest
iniconfig==1.1.1
iniconfig==2.0.0
# via pytest
packaging==21.3
packaging==23.0
# via
# -c requirements.txt
# pytest
pluggy==1.0.0
# via pytest
py==1.11.0
# via pytest
pyasn1==0.4.8
# via
# python-jose
# rsa
pyparsing==3.0.6
# via
# -c requirements.txt
# packaging
pytest==6.2.5
pytest==7.2.1
# via
# -r requirements-devel.in
# pytest-django
@@ -75,27 +66,16 @@ requests==2.27.1
# via
# -c requirements.txt
# coveralls
rsa==4.8
rsa==4.9
# via python-jose
six==1.16.0
# via
# -c requirements.txt
# ecdsa
# python-dateutil
text-unidecode==1.3
# via faker
toml==0.10.2
tomli==2.0.1
# via pytest
typing-extensions==4.0.1
# via
# -c requirements.txt
# faker
# importlib-metadata
urllib3==1.26.8
urllib3==1.26.14
# via
# -c requirements.txt
# requests
zipp==1.2.0
# via
# -c requirements.txt
# importlib-metadata

View File

@@ -1,2 +1,2 @@
factory_boy==2.11.1
pytest==4.4.1
factory-boy==3.2.1
pytest==7.2.1

View File

@@ -1,35 +1,35 @@
asana
bleach
celery==5.2.3
bleach<5
celery==5.2.7
diff-match-patch==20121119
django-ipware==1.1.6
django-jinja==2.7.0
django-picklefield==0.3.2
django-pglocks==1.0.2
django-jinja
django-picklefield==3.1
django-pglocks==1.0.4
django-sampledatahelper==0.4.1
django-sites==0.10
django-sites==0.11
django-sr==0.0.4
djmail==1.0.1
easy-thumbnails==2.7.0
djmail==2.0.0
easy-thumbnails==2.8.5
gunicorn==20.1.0
netaddr==0.7.19
netaddr<0.9
premailer==3.0.1
psd-tools==1.9.18
psycopg2<2.9 # required by django
pymdown-extensions==8.1.1
psycopg2<2.10 # required by django
python-dateutil==2.7.5
python-magic==0.4.15
pytz
raven
redis==2.10.5
redis==4.5.1
requests==2.27.1
requests_oauthlib
rudder-sdk-python==1.0.0b1
serpy==0.1.1
webcolors==1.9.1
CairoSVG>=2.5.1
Django>=2.2,<3
Markdown==3.3.4
Django>=3,<4
Markdown==3.4.1
pymdown-extensions==9.9.2
Pillow
Unidecode==0.4.20
Pygments>=2.7.4

View File

@@ -1,16 +1,20 @@
#
# This file is autogenerated by pip-compile with python 3.7
# To update, run:
# This file is autogenerated by pip-compile with Python 3.8
# by the following command:
#
# pip-compile requirements.in
#
aggdraw==1.3.12
aggdraw==1.3.15
# via psd-tools
amqp==5.0.9
amqp==5.1.1
# via kombu
asana==0.10.3
asana==3.1.0
# via -r requirements.in
attrs==21.4.0
asgiref==3.6.0
# via django
async-timeout==4.0.2
# via redis
attrs==22.2.0
# via psd-tools
backoff==1.6.0
# via rudder-sdk-python
@@ -18,23 +22,21 @@ billiard==3.6.4.0
# via celery
bleach==4.1.0
# via -r requirements.in
cached-property==1.5.2
# via kombu
cairocffi==1.3.0
cairocffi==1.4.0
# via cairosvg
cairosvg==2.5.2
cairosvg==2.6.0
# via -r requirements.in
celery==5.2.3
celery==5.2.7
# via -r requirements.in
certifi==2021.10.8
certifi==2022.12.7
# via requests
cffi==1.15.0
cffi==1.15.1
# via
# cairocffi
# cryptography
charset-normalizer==2.0.10
charset-normalizer==2.0.12
# via requests
click==8.0.3
click==8.1.3
# via
# celery
# click-didyoumean
@@ -46,79 +48,76 @@ click-plugins==1.1.1
# via celery
click-repl==0.2.0
# via celery
cryptography==3.4.8
cryptography==39.0.1
# via oauthlib
cssselect==1.1.0
cssselect==1.2.0
# via premailer
cssselect2==0.4.1
cssselect2==0.7.0
# via cairosvg
cssutils==2.3.0
cssutils==2.6.0
# via premailer
defusedxml==0.7.1
# via cairosvg
diff-match-patch==20121119
# via -r requirements.in
django==2.2.26
django==3.2.18
# via
# -r requirements.in
# django-jinja
# django-picklefield
# django-sampledatahelper
# django-sites
# django-sr
# easy-thumbnails
django-ipware==1.1.6
# via -r requirements.in
django-jinja==2.7.0
django-jinja==2.10.2
# via -r requirements.in
django-pglocks==1.0.2
django-pglocks==1.0.4
# via -r requirements.in
django-picklefield==0.3.2
django-picklefield==3.1
# via -r requirements.in
django-sampledatahelper==0.4.1
# via -r requirements.in
django-sites==0.10
django-sites==0.11
# via -r requirements.in
django-sr==0.0.4
# via -r requirements.in
djmail==1.0.1
djmail==2.0.0
# via -r requirements.in
docopt==0.6.2
# via psd-tools
easy-thumbnails==2.7.0
easy-thumbnails==2.8.5
# via -r requirements.in
gunicorn==20.1.0
# via -r requirements.in
html5lib==1.1
# via -r requirements.in
idna==3.3
idna==3.4
# via requests
imageio==2.13.5
imageio==2.25.1
# via scikit-image
importlib-metadata==4.10.0
# via
# click
# cssutils
# kombu
# markdown
jinja2==3.0.3
importlib-metadata==6.0.0
# via markdown
jinja2==3.1.2
# via django-jinja
kombu==5.2.3
kombu==5.2.4
# via celery
lxml==4.7.1
lxml==4.9.2
# via premailer
markdown==3.3.4
markdown==3.4.1
# via
# -r requirements.in
# pymdown-extensions
markupsafe==2.0.1
markupsafe==2.1.2
# via jinja2
monotonic==1.6
# via rudder-sdk-python
netaddr==0.7.19
netaddr==0.8.0
# via -r requirements.in
networkx==2.6.3
networkx==3.0
# via scikit-image
numpy==1.21.5
numpy==1.24.2
# via
# imageio
# psd-tools
@@ -126,15 +125,15 @@ numpy==1.21.5
# scikit-image
# scipy
# tifffile
oauthlib[signedtoken]==3.1.1
oauthlib[signedtoken]==3.2.2
# via
# -r requirements.in
# requests-oauthlib
packaging==21.3
packaging==23.0
# via
# bleach
# scikit-image
pillow==9.0.0
pillow==9.4.0
# via
# -r requirements.in
# cairosvg
@@ -144,39 +143,37 @@ pillow==9.0.0
# scikit-image
premailer==3.0.1
# via -r requirements.in
prompt-toolkit==3.0.24
prompt-toolkit==3.0.36
# via click-repl
psd-tools==1.9.18
# via -r requirements.in
psycopg2==2.8.6
psycopg2==2.9.5
# via -r requirements.in
pycparser==2.21
# via cffi
pygments==2.11.2
pygments==2.14.0
# via -r requirements.in
pyjwt==2.3.0
pyjwt==2.6.0
# via oauthlib
pymdown-extensions==8.1.1
pymdown-extensions==9.9.2
# via -r requirements.in
pyparsing==3.0.6
# via packaging
python-dateutil==2.7.5
# via
# -r requirements.in
# rudder-sdk-python
python-magic==0.4.15
# via -r requirements.in
pytz==2021.3
pytz==2022.7.1
# via
# -r requirements.in
# celery
# django
# sampledata
pywavelets==1.2.0
pywavelets==1.4.1
# via scikit-image
raven==6.10.0
# via -r requirements.in
redis==2.10.5
redis==4.5.1
# via -r requirements.in
requests==2.27.1
# via
@@ -185,7 +182,7 @@ requests==2.27.1
# premailer
# requests-oauthlib
# rudder-sdk-python
requests-oauthlib==1.3.0
requests-oauthlib==1.3.1
# via
# -r requirements.in
# asana
@@ -193,9 +190,9 @@ rudder-sdk-python==1.0.0b1
# via -r requirements.in
sampledata==0.3.7
# via django-sampledatahelper
scikit-image==0.19.1
scikit-image==0.19.3
# via psd-tools
scipy==1.7.3
scipy==1.10.0
# via
# psd-tools
# scikit-image
@@ -203,9 +200,9 @@ serpy==0.1.1
# via -r requirements.in
six==1.16.0
# via
# asana
# bleach
# click-repl
# django-pglocks
# django-sampledatahelper
# html5lib
# python-dateutil
@@ -213,26 +210,24 @@ six==1.16.0
# sampledata
# serpy
# webcolors
sqlparse==0.4.2
sqlparse==0.4.3
# via django
tifffile==2021.11.2
tifffile==2023.2.3
# via scikit-image
tinycss2==1.1.1
tinycss2==1.2.1
# via
# cairosvg
# cssselect2
typing-extensions==4.0.1
# via importlib-metadata
unidecode==0.4.20
# via -r requirements.in
urllib3==1.26.8
urllib3==1.26.14
# via requests
vine==5.0.0
# via
# amqp
# celery
# kombu
wcwidth==0.2.5
wcwidth==0.2.6
# via prompt-toolkit
webcolors==1.9.1
# via -r requirements.in

View File

@@ -32,6 +32,9 @@ DATABASES = {
}
}
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",

View File

@@ -7,7 +7,7 @@
from functools import partial
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from django.conf import settings
from taiga.base import exceptions as exc

View File

@@ -33,7 +33,7 @@ import bleach
import re
from django.core import validators as core_validators
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from taiga.base.api import serializers
from taiga.base.exceptions import ValidationError

View File

@@ -8,4 +8,4 @@
import django.dispatch
user_registered = django.dispatch.Signal(providing_args=["user"])
user_registered = django.dispatch.Signal() # providing_args=["user"]

View File

@@ -29,4 +29,3 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
default_app_config = 'taiga.auth.token_denylist.apps.TokenDenylistConfig'

View File

@@ -35,6 +35,7 @@ from django.utils.translation import gettext_lazy as _
from .models import DenylistedToken, OutstandingToken
@admin.register(OutstandingToken)
class OutstandingTokenAdmin(admin.ModelAdmin):
list_display = (
'jti',
@@ -74,9 +75,9 @@ class OutstandingTokenAdmin(admin.ModelAdmin):
)
admin.site.register(OutstandingToken, OutstandingTokenAdmin)
@admin.register(DenylistedToken)
class DenylistedTokenAdmin(admin.ModelAdmin):
list_display = (
'token_jti',
@@ -98,25 +99,32 @@ class DenylistedTokenAdmin(admin.ModelAdmin):
return qs.select_related('token__user')
@admin.display(
description=_('jti'),
ordering='token__jti',
)
def token_jti(self, obj):
return obj.token.jti
token_jti.short_description = _('jti')
token_jti.admin_order_field = 'token__jti'
@admin.display(
description=_('user'),
ordering='token__user',
)
def token_user(self, obj):
return obj.token.user
token_user.short_description = _('user')
token_user.admin_order_field = 'token__user'
@admin.display(
description=_('created at'),
ordering='token__created_at',
)
def token_created_at(self, obj):
return obj.token.created_at
token_created_at.short_description = _('created at')
token_created_at.admin_order_field = 'token__created_at'
@admin.display(
description=_('expires at'),
ordering='token__expires_at',
)
def token_expires_at(self, obj):
return obj.token.expires_at
token_expires_at.short_description = _('expires at')
token_expires_at.admin_order_field = 'token__expires_at'
admin.site.register(DenylistedToken, DenylistedTokenAdmin)

View File

@@ -5,4 +5,3 @@
#
# Copyright (c) 2021-present Kaleidos Ventures SL
default_app_config = "taiga.base.apps.BaseAppConfig"

View File

@@ -50,7 +50,7 @@ def get_authorization_header(request):
Hide some test client ickyness where the header can be unicode.
"""
auth = request.META.get('HTTP_AUTHORIZATION', b'')
auth = request.headers.get('authorization', b'')
if type(auth) == type(''):
# Work around django test client oddness
auth = auth.encode(HTTP_HEADER_ENCODING)

View File

@@ -42,17 +42,17 @@ from django.core import validators
from django.db.models.fields import BLANK_CHOICE_DASH
from django.forms import widgets
from django.http import QueryDict
from django.utils import six
import six
from django.utils import timezone
from django.utils.dateparse import parse_date
from django.utils.dateparse import parse_datetime
from django.utils.dateparse import parse_time
from django.utils.encoding import smart_text
from django.utils.encoding import force_text
from django.utils.encoding import smart_str
from django.utils.encoding import force_str
from django.utils.encoding import is_protected_type
from django.utils.functional import Promise
from django.utils.translation import ugettext
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _
from taiga.base.exceptions import ValidationError
@@ -168,7 +168,7 @@ class Field(object):
self.source = source
if label is not None:
self.label = smart_text(label)
self.label = smart_str(label)
else:
self.label = None
@@ -250,7 +250,7 @@ class Field(object):
for key, val in value.items():
ret[key] = self.to_native(val)
return ret
return force_text(value)
return force_str(value)
def attributes(self):
"""
@@ -269,7 +269,7 @@ class Field(object):
for attr in optional_attrs:
value = getattr(self, attr, None)
if value is not None and value != "":
metadata[attr] = force_text(value, strings_only=True)
metadata[attr] = force_str(value, strings_only=True)
return metadata
@@ -509,12 +509,12 @@ class CharField(WritableField):
if value in validators.EMPTY_VALUES:
return ""
return smart_text(value)
return smart_str(value)
def to_native(self, value):
ret = super(CharField, self).to_native(value)
if self.i18n:
ret = ugettext(ret)
ret = gettext(ret)
return ret
@@ -593,10 +593,10 @@ class ChoiceField(WritableField):
if isinstance(v, (list, tuple)):
# This is an optgroup, so look inside the group for options
for k2, v2 in v:
if value == smart_text(k2):
if value == smart_str(k2):
return True
else:
if value == smart_text(k) or value == k:
if value == smart_str(k) or value == k:
return True
return False
@@ -946,7 +946,7 @@ class DecimalField(WritableField):
"""
if value in validators.EMPTY_VALUES:
return None
value = smart_text(value).strip()
value = smart_str(value).strip()
try:
value = Decimal(value)
except DecimalException:

View File

@@ -35,7 +35,7 @@ import warnings
from django.http import Http404
from django.db import transaction as tx
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from taiga.base import response
from taiga.base.exceptions import ValidationError

View File

@@ -120,6 +120,6 @@ class DefaultContentNegotiation(BaseContentNegotiation):
Allows URL style accept override. eg. "?accept=application/json"
"""
header = request.META.get("HTTP_ACCEPT", "*/*")
header = request.headers.get("accept", "*/*")
header = request.QUERY_PARAMS.get(self.settings.URL_ACCEPT_OVERRIDE, header)
return [token.strip() for token in header.split(",")]

View File

@@ -14,7 +14,7 @@ from django.core.paginator import (
)
from django.http import Http404
from django.http import QueryDict
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from .settings import api_settings
@@ -148,7 +148,7 @@ class PaginationMixin(object):
Otherwise defaults to using `self.paginate_by`.
"""
if "HTTP_X_DISABLE_PAGINATION" in self.request.META:
if "x-disable-pagination" in self.request.headers:
return None
if queryset is not None:
@@ -172,10 +172,10 @@ class PaginationMixin(object):
Paginate a queryset if required, either returning a page object,
or `None` if pagination is not configured for this view.
"""
if "HTTP_X_DISABLE_PAGINATION" in self.request.META:
if "x-disable-pagination" in self.request.headers:
return None
if "HTTP_X_LAZY_PAGINATION" in self.request.META:
if "x-lazy-pagination" in self.request.headers:
self.paginator_class = LazyPaginator
deprecated_style = False
@@ -226,7 +226,7 @@ class PaginationMixin(object):
if page is None:
return page
if not "HTTP_X_LAZY_PAGINATION" in self.request.META:
if not "x-lazy-pagination" in self.request.headers:
self.headers["x-pagination-count"] = page.paginator.count
self.headers["x-paginated"] = "true"

View File

@@ -43,7 +43,7 @@ from django.http import QueryDict
from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser
from django.http.multipartparser import MultiPartParserError, parse_header, ChunkIter
from django.utils import six
import six
from taiga.base.exceptions import ParseError
from taiga.base.api import renderers

View File

@@ -12,7 +12,7 @@ from functools import reduce
from taiga.permissions.services import user_has_perm, is_project_admin
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
######################################################################

View File

@@ -43,8 +43,8 @@ from django import forms
from django.db.models.fields import BLANK_CHOICE_DASH
from django.forms import widgets
from django.forms.models import ModelChoiceIterator
from django.utils.encoding import smart_text
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import smart_str
from django.utils.translation import gettext_lazy as _
from .fields import Field, WritableField, get_component, is_simple_callable
from .reverse import reverse
@@ -118,8 +118,8 @@ class RelatedField(WritableField):
"""
Return a readable representation for use with eg. select widgets.
"""
desc = smart_text(obj)
ident = smart_text(self.to_native(obj))
desc = smart_str(obj)
ident = smart_str(self.to_native(obj))
if desc == ident:
return desc
return "%s - %s" % (desc, ident)
@@ -245,8 +245,8 @@ class PrimaryKeyRelatedField(RelatedField):
"""
Return a readable representation for use with eg. select widgets.
"""
desc = smart_text(obj)
ident = smart_text(self.to_native(obj.pk))
desc = smart_str(obj)
ident = smart_str(self.to_native(obj.pk))
if desc == ident:
return desc
return "%s - %s" % (desc, ident)
@@ -262,7 +262,7 @@ class PrimaryKeyRelatedField(RelatedField):
try:
return self.queryset.get(pk=data)
except ObjectDoesNotExist:
msg = self.error_messages["does_not_exist"] % smart_text(data)
msg = self.error_messages["does_not_exist"] % smart_str(data)
raise ValidationError(msg)
except (TypeError, ValueError):
received = type(data).__name__
@@ -342,7 +342,7 @@ class SlugRelatedField(RelatedField):
return self.queryset.get(**{self.slug_field: data})
except ObjectDoesNotExist:
raise ValidationError(self.error_messages["does_not_exist"] %
(self.slug_field, smart_text(data)))
(self.slug_field, smart_str(data)))
except (TypeError, ValueError):
msg = self.error_messages["invalid"]
raise ValidationError(msg)

View File

@@ -44,7 +44,7 @@ from django.core.exceptions import ImproperlyConfigured
from django.http.multipartparser import parse_header
from django.template import RequestContext, loader, Template
from django.test.client import encode_multipart
from django.utils import six
import six
from .utils import encoders

View File

@@ -45,7 +45,7 @@ from django.conf import settings
from django.http import QueryDict
from django.http.multipartparser import parse_header
from django.utils.datastructures import MultiValueDict
from django.utils.six import BytesIO
import six
from taiga.base import exceptions
@@ -332,7 +332,7 @@ class Request(object):
elif hasattr(self._request, "read"):
self._stream = self._request
else:
self._stream = BytesIO(self.raw_post_data)
self._stream = six.BytesIO(self.raw_post_data)
def _perform_form_overloading(self):
"""
@@ -367,7 +367,7 @@ class Request(object):
self._CONTENT_PARAM in self._data and
self._CONTENTTYPE_PARAM in self._data):
self._content_type = self._data[self._CONTENTTYPE_PARAM]
self._stream = BytesIO(self._data[self._CONTENT_PARAM].encode(self.parser_context["encoding"]))
self._stream = six.BytesIO(self._data[self._CONTENT_PARAM].encode(self.parser_context["encoding"]))
self._data, self._files = (Empty, Empty)
def _parse(self):

View File

@@ -49,8 +49,8 @@ from django.apps import apps
from django.core.paginator import Page
from django.db import models
from django.forms import widgets
from django.utils import six
from django.utils.translation import ugettext as _
import six
from django.utils.translation import gettext as _
from .settings import api_settings

View File

@@ -52,7 +52,7 @@ back to the defaults.
from __future__ import unicode_literals
from django.conf import settings
from django.utils import six
import six
import importlib

View File

@@ -205,7 +205,7 @@ class AnonRateThrottle(SimpleRateThrottle):
if request.user.is_authenticated:
return None # Only throttle unauthenticated requests.
ident = request.META.get("HTTP_X_FORWARDED_FOR")
ident = request.headers.get("x-forwarded-for")
if ident is None:
ident = request.META.get("REMOTE_ADDR")

View File

@@ -33,7 +33,7 @@
from django.urls import URLResolver
from django.conf.urls import url, include
from django.urls import include, re_path
from .settings import api_settings
@@ -51,7 +51,7 @@ def apply_suffix_patterns(urlpatterns, suffix_pattern, suffix_required):
patterns = apply_suffix_patterns(urlpattern.url_patterns,
suffix_pattern,
suffix_required)
ret.append(url(regex, include(patterns, namespace, app_name), kwargs))
ret.append(re_path(regex, include(patterns, namespace, app_name), kwargs))
else:
# Regular URL pattern
@@ -62,7 +62,7 @@ def apply_suffix_patterns(urlpatterns, suffix_pattern, suffix_required):
# Add in both the existing and the new urlpattern
if not suffix_required:
ret.append(urlpattern)
ret.append(url(regex, view, kwargs, name))
ret.append(re_path(regex, view, kwargs, name))
return ret

View File

@@ -38,7 +38,7 @@ from django.db.models.query import QuerySet
from django.utils.functional import Promise
from django.utils import timezone
# from django.utils.deprecation import CallableBool
from django.utils.encoding import force_text
from django.utils.encoding import force_str
import datetime
import decimal
@@ -55,7 +55,7 @@ class JSONEncoder(json.JSONEncoder):
# For Date Time string spec, see ECMA 262
# http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.15
if isinstance(o, Promise):
return force_text(o)
return force_str(o)
# elif isinstance(o, CallableBool):
# return bool(o)
elif isinstance(o, datetime.datetime):

View File

@@ -42,8 +42,8 @@ from django.http.response import HttpResponseBase
from django.views.decorators.csrf import csrf_exempt
from django.views.defaults import server_error
from django.views.generic import View
from django.utils.encoding import smart_text
from django.utils.translation import ugettext as _
from django.utils.encoding import smart_str
from django.utils.translation import gettext as _
from .request import Request
from .settings import api_settings
@@ -84,7 +84,7 @@ def get_view_description(view_cls, html=False):
This function is the default for the `VIEW_DESCRIPTION_FUNCTION` setting.
"""
description = view_cls.__doc__ or ''
description = formatting.dedent(smart_text(description))
description = formatting.dedent(smart_str(description))
if html:
return formatting.markup_description(description)
return description
@@ -476,7 +476,7 @@ class APIView(View):
def api_server_error(request, *args, **kwargs):
if settings.DEBUG is False and request.META.get('CONTENT_TYPE', None) == "application/json":
if settings.DEBUG is False and request.headers.get('content-type', None) == "application/json":
return HttpResponse(json.dumps({"error": _("Server application error")}),
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return server_error(request, *args, **kwargs)

View File

@@ -33,7 +33,7 @@
from functools import update_wrapper
from django.utils.decorators import classonlymethod
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from . import views
from . import mixins

View File

@@ -7,7 +7,7 @@
from taiga.base.exceptions import BaseException
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
class ConnectorBaseException(BaseException):

View File

@@ -6,7 +6,7 @@
# Copyright (c) 2021-present Kaleidos Ventures SL
from django.core.serializers.json import DjangoJSONEncoder
from django.contrib.postgres.fields import JSONField as DjangoJSONField
from django.db.models import JSONField as DjangoJSONField
__all__ = ["JSONField"]

View File

@@ -41,8 +41,8 @@ In addition Django's built in 403 and 404 exceptions are handled.
from django.core.exceptions import PermissionDenied as DjangoPermissionDenied
from django.core.exceptions import ValidationError as DjangoValidationError
from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import force_str
from django.utils.translation import gettext_lazy as _
from django.http import Http404
from . import response
@@ -222,7 +222,7 @@ def format_exception(exc):
class_name = exc.__class__.__name__
class_module = exc.__class__.__module__
detail = {
"_error_message": force_text(exc.detail),
"_error_message": force_str(exc.detail),
"_error_type": "{0}.{1}".format(class_module, class_name)
}

View File

@@ -6,7 +6,7 @@
# Copyright (c) 2021-present Kaleidos Ventures SL
from django.forms import widgets
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from taiga.base.api import serializers, ISO_8601
from taiga.base.api.settings import api_settings

View File

@@ -12,7 +12,7 @@ from dateutil.parser import parse as parse_date
from django.apps import apps
from django.contrib.contenttypes.models import ContentType
from django.db.models import Q
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from taiga.base import exceptions as exc
from taiga.base.api.utils import get_object_or_error

View File

@@ -45,7 +45,7 @@ class CorsMiddleware(object):
response["Access-Control-Allow-Credentials"] = "true"
def process_request(self, request):
if "HTTP_ACCESS_CONTROL_REQUEST_METHOD" in request.META:
if "access-control-request-method" in request.headers:
response = http.HttpResponse()
self._populate_response(response)
return response

View File

@@ -9,7 +9,7 @@ from collections import namedtuple
from django.db import connection
from django.core.exceptions import ObjectDoesNotExist
from django.db.models.sql.datastructures import EmptyResultSet
from django.core.exceptions import EmptyResultSet
from taiga.base.api import serializers
from taiga.base.fields import Field, MethodField

View File

@@ -37,7 +37,7 @@ from http.client import responses
from django import http
from django.template.response import SimpleTemplateResponse
from django.utils import six
import six
class Response(SimpleTemplateResponse):

View File

@@ -9,7 +9,7 @@
import itertools
from collections import namedtuple
from django.conf.urls import url
from django.urls import path, re_path
from django.core.exceptions import ImproperlyConfigured
from django.urls import NoReverseMatch, URLResolver
@@ -249,7 +249,7 @@ class SimpleRouter(BaseRouter):
)
view = viewset.as_view(mapping, **route.initkwargs)
name = route.name.format(basename=basename)
ret.append(url(regex, view, name=name))
ret.append(re_path(regex, view, name=name))
return ret
@@ -295,7 +295,7 @@ class DRFDefaultRouter(SimpleRouter):
urls = []
if self.include_root_view:
root_url = url(r'^$', self.get_api_root_view(), name=self.root_view_name)
root_url = path('', self.get_api_root_view(), name=self.root_view_name)
urls.append(root_url)
default_urls = super(DRFDefaultRouter, self).get_urls()

View File

@@ -15,8 +15,8 @@ import logging
logger = logging.getLogger(__name__)
cleanup_pre_delete = Signal(providing_args=["file"])
cleanup_post_delete = Signal(providing_args=["file"])
cleanup_pre_delete = Signal()
cleanup_post_delete = Signal()
def _find_models_with_filefield():

View File

@@ -5,7 +5,7 @@
#
# Copyright (c) 2021-present Kaleidos Ventures SL
from django.utils.encoding import force_text
from django.utils.encoding import force_str
from taiga.base.api.utils import encoders
@@ -18,7 +18,7 @@ def dumps(data, ensure_ascii=True, encoder_class=encoders.JSONEncoder, indent=No
def loads(data):
if isinstance(data, bytes):
data = force_text(data)
data = force_str(data)
return json.loads(data)
load = json.load

View File

@@ -5,7 +5,7 @@
#
# Copyright (c) 2021-present Kaleidos Ventures SL
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from contextlib import contextmanager

View File

@@ -11,7 +11,7 @@ from urllib.parse import urlparse
import django_sites as sites
from django.urls import reverse as django_reverse
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
URL_TEMPLATE = "{scheme}://{domain}/{path}"

View File

@@ -5,4 +5,3 @@
#
# Copyright (c) 2021-present Kaleidos Ventures SL
default_app_config = "taiga.events.apps.EventsAppConfig"

View File

@@ -8,7 +8,7 @@
import collections
from django.db import connection
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from django.conf import settings

View File

@@ -49,7 +49,7 @@ class SessionIDMiddleware(object):
def process_request(self, request):
global _local
session_id = request.META.get("HTTP_X_SESSION_ID", None)
session_id = request.headers.get("x-session-id", None)
_local.session_id = session_id
request.session_id = session_id

View File

@@ -10,7 +10,7 @@ import uuid
import gzip
from django.utils.decorators import method_decorator
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from django.db.transaction import atomic
from django.db.models import signals
from django.conf import settings
@@ -66,7 +66,7 @@ class ProjectExporterViewSet(mixins.ImportThrottlingPolicyMixin, GenericViewSet)
if dump_format == "gzip":
path = "exports/{}/{}-{}.json.gz".format(project.pk, project.slug, uuid.uuid4().hex)
with default_storage.open(path, mode="wb") as outfile:
services.render_project(project, gzip.GzipFile(fileobj=outfile))
services.render_project(project, gzip.GzipFile(fileobj=outfile, mode="wb"))
else:
path = "exports/{}/{}-{}.json".format(project.pk, project.slug, uuid.uuid4().hex)
with default_storage.open(path, mode="wb") as outfile:

View File

@@ -17,7 +17,7 @@ from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist
from django.db import utils
from django.template.defaultfilters import slugify
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from taiga.projects.history.services import make_key_from_model_object, take_snapshot
from taiga.projects.models import Membership

View File

@@ -6,7 +6,7 @@
# Copyright (c) 2021-present Kaleidos Ventures SL
from django.apps import apps
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
def has_available_slot_for_new_project(owner, is_private, member_emails):

View File

@@ -15,7 +15,7 @@ from django.core.files.base import ContentFile
from django.utils import timezone
from django.conf import settings
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from taiga.base.mails import mail_builder
from taiga.base.utils import json
@@ -36,7 +36,7 @@ def dump_project(self, user, project, dump_format):
if dump_format == "gzip":
path = "exports/{}/{}-{}.json.gz".format(project.pk, project.slug, self.request.id)
with default_storage.open(path, mode="wb") as outfile:
services.render_project(project, gzip.GzipFile(fileobj=outfile))
services.render_project(project, gzip.GzipFile(fileobj=outfile, mode="wb"))
else:
path = "exports/{}/{}-{}.json".format(project.pk, project.slug, self.request.id)
with default_storage.open(path, mode="wb") as outfile:

View File

@@ -10,7 +10,7 @@ import copy
from django.core.files.base import ContentFile
from django.core.exceptions import ObjectDoesNotExist
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from django.contrib.contenttypes.models import ContentType
from taiga.base.api import serializers

View File

@@ -5,7 +5,7 @@
#
# Copyright (c) 2021-present Kaleidos Ventures SL
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from taiga.base.api import serializers
from taiga.base.api import validators

View File

@@ -17,7 +17,7 @@ from taiga.base.api import ModelCrudViewSet, ModelRetrieveViewSet
from taiga.base.api.utils import get_object_or_404
from taiga.base.decorators import list_route, detail_route
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
class Application(ModelRetrieveViewSet):

View File

@@ -15,10 +15,10 @@ class Token(BaseAuthentication):
auth_rx = re.compile(r"^Application (.+)$")
def authenticate(self, request):
if "HTTP_AUTHORIZATION" not in request.META:
if "authorization" not in request.headers:
return None
token_rx_match = self.auth_rx.search(request.META["HTTP_AUTHORIZATION"])
token_rx_match = self.auth_rx.search(request.headers["authorization"])
if not token_rx_match:
return None

View File

@@ -8,7 +8,7 @@
from django.conf import settings
from django.core import signing
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
import uuid

View File

@@ -11,7 +11,7 @@ from taiga.base.fields import Field
from . import models
from . import services
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
class ApplicationSerializer(serializers.LightSerializer):

View File

@@ -9,7 +9,7 @@ from taiga.base import exceptions as exc
from taiga.base.api.utils import get_object_or_404
from django.apps import apps
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
def get_user_for_application_token(token:str) -> object:

View File

@@ -5,4 +5,3 @@
#
# Copyright (c) 2021-present Kaleidos Ventures SL
default_app_config = "taiga.feedback.apps.FeedbackAppConfig"

View File

@@ -33,9 +33,9 @@ class FeedbackViewSet(viewsets.ViewSet):
self.object = validator.save(force_insert=True)
extra = {
"HTTP_HOST": request.META.get("HTTP_HOST", None),
"HTTP_REFERER": request.META.get("HTTP_REFERER", None),
"HTTP_USER_AGENT": request.META.get("HTTP_USER_AGENT", None),
"HTTP_HOST": request.headers.get("host", None),
"HTTP_REFERER": request.headers.get("referer", None),
"HTTP_USER_AGENT": request.headers.get("user-agent", None),
}
services.send_feedback(self.object, extra, reply_to=[request.user.email])

View File

@@ -8,7 +8,7 @@
from django.apps import AppConfig
from django.apps import apps
from django.conf import settings
from django.conf.urls import include, url
from django.urls import include, path
class FeedbackAppConfig(AppConfig):
@@ -19,4 +19,4 @@ class FeedbackAppConfig(AppConfig):
if settings.FEEDBACK_ENABLED:
from taiga.urls import urlpatterns
from .routers import router
urlpatterns.append(url(r'^api/v1/', include(router.urls)))
urlpatterns.append(path('api/v1/', include(router.urls)))

View File

@@ -6,7 +6,7 @@
# Copyright (c) 2021-present Kaleidos Ventures SL
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
class FeedbackEntry(models.Model):

View File

@@ -5,7 +5,7 @@
#
# Copyright (c) 2021-present Kaleidos Ventures SL
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from taiga.base import exceptions as exc
from taiga.base import response

View File

@@ -5,7 +5,7 @@
#
# Copyright (c) 2021-present Kaleidos Ventures SL
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from django.conf import settings
from taiga.base import exceptions as exc
@@ -62,4 +62,4 @@ class BitBucketViewSet(BaseWebhookApiViewSet):
return project_secret == secret_key
def _get_event_name(self, request):
return request.META.get('HTTP_X_EVENT_KEY', None)
return request.headers.get('x-event-key', None)

View File

@@ -19,7 +19,7 @@ class BaseBitBucketEventHook():
if wiki_text is None:
wiki_text = ""
template = "\g<1>[BitBucket#\g<2>]({}/issues/\g<2>)\g<3>".format(project_url)
template = fr"\g<1>[BitBucket#\g<2>]({project_url}/issues/\g<2>)\g<3>"
return re.sub(r"(\s|^)#(\d+)(\s|$)", template, wiki_text, 0, re.M)
@@ -49,7 +49,7 @@ class IssueCommentEventHook(BaseBitBucketEventHook, BaseIssueCommentEventHook):
project_url = self.payload.get('repository', {}).get('links', {}).get('html', {}).get('href', None)
issue_url = self.payload.get('issue', {}).get('links', {}).get('html', {}).get('href', None)
comment_id = self.payload.get('comment', {}).get('id', None)
comment_url = "{}#comment-{}".format(issue_url, comment_id)
comment_url = f"{issue_url}#comment-{comment_id}"
return {
"number": self.payload.get('issue', {}).get('id', None),
'url': issue_url,

View File

@@ -7,7 +7,7 @@
import re
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from django.contrib.auth import get_user_model
from taiga.projects.models import IssueStatus, TaskStatus, UserStoryStatus, EpicStatus, ProjectModulesConfig
from taiga.projects.epics.models import Epic

View File

@@ -5,5 +5,4 @@
#
# Copyright (c) 2021-present Kaleidos Ventures SL
default_app_config = "taiga.hooks.github.apps.GithubHooksAppConfig"

View File

@@ -21,7 +21,7 @@ class GitHubViewSet(BaseWebhookApiViewSet):
}
def _validate_signature(self, project, request):
x_hub_signature = request.META.get("HTTP_X_HUB_SIGNATURE", None)
x_hub_signature = request.headers.get("x-hub-signature", None)
if not x_hub_signature:
return False
@@ -41,4 +41,4 @@ class GitHubViewSet(BaseWebhookApiViewSet):
return hmac.compare_digest(mac.hexdigest(), signature)
def _get_event_name(self, request):
return request.META.get("HTTP_X_GITHUB_EVENT", None)
return request.headers.get("x-github-event", None)

View File

@@ -20,7 +20,7 @@ class BaseGitHubEventHook():
if wiki_text is None:
wiki_text = ""
template = "\g<1>[GitHub#\g<2>]({}/issues/\g<2>)\g<3>".format(project_url)
template = fr"\g<1>[GitHub#\g<2>]({project_url}/issues/\g<2>)\g<3>"
return re.sub(r"(\s|^)#(\d+)(\s|$)", template, wiki_text, 0, re.M)

View File

@@ -5,5 +5,4 @@
#
# Copyright (c) 2021-present Kaleidos Ventures SL
default_app_config = "taiga.hooks.gitlab.apps.GitLabHooksAppConfig"

View File

@@ -21,7 +21,7 @@ class BaseGitLabEventHook():
if wiki_text is None:
wiki_text = ""
template = "\g<1>[GitLab#\g<2>]({}/issues/\g<2>)\g<3>".format(project_url)
template = fr"\g<1>[GitLab#\g<2>]({project_url}/issues/\g<2>)\g<3>"
return re.sub(r"(\s|^)#(\d+)(\s|$)", template, wiki_text, 0, re.M)

View File

@@ -29,7 +29,7 @@ class GogsViewSet(BaseWebhookApiViewSet):
if secret is None:
return False
signature = request.META.get("HTTP_X_GOGS_SIGNATURE", None)
signature = request.headers.get("x-gogs-signature", None)
if not signature: # Old format signature support (before 0.11 version)
payload = self._get_payload(request)
return payload.get('secret', None) == secret

View File

@@ -19,7 +19,7 @@ class BaseGogsEventHook():
if wiki_text is None:
wiki_text = ""
template = "\g<1>[Gogs#\g<2>]({}/issues/\g<2>)\g<3>".format(project_url)
template = fr"\g<1>[Gogs#\g<2>]({project_url}/issues/\g<2>)\g<3>"
return re.sub(r"(\s|^)#(\d+)(\s|$)", template, wiki_text, 0, re.M)

View File

@@ -5,7 +5,7 @@
#
# Copyright (c) 2021-present Kaleidos Ventures SL
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from django.conf import settings
from taiga.base.api import viewsets

View File

@@ -8,7 +8,7 @@
import logging
import sys
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from taiga.base.mails import mail_builder
from taiga.users.models import User

View File

@@ -5,7 +5,7 @@
#
# Copyright (c) 2021-present Kaleidos Ventures SL
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from django.conf import settings
from taiga.base.api import viewsets

View File

@@ -8,7 +8,7 @@
import logging
import sys
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from taiga.base.mails import mail_builder
from taiga.celery import app

View File

@@ -9,6 +9,9 @@ import datetime
from collections import OrderedDict
from django.template.defaultfilters import slugify
from django.utils import timezone
from taiga.importers import services as import_service
from taiga.projects.references.models import recalc_reference_counter
from taiga.projects.models import Project, ProjectTemplate, Points
from taiga.projects.userstories.models import UserStory, RolePoints
@@ -18,8 +21,8 @@ from taiga.projects.epics.models import Epic, RelatedUserStory
from taiga.projects.history.services import take_snapshot
from taiga.timeline.rebuilder import rebuild_timeline
from taiga.timeline.models import Timeline
from .common import JiraImporterCommon
from taiga.importers import services as import_service
class JiraAgileImporter(JiraImporterCommon):
@@ -158,8 +161,8 @@ class JiraAgileImporter(JiraImporterCommon):
estimated_finish=end_date,
)
Milestone.objects.filter(id=milestone.id).update(
created_date=start_datetime or datetime.datetime.now(),
modified_date=start_datetime or datetime.datetime.now(),
created_date=start_datetime or timezone.now(),
modified_date=start_datetime or timezone.now(),
)
return project

View File

@@ -7,7 +7,7 @@
import uuid
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from django.conf import settings
from taiga.base.api import viewsets

View File

@@ -8,7 +8,7 @@
import logging
import sys
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from taiga.base.mails import mail_builder
from taiga.users.models import User

View File

@@ -5,7 +5,7 @@
#
# Copyright (c) 2021-present Kaleidos Ventures SL
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from django.conf import settings
from taiga.base.api import viewsets

View File

@@ -8,7 +8,7 @@
import logging
import sys
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from taiga.base.mails import mail_builder
from taiga.users.models import User

View File

@@ -7,7 +7,7 @@
import uuid
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from django.conf import settings
from taiga.base.api import viewsets

View File

@@ -5,7 +5,7 @@
#
# Copyright (c) 2021-present Kaleidos Ventures SL
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from requests_oauthlib import OAuth1Session, OAuth1
from django.conf import settings

View File

@@ -8,7 +8,7 @@
import logging
import sys
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from taiga.base.mails import mail_builder
from taiga.users.models import User

View File

@@ -11,39 +11,32 @@
import re
import markdown
from markdown import inlinepatterns
from markdown.util import AtomicString
from xml.etree import ElementTree as etree
# We can't re-use the built-in AutolinkPattern because we need to add protocols
# to links without them.
class AutolinkPattern(markdown.inlinepatterns.Pattern):
class AutolinkPattern(inlinepatterns.Pattern):
def handleMatch(self, m):
el = markdown.util.etree.Element("a")
el = etree.Element("a")
href = m.group(2)
if not re.match('^(ftp|https?)://', href, flags=re.IGNORECASE):
href = 'http://%s' % href
el.set('href', self.unescape(href))
el.text = markdown.util.AtomicString(m.group(2))
el.text = AtomicString(m.group(2))
return el
class AutolinkExtension(markdown.Extension):
"""An extension that turns all URLs into links.
This is based on the web-only URL regex by John Gruber that's listed on
http://daringfireball.net/2010/07/improved_regex_for_matching_urls (which is
in the public domain).
This regex seems to line up pretty closely with GitHub's URL matching. There
are only two cases I've found where they differ. In both cases, I've
modified the regex slightly to bring it in line with GitHub's parsing:
* GitHub accepts FTP-protocol URLs.
* GitHub only accepts URLs with protocols or "www.", whereas Gruber's regex
accepts things like "foo.com/bar".
"""
"""An extension that turns all URLs into links."""
def extendMarkdown(self, md):
url_re = r'(?i)\b((?:(?:ftp|https?)://|www\d{0,3}[.])([^\s<>]+))'
url_re = '(%s)' % '|'.join([
r'<(?:([Ff][Tt][Pp])|([Hh][Tt])[Tt][Pp][Ss]?)://[^>]*>',
r'\b(?:([Ff][Tt][Pp])|([Hh][Tt])[Tt][Pp][Ss]?)://[^)<>\s]+[^.,)<>\s]',
r'\bwww\.[^)<>\s]+[^.,)<>\s]',
])
autolink = AutolinkPattern(url_re, md)
md.inlinePatterns.add('gfm-autolink', autolink, '_end')
md.inlinePatterns.register(autolink, 'autolink', 70)

View File

@@ -9,23 +9,26 @@
# for details. All rights reserved. Use of this source code is governed by a
# BSD-style license that can be found in the LICENSE file.
import markdown
from markdown import Extension
from markdown.inlinepatterns import Pattern
from markdown.util import AtomicString
from xml.etree import ElementTree as etree
# We can't re-use the built-in AutomailPattern because we need to add mailto:.
# We also don't care about HTML-encoding the email.
class AutomailPattern(markdown.inlinepatterns.Pattern):
class AutomailPattern(Pattern):
def handleMatch(self, m):
el = markdown.util.etree.Element("a")
el = etree.Element("a")
el.set('href', self.unescape('mailto:' + m.group(2)))
el.text = markdown.util.AtomicString(m.group(2))
el.text = AtomicString(m.group(2))
return el
class AutomailExtension(markdown.Extension):
class AutomailExtension(Extension):
"""An extension that turns all email addresses into links."""
def extendMarkdown(self, md):
mail_re = r'\b(?i)([a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]+)\b'
mail_re = r'\b([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]+)\b'
automail = AutomailPattern(mail_re, md)
md.inlinePatterns.add('gfm-automail', automail, '_end')
md.inlinePatterns.register(automail, 'automail', 70)

View File

@@ -160,9 +160,9 @@ class EmojifyExtension(Extension):
def extendMarkdown(self, md):
md.registerExtension(self)
md.preprocessors.add('emojify',
EmojifyPreprocessor(md),
'_end')
md.preprocessors.register(EmojifyPreprocessor(md),
'emojify',
70)
class EmojifyPreprocessor(Preprocessor):

View File

@@ -32,7 +32,9 @@ from django.contrib.auth import get_user_model
from markdown.extensions import Extension
from markdown.inlinepatterns import Pattern
from markdown.util import etree, AtomicString
from markdown.util import AtomicString
from xml.etree import ElementTree as etree
from taiga.front.templatetags.functions import resolve
class MentionsExtension(Extension):
@@ -43,10 +45,10 @@ class MentionsExtension(Extension):
super().__init__(*args, **kwargs)
def extendMarkdown(self, md):
MENTION_RE = r"(@)([\w.-]+)"
MENTION_RE = r"\B(@)([\w.-]+)\b"
mentionsPattern = MentionsPattern(MENTION_RE, project=self.project)
mentionsPattern.md = md
md.inlinePatterns.add("mentions", mentionsPattern, "_end")
md.inlinePatterns.register(mentionsPattern, "mentions", 80)
class MentionsPattern(Pattern):
@@ -66,7 +68,7 @@ class MentionsPattern(Pattern):
except get_user_model().DoesNotExist:
return "@{}".format(username)
url = "/profile/{}".format(username)
url = resolve("user", username)
link_text = "@{}".format(username)

View File

@@ -31,7 +31,7 @@
from markdown.extensions import Extension
from markdown.inlinepatterns import Pattern
from markdown.util import etree
from xml.etree import ElementTree as etree
from taiga.projects.references.services import get_instance_by_ref
from taiga.front.templatetags.functions import resolve
@@ -46,7 +46,7 @@ class TaigaReferencesExtension(Extension):
TAIGA_REFERENCE_RE = r'(?<=^|(?<=[^a-zA-Z0-9-\[]))#(\d+)'
referencesPattern = TaigaReferencesPattern(TAIGA_REFERENCE_RE, self.project)
referencesPattern.md = md
md.inlinePatterns.add('taiga-references', referencesPattern, '_begin')
md.inlinePatterns.register(referencesPattern, 'taiga-references', 65)
class TaigaReferencesPattern(Pattern):

View File

@@ -21,9 +21,9 @@ class RefreshAttachmentExtension(markdown.Extension):
super().__init__(*args, **kwargs)
def extendMarkdown(self, md):
md.treeprocessors.add("refresh_attachment",
RefreshAttachmentTreeprocessor(md, project=self.project),
"<prettify")
md.treeprocessors.register(RefreshAttachmentTreeprocessor(md, project=self.project),
"refresh_attachment",
20)
class RefreshAttachmentTreeprocessor(Treeprocessor):

View File

@@ -35,5 +35,5 @@ class SemiSaneListExtension(markdown.Extension):
"""
def extendMarkdown(self, md):
md.parser.blockprocessors['olist'] = SemiSaneOListProcessor(md.parser)
md.parser.blockprocessors['ulist'] = SemiSaneUListProcessor(md.parser)
md.parser.blockprocessors.register(SemiSaneOListProcessor(md.parser), 'olist', 39)
md.parser.blockprocessors.register(SemiSaneUListProcessor(md.parser), 'ulist', 29)

View File

@@ -22,4 +22,4 @@ class StrikethroughExtension(markdown.Extension):
def extendMarkdown(self, md):
pattern = markdown.inlinepatterns.SimpleTagPattern(STRIKE_RE, 'del')
md.inlinePatterns.add('gfm-strikethrough', pattern, '_end')
md.inlinePatterns.register(pattern, 'strikethrough', 70)

View File

@@ -16,9 +16,9 @@ from taiga.front.templatetags.functions import resolve
class TargetBlankLinkExtension(markdown.Extension):
"""An extension that add target="_blank" to all external links."""
def extendMarkdown(self, md):
md.treeprocessors.add("target_blank_links",
TargetBlankLinksTreeprocessor(md),
"<prettify")
md.treeprocessors.register(TargetBlankLinksTreeprocessor(md),
"target_blank_links",
10)
class TargetBlankLinksTreeprocessor(Treeprocessor):
@@ -28,6 +28,7 @@ class TargetBlankLinksTreeprocessor(Treeprocessor):
for a in links:
href = a.get("href", "")
url = a.get("href", "")
if url.endswith("/"):
url = url[:-1]

View File

@@ -8,8 +8,7 @@
from markdown import Extension
from markdown.inlinepatterns import Pattern
from markdown.treeprocessors import Treeprocessor
from markdown.util import etree
from xml.etree import ElementTree as etree
from taiga.front.templatetags.functions import resolve
from taiga.base.utils.slug import slugify
@@ -24,12 +23,12 @@ class WikiLinkExtension(Extension):
def extendMarkdown(self, md):
WIKILINK_RE = r"\[\[([\w0-9_ -]+)(\|[^\]]+)?\]\]"
md.inlinePatterns.add("wikilinks",
WikiLinksPattern(md, WIKILINK_RE, self.project),
"<not_strong")
md.treeprocessors.add("relative_to_absolute_links",
RelativeLinksTreeprocessor(md, self.project),
"<prettify")
md.inlinePatterns.register(WikiLinksPattern(md, WIKILINK_RE, self.project),
"wikilinks",
20)
md.treeprocessors.register(RelativeLinksTreeprocessor(md, self.project),
"relative_to_absolute_links",
20)
class WikiLinksPattern(Pattern):

View File

@@ -57,6 +57,8 @@ bleach.ALLOWED_ATTRIBUTES["img"] = ["alt", "src"]
bleach.ALLOWED_ATTRIBUTES["input"] = ["type", "checked"]
bleach.ALLOWED_ATTRIBUTES["*"] = ["class", "style", "id"]
ALLOWED_PROTOCOLS = ["http", "https", "ftp", "mailto"]
def _make_extensions_list(project=None):
return ["pymdownx.tasklist",
@@ -126,12 +128,12 @@ def _get_markdown(project):
@cache_by_sha
def render(project, text):
md = _get_markdown(project)
return bleach.clean(md.convert(text))
return bleach.clean(md.convert(text), protocols=ALLOWED_PROTOCOLS)
def render_and_extract(project, text):
md = _get_markdown(project)
result = bleach.clean(md.convert(text))
result = bleach.clean(md.convert(text), protocols=ALLOWED_PROTOCOLS)
return (result, md.extracted_data)

View File

@@ -6,12 +6,12 @@
# Copyright (c) 2021-present Kaleidos Ventures SL
from django_jinja import library
from jinja2 import Markup
from jinja2.utils import markupsafe
from taiga.mdrender.service import render
@library.global_function
def mdrender(project, text) -> str:
if text:
return Markup(render(project, text))
return markupsafe.Markup(render(project, text))
return ""

View File

@@ -5,7 +5,7 @@
#
# Copyright (c) 2021-present Kaleidos Ventures SL
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
ANON_PERMISSIONS = [
('view_project', _('View project')),

View File

@@ -5,4 +5,3 @@
#
# Copyright (c) 2021-present Kaleidos Ventures SL
default_app_config = "taiga.projects.apps.ProjectsAppConfig"

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