New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clarification on using translated field in admin list_display #98

Closed
darklow opened this Issue Feb 11, 2013 · 13 comments

Comments

Projects
None yet
5 participants
@darklow

darklow commented Feb 11, 2013

If i understood correctly - right now there is no way to show translated field in list unless you make some @property methods.

This looks a bit strange (wrong maybe) to me do like this - wrap every translatable property in method with @property decorator, it looks even more awkward if you have 5-6 translatable fields.

  1. Is this two the only way, or i am missing something?
    Any workaround, anyone?

  2. I saw while debugging that translatable fields appears in model class level, so i tried using directly self.title and it worked. Hope this is ok to do that or do i need to use self.safe_translation_getter('title') instead?

class PageAdmin(TranslatableAdmin):
    list_display = ('slug', 'title_', 'subtitle_')


class Page(TranslatableModel):
    slug = models.CharField(max_length=127)

    translations = TranslatedFields(
        title=models.CharField(max_length=127),
        subtitle=models.TextField(),
    )

    @property
    def title_(self):
        return self.title

    @property
    def subtitle_(self):
        return self.subtitle

Thanks

@ojii

This comment has been minimized.

Show comment
Hide comment
@ojii

ojii Feb 11, 2013

Collaborator

There's no real workaround for this pending a fix of #10. Djangos admin class validation is hardcoded and we cannot change it, so this would require some substantial work from our side.

Collaborator

ojii commented Feb 11, 2013

There's no real workaround for this pending a fix of #10. Djangos admin class validation is hardcoded and we cannot change it, so this would require some substantial work from our side.

@darklow

This comment has been minimized.

Show comment
Hide comment
@darklow

darklow Feb 11, 2013

What if use some alternative names, for list property such as
list_display_translatable = ('slug', 'title', 'subtitle')

Then we could iterate these fields, look which of them are translated and automatically rewrite them to something like:
list_display = ('slug', '_trans_title', '_trans_subtitle')

Now all we had to do is provide magic __getattribute__ method either for Model or Admin base classes which can handle names with trans and return proper values

It doesn't take too much special logic to implement this.
If you think this solution would be worth implementing i could make an example and possibly PR.
Otherwise this issue will keep appearing again and again

darklow commented Feb 11, 2013

What if use some alternative names, for list property such as
list_display_translatable = ('slug', 'title', 'subtitle')

Then we could iterate these fields, look which of them are translated and automatically rewrite them to something like:
list_display = ('slug', '_trans_title', '_trans_subtitle')

Now all we had to do is provide magic __getattribute__ method either for Model or Admin base classes which can handle names with trans and return proper values

It doesn't take too much special logic to implement this.
If you think this solution would be worth implementing i could make an example and possibly PR.
Otherwise this issue will keep appearing again and again

@ojii

This comment has been minimized.

Show comment
Hide comment
@ojii

ojii Feb 11, 2013

Collaborator

I had this in previous ML model libs of mine for Django, and tbh it sucks. maybe some magical metaclass + magic in __init__?

Note that this means we have to re-implement everything in d.c.admin.validation.

Collaborator

ojii commented Feb 11, 2013

I had this in previous ML model libs of mine for Django, and tbh it sucks. maybe some magical metaclass + magic in __init__?

Note that this means we have to re-implement everything in d.c.admin.validation.

@darklow

This comment has been minimized.

Show comment
Hide comment
@darklow

darklow Feb 11, 2013

Also we should keep in mind it would be nice if this solution also resolved fields and fieldset problems too.
It is not nice to have workarounds all the time like this:

class PageAdmin(PageAdminBase):
    def __init__(self, *args, **kwargs):
        super(PageAdmin, self).__init__(*args, **kwargs)
        self.fieldsets = (
            (None, {
                'fields': ('title', 'slug', 'publish_at', 'status')
            }),
            ('Body', {
                'fields': ('subtitle', 'body')
            }),
        )

darklow commented Feb 11, 2013

Also we should keep in mind it would be nice if this solution also resolved fields and fieldset problems too.
It is not nice to have workarounds all the time like this:

class PageAdmin(PageAdminBase):
    def __init__(self, *args, **kwargs):
        super(PageAdmin, self).__init__(*args, **kwargs)
        self.fieldsets = (
            (None, {
                'fields': ('title', 'slug', 'publish_at', 'status')
            }),
            ('Body', {
                'fields': ('subtitle', 'body')
            }),
        )
@darklow

This comment has been minimized.

Show comment
Hide comment
@darklow

darklow Feb 11, 2013

@ojii Din't understood precisely, could you give a simple example of what you meant. Magical metaclass and init for what exactly? Admin or Model?

darklow commented Feb 11, 2013

@ojii Din't understood precisely, could you give a simple example of what you meant. Magical metaclass and init for what exactly? Admin or Model?

@ojii

This comment has been minimized.

Show comment
Hide comment
@ojii

ojii Feb 11, 2013

Collaborator

forget about the model, the admin class is the only interesting thing here.

The metaclass would basically strip off all config values (list_display etc) and the __init__ would restore them. Somewhere in between, the whole thing needs to be validated though.

Collaborator

ojii commented Feb 11, 2013

forget about the model, the admin class is the only interesting thing here.

The metaclass would basically strip off all config values (list_display etc) and the __init__ would restore them. Somewhere in between, the whole thing needs to be validated though.

@darklow

This comment has been minimized.

Show comment
Hide comment
@darklow

darklow Feb 11, 2013

Thanks, will try.

darklow commented Feb 11, 2013

Thanks, will try.

@darklow

This comment has been minimized.

Show comment
Hide comment
@darklow

darklow Feb 12, 2013

@ojii Could you take a look on this solution i wrote.

  1. We don't need to extend __init__ at all - there is get_list_display() where we can do that.
  2. Replace translatable fields to fields with unserscore and add them to _list_display_translatable (validation still works on non-translatable fields - nothing has been changed)
  3. Add magic getters that returns correct keys from object
  4. This works, validates, keeps default list_display order and uses safe_translation_getter
  5. If we want, we can access language from request that get_list_display provides and make column name even more user friendly and show "Title EN", instead of just "Title"
class TestTranslatableAdmin(TranslatableAdmin):
    def get_list_display(self, request):
        self._list_display_translatable = []
        list_display = super(TestTranslatableAdmin, self).get_list_display(request)
        if list_display:
            list_display_safe = []
            fields = self.model._meta.translations_model._meta.get_all_field_names()
            for name in self.list_display:
                if name in fields:
                    name = '%s_' % name
                    self._list_display_translatable.append(name)
                list_display_safe.append(name)
            list_display = list_display_safe
        return list_display

    def __getattr__(self, name):
        if name in self._list_display_translatable:
            name = name[:-1]
            getter = lambda obj: obj.safe_translation_getter(name)
            getter.short_description = name
            return getter
        return super(TestTranslatableAdmin, self).__getattr__(name)

darklow commented Feb 12, 2013

@ojii Could you take a look on this solution i wrote.

  1. We don't need to extend __init__ at all - there is get_list_display() where we can do that.
  2. Replace translatable fields to fields with unserscore and add them to _list_display_translatable (validation still works on non-translatable fields - nothing has been changed)
  3. Add magic getters that returns correct keys from object
  4. This works, validates, keeps default list_display order and uses safe_translation_getter
  5. If we want, we can access language from request that get_list_display provides and make column name even more user friendly and show "Title EN", instead of just "Title"
class TestTranslatableAdmin(TranslatableAdmin):
    def get_list_display(self, request):
        self._list_display_translatable = []
        list_display = super(TestTranslatableAdmin, self).get_list_display(request)
        if list_display:
            list_display_safe = []
            fields = self.model._meta.translations_model._meta.get_all_field_names()
            for name in self.list_display:
                if name in fields:
                    name = '%s_' % name
                    self._list_display_translatable.append(name)
                list_display_safe.append(name)
            list_display = list_display_safe
        return list_display

    def __getattr__(self, name):
        if name in self._list_display_translatable:
            name = name[:-1]
            getter = lambda obj: obj.safe_translation_getter(name)
            getter.short_description = name
            return getter
        return super(TestTranslatableAdmin, self).__getattr__(name)
@mkoistinen

This comment has been minimized.

Show comment
Hide comment
@mkoistinen

mkoistinen Apr 7, 2013

Contributor

If it helps anyone landing here, here's what I've done for the list_display issue. Note, in my case, the slug, headline and story are all translatable, so there's not really any useful “human-readable keys” for the list_display, so, I solved it in unicode once, and used this for the human-readable key in the list_display. I don't really consider this a hack because I need a usable unicode() anyway and using it in the list_display is natural.

My model.py:

class Story(TranslatableModel):
  class Meta:
    app_label = 'news'
    verbose_name = _('story')
    verbose_name_plural = _('stories')

  story_date = models.DateField(_('story date'), default='')
  published  = models.BooleanField(_('published'), default=False)

  translations = TranslatedFields(
    headline=models.CharField(_('headline'), max_length=255, default=''),
    slug=models.SlugField(_('slug'), blank=True, default=''),
    story=models.TextField(_('story'), default=''),
    meta={'unique_together': [('language_code', 'slug')]},
  )

  def __unicode__(self):
    return self.safe_translation_getter('headline', 'Story: %s' % self.pk)

My admin.py

class StoryAdmin(TranslatableAdmin):
  list_display  = ('__unicode__', 'story_date', 'published', )
  …
Contributor

mkoistinen commented Apr 7, 2013

If it helps anyone landing here, here's what I've done for the list_display issue. Note, in my case, the slug, headline and story are all translatable, so there's not really any useful “human-readable keys” for the list_display, so, I solved it in unicode once, and used this for the human-readable key in the list_display. I don't really consider this a hack because I need a usable unicode() anyway and using it in the list_display is natural.

My model.py:

class Story(TranslatableModel):
  class Meta:
    app_label = 'news'
    verbose_name = _('story')
    verbose_name_plural = _('stories')

  story_date = models.DateField(_('story date'), default='')
  published  = models.BooleanField(_('published'), default=False)

  translations = TranslatedFields(
    headline=models.CharField(_('headline'), max_length=255, default=''),
    slug=models.SlugField(_('slug'), blank=True, default=''),
    story=models.TextField(_('story'), default=''),
    meta={'unique_together': [('language_code', 'slug')]},
  )

  def __unicode__(self):
    return self.safe_translation_getter('headline', 'Story: %s' % self.pk)

My admin.py

class StoryAdmin(TranslatableAdmin):
  list_display  = ('__unicode__', 'story_date', 'published', )
  …
@GabrielDumbrava

This comment has been minimized.

Show comment
Hide comment
@GabrielDumbrava

GabrielDumbrava Apr 25, 2013

@mkoistinen How would you exactly implement a slug prepopulated from a translated field using this method, because I don't get it.

Thanks!

@mkoistinen How would you exactly implement a slug prepopulated from a translated field using this method, because I don't get it.

Thanks!

@mkoistinen

This comment has been minimized.

Show comment
Hide comment
@mkoistinen

mkoistinen Apr 25, 2013

Contributor

Yeah, you gotta do another trick for that:

class StoryAdmin(TranslatableAdmin):
  list_display  = ('__unicode__', 'story_date', 'published', )

  #
  # Workaround for prepopulated_fields and fieldsets from here:
  # https://github.com/KristianOellegaard/django-hvad/issues/10#issuecomment-5572524
  #
  def __init__(self, *args, **kwargs):
    super(StoryAdmin, self).__init__(*args, **kwargs)
    self.prepopulated_fields = {'slug': ('headline',)}
Contributor

mkoistinen commented Apr 25, 2013

Yeah, you gotta do another trick for that:

class StoryAdmin(TranslatableAdmin):
  list_display  = ('__unicode__', 'story_date', 'published', )

  #
  # Workaround for prepopulated_fields and fieldsets from here:
  # https://github.com/KristianOellegaard/django-hvad/issues/10#issuecomment-5572524
  #
  def __init__(self, *args, **kwargs):
    super(StoryAdmin, self).__init__(*args, **kwargs)
    self.prepopulated_fields = {'slug': ('headline',)}
@GabrielDumbrava

This comment has been minimized.

Show comment
Hide comment
@GabrielDumbrava

GabrielDumbrava Apr 25, 2013

It works like a charm. Thanks!

It works like a charm. Thanks!

@spectras

This comment has been minimized.

Show comment
Hide comment
@spectras

spectras Jun 13, 2014

Collaborator

Hello there,

I will close this issue, for the following reasons:

  • original question got great answers.
  • translated fields in list_display work in Django 1.6+ as the validation was loosened to accept any value from the model, not just regular fields.
  • though the problem with admin validation remains, it is not the focus of this issue (see #10 for this)

If you feel it is needed, feel free to tell and I will re-open it.

Collaborator

spectras commented Jun 13, 2014

Hello there,

I will close this issue, for the following reasons:

  • original question got great answers.
  • translated fields in list_display work in Django 1.6+ as the validation was loosened to accept any value from the model, not just regular fields.
  • though the problem with admin validation remains, it is not the focus of this issue (see #10 for this)

If you feel it is needed, feel free to tell and I will re-open it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment