mirror of
				https://github.com/SectorLabs/django-localized-fields.git
				synced 2025-11-04 03:58:58 +03:00 
			
		
		
		
	Advanced django admin integration
This commit is contained in:
		
							
								
								
									
										14
									
								
								README.rst
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								README.rst
									
									
									
									
									
								
							@@ -244,6 +244,20 @@ Besides ``LocalizedField``, there's also:
 | 
			
		||||
                   title = LocalizedField()
 | 
			
		||||
                   description = LocalizedBleachField()
 | 
			
		||||
 | 
			
		||||
Django Admin Integration
 | 
			
		||||
^^^^^^^^^^^^^^^^^^^^^^^^
 | 
			
		||||
To enable widgets in the admin, you need to inherit from
 | 
			
		||||
``LocalizedFieldsAdminMixin``:
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
    from django.contrib import admin
 | 
			
		||||
    from myapp.models import MyLocalizedModel
 | 
			
		||||
 | 
			
		||||
    from localized_fields.admin import LocalizedFieldsAdminMixin
 | 
			
		||||
    class MyLocalizedModelAdmin(LocalizedFieldsAdminMixin, admin.ModelAdmin):
 | 
			
		||||
        """Any admin options you need go here"""
 | 
			
		||||
    admin.site.register(MyLocalizedModel, MyLocalizedModelAdmin)
 | 
			
		||||
 | 
			
		||||
Frequently asked questions (FAQ)
 | 
			
		||||
--------------------------------
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										27
									
								
								localized_fields/admin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								localized_fields/admin.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
from django.contrib.admin import ModelAdmin
 | 
			
		||||
 | 
			
		||||
from .fields import LocalizedField
 | 
			
		||||
from . import widgets
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
FORMFIELD_FOR_LOCALIZED_FIELDS_DEFAULTS = {
 | 
			
		||||
    LocalizedField: {'widget': widgets.AdminLocalizedFieldWidget},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LocalizedFieldsAdminMixin(ModelAdmin):
 | 
			
		||||
    class Media:
 | 
			
		||||
        css = {
 | 
			
		||||
            'all': (
 | 
			
		||||
                'localized_fields/localized-fields-admin.css',
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
        js = (
 | 
			
		||||
            'localized_fields/localized-fields-admin.js',
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super(LocalizedFieldsAdminMixin, self).__init__(*args, **kwargs)
 | 
			
		||||
        overrides = FORMFIELD_FOR_LOCALIZED_FIELDS_DEFAULTS.copy()
 | 
			
		||||
        overrides.update(self.formfield_overrides)
 | 
			
		||||
        self.formfield_overrides = overrides
 | 
			
		||||
@@ -2,53 +2,18 @@ from typing import List
 | 
			
		||||
 | 
			
		||||
from django import forms
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.forms import MultiWidget
 | 
			
		||||
 | 
			
		||||
from .localized_value import LocalizedValue
 | 
			
		||||
from .widgets import LocalizedFieldWidget
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LocalizedFieldWidget(MultiWidget):
 | 
			
		||||
    """Widget that has an input box for every language."""
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        """Initializes a new instance of :see:LocalizedFieldWidget."""
 | 
			
		||||
 | 
			
		||||
        widgets = []
 | 
			
		||||
 | 
			
		||||
        for _ in settings.LANGUAGES:
 | 
			
		||||
            widgets.append(forms.Textarea())
 | 
			
		||||
 | 
			
		||||
        super(LocalizedFieldWidget, self).__init__(widgets, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def decompress(self, value: LocalizedValue) -> List[str]:
 | 
			
		||||
        """Decompresses the specified value so
 | 
			
		||||
        it can be spread over the internal widgets.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            value:
 | 
			
		||||
                The :see:LocalizedValue to display in this
 | 
			
		||||
                widget.
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            All values to display in the inner widgets.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        result = []
 | 
			
		||||
 | 
			
		||||
        for lang_code, _ in settings.LANGUAGES:
 | 
			
		||||
            if value:
 | 
			
		||||
                result.append(value.get(lang_code))
 | 
			
		||||
            else:
 | 
			
		||||
                result.append(None)
 | 
			
		||||
 | 
			
		||||
        return result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LocalizedFieldForm(forms.MultiValueField):
 | 
			
		||||
    """Form for a localized field, allows editing
 | 
			
		||||
    the field in multiple languages."""
 | 
			
		||||
 | 
			
		||||
    widget = LocalizedFieldWidget()
 | 
			
		||||
    widget = LocalizedFieldWidget
 | 
			
		||||
    value_class = LocalizedValue
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        """Initializes a new instance of :see:LocalizedFieldForm."""
 | 
			
		||||
@@ -59,17 +24,21 @@ class LocalizedFieldForm(forms.MultiValueField):
 | 
			
		||||
            field_options = {'required': False}
 | 
			
		||||
 | 
			
		||||
            if lang_code == settings.LANGUAGE_CODE:
 | 
			
		||||
                field_options['required'] = True
 | 
			
		||||
                field_options['required'] = kwargs.get('required', True)
 | 
			
		||||
 | 
			
		||||
            field_options['label'] = lang_code
 | 
			
		||||
            fields.append(forms.fields.CharField(**field_options))
 | 
			
		||||
 | 
			
		||||
        super(LocalizedFieldForm, self).__init__(
 | 
			
		||||
            fields,
 | 
			
		||||
            require_all_fields=False
 | 
			
		||||
            require_all_fields=False,
 | 
			
		||||
            *args, **kwargs
 | 
			
		||||
        )
 | 
			
		||||
        # set 'required' attribute for each widget separately
 | 
			
		||||
        for f, w in zip(self.fields, self.widget.widgets):
 | 
			
		||||
            w.is_required = f.required
 | 
			
		||||
 | 
			
		||||
    def compress(self, value: List[str]) -> LocalizedValue:
 | 
			
		||||
    def compress(self, value: List[str]) -> value_class:
 | 
			
		||||
        """Compresses the values from individual fields
 | 
			
		||||
        into a single :see:LocalizedValue instance.
 | 
			
		||||
 | 
			
		||||
@@ -82,7 +51,7 @@ class LocalizedFieldForm(forms.MultiValueField):
 | 
			
		||||
            the value in several languages.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        localized_value = LocalizedValue()
 | 
			
		||||
        localized_value = self.value_class()
 | 
			
		||||
 | 
			
		||||
        for (lang_code, _), value in zip(settings.LANGUAGES, value):
 | 
			
		||||
            localized_value.set(lang_code, value)
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,49 @@
 | 
			
		||||
.localized-fields-widget {
 | 
			
		||||
    margin-left: 160px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.localized-fields-widget.tabs {
 | 
			
		||||
    display: block;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    border-bottom: 1px solid #eee;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.localized-fields-widget.tabs .localized-fields-widget.tab {
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    margin-left: 5px;
 | 
			
		||||
    border: 1px solid #79aec8;
 | 
			
		||||
    border-bottom: none;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    border-bottom-left-radius: 0;
 | 
			
		||||
    border-bottom-right-radius: 0;
 | 
			
		||||
    background: #79aec8;
 | 
			
		||||
    color: #fff;
 | 
			
		||||
    font-weight: 400;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.localized-fields-widget.tabs .localized-fields-widget.tab:first-child {
 | 
			
		||||
    margin-left: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.localized-fields-widget.tabs .localized-fields-widget.tab:hover {
 | 
			
		||||
    background: #417690;
 | 
			
		||||
    border-color: #417690;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.localized-fields-widget.tabs .localized-fields-widget.tab a {
 | 
			
		||||
    padding: 5px 10px;
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    color: #fff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.localized-fields-widget.tabs .localized-fields-widget.tab.active,
 | 
			
		||||
.localized-fields-widget.tabs .localized-fields-widget.tab.active:hover {
 | 
			
		||||
    background: #fff;
 | 
			
		||||
    border-color: #aaaaaa;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.localized-fields-widget.tabs .localized-fields-widget.tab.active a,
 | 
			
		||||
.localized-fields-widget.tabs .localized-fields-widget.tab.active:hover a{
 | 
			
		||||
    color: #000;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,35 @@
 | 
			
		||||
(function($) {
 | 
			
		||||
    var syncTabs = function(lang) {
 | 
			
		||||
        $('.localized-fields-widget.tab a:contains("'+lang+'")').each(function(){
 | 
			
		||||
            $(this).parents('.localized-fields-widget[role="tabs"]').find('.localized-fields-widget.tab').removeClass('active');
 | 
			
		||||
            $(this).parents('.localized-fields-widget.tab').addClass('active');
 | 
			
		||||
            $(this).parents('.localized-fields-widget[role="tabs"]').children('.localized-fields-widget [role="tabpanel"]').hide();
 | 
			
		||||
            $($(this).attr('href')).show();
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $(function (){
 | 
			
		||||
        $('.localized-fields-widget [role="tabpanel"]').hide();
 | 
			
		||||
        // set first tab as active
 | 
			
		||||
        $('.localized-fields-widget[role="tabs"]').each(function () {
 | 
			
		||||
            $(this).find('.localized-fields-widget.tab:first').addClass('active');
 | 
			
		||||
            $($(this).find('.localized-fields-widget.tab:first a').attr('href')).show();
 | 
			
		||||
        });
 | 
			
		||||
        // try set active last selected tab
 | 
			
		||||
        if (window.sessionStorage) {
 | 
			
		||||
            var lang = window.sessionStorage.getItem('localized-field-lang');
 | 
			
		||||
            if (lang) {
 | 
			
		||||
                syncTabs(lang);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $('.localized-fields-widget.tab a').click(function(event) {
 | 
			
		||||
            event.preventDefault();
 | 
			
		||||
            syncTabs(this.innerText);
 | 
			
		||||
            if (window.sessionStorage) {
 | 
			
		||||
                window.sessionStorage.setItem('localized-field-lang', this.innerText);
 | 
			
		||||
            }
 | 
			
		||||
            return false;
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
})(django.jQuery)
 | 
			
		||||
@@ -0,0 +1,14 @@
 | 
			
		||||
<div class="localized-fields-widget" role="tabs" data-synctabs="translation">
 | 
			
		||||
    <ul class="localized-fields-widget tabs">
 | 
			
		||||
    {% for key, lang in available_languages %}
 | 
			
		||||
        <li class="localized-fields-widget tab">
 | 
			
		||||
            <a href="#{{ id }}_{{ key }}">{{ lang|capfirst }}</a>
 | 
			
		||||
        </li>
 | 
			
		||||
    {% endfor %}
 | 
			
		||||
    </ul>
 | 
			
		||||
{% for key, widget in widgets %}
 | 
			
		||||
    <div role="tabpanel" id="{{ id }}_{{ key }}">
 | 
			
		||||
        {{ widget }}
 | 
			
		||||
    </div>
 | 
			
		||||
{% endfor %}
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										86
									
								
								localized_fields/widgets.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								localized_fields/widgets.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,86 @@
 | 
			
		||||
from typing import List
 | 
			
		||||
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django import forms
 | 
			
		||||
from django.contrib.admin import widgets
 | 
			
		||||
from django.template.loader import render_to_string
 | 
			
		||||
 | 
			
		||||
from .localized_value import LocalizedValue
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LocalizedFieldWidget(forms.MultiWidget):
 | 
			
		||||
    """Widget that has an input box for every language."""
 | 
			
		||||
    widget = forms.Textarea
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        """Initializes a new instance of :see:LocalizedFieldWidget."""
 | 
			
		||||
 | 
			
		||||
        widgets = []
 | 
			
		||||
 | 
			
		||||
        for _ in settings.LANGUAGES:
 | 
			
		||||
            widgets.append(self.widget)
 | 
			
		||||
 | 
			
		||||
        super(LocalizedFieldWidget, self).__init__(widgets, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def decompress(self, value: LocalizedValue) -> List[str]:
 | 
			
		||||
        """Decompresses the specified value so
 | 
			
		||||
        it can be spread over the internal widgets.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            value:
 | 
			
		||||
                The :see:LocalizedValue to display in this
 | 
			
		||||
                widget.
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            All values to display in the inner widgets.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        result = []
 | 
			
		||||
 | 
			
		||||
        for lang_code, _ in settings.LANGUAGES:
 | 
			
		||||
            if value:
 | 
			
		||||
                result.append(value.get(lang_code))
 | 
			
		||||
            else:
 | 
			
		||||
                result.append(None)
 | 
			
		||||
 | 
			
		||||
        return result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AdminLocalizedFieldWidget(LocalizedFieldWidget):
 | 
			
		||||
    widget = widgets.AdminTextareaWidget
 | 
			
		||||
    template = 'localized_fields/admin/widget.html'
 | 
			
		||||
 | 
			
		||||
    def render(self, name, value, attrs=None):
 | 
			
		||||
        if self.is_localized:
 | 
			
		||||
            for widget in self.widgets:
 | 
			
		||||
                widget.is_localized = self.is_localized
 | 
			
		||||
        # value is a list of values, each corresponding to a widget
 | 
			
		||||
        # in self.widgets.
 | 
			
		||||
        if not isinstance(value, list):
 | 
			
		||||
            value = self.decompress(value)
 | 
			
		||||
        output = []
 | 
			
		||||
        final_attrs = self.build_attrs(attrs)
 | 
			
		||||
        id_ = final_attrs.get('id')
 | 
			
		||||
        for i, widget in enumerate(self.widgets):
 | 
			
		||||
            try:
 | 
			
		||||
                widget_value = value[i]
 | 
			
		||||
            except IndexError:
 | 
			
		||||
                widget_value = None
 | 
			
		||||
            if id_:
 | 
			
		||||
                final_attrs = dict(final_attrs, id='%s_%s' % (id_, i))
 | 
			
		||||
            widget_attrs = self.build_widget_attrs(widget, widget_value, final_attrs)
 | 
			
		||||
            output.append(widget.render(name + '_%s' % i, widget_value, widget_attrs))
 | 
			
		||||
        context = {
 | 
			
		||||
            'id': final_attrs.get('id'),
 | 
			
		||||
            'name': name,
 | 
			
		||||
            'widgets': zip([code for code, lang in settings.LANGUAGES], output),
 | 
			
		||||
            'available_languages': settings.LANGUAGES
 | 
			
		||||
        }
 | 
			
		||||
        return render_to_string(self.template, context)
 | 
			
		||||
 | 
			
		||||
    def build_widget_attrs(self, widget, value, attrs):
 | 
			
		||||
        attrs = dict(attrs)  # Copy attrs to avoid modifying the argument.
 | 
			
		||||
        if (not widget.use_required_attribute(value) or not widget.is_required) \
 | 
			
		||||
                and 'required' in attrs:
 | 
			
		||||
            del attrs['required']
 | 
			
		||||
        return attrs
 | 
			
		||||
		Reference in New Issue
	
	Block a user