diff --git a/.gitignore b/.gitignore index b393d99..26abb08 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,12 @@ -.idea *.sqlite3 -result *.pyc *.pyo __pycache__ +*.DS_Store +*.egg* + +dist/ +docs/_build/ +.idea/ +node_modules/ +result diff --git a/translatable_fields/CHANGELOG.rst b/translatable_fields/CHANGELOG.rst new file mode 100644 index 0000000..126a25d --- /dev/null +++ b/translatable_fields/CHANGELOG.rst @@ -0,0 +1,7 @@ +Changelog +========= + +0.0.1 +----- + +* Initial release diff --git a/translatable_fields/LICENSE b/translatable_fields/LICENSE new file mode 100644 index 0000000..67ec320 --- /dev/null +++ b/translatable_fields/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2012 Selwin Ong + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/translatable_fields/README.rst b/translatable_fields/README.rst new file mode 100644 index 0000000..9998a5a --- /dev/null +++ b/translatable_fields/README.rst @@ -0,0 +1,90 @@ +========================== +Django Translatable Fields +========================== + +Translatable model fields for Django with admin integration. Uses PostgreSQL JSONField. + +Installation +============ + + +* Add application in `settings.py` + +.. code:: python + + INSTALLED_APPS = ( + ... + 'translatable_fields', + ... + ) + +* Specify languages in `settings.py` + +.. code:: python + + # Internationalization + + LANGUAGE_CODE = 'en' + LANGUAGES = ( + ('en', 'English'), + ('ru', 'Русский') + ) + + +* Add `TranslatableField` model fields + +.. code:: python + + from django.db import models + from django.utils.translation import ugettext_lazy as _ + + from translatable_fields.models import TranslatableField + + + class Position(models.Model): + ... + title = TranslatableField( + verbose_name=_('title') + ) + description = TranslatableField( + verbose_name=_('description') + ) + ... + +* Create custom model admin form + +.. code:: python + + from django import forms + from django.contrib.postgres.forms import JSONField + from ckeditor_uploader.widgets import CKEditorUploadingWidget + + from careers.models.position import Position + from translatable_fields.widgets import TranslatableWidget + + + class PositionAdminForm(forms.ModelForm): + title = JSONField(widget=TranslatableWidget(widget=forms.TextInput)) + description = JSONField(widget=TranslatableWidget(widget=CKEditorUploadingWidget)) + + class Meta: + model = Position + fields = ( + ... + 'title', + 'description', + ... + ) + +* Create custom model admin with custom form + +.. code:: python + + from django.contrib import admin + + from careers.forms.admin.position import PositionAdminForm + + + class PositionAdmin(admin.ModelAdmin): + form = PositionAdminForm + diff --git a/translatable_fields/__init__.py b/translatable_fields/__init__.py new file mode 100644 index 0000000..901e511 --- /dev/null +++ b/translatable_fields/__init__.py @@ -0,0 +1 @@ +VERSION = "0.0.1" diff --git a/translatable_fields/models.py b/translatable_fields/models.py new file mode 100644 index 0000000..33c3bc2 --- /dev/null +++ b/translatable_fields/models.py @@ -0,0 +1,26 @@ +from django.contrib.postgres.fields import JSONField + +from translatable_fields.value import TranslatableValue + + +class TranslatableField(JSONField): + def from_db_value(self, value, *args, **kwargs): + if value is None: + return value + + instance = TranslatableValue() + instance.update(value) + + return instance + + def to_python(self, value): + if isinstance(value, TranslatableValue): + return value + + if value is None: + return value + + instance = TranslatableValue() + instance.update(value) + + return instance diff --git a/translatable_fields/static/translatable_fields/translatable_fields.css b/translatable_fields/static/translatable_fields/translatable_fields.css new file mode 100755 index 0000000..4575037 --- /dev/null +++ b/translatable_fields/static/translatable_fields/translatable_fields.css @@ -0,0 +1,47 @@ +.translatable_field { + display: inline-block; +} + +.translatable_field__tabs { + display: block; + margin: 0 0 6px 0; +} + +.translatable_field__tabs-item { + display: inline-block; + margin-left: 5px; + border-radius: 4px; + padding: 0 10px; + background: #79aec8; + color: #fff; + font-weight: 400; + opacity: 0.5; +} + +.translatable_field__tabs-item:first-child { + margin-left: 0; +} + +.translatable_field__tabs-item:hover { + background: #417690; + opacity: 1; +} + +.translatable_field__tabs-item label, .inline-group .translatable_field__tabs-item label { + width: auto; + font-size: 11px; + padding: 2px 0 1px 0; + text-decoration: none; + color: #fff; +} + +.translatable_field__tabs-item_active, +.translatable_field__tabs-item_active:hover { + background: #79aec8; + border-color: #79aec8; + opacity: 1; +} + +.translatable_field p.file-upload { + margin-left: 0; +} diff --git a/translatable_fields/static/translatable_fields/translatable_fields.js b/translatable_fields/static/translatable_fields/translatable_fields.js new file mode 100755 index 0000000..e922f34 --- /dev/null +++ b/translatable_fields/static/translatable_fields/translatable_fields.js @@ -0,0 +1,37 @@ +(function($) { + var syncTabs = function($container, lang) { + $container.find('.translatable_field__tabs-item label:contains("'+lang+'")').each(function(){ + $(this).parents('.translatable_field').find('.translatable_field__tabs-item').removeClass('translatable_field__tabs-item_active'); + $(this).parents('.translatable_field__tabs-item').addClass('translatable_field__tabs-item_active'); + $(this).parents('.translatable_field').find('.translatable_field__widgets-item').hide(); + $('#'+$(this).attr('for')).parents('.translatable_field__widgets-item').show(); + }); + }; + + $(function (){ + $('.translatable_field__widgets-item').hide(); + // set first tab as active + $('.translatable_field').each(function () { + $(this).find('.translatable_field__tabs-item:first').addClass('translatable_field__tabs-item_active'); + syncTabs($(this), $(this).find('.translatable_field__tabs-item:first label').text()); + }); + // try set active last selected tab + if (window.sessionStorage) { + var lang = window.sessionStorage.getItem('translatable-field-lang'); + if (lang) { + $('.translatable_field').each(function () { + syncTabs($(this), lang); + }); + } + } + + $('.translatable_field__tabs-item label').click(function(event) { + event.preventDefault(); + syncTabs($(this).parents('.translatable_field'), $(this).text()); + if (window.sessionStorage) { + window.sessionStorage.setItem('translatable-field-lang', $(this).text()); + } + return false; + }); + }); +})(django.jQuery); diff --git a/translatable_fields/templates/translatable_fields/multiwidget.html b/translatable_fields/templates/translatable_fields/multiwidget.html new file mode 100755 index 0000000..cc80bc6 --- /dev/null +++ b/translatable_fields/templates/translatable_fields/multiwidget.html @@ -0,0 +1,17 @@ +