Django model string representation [duplicate] - python

Django's internationalization is very nice (gettext based, LocaleMiddleware), but what is the proper way to translate the model name and the attributes for admin pages? I did not find anything about this in the documentation:
http://docs.djangoproject.com/en/dev/topics/i18n/internationalization/
http://www.djangobook.com/en/2.0/chapter19/
I would like to have "Выберите заказ для изменения" instead of "Выберите order для изменения". Note, the 'order' is not translated.
First, I defined a model, activated USE_I18N = True in settings.py, run django-admin makemessages -l ru. No entries are created by default for model names and attributes.
Grepping in the Django source code I found:
$ ack "Select %s to change"
contrib/admin/views/main.py
70: self.title = (self.is_popup and ugettext('Select %s') % force_unicode(self.opts.verbose_name) or ugettext('Select %s to change') % force_unicode(self.opts.verbose_name))
So the verbose_name meta property seems to play some role here. Tried to use it:
class Order(models.Model):
subject = models.CharField(max_length=150)
description = models.TextField()
class Meta:
verbose_name = _('order')
Now the updated po file contains msgid 'order' that can be translated. So I put the translation in. Unfortunately running the admin pages show the same mix of "Выберите order для изменения".
I'm currently using Django 1.1.1.
Could somebody point me to the relevant documentation? Because google can not. ;-) In the mean time I'll dig deeper into the django source code...

Important things not mentioned in the Django documentation:
run django-admin compilemessages, e.g. as a part of your build
process. Thanks stevejalim!
apply django's ugettext_lazy() to model names ( Meta class and verbose_name )
attribute (model field verbose_name) names can also be translated with ugettext_lazy()
use lazy translation in your model metadata, otherwise the translation
happens while loading the model classes and the settings of your users,
especially the browser settings, will not be taken into account
I use some scoping for attribute names, e.g. separating the model name
and attribute names with a pipe. The same convention is used in
ruby-gettext. Background: attribute names like 'title' or 'name' translated
differently in the most languages depending on context. Example
'Book|title' -> 'Titel' or 'Buchtitel' in German. But
'Chapter|title' would be translated as 'Überschrift'.
Example using above principles:
from django.utils.translation import ugettext_lazy as _
class Order(models.Model):
subject = models.CharField(max_length=150, verbose_name = _('Order|subject'))
description = models.TextField( verbose_name = _('Order|description'))
class Meta:
verbose_name = _('order')
verbose_name_plural = _('orders')
Or is there a better way to translate the model and admin pages?
Either way we should enhance the Django documentation and fill the gap!

See https://automationpanda.com/2018/04/21/django-admin-translations/
It's author made an excellent work in showing how to master all django translation features step by step. It's much better than oficial documentation to me.

I use both ugettext with _ and ugettext_lazy with a double __. Consequently, the makemessages management command was only collecting the first ones.
To collect both _ and __ we can ask the gettext tool to collect more than the default strings enclosed in _( ). To do this, we override the makemessages command: https://docs.djangoproject.com/en/dev/topics/i18n/translation/#customizing-the-makemessages-command
# myapp/management/commands/makemessages.py
from django.core.management.commands import makemessages
class Command(makemessages.Command):
self.stdout.write("----> Using our custom makemessages command to collect both _ and double __")
xgettext_options = makemessages.Command.xgettext_options + ['--keyword=__'] # <-- added __
My models in Admin are now finally fully translated.

Related

Model name visible in Admin section

I am using Python 3.10.0 and Django 3.2.9
I created a new app in my django project, and then created a model called Posts. Everything works as intended except for how it's name is being displayed in the admin section of the website. It says "Postss" (double s in the end).
If I change the model name to "Post" or just one letter "P" for instance, django automatically adds "s" to the end of it's name, so it is being displayed as "Posts" or "Ps" then.
Other words, whatever the model name is, django automatically adds "s" in the end of it's name in the admin pannel of the website.
Is it possible to tell django I don't want that "s" to be added? How?
Thanks a mil, guys.
Normally models have singular names, so Post, not Posts. You thus might want to consider renaming your model.
If you still want to use Posts as model name, you can specify the verbose name of the model, and the plural verbose name with the verbose_name [Django-doc] and the verbose_name_plural model options [Django-doc] respectively:
class Posts(models.Model):
# …
class Meta:
verbose_name = 'post'
verbose_name_plural = 'posts'
But as said before, if you rename your model Post, Django will convert the PerlCase to a name with spaces and as plural append the name with an s.

Django ArrayField - Choices from a class of constants - ERROR fields.E005

I have a file with constants declared inside classes:
class LanguageChoices:
EN = "English"
FR = "French"
#classmethod
def choices(cls):
return (
(cls.EN, _("English")),
(cls.FR, _("French")),
)
And my models:
from django.contrib.postgres.fields import ArrayField
from apps.users.constants import LanguageChoices
class Data(models.Model):
language = ArrayField(
models.CharField(
max_length=30, choices=LanguageChoices.choices()),
blank=True, null=True
)
)
When I try to run migrations it run into this error (this happens even if I erase all previous migration files and use a new database):
SystemCheckError: System check identified some issues:
ERRORS:
users.Data.language: (postgres.E001) Base field for array has errors:
'choices' must be an iterable containing (actual value, human readable name) tuples. (fields.E005)
ERROR: 1
Any ideas?
Well, turns out the code is good, there was a little comma after one of the values (like EN = "English",) right in the middle. VSCode pulled no warns and my vision played me some tricks and had hide it from me.

How to dynamically make an existing non-abstract django model, abstract?

I think I have a more or less unorthodox and hackish question for you. What I currently have is django project with multiple apps.
I want to use a non-abstract model (ModelA) of one app (app1) and use it in another app (app2) by subclassing it. App1's models
should not be migrated to the DB, I just want to use the capabilities of app1 and it's model classes, by extending its functionality and logic.
I achieved that by adding both apps to settings.INSTALLED_APPS, and preventing app1's models being migrated to the DB.
INSTALLED_APPS += (
'App1',
'App2',
)
# This is needed to just use App1's models
# without creating it's database tables
# See: http://stackoverflow.com/a/35921487/1230358
MIGRATION_MODULES = {
'App1': None,
}
So far so good, ugly and hackish, I know... The remaining problem is now that most of app1's models are non-abstract (ModelA) and if I try
to subclass them, none of the ModelA's fields get populated to the db into the table named app2_modelb. This is clear to me, because I excluded the App1 from
migrating to the DB and therefore the table app1_modela is completely missing in the DB.
My idea now was to clone ModelA, preserve all its functionallity, and changing it's Meta information from non-abstract to abstract (ModelB.Meta.abstract = True).
I hope that by this, all the original fields of ModelA will be inherited to ModelB and can be found in its respective DB table and columns (app1_modelb).
What I have right now is:
# In app1 -> models.py
class ModelA(models.Model):
title = models.CharField(_('title'), max_length=255)
subtitle = models.CharField(_('subtitle'), max_length=255)
class Meta:
abstract = False # just explicitly for demonstration
# In app2 -> models.py
from app1.models import ModelA
class ModelB(ModelA):
pass
# Just extending ModelAdoes not create the fields title and subtitle fields in app2_modelb
# because ModelA.meta.abstract = False
My current way (pseudo code) to make an existing non-abstract model abstract looks like this:
# In app2 -> models.py
from app1.models import ModelA
def get_abstract_class(cls):
o = dict(cls.__dict__)
o['_meta'].abstract = True
o['_meta'].app_label = 'app2'
o['__module__'] = 'app2.models'
#return type('Abstract{}'.format(cls.__name__), cls.__bases__, o)
return type('Abstract{}'.format(cls.__name__), (cls,), o)
ModelB = get_abstract_class(ModelA)
class ModelC(ModelB):
# title and subtitle are inherited from ModelA
description = models.CharField(_('description'), max_length=255)
This does not work, and after this lengthy description my (simple) question would be, if and how is it possible to clone a non-abstract model class preserving all its functionality and how to change it to be abstract?
Just to be clear. All upper fuzz is about, that I can't change any code in app1. May it be that app1 is a django app installed via pip.
Why not, in app1
AbstractBaseModelA(models.Model):
# other stuff here
class Meta:
is_abstract=True
ModelA(AbstractBaseModelA):
# stuff
in app2:
MobelB(AbstractBaseModelA):
# stuff
Sorry if I've misunderstood your aims, but I think the above should achieve the same end result.

Django: GenericForeignKey and unique_together

In the application I'm working on I'm trying to share access tokens within a company. Example: a local office can use the headquarter's tokens to post something on their Facebook page.
class AccessToken(models.Model):
"""Abstract class for Access tokens."""
owner = models.ForeignKey('publish.Publisher')
socialMediaChannel = models.IntegerField(
choices=socialMediaChannelList, null=False, blank=False
)
lastUpdate = models.DateField(auto_now=True)
class Meta:
abstract = True
Since Facebook, Twitter and other social media sites handle access tokens in their own way I made and abstract class AccessToken. Each site gets its own class e.g.
class FacebookAccessToken(AccessToken):
# class stuff
After doing some reading I found out that I must use a GenericForeignKey to point to classes that inherit AccessToken. I made the following class:
class ShareAccessToken(models.Model):
"""Share access tokens with other publishers."""
sharedWith = models.ForeignKey('publish.Publisher')
sharedBy = models.ForeignKey(User)
# for foreignkey to abstract model's children
contentType = models.ForeignKey(ContentType)
objectId = models.PositiveIntegerField()
contentObject = GenericForeignKey('contentType', 'objectId')
class Meta:
unique_together = (('contentObject', 'sharedWith'))
When I run the django test server I get the following error:
core.ShareAccessToken: (models.E016) 'unique_together' refers to field
'contentObject' which is not local to model 'ShareAccessToken'. HINT:
This issue may be caused by multi-table inheritance.
I don't understand why I get this error, first time using GenericForeignKey. What am I doing wrong?
If there is a smarter way to share the access tokens I would love to hear about it.
Your use of the generic foreign key in this situation is correct.
The error is coming from your unique_together declaration in your model. unique_together can only be used with columns that exist in the database. Since contentObject is not a real column, Django complains about the constraint.
Instead, you can do the following:
unique_together = (('contentType', 'contentId', 'sharedWidth'),)
This is equivalent to what you had defined in your question because contentObject is really just the combination of contentType and contentId behind the scenes.

Getting error with django-taggit with admin

In a Django project I installed django_taggit. I'm getting this error when I syncdb my project.
AttributeError: 'TaggableManager' object has no attribute 'related'
My models.py something like this...
from taggit.managers import TaggableManager
class Post(models.Model):
title = models.CharField(max_length=100)
tags = TaggableManager()
and admin.py something like this...
from django.contrib import admin
admin.site.register(Post)
Django admin is trying to use the TaggableManager to manage your post objects. You need to be careful when using custom managers; as the docs specify:
If you use custom Manager objects, take note that the first Manager Django encounters (in the order in which they’re defined in the model) has a special status. Django interprets the first Manager defined in a class as the “default” Manager, and several parts of Django (including dumpdata) will use that Manager exclusively for that model. As a result, it’s a good idea to be careful in your choice of default manager in order to avoid a situation where overriding get_query_set() results in an inability to retrieve objects you’d like to work with.
An easy way to get around this is to manually specify Post.objects first:
class Post(models.Model):
title = models.CharField(max_length=100)
objects = models.Manager()
tags = TaggableManager()

Categories