mirror of
https://github.com/Hopiu/django-modeltranslation.git
synced 2026-05-20 18:31:53 +00:00
Added support for related fields - ForeignKey, ManyToManyField and OneToOneField. Resolves issue 15.
This commit is contained in:
parent
f1aa871b75
commit
2e4a85e791
6 changed files with 107 additions and 99 deletions
|
|
@ -1,3 +1,8 @@
|
|||
ADDED: Support for related fields - ForeignKey, ManyToManyField and
|
||||
OneToOneField.
|
||||
(resolves issue 15)
|
||||
|
||||
|
||||
v0.2
|
||||
====
|
||||
Date: 2010-06-15
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from copy import deepcopy
|
||||
from copy import copy
|
||||
|
||||
from django import forms, template
|
||||
from django.conf import settings
|
||||
|
|
@ -41,7 +41,7 @@ class TranslationAdminBase(object):
|
|||
field.required = True
|
||||
field.blank = False
|
||||
|
||||
field.widget = deepcopy(orig_formfield.widget)
|
||||
field.widget = copy(orig_formfield.widget)
|
||||
|
||||
|
||||
class TranslationAdmin(admin.ModelAdmin, TranslationAdminBase):
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from django.conf import settings
|
||||
from django.db.models.fields import Field, CharField
|
||||
from django.db.models.fields.related import (ForeignKey, OneToOneField,
|
||||
ManyToManyField)
|
||||
|
||||
from modeltranslation.utils import (get_language, build_localized_fieldname,
|
||||
build_localized_verbose_name)
|
||||
|
|
@ -25,22 +27,27 @@ class TranslationField(Field):
|
|||
that needs to be specified when the field is created.
|
||||
"""
|
||||
def __init__(self, translated_field, language, *args, **kwargs):
|
||||
# Store the originally wrapped field for later
|
||||
self.translated_field = translated_field
|
||||
self.language = language
|
||||
|
||||
# Update the dict of this field with the content of the original one
|
||||
# This might be a bit radical?! Seems to work though...
|
||||
self.__dict__.update(translated_field.__dict__)
|
||||
|
||||
# Common init
|
||||
self._post_init(translated_field, language)
|
||||
|
||||
def _post_init(self, translated_field, language):
|
||||
"""Common init for subclasses of TranslationField."""
|
||||
# Store the originally wrapped field for later
|
||||
self.translated_field = translated_field
|
||||
self.language = language
|
||||
|
||||
# Translation are always optional (for now - maybe add some parameters
|
||||
# to the translation options for configuring this)
|
||||
self.null = True
|
||||
self.blank = True
|
||||
|
||||
# Adjust the name of this field to reflect the language
|
||||
self.attname = build_localized_fieldname(translated_field.name,
|
||||
language)
|
||||
self.attname = build_localized_fieldname(self.translated_field.name,
|
||||
self.language)
|
||||
self.name = self.attname
|
||||
|
||||
# Copy the verbose name and append a language suffix
|
||||
|
|
@ -66,11 +73,6 @@ class TranslationField(Field):
|
|||
def get_internal_type(self):
|
||||
return self.translated_field.get_internal_type()
|
||||
|
||||
#def contribute_to_class(self, cls, name):
|
||||
#super(TranslationField, self).contribute_to_class(cls, name)
|
||||
##setattr(cls, 'get_%s_display' % self.name,
|
||||
##curry(cls._get_FIELD_display, field=self))
|
||||
|
||||
def south_field_triple(self):
|
||||
"""Returns a suitable description of this field for South."""
|
||||
# We'll just introspect the _actual_ field.
|
||||
|
|
@ -89,26 +91,59 @@ class TranslationField(Field):
|
|||
return super(TranslationField, self).formfield(*args, **defaults)
|
||||
|
||||
|
||||
#class CurrentLanguageField(CharField):
|
||||
#def __init__(self, **kwargs):
|
||||
#super(CurrentLanguageField, self).__init__(null=True, max_length=5,
|
||||
#**kwargs)
|
||||
class RelatedTranslationField(object):
|
||||
"""
|
||||
Mixin class which handles shared init of a translated relation field.
|
||||
"""
|
||||
def _related_pre_init(self, translated_field, language, *args, **kwargs):
|
||||
self.translated_field = translated_field
|
||||
self.language = language
|
||||
|
||||
#def contribute_to_class(self, cls, name):
|
||||
#super(CurrentLanguageField, self).contribute_to_class(cls, name)
|
||||
#registry = CurrentLanguageFieldRegistry()
|
||||
#registry.add_field(cls, self)
|
||||
self.field_name = self.translated_field.name
|
||||
self.translated_field_name = \
|
||||
build_localized_fieldname(self.translated_field.name,
|
||||
self.language)
|
||||
|
||||
# Dynamically add a related_name to the original field
|
||||
translated_field.rel.related_name = \
|
||||
'%s%s' % (self.translated_field.model._meta.module_name,
|
||||
self.field_name)
|
||||
|
||||
TranslationField.__init__(self, self.translated_field, self.language,
|
||||
*args, **kwargs)
|
||||
|
||||
def _related_post_init(self):
|
||||
# Dynamically add a related_name to the translation fields
|
||||
self.rel.related_name = \
|
||||
'%s%s' % (self.translated_field.model._meta.module_name,
|
||||
self.translated_field_name)
|
||||
|
||||
# ForeignKey's init overrides some essential values from
|
||||
# TranslationField, they have to be reassigned.
|
||||
TranslationField._post_init(self, self.translated_field, self.language)
|
||||
|
||||
|
||||
#class CurrentLanguageFieldRegistry(object):
|
||||
#_registry = {}
|
||||
class ForeignKeyTranslationField(ForeignKey, TranslationField,
|
||||
RelatedTranslationField):
|
||||
def __init__(self, translated_field, language, to, to_field=None, *args,
|
||||
**kwargs):
|
||||
self._related_pre_init(translated_field, language, *args, **kwargs)
|
||||
ForeignKey.__init__(self, to, to_field, **kwargs)
|
||||
self._related_post_init()
|
||||
|
||||
#def add_field(self, model, field):
|
||||
#reg = self.__class__._registry.setdefault(model, [])
|
||||
#reg.append(field)
|
||||
|
||||
#def get_fields(self, model):
|
||||
#return self.__class__._registry.get(model, [])
|
||||
class OneToOneTranslationField(OneToOneField, TranslationField,
|
||||
RelatedTranslationField):
|
||||
def __init__(self, translated_field, language, to, to_field=None, *args,
|
||||
**kwargs):
|
||||
self._related_pre_init(translated_field, language, *args, **kwargs)
|
||||
OneToOneField.__init__(self, to, to_field, **kwargs)
|
||||
self._related_post_init()
|
||||
|
||||
#def __contains__(self, model):
|
||||
#return model in self.__class__._registry
|
||||
|
||||
class ManyToManyTranslationField(ManyToManyField, TranslationField,
|
||||
RelatedTranslationField):
|
||||
def __init__(self, translated_field, language, to, *args, **kwargs):
|
||||
self._related_pre_init(translated_field, language, *args, **kwargs)
|
||||
ManyToManyField.__init__(self, to, **kwargs)
|
||||
self._related_post_init()
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from django.utils.translation import ugettext_lazy
|
|||
|
||||
from modeltranslation import translator
|
||||
|
||||
# TODO: tests for TranslationAdmin
|
||||
# TODO: Tests for TranslationAdmin, RelatedTranslationField and subclasses
|
||||
|
||||
settings.LANGUAGES = (('de', 'Deutsch'),
|
||||
('en', 'English'))
|
||||
|
|
|
|||
|
|
@ -6,11 +6,17 @@ from django.db.models import signals
|
|||
from django.db.models.base import ModelBase
|
||||
from django.utils.functional import curry
|
||||
|
||||
from modeltranslation.fields import TranslationField
|
||||
from modeltranslation.fields import (TranslationField,
|
||||
ForeignKeyTranslationField,
|
||||
OneToOneTranslationField,
|
||||
ManyToManyTranslationField)
|
||||
from modeltranslation.utils import (TranslationFieldDescriptor,
|
||||
build_localized_fieldname)
|
||||
|
||||
|
||||
RELATED_CLASSES = ('ManyToOneRel', 'OneToOneRel', 'ManyToManyRel',)
|
||||
|
||||
|
||||
class AlreadyRegistered(Exception):
|
||||
pass
|
||||
|
||||
|
|
@ -60,9 +66,26 @@ def add_localized_fields(model):
|
|||
|
||||
# This approach implements the translation fields as full valid
|
||||
# django model fields and therefore adds them via add_to_class
|
||||
localized_field = model.add_to_class( \
|
||||
localized_field_name,
|
||||
TranslationField(model._meta.get_field(field_name), l[0]))
|
||||
field = model._meta.get_field(field_name)
|
||||
field_class_name = field.rel.__class__.__name__
|
||||
if field_class_name in RELATED_CLASSES:
|
||||
to = field.rel.to._meta.object_name
|
||||
if field_class_name == 'ManyToOneRel':
|
||||
translation_field = ForeignKeyTranslationField(\
|
||||
translated_field=field,
|
||||
language=l[0], to=to)
|
||||
elif field_class_name == 'OneToOneRel':
|
||||
translation_field = OneToOneTranslationField(\
|
||||
translated_field=field,
|
||||
language=l[0], to=to)
|
||||
elif field_class_name == 'ManyToManyRel':
|
||||
translation_field = ManyToManyTranslationField(\
|
||||
translated_field=field,
|
||||
language=l[0], to=to)
|
||||
else:
|
||||
translation_field = TranslationField(field, l[0])
|
||||
localized_field = model.add_to_class(localized_field_name,
|
||||
translation_field)
|
||||
localized_fields[field_name].append(localized_field_name)
|
||||
return localized_fields
|
||||
|
||||
|
|
@ -147,14 +170,14 @@ class Translator(object):
|
|||
# Create a reverse dict mapping the localized_fieldnames to the
|
||||
# original fieldname
|
||||
rev_dict = dict()
|
||||
for orig_name, loc_names in \
|
||||
for orig_name, loc_names in\
|
||||
translation_opts.localized_fieldnames.items():
|
||||
for ln in loc_names:
|
||||
rev_dict[ln] = orig_name
|
||||
|
||||
translation_opts.localized_fieldnames_rev = rev_dict
|
||||
|
||||
# print "Applying descriptor field for model %s" % model
|
||||
#print "Applying descriptor field for model %s" % model
|
||||
for field_name in translation_opts.fields:
|
||||
setattr(model, field_name, TranslationFieldDescriptor(field_name))
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ def get_language():
|
|||
"""
|
||||
Return an active language code that is guaranteed to be in
|
||||
settings.LANGUAGES (Django does not seem to guarantee this for us.)
|
||||
|
||||
"""
|
||||
lang = _get_language()
|
||||
available_languages = [l[0] for l in settings.LANGUAGES]
|
||||
|
|
@ -65,65 +64,11 @@ class TranslationFieldDescriptor(object):
|
|||
if hasattr(instance, loc_field_name):
|
||||
return getattr(instance, loc_field_name) or \
|
||||
instance.__dict__[self.name]
|
||||
return instance.__dict__[self.name]
|
||||
#return instance.__dict__[self.name]
|
||||
# FIXME: KeyError raised for ForeignKeyTanslationField
|
||||
# in admin list view
|
||||
try:
|
||||
return instance.__dict__[self.name]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
|
||||
#def create_model(name, fields=None, app_label='', module='', options=None,
|
||||
#admin_opts=None):
|
||||
#"""
|
||||
#Create specified model.
|
||||
#This is taken from http://code.djangoproject.com/wiki/DynamicModels
|
||||
#"""
|
||||
#class Meta:
|
||||
## Using type('Meta', ...) gives a dictproxy error during model
|
||||
## creation
|
||||
#pass
|
||||
|
||||
#if app_label:
|
||||
## app_label must be set using the Meta inner class
|
||||
#setattr(Meta, 'app_label', app_label)
|
||||
|
||||
## Update Meta with any options that were provided
|
||||
#if options is not None:
|
||||
#for key, value in options.iteritems():
|
||||
#setattr(Meta, key, value)
|
||||
|
||||
## Set up a dictionary to simulate declarations within a class
|
||||
#attrs = {'__module__': module, 'Meta': Meta}
|
||||
|
||||
## Add in any fields that were provided
|
||||
#if fields:
|
||||
#attrs.update(fields)
|
||||
|
||||
## Create the class, which automatically triggers ModelBase processing
|
||||
#model = type(name, (models.Model,), attrs)
|
||||
|
||||
## Create an Admin class if admin options were provided
|
||||
#if admin_opts is not None:
|
||||
#class Admin(admin.ModelAdmin):
|
||||
#pass
|
||||
#for key, value in admin_opts:
|
||||
#setattr(Admin, key, value)
|
||||
#admin.site.register(model, Admin)
|
||||
|
||||
#return model
|
||||
|
||||
|
||||
def copy_field(field):
|
||||
"""
|
||||
Instantiate a new field, with all of the values from the old one, except
|
||||
the to and to_field in the case of related fields.
|
||||
|
||||
This taken from http://www.djangosnippets.org/snippets/442/
|
||||
"""
|
||||
base_kw = dict([(n, getattr(field, n, '_null')) for n in \
|
||||
models.fields.Field.__init__.im_func.func_code.co_varnames])
|
||||
if isinstance(field, models.fields.related.RelatedField):
|
||||
rel = base_kw.get('rel')
|
||||
rel_kw = dict([(n, getattr(rel, n, '_null')) for n in \
|
||||
rel.__init__.im_func.func_code.co_varnames])
|
||||
if isinstance(field, models.fields.related.ForeignKey):
|
||||
base_kw['to_field'] = rel_kw.pop('field_name')
|
||||
base_kw.update(rel_kw)
|
||||
base_kw.pop('self')
|
||||
return field.__class__(**base_kw)
|
||||
|
|
|
|||
Loading…
Reference in a new issue