Compare commits

...

12 Commits

Author SHA1 Message Date
Raphael Michel
73775786f4 Bump to 4.20.4 2023-09-12 12:17:17 +02:00
Raphael Michel
8898b58be3 Move new settings to _base_settings 2023-09-12 12:17:08 +02:00
Raphael Michel
0376690c5a Bump to 4.20.3 2023-09-12 11:52:45 +02:00
Raphael Michel
2ce5dedfcf [SECURITY] Do not allow Pillow to parse EPS files 2023-09-12 11:52:38 +02:00
Raphael Michel
9612e678fe Bump to 4.20.2.post1 2023-09-11 10:16:57 +02:00
Raphael Michel
213dbbb847 Loosen a version requirement 2023-09-11 10:16:22 +02:00
Raphael Michel
db0786619b Bump to 4.20.2 2023-09-11 10:00:40 +02:00
Raphael Michel
91b45a8707 Fix incorrect handling of boolean configuration flags 2023-09-11 10:00:21 +02:00
Raphael Michel
bff2881573 Bump to 4.20.1 2023-06-12 10:14:13 +02:00
Raphael Michel
dc83b61071 Fix incorrect directory check 2023-06-12 10:14:00 +02:00
Raphael Michel
a0870b1429 Add dependency on pretix-plugin-build to avoid trouble 2023-06-12 09:39:09 +02:00
Raphael Michel
ff68bb9f03 Do not run custom build commands on other packages 2023-06-12 09:39:07 +02:00
18 changed files with 74 additions and 33 deletions

View File

@@ -60,7 +60,7 @@ dependencies = [
"dnspython==2.3.*", "dnspython==2.3.*",
"drf_ujson2==1.7.*", "drf_ujson2==1.7.*",
"geoip2==4.*", "geoip2==4.*",
"importlib_metadata==6.6.*", # Polyfill, we can probably drop this once we require Python 3.10+ "importlib_metadata==6.*", # Polyfill, we can probably drop this once we require Python 3.10+
"isoweek", "isoweek",
"jsonschema", "jsonschema",
"kombu==5.2.*", "kombu==5.2.*",
@@ -77,6 +77,7 @@ dependencies = [
"PyJWT==2.6.*", "PyJWT==2.6.*",
"phonenumberslite==8.13.*", "phonenumberslite==8.13.*",
"Pillow==9.5.*", "Pillow==9.5.*",
"pretix-plugin-build",
"protobuf==4.23.*", "protobuf==4.23.*",
"psycopg2-binary", "psycopg2-binary",
"pycountry", "pycountry",

View File

@@ -29,7 +29,6 @@ sys.path.append(str(Path.cwd() / 'src'))
def _CustomBuild(*args, **kwargs): def _CustomBuild(*args, **kwargs):
print(sys.path)
from pretix._build import CustomBuild from pretix._build import CustomBuild
return CustomBuild(*args, **kwargs) return CustomBuild(*args, **kwargs)

View File

@@ -19,4 +19,4 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see # You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>. # <https://www.gnu.org/licenses/>.
# #
__version__ = "4.20.0" __version__ = "4.20.4"

View File

@@ -249,3 +249,20 @@ PRETIX_PRIMARY_COLOR = '#8E44B3'
# stressful for some cache setups so it is enabled by default and currently can't be enabled through pretix.cfg # stressful for some cache setups so it is enabled by default and currently can't be enabled through pretix.cfg
CACHE_LARGE_VALUES_ALLOWED = False CACHE_LARGE_VALUES_ALLOWED = False
CACHE_LARGE_VALUES_ALIAS = 'default' CACHE_LARGE_VALUES_ALIAS = 'default'
# Allowed file extensions for various places plus matching Pillow formats.
# Never allow EPS, it is full of dangerous bugs.
FILE_UPLOAD_EXTENSIONS_IMAGE = (".png", ".jpg", ".gif", ".jpeg")
PILLOW_FORMATS_IMAGE = ('PNG', 'GIF', 'JPEG')
FILE_UPLOAD_EXTENSIONS_FAVICON = (".ico", ".png", "jpg", ".gif", ".jpeg")
FILE_UPLOAD_EXTENSIONS_QUESTION_IMAGE = (".png", "jpg", ".gif", ".jpeg", ".bmp", ".tif", ".tiff", ".jfif")
PILLOW_FORMATS_QUESTIONS_IMAGE = ('PNG', 'GIF', 'JPEG', 'BMP', 'TIFF')
FILE_UPLOAD_EXTENSIONS_EMAIL_ATTACHMENT = (
".png", ".jpg", ".gif", ".jpeg", ".pdf", ".txt", ".docx", ".gif", ".svg",
".pptx", ".ppt", ".doc", ".xlsx", ".xls", ".jfif", ".heic", ".heif", ".pages",
".bmp", ".tif", ".tiff"
)
FILE_UPLOAD_EXTENSIONS_OTHER = FILE_UPLOAD_EXTENSIONS_EMAIL_ATTACHMENT

View File

@@ -45,6 +45,10 @@ def npm_install():
class CustomBuild(build): class CustomBuild(build):
def run(self): def run(self):
if "src" not in os.listdir(".") or "pretix" not in os.listdir("src"):
# Only run this command on the pretix module, not on other modules even if it's registered globally
# in some cases
return build.run(self)
if "PRETIX_DOCKER_BUILD" in os.environ: if "PRETIX_DOCKER_BUILD" in os.environ:
return # this is a hack to allow calling this file early in our docker build to make use of caching return # this is a hack to allow calling this file early in our docker build to make use of caching
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pretix._build_settings") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pretix._build_settings")
@@ -68,6 +72,10 @@ class CustomBuild(build):
class CustomBuildExt(build_ext): class CustomBuildExt(build_ext):
def run(self): def run(self):
if "src" not in os.listdir(".") or "pretix" not in os.listdir("src"):
# Only run this command on the pretix module, not on other modules even if it's registered globally
# in some cases
return build_ext.run(self)
if "PRETIX_DOCKER_BUILD" in os.environ: if "PRETIX_DOCKER_BUILD" in os.environ:
return # this is a hack to allow calling this file early in our docker build to make use of caching return # this is a hack to allow calling this file early in our docker build to make use of caching
npm_install() npm_install()

View File

@@ -26,6 +26,7 @@ from decimal import Decimal
import django_filters import django_filters
import pytz import pytz
from django.conf import settings
from django.db import transaction from django.db import transaction
from django.db.models import ( from django.db.models import (
Exists, F, OuterRef, Prefetch, Q, Subquery, prefetch_related_objects, Exists, F, OuterRef, Prefetch, Q, Subquery, prefetch_related_objects,
@@ -1181,7 +1182,7 @@ class OrderPositionViewSet(viewsets.ModelViewSet):
ftype, ignored = mimetypes.guess_type(image_file.name) ftype, ignored = mimetypes.guess_type(image_file.name)
extension = os.path.basename(image_file.name).split('.')[-1] extension = os.path.basename(image_file.name).split('.')[-1]
else: else:
img = Image.open(image_file) img = Image.open(image_file, formats=settings.PILLOW_FORMATS_QUESTIONS_IMAGE)
ftype = Image.MIME[img.format] ftype = Image.MIME[img.format]
extensions = { extensions = {
'GIF': 'gif', 'TIFF': 'tif', 'BMP': 'bmp', 'JPEG': 'jpg', 'PNG': 'png' 'GIF': 'gif', 'TIFF': 'tif', 'BMP': 'bmp', 'JPEG': 'jpg', 'PNG': 'png'

View File

@@ -496,14 +496,14 @@ class PortraitImageField(SizeValidationMixin, ExtValidationMixin, forms.FileFiel
file = BytesIO(data['content']) file = BytesIO(data['content'])
try: try:
image = Image.open(file) image = Image.open(file, formats=settings.PILLOW_FORMATS_QUESTIONS_IMAGE)
# verify() must be called immediately after the constructor. # verify() must be called immediately after the constructor.
image.verify() image.verify()
# We want to do more than just verify(), so we need to re-open the file # We want to do more than just verify(), so we need to re-open the file
if hasattr(file, 'seek'): if hasattr(file, 'seek'):
file.seek(0) file.seek(0)
image = Image.open(file) image = Image.open(file, formats=settings.PILLOW_FORMATS_QUESTIONS_IMAGE)
# load() is a potential DoS vector (see Django bug #18520), so we verify the size first # load() is a potential DoS vector (see Django bug #18520), so we verify the size first
if image.width > 10_000 or image.height > 10_000: if image.width > 10_000 or image.height > 10_000:
@@ -562,7 +562,7 @@ class PortraitImageField(SizeValidationMixin, ExtValidationMixin, forms.FileFiel
return f return f
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs.setdefault('ext_whitelist', (".png", ".jpg", ".jpeg", ".jfif", ".tif", ".tiff", ".bmp")) kwargs.setdefault('ext_whitelist', settings.FILE_UPLOAD_EXTENSIONS_QUESTION_IMAGE)
kwargs.setdefault('max_size', settings.FILE_UPLOAD_MAX_SIZE_IMAGE) kwargs.setdefault('max_size', settings.FILE_UPLOAD_MAX_SIZE_IMAGE)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@@ -822,11 +822,7 @@ class BaseQuestionsForm(forms.Form):
help_text=help_text, help_text=help_text,
initial=initial.file if initial else None, initial=initial.file if initial else None,
widget=UploadedFileWidget(position=pos, event=event, answer=initial), widget=UploadedFileWidget(position=pos, event=event, answer=initial),
ext_whitelist=( ext_whitelist=settings.FILE_UPLOAD_EXTENSIONS_OTHER,
".png", ".jpg", ".gif", ".jpeg", ".pdf", ".txt", ".docx", ".gif", ".svg",
".pptx", ".ppt", ".doc", ".xlsx", ".xls", ".jfif", ".heic", ".heif", ".pages",
".bmp", ".tif", ".tiff"
),
max_size=settings.FILE_UPLOAD_MAX_SIZE_OTHER, max_size=settings.FILE_UPLOAD_MAX_SIZE_OTHER,
) )
elif q.type == Question.TYPE_DATE: elif q.type == Question.TYPE_DATE:

View File

@@ -1213,7 +1213,7 @@ class QuestionAnswer(models.Model):
@property @property
def is_image(self): def is_image(self):
return any(self.file.name.lower().endswith(e) for e in ('.jpg', '.png', '.gif', '.tiff', '.bmp', '.jpeg')) return any(self.file.name.lower().endswith(e) for e in settings.FILE_UPLOAD_EXTENSIONS_QUESTION_IMAGE)
@property @property
def file_name(self): def file_name(self):

View File

@@ -521,7 +521,7 @@ def images_from_questions(sender, *args, **kwargs):
else: else:
a = op.answers.filter(question_id=question_id).first() or a a = op.answers.filter(question_id=question_id).first() or a
if not a or not a.file or not any(a.file.name.lower().endswith(e) for e in (".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tif", ".tiff")): if not a or not a.file or not any(a.file.name.lower().endswith(e) for e in settings.FILE_UPLOAD_EXTENSIONS_QUESTION_IMAGE):
return None return None
else: else:
if etag: if etag:

View File

@@ -2710,7 +2710,7 @@ Your {organizer} team"""))
'form_class': ExtFileField, 'form_class': ExtFileField,
'form_kwargs': dict( 'form_kwargs': dict(
label=_('Header image'), label=_('Header image'),
ext_whitelist=(".png", ".jpg", ".gif", ".jpeg"), ext_whitelist=settings.FILE_UPLOAD_EXTENSIONS_IMAGE,
max_size=settings.FILE_UPLOAD_MAX_SIZE_IMAGE, max_size=settings.FILE_UPLOAD_MAX_SIZE_IMAGE,
help_text=_('If you provide a logo image, we will by default not show your event name and date ' help_text=_('If you provide a logo image, we will by default not show your event name and date '
'in the page header. By default, we show your logo with a size of up to 1140x120 pixels. You ' 'in the page header. By default, we show your logo with a size of up to 1140x120 pixels. You '
@@ -2753,7 +2753,7 @@ Your {organizer} team"""))
'form_class': ExtFileField, 'form_class': ExtFileField,
'form_kwargs': dict( 'form_kwargs': dict(
label=_('Header image'), label=_('Header image'),
ext_whitelist=(".png", ".jpg", ".gif", ".jpeg"), ext_whitelist=settings.FILE_UPLOAD_EXTENSIONS_IMAGE,
max_size=settings.FILE_UPLOAD_MAX_SIZE_IMAGE, max_size=settings.FILE_UPLOAD_MAX_SIZE_IMAGE,
help_text=_('If you provide a logo image, we will by default not show your organization name ' help_text=_('If you provide a logo image, we will by default not show your organization name '
'in the page header. By default, we show your logo with a size of up to 1140x120 pixels. You ' 'in the page header. By default, we show your logo with a size of up to 1140x120 pixels. You '
@@ -2793,7 +2793,7 @@ Your {organizer} team"""))
'form_class': ExtFileField, 'form_class': ExtFileField,
'form_kwargs': dict( 'form_kwargs': dict(
label=_('Social media image'), label=_('Social media image'),
ext_whitelist=(".png", ".jpg", ".gif", ".jpeg"), ext_whitelist=settings.FILE_UPLOAD_EXTENSIONS_IMAGE,
max_size=settings.FILE_UPLOAD_MAX_SIZE_IMAGE, max_size=settings.FILE_UPLOAD_MAX_SIZE_IMAGE,
help_text=_('This picture will be used as a preview if you post links to your ticket shop on social media. ' help_text=_('This picture will be used as a preview if you post links to your ticket shop on social media. '
'Facebook advises to use a picture size of 1200 x 630 pixels, however some platforms like ' 'Facebook advises to use a picture size of 1200 x 630 pixels, however some platforms like '
@@ -2814,7 +2814,7 @@ Your {organizer} team"""))
'form_class': ExtFileField, 'form_class': ExtFileField,
'form_kwargs': dict( 'form_kwargs': dict(
label=_('Logo image'), label=_('Logo image'),
ext_whitelist=(".png", ".jpg", ".gif", ".jpeg"), ext_whitelist=settings.FILE_UPLOAD_EXTENSIONS_IMAGE,
required=False, required=False,
max_size=settings.FILE_UPLOAD_MAX_SIZE_IMAGE, max_size=settings.FILE_UPLOAD_MAX_SIZE_IMAGE,
help_text=_('We will show your logo with a maximal height and width of 2.5 cm.') help_text=_('We will show your logo with a maximal height and width of 2.5 cm.')

View File

@@ -127,7 +127,7 @@ class ClearableBasenameFileInput(forms.ClearableFileInput):
@property @property
def is_img(self): def is_img(self):
return any(self.file.name.lower().endswith(e) for e in ('.jpg', '.jpeg', '.png', '.gif')) return any(self.file.name.lower().endswith(e) for e in settings.FILE_UPLOAD_EXTENSIONS_IMAGE)
def __str__(self): def __str__(self):
if hasattr(self.file, 'display_name'): if hasattr(self.file, 'display_name'):

View File

@@ -416,7 +416,7 @@ class OrganizerSettingsForm(SettingsForm):
organizer_logo_image = ExtFileField( organizer_logo_image = ExtFileField(
label=_('Header image'), label=_('Header image'),
ext_whitelist=(".png", ".jpg", ".gif", ".jpeg"), ext_whitelist=settings.FILE_UPLOAD_EXTENSIONS_IMAGE,
max_size=settings.FILE_UPLOAD_MAX_SIZE_IMAGE, max_size=settings.FILE_UPLOAD_MAX_SIZE_IMAGE,
required=False, required=False,
help_text=_('If you provide a logo image, we will by default not show your organization name ' help_text=_('If you provide a logo image, we will by default not show your organization name '
@@ -426,7 +426,7 @@ class OrganizerSettingsForm(SettingsForm):
) )
favicon = ExtFileField( favicon = ExtFileField(
label=_('Favicon'), label=_('Favicon'),
ext_whitelist=(".ico", ".png", ".jpg", ".gif", ".jpeg"), ext_whitelist=settings.FILE_UPLOAD_EXTENSIONS_FAVICON,
required=False, required=False,
max_size=settings.FILE_UPLOAD_MAX_SIZE_FAVICON, max_size=settings.FILE_UPLOAD_MAX_SIZE_FAVICON,
help_text=_('If you provide a favicon, we will show it instead of the default pretix icon. ' help_text=_('If you provide a favicon, we will show it instead of the default pretix icon. '

View File

@@ -22,6 +22,7 @@
import logging import logging
from io import BytesIO from io import BytesIO
from django.conf import settings
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from PIL.Image import MAX_IMAGE_PIXELS, DecompressionBombError from PIL.Image import MAX_IMAGE_PIXELS, DecompressionBombError
@@ -51,7 +52,7 @@ def validate_uploaded_file_for_valid_image(f):
try: try:
try: try:
image = Image.open(file) image = Image.open(file, formats=settings.PILLOW_FORMATS_QUESTIONS_IMAGE)
# verify() must be called immediately after the constructor. # verify() must be called immediately after the constructor.
image.verify() image.verify()
except DecompressionBombError: except DecompressionBombError:

View File

@@ -21,6 +21,8 @@
# #
from datetime import datetime from datetime import datetime
from PIL import Image
def monkeypatch_vobject_performance(): def monkeypatch_vobject_performance():
""" """
@@ -52,5 +54,19 @@ def monkeypatch_vobject_performance():
icalendar.tzinfo_eq = new_tzinfo_eq icalendar.tzinfo_eq = new_tzinfo_eq
def monkeypatch_pillow_safer():
"""
Pillow supports many file formats, among them EPS. For EPS, Pillow loads GhostScript whenever GhostScript
is installed (cannot officially be disabled). However, GhostScript is known for regular security vulnerabilities.
We have no use of reading EPS files and usually prevent this by using `Image.open(…, formats=[…])` to disable EPS
support explicitly. However, we are worried about our dependencies like reportlab using `Image.open` without the
`formats=` parameter. Therefore, as a defense in depth approach, we monkeypatch EPS support away by modifying the
internal image format registry of Pillow.
"""
if "EPS" in Image.ID:
Image.ID.remove("EPS")
def monkeypatch_all_at_ready(): def monkeypatch_all_at_ready():
monkeypatch_vobject_performance() monkeypatch_vobject_performance()
monkeypatch_pillow_safer()

View File

@@ -20,8 +20,9 @@
# <https://www.gnu.org/licenses/>. # <https://www.gnu.org/licenses/>.
# #
from arabic_reshaper import ArabicReshaper from arabic_reshaper import ArabicReshaper
from django.conf import settings
from django.utils.functional import SimpleLazyObject from django.utils.functional import SimpleLazyObject
from PIL.Image import Resampling from PIL import Image
from reportlab.lib.utils import ImageReader from reportlab.lib.utils import ImageReader
@@ -33,7 +34,7 @@ class ThumbnailingImageReader(ImageReader):
height = width * self._image.size[1] / self._image.size[0] height = width * self._image.size[1] / self._image.size[0]
self._image.thumbnail( self._image.thumbnail(
size=(int(width * dpi / 72), int(height * dpi / 72)), size=(int(width * dpi / 72), int(height * dpi / 72)),
resample=Resampling.BICUBIC resample=Image.Resampling.BICUBIC
) )
self._data = None self._data = None
return width, height return width, height
@@ -44,6 +45,9 @@ class ThumbnailingImageReader(ImageReader):
# (smaller) size of the modified image. # (smaller) size of the modified image.
return None return None
def _read_image(self, fp):
return Image.open(fp, formats=settings.PILLOW_FORMATS_IMAGE)
reshaper = SimpleLazyObject(lambda: ArabicReshaper(configuration={ reshaper = SimpleLazyObject(lambda: ArabicReshaper(configuration={
'delete_harakat': True, 'delete_harakat': True,

View File

@@ -23,6 +23,7 @@ import hashlib
import math import math
from io import BytesIO from io import BytesIO
from django.conf import settings
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from django.core.files.storage import default_storage from django.core.files.storage import default_storage
from PIL import Image, ImageOps, ImageSequence from PIL import Image, ImageOps, ImageSequence
@@ -165,7 +166,7 @@ def resize_image(image, size):
def create_thumbnail(sourcename, size): def create_thumbnail(sourcename, size):
source = default_storage.open(sourcename) source = default_storage.open(sourcename)
image = Image.open(BytesIO(source.read())) image = Image.open(BytesIO(source.read()), formats=settings.PILLOW_FORMATS_QUESTIONS_IMAGE)
try: try:
image.load() image.load()
except: except:

View File

@@ -76,11 +76,7 @@ class BaseMailForm(FormPlaceholderMixin, forms.Form):
attachment = CachedFileField( attachment = CachedFileField(
label=_("Attachment"), label=_("Attachment"),
required=False, required=False,
ext_whitelist=( ext_whitelist=settings.FILE_UPLOAD_EXTENSIONS_EMAIL_ATTACHMENT,
".png", ".jpg", ".gif", ".jpeg", ".pdf", ".txt", ".docx", ".gif", ".svg",
".pptx", ".ppt", ".doc", ".xlsx", ".xls", ".jfif", ".heic", ".heif", ".pages",
".bmp", ".tif", ".tiff"
),
help_text=_('Sending an attachment increases the chance of your email not arriving or being sorted into spam folders. We recommend only using PDFs ' help_text=_('Sending an attachment increases the chance of your email not arriving or being sorted into spam folders. We recommend only using PDFs '
'of no more than 2 MB in size.'), 'of no more than 2 MB in size.'),
max_size=settings.FILE_UPLOAD_MAX_SIZE_EMAIL_ATTACHMENT max_size=settings.FILE_UPLOAD_MAX_SIZE_EMAIL_ATTACHMENT

View File

@@ -176,13 +176,13 @@ if SITE_URL.endswith('/'):
CSRF_TRUSTED_ORIGINS = [urlparse(SITE_URL).hostname] CSRF_TRUSTED_ORIGINS = [urlparse(SITE_URL).hostname]
TRUST_X_FORWARDED_FOR = config.get('pretix', 'trust_x_forwarded_for', fallback=False) TRUST_X_FORWARDED_FOR = config.getboolean('pretix', 'trust_x_forwarded_for', fallback=False)
USE_X_FORWARDED_HOST = config.get('pretix', 'trust_x_forwarded_host', fallback=False) USE_X_FORWARDED_HOST = config.getboolean('pretix', 'trust_x_forwarded_host', fallback=False)
REQUEST_ID_HEADER = config.get('pretix', 'request_id_header', fallback=False) REQUEST_ID_HEADER = config.get('pretix', 'request_id_header', fallback=False)
if config.get('pretix', 'trust_x_forwarded_proto', fallback=False): if config.getboolean('pretix', 'trust_x_forwarded_proto', fallback=False):
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
PRETIX_PLUGINS_DEFAULT = config.get('pretix', 'plugins_default', PRETIX_PLUGINS_DEFAULT = config.get('pretix', 'plugins_default',
@@ -694,4 +694,5 @@ FILE_UPLOAD_MAX_SIZE_EMAIL_ATTACHMENT = 1024 * 1024 * config.getint("pretix_file
FILE_UPLOAD_MAX_SIZE_EMAIL_AUTO_ATTACHMENT = 1024 * 1024 * config.getint("pretix_file_upload", "max_size_email_auto_attachment", fallback=1) FILE_UPLOAD_MAX_SIZE_EMAIL_AUTO_ATTACHMENT = 1024 * 1024 * config.getint("pretix_file_upload", "max_size_email_auto_attachment", fallback=1)
FILE_UPLOAD_MAX_SIZE_OTHER = 1024 * 1024 * config.getint("pretix_file_upload", "max_size_other", fallback=10) FILE_UPLOAD_MAX_SIZE_OTHER = 1024 * 1024 * config.getint("pretix_file_upload", "max_size_other", fallback=10)
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' # sadly. we would prefer BigInt, and should use it for all new models but the migration will be hard DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' # sadly. we would prefer BigInt, and should use it for all new models but the migration will be hard