mirror of
				https://github.com/SectorLabs/django-localized-fields.git
				synced 2025-11-03 19:58:56 +03:00 
			
		
		
		
	added new LocalizedCharField, LocalizedTextField and LocalizedFileField fields
This commit is contained in:
		@@ -1,6 +1,7 @@
 | 
			
		||||
from .forms import LocalizedFieldForm, LocalizedFieldWidget
 | 
			
		||||
from .fields import (LocalizedAutoSlugField, LocalizedField,
 | 
			
		||||
                     LocalizedUniqueSlugField)
 | 
			
		||||
                     LocalizedUniqueSlugField, LocalizedCharField,
 | 
			
		||||
                     LocalizedTextField, LocalizedFileField)
 | 
			
		||||
from .localized_value import LocalizedValue
 | 
			
		||||
from .mixins import AtomicSlugRetryMixin
 | 
			
		||||
from .models import LocalizedModel
 | 
			
		||||
@@ -14,6 +15,9 @@ __all__ = [
 | 
			
		||||
    'LocalizedAutoSlugField',
 | 
			
		||||
    'LocalizedUniqueSlugField',
 | 
			
		||||
    'LocalizedBleachField',
 | 
			
		||||
    'LocalizedCharField',
 | 
			
		||||
    'LocalizedTextField',
 | 
			
		||||
    'LocalizedFileField',
 | 
			
		||||
    'LocalizedFieldWidget',
 | 
			
		||||
    'LocalizedFieldForm',
 | 
			
		||||
    'AtomicSlugRetryMixin'
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,15 @@
 | 
			
		||||
from django.contrib.admin import ModelAdmin
 | 
			
		||||
 | 
			
		||||
from .fields import LocalizedField
 | 
			
		||||
from .fields import LocalizedField, LocalizedCharField, LocalizedTextField, \
 | 
			
		||||
    LocalizedFileField
 | 
			
		||||
from . import widgets
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
FORMFIELD_FOR_LOCALIZED_FIELDS_DEFAULTS = {
 | 
			
		||||
    LocalizedField: {'widget': widgets.AdminLocalizedFieldWidget},
 | 
			
		||||
    LocalizedCharField: {'widget': widgets.AdminLocalizedCharFieldWidget},
 | 
			
		||||
    LocalizedTextField: {'widget': widgets.AdminLocalizedFieldWidget},
 | 
			
		||||
    LocalizedFileField: {'widget': widgets.AdminLocalizedFileFieldWidget},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,18 @@
 | 
			
		||||
from .localized_field import LocalizedField
 | 
			
		||||
from .localized_autoslug_field import LocalizedAutoSlugField
 | 
			
		||||
from .localized_uniqueslug_field import LocalizedUniqueSlugField
 | 
			
		||||
from .localized_char_field import LocalizedCharField
 | 
			
		||||
from .localized_text_field import LocalizedTextField
 | 
			
		||||
from .localized_file_field import LocalizedFileField
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
__all__ = [
 | 
			
		||||
    'LocalizedField',
 | 
			
		||||
    'LocalizedAutoSlugField',
 | 
			
		||||
    'LocalizedUniqueSlugField',
 | 
			
		||||
    'LocalizedCharField',
 | 
			
		||||
    'LocalizedTextField',
 | 
			
		||||
    'LocalizedFileField'
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										16
									
								
								localized_fields/fields/localized_char_field.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								localized_fields/fields/localized_char_field.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
from ..forms import LocalizedCharFieldForm
 | 
			
		||||
from .localized_field import LocalizedField
 | 
			
		||||
from ..localized_value import LocalizedStingValue
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LocalizedCharField(LocalizedField):
 | 
			
		||||
    attr_class = LocalizedStingValue
 | 
			
		||||
 | 
			
		||||
    def formfield(self, **kwargs):
 | 
			
		||||
        """Gets the form field associated with this field."""
 | 
			
		||||
        defaults = {
 | 
			
		||||
            'form_class': LocalizedCharFieldForm
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        defaults.update(kwargs)
 | 
			
		||||
        return super().formfield(**defaults)
 | 
			
		||||
@@ -187,7 +187,10 @@ class LocalizedField(HStoreField):
 | 
			
		||||
        # are any of the language fiels None/empty?
 | 
			
		||||
        is_all_null = True
 | 
			
		||||
        for lang_code, _ in settings.LANGUAGES:
 | 
			
		||||
            if value.get(lang_code):
 | 
			
		||||
            # NOTE(seroy): use check for None, instead of
 | 
			
		||||
            # `bool(value.get(lang_code))==True` condition, cause in this way
 | 
			
		||||
            # we can not save '' value
 | 
			
		||||
            if value.get(lang_code) is not None:
 | 
			
		||||
                is_all_null = False
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
@@ -215,7 +218,9 @@ class LocalizedField(HStoreField):
 | 
			
		||||
 | 
			
		||||
        primary_lang_val = getattr(value, settings.LANGUAGE_CODE)
 | 
			
		||||
 | 
			
		||||
        if not primary_lang_val:
 | 
			
		||||
        # NOTE(seroy): use check for None, instead of `not primary_lang_val`
 | 
			
		||||
        # condition, cause in this way we can not save '' value
 | 
			
		||||
        if primary_lang_val is None:
 | 
			
		||||
            raise IntegrityError(
 | 
			
		||||
                'null value in column "%s.%s" violates not-null constraint' % (
 | 
			
		||||
                    self.name,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										151
									
								
								localized_fields/fields/localized_file_field.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								localized_fields/fields/localized_file_field.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,151 @@
 | 
			
		||||
import datetime
 | 
			
		||||
import posixpath
 | 
			
		||||
 | 
			
		||||
from django.core.files import File
 | 
			
		||||
from django.db.models.fields.files import FieldFile
 | 
			
		||||
from django.utils import six
 | 
			
		||||
from django.core.files.storage import default_storage
 | 
			
		||||
from django.utils.encoding import force_str, force_text
 | 
			
		||||
 | 
			
		||||
from localized_fields.fields import LocalizedField
 | 
			
		||||
from localized_fields.fields.localized_field import LocalizedValueDescriptor
 | 
			
		||||
from localized_fields.localized_value import LocalizedValue
 | 
			
		||||
 | 
			
		||||
from ..localized_value import LocalizedFileValue
 | 
			
		||||
from ..forms import LocalizedFileFieldForm
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LocalizedFieldFile(FieldFile):
 | 
			
		||||
    def __init__(self, instance, field, name):
 | 
			
		||||
        super(FieldFile, self).__init__(None, name)
 | 
			
		||||
        self.instance = instance
 | 
			
		||||
        self.field = field
 | 
			
		||||
        self.storage = field.storage
 | 
			
		||||
        self._committed = True
 | 
			
		||||
 | 
			
		||||
    def save(self, name, content, lang, save=True):
 | 
			
		||||
        name = self.field.generate_filename(self.instance, name, lang)
 | 
			
		||||
        self.name = self.storage.save(name, content,
 | 
			
		||||
                                      max_length=self.field.max_length)
 | 
			
		||||
        self._committed = True
 | 
			
		||||
 | 
			
		||||
        if save:
 | 
			
		||||
            self.instance.save()
 | 
			
		||||
 | 
			
		||||
    save.alters_data = True
 | 
			
		||||
 | 
			
		||||
    def delete(self, save=True):
 | 
			
		||||
        if not self:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        if hasattr(self, '_file'):
 | 
			
		||||
            self.close()
 | 
			
		||||
            del self.file
 | 
			
		||||
 | 
			
		||||
        self.storage.delete(self.name)
 | 
			
		||||
 | 
			
		||||
        self.name = None
 | 
			
		||||
        self._committed = False
 | 
			
		||||
 | 
			
		||||
        if save:
 | 
			
		||||
            self.instance.save()
 | 
			
		||||
 | 
			
		||||
    delete.alters_data = True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LocalizedFileValueDescriptor(LocalizedValueDescriptor):
 | 
			
		||||
    def __get__(self, instance, cls=None):
 | 
			
		||||
        value = super().__get__(instance, cls)
 | 
			
		||||
        for k, file in value.__dict__.items():
 | 
			
		||||
            if isinstance(file, six.string_types) or file is None:
 | 
			
		||||
                file = self.field.value_class(instance, self.field, file)
 | 
			
		||||
                value.set(k, file)
 | 
			
		||||
 | 
			
		||||
            elif isinstance(file, File) and \
 | 
			
		||||
                    not isinstance(file, LocalizedFieldFile):
 | 
			
		||||
                file_copy = self.field.value_class(instance, self.field,
 | 
			
		||||
                                                   file.name)
 | 
			
		||||
                file_copy.file = file
 | 
			
		||||
                file_copy._committed = False
 | 
			
		||||
                value.set(k, file_copy)
 | 
			
		||||
 | 
			
		||||
            elif isinstance(file, LocalizedFieldFile) and \
 | 
			
		||||
                    not hasattr(file, 'field'):
 | 
			
		||||
                file.instance = instance
 | 
			
		||||
                file.field = self.field
 | 
			
		||||
                file.storage = self.field.storage
 | 
			
		||||
 | 
			
		||||
            # Make sure that the instance is correct.
 | 
			
		||||
            elif isinstance(file, LocalizedFieldFile) \
 | 
			
		||||
                    and instance is not file.instance:
 | 
			
		||||
                file.instance = instance
 | 
			
		||||
        return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LocalizedFileField(LocalizedField):
 | 
			
		||||
    descriptor_class = LocalizedFileValueDescriptor
 | 
			
		||||
    attr_class = LocalizedFileValue
 | 
			
		||||
    value_class = LocalizedFieldFile
 | 
			
		||||
 | 
			
		||||
    def __init__(self, verbose_name=None, name=None, upload_to='', storage=None,
 | 
			
		||||
                 **kwargs):
 | 
			
		||||
 | 
			
		||||
        self.storage = storage or default_storage
 | 
			
		||||
        self.upload_to = upload_to
 | 
			
		||||
 | 
			
		||||
        super().__init__(verbose_name, name, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def deconstruct(self):
 | 
			
		||||
        name, path, args, kwargs = super(LocalizedFileField, self).deconstruct()
 | 
			
		||||
        kwargs['upload_to'] = self.upload_to
 | 
			
		||||
        if self.storage is not default_storage:
 | 
			
		||||
            kwargs['storage'] = self.storage
 | 
			
		||||
        return name, path, args, kwargs
 | 
			
		||||
 | 
			
		||||
    def get_prep_value(self, value):
 | 
			
		||||
        """Returns field's value prepared for saving into a database."""
 | 
			
		||||
 | 
			
		||||
        if isinstance(value, LocalizedValue):
 | 
			
		||||
            prep_value = LocalizedValue()
 | 
			
		||||
            for k, v in value.__dict__.items():
 | 
			
		||||
                if v is None:
 | 
			
		||||
                    prep_value.set(k, '')
 | 
			
		||||
                else:
 | 
			
		||||
                    # Need to convert File objects provided via a form to
 | 
			
		||||
                    # unicode for database insertion
 | 
			
		||||
                    prep_value.set(k, six.text_type(v))
 | 
			
		||||
            return super().get_prep_value(prep_value)
 | 
			
		||||
        return super().get_prep_value(value)
 | 
			
		||||
 | 
			
		||||
    def pre_save(self, model_instance, add):
 | 
			
		||||
        """Returns field's value just before saving."""
 | 
			
		||||
        value = super().pre_save(model_instance, add)
 | 
			
		||||
        if isinstance(value, LocalizedValue):
 | 
			
		||||
            for lang, file in value.__dict__.items():
 | 
			
		||||
                if file and not file._committed:
 | 
			
		||||
                    file.save(file.name, file, lang, save=False)
 | 
			
		||||
        return value
 | 
			
		||||
 | 
			
		||||
    def generate_filename(self, instance, filename, lang):
 | 
			
		||||
        if callable(self.upload_to):
 | 
			
		||||
            filename = self.upload_to(instance, filename, lang)
 | 
			
		||||
        else:
 | 
			
		||||
            now = datetime.datetime.now()
 | 
			
		||||
            dirname = force_text(now.strftime(force_str(self.upload_to)))
 | 
			
		||||
            dirname = dirname.format(lang=lang)
 | 
			
		||||
            filename = posixpath.join(dirname, filename)
 | 
			
		||||
        return self.storage.generate_filename(filename)
 | 
			
		||||
 | 
			
		||||
    def save_form_data(self, instance, data):
 | 
			
		||||
        if isinstance(data, LocalizedValue):
 | 
			
		||||
            for k, v in data.__dict__.items():
 | 
			
		||||
                if v is not None and not v:
 | 
			
		||||
                    data.set(k, '')
 | 
			
		||||
            setattr(instance, self.attname, data)
 | 
			
		||||
 | 
			
		||||
    def formfield(self, **kwargs):
 | 
			
		||||
        defaults = {'form_class': LocalizedFileFieldForm}
 | 
			
		||||
        if 'initial' in kwargs:
 | 
			
		||||
            defaults['required'] = False
 | 
			
		||||
        defaults.update(kwargs)
 | 
			
		||||
        return super().formfield(**defaults)
 | 
			
		||||
							
								
								
									
										14
									
								
								localized_fields/fields/localized_text_field.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								localized_fields/fields/localized_text_field.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
from ..forms import LocalizedTextFieldForm
 | 
			
		||||
from .localized_char_field import LocalizedCharField
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LocalizedTextField(LocalizedCharField):
 | 
			
		||||
    def formfield(self, **kwargs):
 | 
			
		||||
        """Gets the form field associated with this field."""
 | 
			
		||||
 | 
			
		||||
        defaults = {
 | 
			
		||||
            'form_class': LocalizedTextFieldForm
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        defaults.update(kwargs)
 | 
			
		||||
        return super().formfield(**defaults)
 | 
			
		||||
@@ -2,10 +2,13 @@ from typing import List
 | 
			
		||||
 | 
			
		||||
from django import forms
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.core.exceptions import ValidationError
 | 
			
		||||
from django.forms.widgets import FILE_INPUT_CONTRADICTION
 | 
			
		||||
 | 
			
		||||
from .localized_value import LocalizedValue
 | 
			
		||||
from .widgets import LocalizedFieldWidget
 | 
			
		||||
 | 
			
		||||
from .localized_value import LocalizedValue, LocalizedStingValue, \
 | 
			
		||||
    LocalizedFileValue
 | 
			
		||||
from .widgets import LocalizedFieldWidget, LocalizedCharFieldWidget, \
 | 
			
		||||
    LocalizedFileWidget
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LocalizedFieldForm(forms.MultiValueField):
 | 
			
		||||
@@ -13,6 +16,7 @@ class LocalizedFieldForm(forms.MultiValueField):
 | 
			
		||||
    the field in multiple languages."""
 | 
			
		||||
 | 
			
		||||
    widget = LocalizedFieldWidget
 | 
			
		||||
    field_class = forms.fields.CharField
 | 
			
		||||
    value_class = LocalizedValue
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
@@ -27,7 +31,7 @@ class LocalizedFieldForm(forms.MultiValueField):
 | 
			
		||||
                field_options['required'] = kwargs.get('required', True)
 | 
			
		||||
 | 
			
		||||
            field_options['label'] = lang_code
 | 
			
		||||
            fields.append(forms.fields.CharField(**field_options))
 | 
			
		||||
            fields.append(self.field_class(**field_options))
 | 
			
		||||
 | 
			
		||||
        super(LocalizedFieldForm, self).__init__(
 | 
			
		||||
            fields,
 | 
			
		||||
@@ -57,3 +61,106 @@ class LocalizedFieldForm(forms.MultiValueField):
 | 
			
		||||
            localized_value.set(lang_code, value)
 | 
			
		||||
 | 
			
		||||
        return localized_value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LocalizedCharFieldForm(LocalizedFieldForm):
 | 
			
		||||
    """Form for a localized char field, allows editing
 | 
			
		||||
    the field in multiple languages."""
 | 
			
		||||
 | 
			
		||||
    widget = LocalizedCharFieldWidget
 | 
			
		||||
    value_class = LocalizedStingValue
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LocalizedTextFieldForm(LocalizedFieldForm):
 | 
			
		||||
    """Form for a localized text field, allows editing
 | 
			
		||||
    the field in multiple languages."""
 | 
			
		||||
 | 
			
		||||
    value_class = LocalizedStingValue
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LocalizedFileFieldForm(LocalizedFieldForm, forms.FileField):
 | 
			
		||||
    """Form for a localized file field, allows editing
 | 
			
		||||
    the field in multiple languages."""
 | 
			
		||||
 | 
			
		||||
    widget = LocalizedFileWidget
 | 
			
		||||
    field_class = forms.fields.FileField
 | 
			
		||||
    value_class = LocalizedFileValue
 | 
			
		||||
 | 
			
		||||
    def clean(self, value, initial=None):
 | 
			
		||||
        """
 | 
			
		||||
        Most part of this method is a copy of 
 | 
			
		||||
        django.forms.MultiValueField.clean, with the exception of initial
 | 
			
		||||
        value handling (this need for correct processing FileField's).
 | 
			
		||||
        All original comments saved.
 | 
			
		||||
        """
 | 
			
		||||
        if initial is None:
 | 
			
		||||
            initial = [None for x in range(0, len(value))]
 | 
			
		||||
        else:
 | 
			
		||||
            if not isinstance(initial, list):
 | 
			
		||||
                initial = self.widget.decompress(initial)
 | 
			
		||||
 | 
			
		||||
        clean_data = []
 | 
			
		||||
        errors = []
 | 
			
		||||
        if not value or isinstance(value, (list, tuple)):
 | 
			
		||||
            if (not value or not [v for v in value if
 | 
			
		||||
                                  v not in self.empty_values]) \
 | 
			
		||||
                    and (not initial or not [v for v in initial if
 | 
			
		||||
                                             v not in self.empty_values]):
 | 
			
		||||
                if self.required:
 | 
			
		||||
                    raise ValidationError(self.error_messages['required'],
 | 
			
		||||
                                          code='required')
 | 
			
		||||
        else:
 | 
			
		||||
            raise ValidationError(self.error_messages['invalid'],
 | 
			
		||||
                                  code='invalid')
 | 
			
		||||
        for i, field in enumerate(self.fields):
 | 
			
		||||
            try:
 | 
			
		||||
                field_value = value[i]
 | 
			
		||||
            except IndexError:
 | 
			
		||||
                field_value = None
 | 
			
		||||
            try:
 | 
			
		||||
                field_initial = initial[i]
 | 
			
		||||
            except IndexError:
 | 
			
		||||
                field_initial = None
 | 
			
		||||
            if field_value in self.empty_values and \
 | 
			
		||||
                            field_initial in self.empty_values:
 | 
			
		||||
                if self.require_all_fields:
 | 
			
		||||
                    # Raise a 'required' error if the MultiValueField is
 | 
			
		||||
                    # required and any field is empty.
 | 
			
		||||
                    if self.required:
 | 
			
		||||
                        raise ValidationError(self.error_messages['required'],
 | 
			
		||||
                                              code='required')
 | 
			
		||||
                elif field.required:
 | 
			
		||||
                    # Otherwise, add an 'incomplete' error to the list of
 | 
			
		||||
                    # collected errors and skip field cleaning, if a required
 | 
			
		||||
                    # field is empty.
 | 
			
		||||
                    if field.error_messages['incomplete'] not in errors:
 | 
			
		||||
                        errors.append(field.error_messages['incomplete'])
 | 
			
		||||
                    continue
 | 
			
		||||
            try:
 | 
			
		||||
                clean_data.append(field.clean(field_value, field_initial))
 | 
			
		||||
            except ValidationError as e:
 | 
			
		||||
                # Collect all validation errors in a single list, which we'll
 | 
			
		||||
                # raise at the end of clean(), rather than raising a single
 | 
			
		||||
                # exception for the first error we encounter. Skip duplicates.
 | 
			
		||||
                errors.extend(m for m in e.error_list if m not in errors)
 | 
			
		||||
        if errors:
 | 
			
		||||
            raise ValidationError(errors)
 | 
			
		||||
 | 
			
		||||
        out = self.compress(clean_data)
 | 
			
		||||
        self.validate(out)
 | 
			
		||||
        self.run_validators(out)
 | 
			
		||||
        return out
 | 
			
		||||
 | 
			
		||||
    def bound_data(self, data, initial):
 | 
			
		||||
        bound_data = []
 | 
			
		||||
        if initial is None:
 | 
			
		||||
            initial = [None for x in range(0, len(data))]
 | 
			
		||||
        else:
 | 
			
		||||
            if not isinstance(initial, list):
 | 
			
		||||
                initial = self.widget.decompress(initial)
 | 
			
		||||
        for d, i in zip(data, initial):
 | 
			
		||||
            if d in (None, FILE_INPUT_CONTRADICTION):
 | 
			
		||||
                bound_data.append(i)
 | 
			
		||||
            else:
 | 
			
		||||
                bound_data.append(d)
 | 
			
		||||
        return bound_data
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ from django.utils import translation
 | 
			
		||||
 | 
			
		||||
class LocalizedValue(dict):
 | 
			
		||||
    """Represents the value of a :see:LocalizedField."""
 | 
			
		||||
    default_value = None
 | 
			
		||||
 | 
			
		||||
    def __init__(self, keys: dict=None):
 | 
			
		||||
        """Initializes a new instance of :see:LocalizedValue.
 | 
			
		||||
@@ -15,12 +16,15 @@ class LocalizedValue(dict):
 | 
			
		||||
                different language.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        # NOTE(seroy): First fill all the keys with default value,
 | 
			
		||||
        # in order to attributes will be for each language
 | 
			
		||||
        for lang_code, _ in settings.LANGUAGES:
 | 
			
		||||
            value = keys.get(lang_code) if isinstance(keys, dict) else \
 | 
			
		||||
                self.default_value
 | 
			
		||||
            self.set(lang_code, value)
 | 
			
		||||
 | 
			
		||||
        if isinstance(keys, str):
 | 
			
		||||
            setattr(self, settings.LANGUAGE_CODE, keys)
 | 
			
		||||
        else:
 | 
			
		||||
            for lang_code, _ in settings.LANGUAGES:
 | 
			
		||||
                value = keys.get(lang_code) if keys else None
 | 
			
		||||
                self.set(lang_code, value)
 | 
			
		||||
 | 
			
		||||
    def get(self, language: str=None) -> str:
 | 
			
		||||
        """Gets the underlying value in the specified or
 | 
			
		||||
@@ -62,7 +66,7 @@ class LocalizedValue(dict):
 | 
			
		||||
            contained in this instance.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        path = 'localized_fields.fields.LocalizedValue'
 | 
			
		||||
        path = 'localized_fields.localized_value.%s' % self.__class__.__name__
 | 
			
		||||
        return path, [self.__dict__], {}
 | 
			
		||||
 | 
			
		||||
    def __str__(self) -> str:
 | 
			
		||||
@@ -124,4 +128,25 @@ class LocalizedValue(dict):
 | 
			
		||||
    def __repr__(self):  # pragma: no cover
 | 
			
		||||
        """Gets a textual representation of this object."""
 | 
			
		||||
 | 
			
		||||
        return 'LocalizedValue<%s> 0x%s' % (dict(self), id(self))
 | 
			
		||||
        return '%s<%s> 0x%s' % (self.__class__.__name__,
 | 
			
		||||
                                self.__dict__, id(self))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LocalizedStingValue(LocalizedValue):
 | 
			
		||||
    default_value = ''
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LocalizedFileValue(LocalizedValue):
 | 
			
		||||
 | 
			
		||||
    def __getattr__(self, name):
 | 
			
		||||
        value = self.get(translation.get_language())
 | 
			
		||||
        if hasattr(value, name):
 | 
			
		||||
            return getattr(value, name)
 | 
			
		||||
        raise AttributeError("'{}' object has no attribute '{}'".
 | 
			
		||||
                             format(self.__class__.__name__, name))
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return str(super().__str__())
 | 
			
		||||
 | 
			
		||||
    def localized(self):
 | 
			
		||||
        return self.get(translation.get_language())
 | 
			
		||||
 
 | 
			
		||||
@@ -46,6 +46,16 @@ class LocalizedFieldWidget(forms.MultiWidget):
 | 
			
		||||
        return result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LocalizedCharFieldWidget(LocalizedFieldWidget):
 | 
			
		||||
    """Widget that has an input box for every language."""
 | 
			
		||||
    widget = forms.TextInput
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LocalizedFileWidget(LocalizedFieldWidget):
 | 
			
		||||
    """Widget that has an file input box for every language."""
 | 
			
		||||
    widget = forms.ClearableFileInput
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AdminLocalizedFieldWidget(LocalizedFieldWidget):
 | 
			
		||||
    widget = widgets.AdminTextareaWidget
 | 
			
		||||
    template = 'localized_fields/admin/widget.html'
 | 
			
		||||
@@ -84,3 +94,11 @@ class AdminLocalizedFieldWidget(LocalizedFieldWidget):
 | 
			
		||||
                and 'required' in attrs:
 | 
			
		||||
            del attrs['required']
 | 
			
		||||
        return attrs
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AdminLocalizedCharFieldWidget(AdminLocalizedFieldWidget):
 | 
			
		||||
    widget = widgets.AdminTextInputWidget
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AdminLocalizedFileFieldWidget(AdminLocalizedFieldWidget):
 | 
			
		||||
    widget = widgets.AdminFileWidget
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user