Override default Django translations - django

I have a template with this:
{% trans "Log out" %}
This is translated automatically by Django to Spanish as Terminar sesión. However I would like to translate it as Cerrar sesión.
I have tried to add this literal to the .po file, however I get an error saying this literal is duplicated when I compile the messages.
Is there a way to change/override default Django translations?
Thanks.

This is what worked for me:
create a file in your app folder which will hold django messages for which translations need to be overridden, e.g. django_standard_messages.py
in django lib folder or in django.po files find the message (string) that needs to be overridden, e.g. django.forms/fields.py has message _(u"This field is required.") which we want to translate to german differently
in django_standard_messages.py add all such messages like this:
# coding: utf-8
_ = lambda s: s
django_standard_messages_to_override = [
_("This field is required."),
...
]
Translate the file (makemessages, compilemessages) - makemessages will add added django standard messages in your application .po file, find them and translate, run compilemessages to update .mo file
tryout
The logic behind: (I think ;) ) - when ugettext function searches translation for one message (string), there are several .po/.mo files that needs to be searched through. The first match is used. So, if our local app .po/.mo is first in that order, our translations will override all other (e.g. django default).
Alternative
When you need to translate all or most of django default messages, the other possibility (which I didn't tried) is to copy default django .po file in our locale or some other special folder, and fix translations and register the folder (if new) in LOCALE_PATHS django settings file as first entry in the list.
The logic behind: is the very similar as noted in previous section.

Based on Robert Lujo answer, his alternative is totally working. And IMO simpler (keep the overriden locales in a special .po file only). Here are the steps:
Add an extra path to the LOCALE_PATHS Django settings.
LOCALE_PATHS = (
# the default one, where the makemessages command will generate the files
os.path.join(BASE_DIR, 'myproject', 'locale'),
# our new, extended locale dir
os.path.join(BASE_DIR, 'myproject', 'locale_extra'),
)
find the original Django (or 3rd party) string to be translated
ex.: "recent actions" for the Django admin 'recent actions' block
Add the new .po file "myproject/locale_extra/en/LC_MESSAGES/django.po" with the alternative translation :
msgid "Recent actions"
msgstr "Last actions"
Compile your messages as usual

The easiest way is to collect the .po file found in the django.contrib.admin locale folder and re-compiling it (you can use POEdit for doing so).
You could also override the django.contrib.admin templates by putting them in your projects templates folder (for example: yourproject/templates/admin/change_form.html) then running makemessages from the project root (although this is no longer supported for django 1.4 alpha if i'm correct)
edit: Robert Lujo's answer is the clean method

This is another solution we deployed. It involved monkey patching the _add_installed_apps_translations method of the DjangoTranslation class to prioritize the translations of the project apps over the translations of the Django apps.
# patches.py
from __future__ import absolute_import, unicode_literals
import os
from django.apps import apps
from django.core.exceptions import AppRegistryNotReady
from django.utils.translation.trans_real import DjangoTranslation
def patchDjangoTranslation():
"""
Patch Django to prioritize the project's app translations over
its own. Fixes GitLab issue #734 for Django 1.11.
Might needs to be updated for future Django versions.
"""
def _add_installed_apps_translations_new(self):
"""Merges translations from each installed app."""
try:
# Django apps
app_configs = [
app for app in apps.get_app_configs() if app.name.startswith('django.')
]
# Non Django apps
app_configs = [
app for app in apps.get_app_configs() if not app.name.startswith('django.')
]
app_configs = reversed(app_configs)
except AppRegistryNotReady:
raise AppRegistryNotReady(
"The translation infrastructure cannot be initialized before the "
"apps registry is ready. Check that you don't make non-lazy "
"gettext calls at import time.")
for app_config in app_configs:
localedir = os.path.join(app_config.path, 'locale')
if os.path.exists(localedir):
translation = self._new_gnu_trans(localedir)
self.merge(translation)
DjangoTranslation._add_installed_apps_translations = _add_installed_apps_translations_new
Then in the .ready() method of your main app, call patchDjangoTranslation:
from .patches import patchDjangoTranslation
class CommonApp(MayanAppConfig):
app_namespace = 'common'
app_url = ''
has_rest_api = True
has_tests = True
name = 'mayan.apps.common'
verbose_name = _('Common')
def ready(self):
super(CommonApp, self).ready()
patchDjangoTranslation() # Apply patch
The main change are these lines:
# Django apps
app_configs = [
app for app in apps.get_app_configs() if app.name.startswith('django.')
]
# Non Django apps
app_configs = [
app for app in apps.get_app_configs() if not app.name.startswith('django.')
]
app_configs = reversed(app_configs)
The original are:
app_configs = reversed(list(apps.get_app_configs()))
Instead of interpreting the translations of the apps in the order they appear in the INSTALLED_APPS setting, this block outputs the list of apps placing the project apps before the Django apps. Since this only happens when determining the translation to use, it doesn't affect any other part of the code and no other changes are necessary.
It works on Django version 1.11 up to 2.2.

Related

Django / app import problem from submodule

I'm writing my own Django app, and trying to import submodule from my core library like that:
INSTALLED_APPS = [
'django.contrib.admin',
...
'core.login',
]
And interpreter gives me:
django.core.exceptions.ImproperlyConfigured:
Cannot import 'login'.
Check that 'core.login.apps.CustomloginConfig.name' is correct.
So login.apps looks like that
from django.apps import AppConfig
class CustomloginConfig(AppConfig):
name = 'login'
Are there any rules how I can edit this files to start Django properly?
apps.py file needs to be as so
from django.apps import AppConfig
class CustomloginConfig(AppConfig):
name = 'core.login'
This is where you tell django that I have registered this app 'core.login' and where to find it.
If login folder is in a core folder, then the above should work.
I think theres a lot of django apps out there that have organized things this way.
One being Kiwi but Im sure theres many others.

Migrating models of dependencies when changing DEFAULT_AUTO_FIELD

I'm using Django 3.2. I've changed added this line to settings.py:
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
I then ran these commands:
$ python manage.py makemigrations
$ python manage.py migrate
The makemigrations command creates new migration files for my apps, not just the apps that I have created, but also in my dependencies. For example, I'm using django-allauth, and this file was created in my virtual environment (virtualenv):
.venv/lib/python3.8/site-packages/allauth/account/migrations/0003_auto_20210408_1526.py
This file is not shipped with django-allauth. When I deploy this application from git, this file is not included.
What should I do instead? How can I switch DEFAULT_AUTO_FIELD without the need to create new migration files for dependencies like django-allauth?
Ideally, your third party dependencies would include this line in the config found in apps.py:
from django.apps import AppConfig
class ExampleConfig(AppConfig):
default_auto_field = 'django.db.models.AutoField'
While waiting for upstream dependencies to update their apps.py or migration files, you can override the app config yourself. If it doesn't exist already, create an apps.py file in your main app directory (eg: project/apps.py), and override the config of a dependency. In this example, I'm overriding the config of django-allauth:
from allauth.account.apps import AccountConfig
from allauth.socialaccount.apps import SocialAccountConfig
class ModifiedAccountConfig(AccountConfig):
default_auto_field = 'django.db.models.AutoField'
class ModifiedSocialAccountConfig(SocialAccountConfig):
default_auto_field = 'django.db.models.AutoField'
Then modify INSTALLED_APPS in settings.py to look like this, replacing the old entries for django-allauth in this example:
INSTALLED_APPS = [
# ....
# replace: "allauth.account", with
"projectname.apps.ModifiedAccountConfig",
# replace: "allauth.socialaccount", with
"projectname.apps.ModifiedSocialAccountConfig",
]
If the dependency doesn't have an apps.py file to override, you can still create an AppConfig sub-class in project/apps.py like this:
from django.apps import AppConfig
class ModifiedExampleDependencyConfig(AppConfig):
name = 'exampledependency' # the python module
default_auto_field = 'django.db.models.AutoField'
Now when you run python manage.py makemigrations, no migration files should be created for the dependencies.
I work on a big project, we upgraded Django from 2.2. to 3.2 and then have got a need to create all new models with Big Integer (Int8) (PostgreSQL) field instead of default Integer (Int4).
When I defined it in settings.py:
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
I got the same problem, but with own apps - Django tried to make me to migrate 135 models I had, but I didn't want to do it. I only wanted to create new models with BigInt and manipuate olds manually.
I found the next solution. I changed the field to custom:
DEFAULT_AUTO_FIELD = 'project.db.models.CustomBigAutoField'
And then overrided its deconstruction:
from django.db import models
class CustomBigAutoField(models.BigAutoField):
"""Int8 field that is applied only for new models."""
def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
if getattr(self, 'model', None):
path = 'django.db.models.AutoField'
return name, path, args, kwargs
As I discovered, fields of new models don't have a back reference to their models, so path wouldn't be overridden for them.
We override path because Django checks whether a model is changed by a key, that includes the path to this field. So we deceive Django and it thinks that existing model didn't changed.
I might not see the whole picture, but I tested it with different existing and new models and it worked for me. If someone tells me why this solution is bad, I'd be grateful.

Django executing tests for app not in INSTALLED_APPS

Under my Django project there are a few apps and all of them have unit tests. One of them that I'm working right now is supposed to be included only in dev/stage environments, so I'm enabling it using a environment variable.
When this variable is present it is added to INSTALLED_APPS and it is working just fine, the problem is that Django is executing the tests for this app even when it is not in INSTALLED_APPS, and it fails with the following message:
ImportError: Failed to import test module: debug.tests.unit.test_services`
...(traceback information)...
RuntimeError: Model class debug.models.Email doesn't declare an explicit app_label and isn't in an application in INSTALLED_APPS.
When I define the app_label in the class Meta of models in this app the error is different, it says it can't find a table, I assume that this is because the app is not in INSTALLED_APPS, so it's migrations are not executed.
OperationalError: no such table: debug_email
I'm not sure why Django executes the tests for all apps, but not it's migrations.
Am I missing something from Django configuration for tests?
https://docs.python.org/3/library/unittest.html#unittest.TestLoader.discover says:
If load_tests exists then discovery does not recurse into the package, load_tests is responsible for loading all tests in the package.
So in the lowest __init__.py in your app which you don't always want run:
from django.apps import apps
def load_tests(loader, tests, pattern):
from django.conf import settings
if apps.is_installed("your_dev_app"):
# Actually load the tests - thanks to #barney-szabolcs
return loader.discover(start_dir=dirname(abspath(__file__)), pattern=pattern)
You need to return the discovered tests in load_tests.
So, adding to #DaveLawrence's answer, the complete code is:
# your_dev_app/__init__.py
from django.apps import apps
from os.path import dirname, abspath
def load_tests(loader, tests, pattern):
"""
loads tests for your_dev_app if it is installed.
"""
from django.conf import settings
if apps.is_installed("your_dev_app"):
return loader.discover(start_dir=dirname(abspath(__file__)), pattern=pattern)
When you run:
python manage.py test
the command will look per default recursive for all files with the pattern test*.py in the working directory. It isn't affected by INSTALLED_APPS in settings.py.
You can specify a certain app to test it:
python manage.py test app_label
or specify a path:
python manage.py test myapp/tests
If you want to exclude some tests you can tag them and use the option --exclude-tag.
Run python manage.py test --help to get information on all options.
The official documentation gives a lot of information on the different possibilities how to run the tests.
EDIT:
If you have apps that are required only in the development environment, but not in the production, you could split your settings.py. One possible solution would be to outsource all development settings into a file local_settings.py and exclude it from versioning or from the production branch, i.e. don't push it in the production environment.
local_settings.py
DEBUG = True
INSTALLED_APPS += (
# Django Debug Toolbar would be for example
# used only in development
'debug_toolbar',
'your dev app',
)
settings.py
try:
from .local_settings import *
except ImportError:
pass

Django - split views into separate files while maintaining views.py

Hopefully this is a simple noob question. I'm working on my first large(ish) Django project. The first engineer on the project was following the default Django code layout. As things have grown we've recently split out models into their own directory with one file per model.
It's time to start doing the same for views. However, I don't want/need to do it all in one go. I'd like to just start moving things out of the default views.py one by one as I work on them. However, I'm having difficulty getting urls.py to work with both a views directory and views.py
Is this just going to cause a naming collision when I try to import 'views' in my urls.py file? Is the simple answer just to call "views" something else while I make the transition? Or just bite the bullet and do it all at once?
The direct answer is yes, you have a python naming collision. See for example Python Import Class With Same Name as Directory
You don't need to do it all as once though -- you can simply rename / move your views.py file or your new views directory. Moving the new directory would likely be easiest, then you won't have change your existing url routes.
There's nothing special about views.py files, as long as your urls.py points to the appropriate function, you can place them anywhere, and call them anything.
I recommend creating feature sub-directories within your app folder on your Django project. Then, simply import them from the new directory using the app.views namespace by using the from ... import * incantation. This will import any views without the module name prepended (e.g. what would normally be referenced by app_name.views.feature_one.view_class becomes app_name.views.view_class like you want). See below:
# app_name/views.py
from feature_one.views import *
from feature_two.views import *
# ... copy views from here
# new file: app_name/feature_one/views.py
# ... paste some views here
# new file: app_name/feature_two/views.py
# ... paste some other views here
# new file: app_name/feature_one/__init__.py
# ... this file can be blank. required for importing "feature_one" like a module
# new file: app_name/feature_two/__init__.py
# ... this file can be blank. required for importing "feature_two" like a module
Now, while your views will be spread across sub-directories, they are all imported with the same names into app_name.views so you can still reference the same names in urls.py despite having moved some views to other files.
Since you have divided models into their sub-apps, you can define urls specific to these sub-apps in the created sub directories and set up a url hierarchy. This is how your default urls.py will look like:
from django.conf.urls import include, patterns, url
urlpatterns = patterns('',
# ... snip ...
url(r'^comments/', include('your_website.comments.urls')),
url(r'^community/', include('your_website.community.urls')),
url(r'^contact/', include('your_website.contact.urls')),
# ... snip ...
)
where comments, community and contact are your newly created sub-apps/sub-directories. More info on how the url dispatcher works, here

Version number in Django applications

I'm working on a Django application and I want to display the version of the application (such that people, who find bugs know the version of the application and can provide better bug reports).
Is there a universally accepted way to store version number in Django (I mean the version of my application, not Django) ?
I was looking for this exact same question, and found your question. The answer you accepted is not quite satisfactory to me.
I am working with django debugtoolbar, in there you can also show all versions of the apps used. I was wondering how to get the versions of my custom applications to show there as well.
Looking a bit further I found this question and answer:
How to check the version of a installed application in Django in running time?
This answer however does not tell me where to put this __version__
So I looked in to an open application, which does show up in django toolbar.
I looked in to the django restframework code, there I found out:
the version is put in the __init__.py file
(see https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/init.py)
and it is put here as:
__version__ = '2.2.7'
VERSION = __version__ # synonym
And after this, in his setup.py, he gets this version from this __init__.py :
see: https://github.com/tomchristie/django-rest-framework/blob/master/setup.py
like this:
import re
def get_version(package):
"""
Return package version as listed in `__version__` in `init.py`.
"""
init_py = open(os.path.join(package, '__init__.py')).read()
return re.match("__version__ = ['\"]([^'\"]+)['\"]", init_py).group(1)
version = get_version('rest_framework')
When using buildout and zestreleaser:
By the way, I am using buildout and zest.releaser for building and versioning.
In this case, above is a bit different (but basically the same idea):
see http://zestreleaser.readthedocs.org/en/latest/versions.html#using-the-version-number-in-setup-py-and-as-version
The version in setup.py is automatically numbered by setup.py, so in __init__.py you do:
import pkg_resources
__version__ = pkg_resources.get_distribution("fill in yourpackage name").version
VERSION = __version__ # synonym
There are many places where you can store your app version number and a few methods that allow you to show it in django templates. A lot depends on the release tool you're using and your own preferences.
Below is the approach I'm using in my current project.
Put the version number into version.txt
I'm storing the app version number in the version.txt file. It's one of the locations the zest.releaser release tool (that I'm using) takes into account while doing a release.
The whole content of version.txt is just the app version number, for example: 1.0.1.dev0
Read the number to a variable in settings.py
...
with open(version_file_path) as v_file:
APP_VERSION_NUMBER = v_file.read()
...
Create a custom context processor
This paragraph and the following ownes are based on the wonderful answer by bcchun to Can I access constants in settings.py from templates in Django?
A custom context processor will allow you to add the app version number to the context of every rendered template. You won't have to add it manually every time you render a template (and usually you'll want to have the version number somewhere in the footer of every page).
Create context_processors.py file in your app directory:
from django.conf import settings
def selected_settings(request):
# return the version value as a dictionary
# you may add other values here as well
return {'APP_VERSION_NUMBER': settings.APP_VERSION_NUMBER}
Add the context processor to settings.py
TEMPLATES = [{
...
'OPTIONS': {
'context_processors': [
...
'your_app.context_processors.selected_settings'
],
},
}]
Use RequestContext or render in views
RequestContext and render populate the context with variables supplied by context_processors you set in settings.py.
Example:
def some_view(request):
return render(request, 'content.html')
Use it in a template
...
<div>{% trans 'App version' %}:{{APP_VERSION_NUMBER}}</div>
....
For me the best result/approach is to use the __init__.py on the project folder, such as
.
├── project_name
│   ├── __init__.py
and later check using the standar way, as said in (PEP396)
>>> import project_name
>>> project_name.__version__
'1.0.0'
I solved this by adding a templatetag to my django project:
in proj/templatetags, added version.py:
from django import template
import time
import os
register = template.Library()
#register.simple_tag
def version_date():
return time.strftime('%m/%d/%Y', time.gmtime(os.path.getmtime('../.git')))
Then, in my base.html (or whichever template), adding:
{% load version %}
<span class='version'>Last Updated: {% version_date %}</span>
If using GIT for source versioning, you might want manual promotion of stable
releases, and automatic numbering for development commits.
One why to obtain this in a Django project is:
In "PROJECT/_ init _.py" define:
__version__ = '1.0.1'
__build__ = ''
Then in setting.py do:
import os
import subprocess
import PROJECT
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
try:
PROJECT.__build__ = subprocess.check_output(["git", "describe", "--tags", "--always"], cwd=BASE_DIR).decode('utf-8').strip()
except:
PROJECT.__build__ = PROJECT.__version__ + " ?"
Thus, PROJECT._ build _ will show:
v1.0.1 in stable releases
and
v1.0.1-N-g8d2ec45
when the most recent tag doesn't point to the last commit (where N counts the number of additional commits after tag, followed by commit signature)
It seems the settings file would be a reasonable location to store the version number. I don't believe there is any Django accepted way to store a version number of your personal application. It seems like an application specific variable that you should define.
For more information on getting the version number out of svn: Getting SVN revision number into a program automatically
Not for Django applications, per se, but for Python modules, yes. See PEP 396, PEP 386 and the verlib library (easy_install verlib).
(I’d elaborate, but I just now discovered this, myself.)
Version info is typically maintained in git commit tags. Else, even git commits and last updated time is a good indicator of which version is running and when it was deployed.
For those using django-rest-framework and only having an API, you can return both of these; "last updated" as well as "last git commit" using an /api/version endpoint:
In views.py:
import os
import time
import subprocess
import json
class VersionViewSet(ViewSet):
def list(self, request):
# ['git', 'describe', '--tags'] # use this for named tags (version etc)
# ['git', 'describe', '--all', '--long'] # use this for any commit
# git log -1 --pretty=format:"Last commit %h by %an, %ar ("%s")"
# {"commit_hash": "%h", "full_commit_hash": "%H", "author_name": "%an", "commit_date": "%aD", "comment": "%s"}
FILE_DIR = os.path.dirname(os.path.abspath(__file__))
git_command = ['git', 'log', '-1', '--pretty={"commit_hash": "%h", "full_commit_hash": "%H", "author_name": "%an", "commit_date": "%aD", "comment": "%s"}']
git_identifier = subprocess.check_output(git_command, cwd=FILE_DIR).decode('utf-8').strip()
git_identifier = json.loads(git_identifier)
last_updated = time.strftime('%a, %-e %b %Y, %I:%M:%S %p (%Z)', time.localtime(os.path.getmtime('.git'))).strip()
return Response({
"last_updated": last_updated,
"git_commit": git_identifier
}, status=200)
In urls.py:
from myapp.views import VersionViewSet
router = routers.DefaultRouter()
...
router.register(r'version', VersionViewSet, base_name='version')
This creates the endpoint in line with the other endpoints in your API.
Output will be seen like this at http://www.example.com/api/version/:
HTTP 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
{
"last_updated": "Mon, 6 May 2019, 11:19:58 PM (IST)",
"git_commit": {
"commit_hash": "e265270",
"full_commit_hash": "e265270dda196f4878f4fa194187a3748609dde0",
"author_name": "Authorname",
"commit_date": "Mon, 6 May 2019 23:19:51 +0530",
"comment": "The git commit message or subject or title here"
}
}
I use this option __import__('project').VERSION or __import__('project').__version__. The version is put in the __init__.py file as everybody said, for example:
proyect_name
| __init__.py
# __init__.py file
VERSION = '1.0.0' # or __version__ = '1.0.0'
Now from everywhere you can get it:
# Error tracking settings
sentry_sdk.init(
...
release=__import__('cjvc_project').VERSION
)
I used a context processor and it looks like this:
import sys
sys.path.append('..')
from content_services import __version__
def get_app_version(request):
"""
Get the app version
:param request:
:return:
"""
return {'APP_VERSION': __version__}
Since the project name is content_services I have to change the sys path up 1 level so I can import it.
In case you use Git and version tagging you can display the application version in admin site header.
Create a version.py file in the project or any app module:
import os
import subprocess
FILE_DIR = os.path.dirname(os.path.abspath(__file__))
def get_version_from_git():
try:
return subprocess.check_output(['git', 'describe', '--tags'],
cwd=FILE_DIR).decode('utf-8').strip()
except:
return '?'
VERSION = get_version_from_git()
Add the version to admin site header in urls.py:
from django.contrib import admin
from django.utils.safestring import mark_safe
from utils import version
...
admin.site.site_header = mark_safe('MyApp admin <span style="font-size: x-small">'
f'({version.VERSION})</span>')
If you need to provide the version to external tools like Django Debug Toolbar, you can expose the version in project __init__.py as suggested above:
from utils import version
__version__ = version.VERSION
VERSION = __version__ # synonym