From 7f4b9d826b34a00044b3637923a07bb047e1da11 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Sat, 15 Nov 2025 22:04:23 +0100 Subject: [PATCH 1/2] Performance hack --- i18nfield/forms.py | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/i18nfield/forms.py b/i18nfield/forms.py index b5d604d..b02cf58 100644 --- a/i18nfield/forms.py +++ b/i18nfield/forms.py @@ -193,7 +193,7 @@ def clean(self, value) -> LazyI18nString: def __init__(self, *args, **kwargs): fields = [] - defaults = { + self.__defaults = { 'widget': self.widget, 'max_length': kwargs.pop('max_length', None), } @@ -204,17 +204,40 @@ def __init__(self, *args, **kwargs): kwargs['widget'] = kwargs['widget']( locales=self.locales, field=self, **kwargs.pop('widget_kwargs', {}) ) - defaults.update(**kwargs) - for lngcode in self.locales: - defaults['label'] = '%s (%s)' % (defaults.get('label'), lngcode) - field = forms.CharField(**defaults) - field.locale = lngcode - fields.append(field) + self.__defaults.update(**kwargs) + + # Performance hack: In theory, we'd need to generate the subfields in __init__. However, that uses many + # useless CPU cycles at form definition time, since it causes a __deepcopy__ on the widget, which is slow. + # If your codebase contains hundreds of I18nFormField instances, this makes a difference on process startup. + # We do it in __deepcopy__ instead, which is executed when the field is first bound to a real form. super().__init__( fields=fields, require_all_fields=False, *args, **kwargs ) self.require_all_fields = require_all_fields + def __deepcopy__(self, memo): + # Performance hack: In theory, we'd need to generate the subfields in __init__. However, that uses many + # useless CPU cycles at form definition time, since it causes a __deepcopy__ on the widget, which is slow. + # If your codebase contains hundreds of I18nFormField instances, this makes a difference on process startup. + # We do it in __deepcopy__ instead, which is executed when the field is first bound to a real form. + result = super().__deepcopy__(memo) + fields = [] + for lngcode in result.locales: + d = {**self.__defaults, 'label': '%s (%s)' % (self.__defaults.get('label'), lngcode)} + field = forms.CharField(**d) + field.locale = lngcode + field.error_messages.setdefault("incomplete", self.error_messages["incomplete"]) + if self.disabled: + field.disabled = True + if self.require_all_fields: + # Set 'required' to False on the individual fields, because the + # required validation will be handled by MultiValueField, not + # by those individual fields. + field.required = False + fields.append(field) + result.fields = tuple(fields) + return result + def has_changed(self, initial, data): if self.disabled: return False From f13ff9e00a59241dc246b983eb7891e623151b3e Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Thu, 27 Nov 2025 11:17:15 +0100 Subject: [PATCH 2/2] New approach --- i18nfield/forms.py | 49 +++++++++++++++++++++------------------------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/i18nfield/forms.py b/i18nfield/forms.py index b02cf58..0df817d 100644 --- a/i18nfield/forms.py +++ b/i18nfield/forms.py @@ -9,6 +9,7 @@ ) from django.forms.forms import DeclarativeFieldsMetaclass from django.forms.models import ModelFormMetaclass +from django.utils.functional import lazy from django.utils.html import escape from django.utils.safestring import mark_safe @@ -192,8 +193,7 @@ def clean(self, value) -> LazyI18nString: return out def __init__(self, *args, **kwargs): - fields = [] - self.__defaults = { + defaults = { 'widget': self.widget, 'max_length': kwargs.pop('max_length', None), } @@ -204,40 +204,35 @@ def __init__(self, *args, **kwargs): kwargs['widget'] = kwargs['widget']( locales=self.locales, field=self, **kwargs.pop('widget_kwargs', {}) ) - self.__defaults.update(**kwargs) + defaults.update(**kwargs) # Performance hack: In theory, we'd need to generate the subfields in __init__. However, that uses many # useless CPU cycles at form definition time, since it causes a __deepcopy__ on the widget, which is slow. # If your codebase contains hundreds of I18nFormField instances, this makes a difference on process startup. # We do it in __deepcopy__ instead, which is executed when the field is first bound to a real form. + def generate_fields(): + fields = [] + for lngcode in self.locales: + d = {**defaults, 'label': '%s (%s)' % (defaults.get('label'), lngcode)} + field = forms.CharField(**d) + field.locale = lngcode + field.error_messages.setdefault("incomplete", self.error_messages["incomplete"]) + if self.disabled: + field.disabled = True + if self.require_all_fields: + # Set 'required' to False on the individual fields, because the + # required validation will be handled by MultiValueField, not + # by those individual fields. + field.required = False + fields.append(field) + return fields + super().__init__( - fields=fields, require_all_fields=False, *args, **kwargs + fields=[], require_all_fields=False, *args, **kwargs ) + self.fields = lazy(generate_fields, list)() self.require_all_fields = require_all_fields - def __deepcopy__(self, memo): - # Performance hack: In theory, we'd need to generate the subfields in __init__. However, that uses many - # useless CPU cycles at form definition time, since it causes a __deepcopy__ on the widget, which is slow. - # If your codebase contains hundreds of I18nFormField instances, this makes a difference on process startup. - # We do it in __deepcopy__ instead, which is executed when the field is first bound to a real form. - result = super().__deepcopy__(memo) - fields = [] - for lngcode in result.locales: - d = {**self.__defaults, 'label': '%s (%s)' % (self.__defaults.get('label'), lngcode)} - field = forms.CharField(**d) - field.locale = lngcode - field.error_messages.setdefault("incomplete", self.error_messages["incomplete"]) - if self.disabled: - field.disabled = True - if self.require_all_fields: - # Set 'required' to False on the individual fields, because the - # required validation will be handled by MultiValueField, not - # by those individual fields. - field.required = False - fields.append(field) - result.fields = tuple(fields) - return result - def has_changed(self, initial, data): if self.disabled: return False