Code Monkey home page Code Monkey logo

django-transmeta's Introduction

Introduction

https://badge.fury.io/py/django-transmeta.png https://pypip.in/d/django-transmeta/badge.png

Transmeta is an application for translatable content in Django's models. Each language is stored and managed automatically in a different column at database level.

Features

  • Automatic schema creation with translatable fields.
  • Translatable fields integrated into Django's admin interface.
  • Command to synchronize database schema to add new translatable fields and new languages.

Using transmeta

Creating translatable models

Look at this model:

class Book(models.Model):
    title = models.CharField(max_length=200)
    description = models.TextField()
    body = models.TextField(default='')
    price = models.FloatField()

Suppose you want to make description and body translatable. The resulting model after using transmeta is:

from transmeta import TransMeta

class Book(models.Model):
    __metaclass__ = TransMeta

    title = models.CharField(max_length=200)
    description = models.TextField()
    body = models.TextField(default='')
    price = models.FloatField()

    class Meta:
        translate = ('description', 'body', )

In python 3:

from transmeta import TransMeta

class Book(models.Model, metaclass=transmeta.TransMeta):

    title = models.CharField(max_length=200)
    description = models.TextField()
    body = models.TextField(default='')
    price = models.FloatField()

    class Meta:
        translate = ('description', 'body', )

Make sure you have set the default and available languages in your settings.py:

LANGUAGE_CODE = 'es'

ugettext = lambda s: s # dummy ugettext function, as django's docs say

LANGUAGES = (
    ('es', ugettext('Spanish')),
    ('en', ugettext('English')),
)

Notes:

  • It's possible that you want have a default language in your site, but this is not the default language to transmeta. You can set this variable in your settings:

    TRANSMETA_DEFAULT_LANGUAGE = 'it'
    
  • The same it's possible with the languages:

    TRANSMETA_LANGUAGES = (
        ('es', ugettext('Spanish')),
        ('en', ugettext('English')),
        ('it', ugettext('Italian')),
    )
    

This is the SQL generated with the ./manage.py sqlall command:

BEGIN;
CREATE TABLE "fooapp_book" (
    "id" serial NOT NULL PRIMARY KEY,
    "title" varchar(200) NOT NULL,
    "description_en" text,
    "description_es" text NOT NULL,
    "body_es" text NOT NULL,
    "body_en" text NOT NULL,
    "price" double precision NOT NULL
)
;
COMMIT;

Notes:

  • transmeta creates one column for each language. Don't worry about needing new languages in the future, transmeta solves this problem for you.
  • If one field is null=False and doesn't have a default value, transmeta will create only one NOT NULL field, for the default language. Fields for other secondary languages will be nullable. Also, the primary language will be required in the admin app, while the other fields will be optional (with blank=True). This was done so because the normal approach for content translation is first add content in the main language and later have translators translate into other languages.
  • You can use ./manage.py syncdb to create database schema.

Playing in the python shell

transmeta creates one field for every available language for every translatable field defined in a model. Field names are suffixed with language short codes, e.g.: description_es, description_en, and so on. In addition it creates a field_name getter to retrieve the field value in the active language.

Let's play a bit in a python shell to best understand how this works:

>>> from fooapp.models import Book
>>> b = Book.objects.create(description_es=u'mi descripcion', description_en=u'my description')
>>> b.description
u'my description'
>>> from django.utils.translation import activate
>>> activate('es')
>>> b.description
u'mi descripcion'
>>> b.description_en
u'my description'

Adding new languages

If you need to add new languages to the existing ones you only need to change your settings.py and ask transmeta to sync the DB again. For example, to add French to our project, you need to add it to LANGUAGES in settings.py:

LANGUAGES = (
    ('es', ugettext('Spanish')),
    ('en', ugettext('English')),
    ('fr', ugettext('French')),
)

And execute a special sync_transmeta_db command:

$ ./manage.py sync_transmeta_db

This languages can change in "description" field from "fooapp.book" model: fr

SQL to synchronize "fooapp.book" schema:
   ALTER TABLE "fooapp_book" ADD COLUMN "description_fr" text

Are you sure that you want to execute the previous SQL: (y/n) [n]: y
Executing SQL... Done

This languages can change in "body" field from "fooapp.book" model: fr

SQL to synchronize "fooapp.book" schema:
   ALTER TABLE "fooapp_book" ADD COLUMN "body_fr" text

Are you sure that you want to execute the previous SQL: (y/n) [n]: y
Executing SQL... Done

And done!

Adding new translatable fields

Now imagine that, after several months using this web app (with many books created), you need to make book price translatable (for example because book price depends on currency).

To achieve this, first add price to the model's translatable fields list:

class Book(models.Model):
    ...
    price = models.FloatField()

    class Meta:
        translate = ('description', 'body', 'price', )

All that's left now is calling the sync_transmeta_db command to update the DB schema:

$ ./manage.py sync_transmeta_db

This languages can change in "price" field from "fooapp.book" model: es, en

SQL to synchronize "fooapp.book" schema:
    ALTER TABLE "fooapp_book" ADD COLUMN "price_es" double precision
    UPDATE "fooapp_book" SET "price_es" = "price"
    ALTER TABLE "fooapp_book" ALTER COLUMN "price_es" SET NOT NULL
    ALTER TABLE "fooapp_book" ADD COLUMN "price_en" double precision
    ALTER TABLE "fooapp_book" DROP COLUMN "price"

Are you sure that you want to execute the previous SQL: (y/n) [n]: y
Executing SQL...Done

What the hell this command does?

sync_transmeta_db command not only creates new database columns for new translatable field... it copy data from old price field into one of languages, and that is why command ask you for destination language field for actual data. It's very important that the LANGUAGE_CODE and LANGUAGES (or TRANSMETA_DEFAULT_LANGUAGE, TRANSMETA_LANGUAGES) settings have good values.

This command also you can execute, when you want add a language to the site, or you want to change the default language in transmeta. For this last case, you can define a variable in the settings file:

TRANSMETA_VALUE_DEFAULT = '---'

Admin integration

transmeta transparently displays all translatable fields into the admin interface. This is easy because models have in fact many fields (one for each language).

Changing form fields in the admin is quite a common task, and transmeta includes the canonical_fieldname utility function to apply these changes for all language fields at once. It's better explained with an example:

from transmeta import canonical_fieldname

class BookAdmin(admin.ModelAdmin):
    def formfield_for_dbfield(self, db_field, **kwargs):
        field = super(BookAdmin, self).formfield_for_dbfield(db_field, **kwargs)
        db_fieldname = canonical_fieldname(db_field)
        if db_fieldname == 'description':
            # this applies to all description_* fields
            field.widget = MyCustomWidget()
        elif field.name == 'body_es':
            # this applies only to body_es field
            field.widget = MyCustomWidget()
        return field

django-transmeta's People

Contributors

dmascialino avatar goinnn avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

django-transmeta's Issues

Got the `Invalid order_by arguments` exception if the language with `-` symbol.

It might get an Invalid order_by arguments exception if ordering the queryset with the translated field.

For example:

Book.objects.order_by('title_zh-tw')

Will get the exception:

FieldError: Invalid order_by arguments: ['title_zh-tw']

Because the definition of ORDER_PATTERN in django.db.models.sql.constants is:

ORDER_PATTERN = re.compile(r'\?|[-+]?[.\w]+$')

Support for ForeignKey fields

Recently I tried to create a translatable ForeignKey field. However, it complains about the related_name property. I found an issue on the old repo (which included a patch that required some adjusting to actually work) https://code.google.com/p/django-transmeta/issues/detail?id=11 .
I can find some spare time to actually fix this and make a pull request, but I'm just wondering if it's going to be integrated (i.e. if it's a desirable feature).

Thanks in advance!

sync_transmeta_db fails with SQL Server native client driver

(venv) PS C:\inetpub\wwwroot\admin\iO_web> python manage.py sync_transmeta_db --settings=iO_web.settings.staging
C:\inetpub\wwwroot\admin\iO_web\questionnaire\generate.py:6: DeprecationWarning: the md5 module is deprecated; use hashl
ib instead
import md5, random

This languages can change in "text" field from "questionnaire.questionset" model: en-us, fr, de, en, it

SQL to synchronize "questionnaire.questionset" schema:
ALTER TABLE [questionnaire_questionset] ADD COLUMN [text_en-us] nvarchar(max)
ALTER TABLE [questionnaire_questionset] ADD COLUMN [text_fr] nvarchar(max)
ALTER TABLE [questionnaire_questionset] ADD COLUMN [text_de] nvarchar(max)
ALTER TABLE [questionnaire_questionset] ADD COLUMN [text_it] nvarchar(max)

Are you sure that you want to execute the previous SQL: (y/n) [n]: y
Executing SQL...
DatabaseError: ('42000', "[42000] [Microsoft][SQL Server Native Client 10.0][SQL Server]Incorrect syntax near the keywor
d 'COLUMN'. (156) (SQLExecDirectW)")

sync_transmeta_db fails in py3

It uses raw_input() rather than input(). I think all you need to do is add
raw_input = input
to transmeta/management/commands/sync_transmeta_db.py but I don't know if that will break things in py2, in which case you need to tweak the approach.

sync_transmeta_db: There is no ALTER COLUMN in SQLite

Hello! I get the following error while doing 'sync_transmeta_db' for sqlite database:
SQL to synchronize "tags.tagcategory" schema:
UPDATE "tags_tagcategory" SET "name_ru" = "name"
ALTER TABLE "tags_tagcategory" ALTER COLUMN "name_ru" SET NOT NULL
ALTER TABLE "tags_tagcategory" DROP COLUMN "name"
<...>
django.db.utils.OperationalError: near "ALTER": syntax error

It seems that sqlite does not have ALTER COLUMN statement: http://stackoverflow.com/questions/4007014/alter-column-in-sqlite

Django Oscar with django-transmeta

I am working with django-oscar, in which all field attribute reside in an abstract class(say, AbstractProduct) and the models then Product (have no fields on them ) inherits the AbstractProduct.
I tried using this app, put metaclass = TransMeta, and provided translate in in class Meta. But i am getting this error "There is no field title in model Product, as specified in Meta's translate attribute".
Does this package does not takes into account this or is there something i missed out.Please help me overcome this problem.

Form label says: <transmeta.LazyString object at 0x7f0feaf45780>

Hi,

I'm trying to get transmeta working with Django 1.7 and Python 3.4.2, and so far my translated models are working correctly.

However, on the admins change_form view, the field labels aren't rendered correctly. Instead of 'title', 'description' etcetera I'm seeing strings like "<transmeta.LazyString object at 0x7f0feaf45780>".

Where should I begin to look to identify the cause of this issue?

No en in laguages raise exception when syncing

If I have the following settings

LANGUAGE_CODE = 'it'

LANGUAGES = (
('it', 'Italian'),
('en', 'English'),
)

there are no problems, but removing the ('en', 'English'), language raise an exception on nullable fields
when launcing sync_transmeta_db

with the following trace

Traceback (most recent call last):
File "Z:\Exprivia\Programmi_e_configurazioni\workspaceEclipseLuna\Consumattori\manage.py", line 10, in
execute_from_command_line(sys.argv)
File "C:\Python27\lib\site-packages\django\core\management__init__.py", line 385, in execute_from_command_line
utility.execute()
File "C:\Python27\lib\site-packages\django\core\management__init__.py", line 377, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File "C:\Python27\lib\site-packages\django\core\management\base.py", line 288, in run_from_argv
self.execute(_args, *_options.dict)
File "C:\Python27\lib\site-packages\django\core\management\base.py", line 338, in execute
output = self.handle(_args, *_options)
File "c:\python27\lib\site-packages\django_transmeta-0.7.3-py2.7.egg\transmeta\management\commands\sync_transmeta_db.py", line 88, in handle
sql_sentences = self.get_sync_sql(field_name, db_change_langs, model, db_table_fields)
File "c:\python27\lib\site-packages\django_transmeta-0.7.3-py2.7.egg\transmeta\management\commands\sync_transmeta_db.py", line 197, in get_sync_sql
col_type = self.get_type_of_db_field(field_name, model)
File "c:\python27\lib\site-packages\django_transmeta-0.7.3-py2.7.egg\transmeta\management\commands\sync_transmeta_db.py", line 171, in get_type_of_db_field
field = model._meta.get_field(get_real_fieldname(field_name))
File "C:\Python27\lib\site-packages\django\db\models\options.py", line 398, in get_field
raise FieldDoesNotExist('%s has no field named %r' % (self.object_name, name))
django.db.models.fields.FieldDoesNotExist: Question has no field named 'extra_en'

the model is:

class Question(models.Model):
    __metaclass__ = TransMeta

    questionset = models.ForeignKey(QuestionSet)
    number = models.CharField(max_length=8, help_text=
        "eg. <tt>1</tt>, <tt>2a</tt>, <tt>2b</tt>, <tt>3c</tt><br /> "
        "Number is also used for ordering questions.")
    text = models.TextField(blank=True, verbose_name=_("Text"))
    type = models.CharField(u"Type of question", max_length=32,
        choices = QuestionChoices,
        help_text = u"Determines the means of answering the question. " \
        "An open question gives the user a single-line textfield, " \
        "multiple-choice gives the user a number of choices he/she can " \
        "choose from. If a question is multiple-choice, enter the choices " \
        "this user can choose from below'.")
    extra = models.CharField(u"Extra information", max_length=512, blank=True, null=True, help_text=u"Extra information (use  on question type)")
    checks = models.CharField(u"Additional checks", max_length=512, blank=True,
        null=True, help_text="Additional checks to be performed for this "
        "value (space separated)  <br /><br />"
        "For text fields, <tt>required</tt> is a valid check.<br />"
        "For yes/no choice, <tt>required</tt>, <tt>required-yes</tt>, "
        "and <tt>required-no</tt> are valid.<br /><br />"
        "If this question is required only if another question's answer is "
        'something specific, use <tt>requiredif="QuestionNumber,Value"</tt> '
        'or <tt>requiredif="QuestionNumber,!Value"</tt> for anything but '
        "a specific value.  "
        "You may also combine tests appearing in <tt>requiredif</tt> "
        "by joining them with the words <tt>and</tt> or <tt>or</tt>, "
        'eg. <tt>requiredif="Q1,A or Q2,B"</tt>')
    footer = models.TextField(u"Footer", help_text="Footer rendered below the question interpreted as textile", blank=True)


    def questionnaire(self):
        return self.questionset.questionnaire

    def getcheckdict(self):
        """getcheckdict returns a dictionary of the values in self.checks"""
        if(hasattr(self, '__checkdict_cached')):
            return self.__checkdict_cached
        try:
            self.__checkdict_cached = d = parse_checks(self.sameas().checks or '')
        except ParseException:
            raise Exception("Error Parsing Checks for Question %s: %s" % (
                self.number, self.sameas().checks))
        return d

    def __unicode__(self):
        return u'{%s} (%s) %s' % (unicode(self.questionset), self.number, self.text)

    def sameas(self):
        if self.type == 'sameas':
            try:
                kwargs = {}
                for check, value in parse_checks(self.checks):
                    if check == 'sameasid':
                        kwargs['id'] = value
                        break
                    elif check == 'sameas':
                        kwargs['number'] = value
                        kwargs['questionset__questionnaire'] = self.questionset.questionnaire
                        break

                self.__sameas = res = getattr(self, "__sameas", Question.objects.get(**kwargs))
                return res
            except Question.DoesNotExist:
                return Question(type='comment') # replace with something benign
        return self

    def display_number(self):
        "Return either the number alone or the non-number part of the question number indented"
        m = _numre.match(self.number)
        if m:
            sub = m.group(2)
            return "&nbsp;&nbsp;&nbsp;" + sub
        return self.number

    def choices(self):
        if self.type == 'sameas':
            return self.sameas().choices()
        res = Choice.objects.filter(question=self).order_by('sortid')
        return res

    def is_custom(self):
        return "custom" == self.sameas().type

    def get_type(self):
        "Get the type name, treating sameas and custom specially"
        t = self.sameas().type
        if t == 'custom':
            cd = self.sameas().getcheckdict()
            if 'type' not in cd:
                raise Exception("When using custom types, you must have type=<name> in the additional checks field")
            return cd.get('type')
        return t

    def questioninclude(self):
        return "questionnaire/" + self.get_type() + ".html"

#     def __cmp__(a, b):
#         anum, astr = split_numal(a.number)
#         bnum, bstr = split_numal(b.number)
#         cmpnum = cmp(anum, bnum)
#         return cmpnum or cmp(astr, bstr)

    class Meta:
        translate = ('text', 'extra', 'footer')

Problems with Oracle

The "ADD COLUMN" syntax is not correct. It seems only to support "ADD column_name column_type" syntax. See #19

Add option for silence output

When running unit tests, for each test function of a TestCase class I got:
.
No new translatable fields detected
...

It would be nice to have a -q (--quiet) option to silence print outputs.

Support language dialects

Transmeta get_real_fieldname only supports simple languages if language is not given. I would like to have fields like this one:

  • help_ca-va, being ca-va the valencian dialect of the catalan language.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.