[damned-lies/develop] refactor: apply black and isort on all files
- From: Guillaume Bernard <gbernard src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [damned-lies/develop] refactor: apply black and isort on all files
- Date: Mon, 5 Sep 2022 15:42:48 +0000 (UTC)
commit 8a6fe6783cadac151b831600761e2d93a1c832ef
Author: Guillaume Bernard <associations guillaume-bernard fr>
Date: Mon Sep 5 17:07:01 2022 +0200
refactor: apply black and isort on all files
api/tests.py | 203 +--
api/urls.py | 42 +-
api/views.py | 224 ++--
common/backends.py | 4 +-
common/context_processors.py | 2 +-
common/fields.py | 5 +-
common/middleware.py | 4 +-
common/templatetags/list_to_columns.py | 4 +-
common/tests.py | 107 +-
common/utils.py | 61 +-
common/views.py | 95 +-
damnedlies/context_processors.py | 1 -
damnedlies/settings.py | 200 ++-
damnedlies/settings_tests.py | 8 +-
damnedlies/urls.py | 156 +--
docs/source/conf.py | 46 +-
feeds/urls.py | 6 +-
languages/admin.py | 3 +-
languages/management/commands/load-plurals.py | 218 ++--
languages/migrations/0001_initial.py | 23 +-
languages/models.py | 35 +-
languages/tests.py | 24 +-
languages/urls.py | 32 +-
languages/views.py | 139 +-
people/admin.py | 7 +-
people/forms.py | 107 +-
people/migrations/0001_initial.py | 62 +-
.../0002_set_use_gravatar_verbose_name.py | 10 +-
people/migrations/0003_person_avatar_service.py | 13 +-
people/migrations/0004_migrate_use_gravatar.py | 5 +-
.../migrations/0005_remove_person_use_gravatar.py | 6 +-
.../0006_remove_person_bugzilla_account.py | 6 +-
people/migrations/0007_person_auth_token.py | 8 +-
people/models.py | 83 +-
people/templatetags/people.py | 15 +-
people/tests.py | 227 ++--
people/urls.py | 36 +-
people/views.py | 84 +-
pyproject.toml | 8 +-
setup.py | 5 +-
stats/admin.py | 151 ++-
stats/doap.py | 40 +-
stats/forms.py | 55 +-
stats/management/commands/archive-release.py | 28 +-
stats/management/commands/compile-trans.py | 25 +-
stats/management/commands/run-maintenance.py | 2 +-
stats/management/commands/update-from-doap.py | 7 +-
stats/management/commands/update-stats.py | 68 +-
stats/management/commands/update-trans.py | 7 +-
stats/migrations/0001_initial.py | 368 ++++--
stats/migrations/0002_add_category_name.py | 20 +-
stats/migrations/0003_migrate_category_names.py | 40 +-
stats/migrations/0004_remove_old_cat_name.py | 20 +-
stats/migrations/0005_update_module_name_field.py | 21 +-
stats/migrations/0006_add_domain_branch_from_to.py | 31 +-
stats/migrations/0007_extend_bugs_base.py | 6 +-
stats/migrations/0008_domain_extra_its_dirs.py | 10 +-
.../migrations/0009_remove_null_on_text_fields.py | 96 +-
stats/migrations/0010_pot_method.py | 36 +-
stats/migrations/0011_migrate_pot_method.py | 22 +-
.../0012_remove_domain_pot_method_old.py | 6 +-
stats/migrations/0013_domain_layout.py | 12 +-
stats/migrations/0014_migrate_dir_to_layout.py | 10 +-
stats/migrations/0015_remove_domain_directory.py | 6 +-
stats/migrations/0016_removed_bugs_fields.py | 10 +-
stats/migrations/0017_pofile_path_relative.py | 14 +-
stats/migrations/0018_new_jsonfields.py | 22 +-
stats/migrations/0019_migrate_old_custom_files.py | 4 +-
stats/migrations/0020_remove_pofile_figures_old.py | 10 +-
stats/migrations/0021_release_name_unique.py | 6 +-
stats/models.py | 1374 ++++++++++----------
stats/potdiff.py | 9 +-
stats/repos.py | 78 +-
stats/templatetags/stats_extras.py | 152 ++-
stats/tests/fixture_factory.py | 143 +-
stats/tests/tests.py | 845 ++++++------
stats/tests/utils.py | 20 +-
stats/utils.py | 345 +++--
stats/views.py | 225 ++--
teams/admin.py | 18 +-
teams/forms.py | 64 +-
teams/migrations/0001_initial.py | 73 +-
teams/models.py | 93 +-
teams/tests.py | 175 ++-
teams/urls.py | 13 +-
teams/views.py | 108 +-
vertimus/admin.py | 18 +-
vertimus/feeds.py | 71 +-
vertimus/forms.py | 79 +-
vertimus/migrations/0001_initial.py | 278 ++--
vertimus/migrations/0002_state_person_blank.py | 8 +-
vertimus/migrations/0003_add_action_sent_to_ml.py | 10 +-
vertimus/migrations/0004_tables_to_utf8mb4.py | 7 +-
vertimus/migrations/0005_action_proxy_pofile.py | 31 +-
vertimus/migrations/0006_pofile_path_relative.py | 5 +-
.../migrations/0008_actions_on_delete_setnull.py | 16 +-
vertimus/models.py | 362 +++---
vertimus/templatetags/vertimus.py | 32 +-
vertimus/tests/tests.py | 627 ++++-----
vertimus/urls.py | 53 +-
vertimus/views.py | 322 ++---
101 files changed, 4667 insertions(+), 4464 deletions(-)
---
diff --git a/api/tests.py b/api/tests.py
index 482497ab..0ef7abf8 100644
--- a/api/tests.py
+++ b/api/tests.py
@@ -14,189 +14,194 @@ from vertimus.views import get_vertimus_state
class APITests(TestCase):
- fixtures = ['sample_data.json']
+ fixtures = ["sample_data.json"]
def test_home(self):
- response = self.client.get(reverse('api-home'))
- self.assertEqual(response.json(), {
- 'languages': '/api/v1/languages/',
- 'modules': '/api/v1/modules/',
- 'releases': '/api/v1/releases/',
- 'teams': '/api/v1/teams/',
- })
+ response = self.client.get(reverse("api-home"))
+ self.assertEqual(
+ response.json(),
+ {
+ "languages": "/api/v1/languages/",
+ "modules": "/api/v1/modules/",
+ "releases": "/api/v1/releases/",
+ "teams": "/api/v1/teams/",
+ },
+ )
def test_modules(self):
- response = self.client.get(reverse('api-modules'))
+ response = self.client.get(reverse("api-modules"))
result = response.json()
- self.assertEqual(result[0], {'name': 'gnome-hello', 'href': '/api/v1/modules/gnome-hello'})
+ self.assertEqual(result[0], {"name": "gnome-hello", "href": "/api/v1/modules/gnome-hello"})
def test_module(self):
- response = self.client.get(reverse('api-module', args=['gnome-hello']))
+ response = self.client.get(reverse("api-module", args=["gnome-hello"]))
result = response.json()
- self.assertEqual(result['vcs_web'], 'https://gitlab.gnome.org/GNOME/gnome-hello/')
- self.assertEqual(result['branches'], [{'name': 'master'}])
+ self.assertEqual(result["vcs_web"], "https://gitlab.gnome.org/GNOME/gnome-hello/")
+ self.assertEqual(result["branches"], [{"name": "master"}])
self.assertEqual(
- result['domains'][1],
- {'description': 'UI Translations', 'name': 'po', 'dtype': 'ui', 'layout': 'po/{lang}.po'}
+ result["domains"][1],
+ {"description": "UI Translations", "name": "po", "dtype": "ui", "layout": "po/{lang}.po"},
)
def test_module_branch(self):
- response = self.client.get(reverse('api-module-branch', args=['gnome-hello', 'master']))
+ response = self.client.get(reverse("api-module-branch", args=["gnome-hello", "master"]))
result = response.json()
self.assertEqual(
- result, {
- 'module': 'gnome-hello',
- 'branch': 'master',
- 'domains': [{
- 'name': 'po',
- 'statistics': {
- 'fr': {'fuzzy': 0, 'trans': 47, 'untrans': 0},
- 'it': {'fuzzy': 10, 'trans': 30, 'untrans': 7}
- }}, {
- 'name': 'help',
- 'statistics': {
- 'fr': {'fuzzy': 0, 'trans': 20, 'untrans': 0},
- 'it': {'fuzzy': 0, 'trans': 20, 'untrans': 0}
- }}
+ result,
+ {
+ "module": "gnome-hello",
+ "branch": "master",
+ "domains": [
+ {
+ "name": "po",
+ "statistics": {
+ "fr": {"fuzzy": 0, "trans": 47, "untrans": 0},
+ "it": {"fuzzy": 10, "trans": 30, "untrans": 7},
+ },
+ },
+ {
+ "name": "help",
+ "statistics": {
+ "fr": {"fuzzy": 0, "trans": 20, "untrans": 0},
+ "it": {"fuzzy": 0, "trans": 20, "untrans": 0},
+ },
+ },
],
- }
+ },
)
def test_teams(self):
- response = self.client.get(reverse('api-teams'))
+ response = self.client.get(reverse("api-teams"))
result = response.json()
- self.assertEqual(result[0], {'name': 'fr', 'href': '/api/v1/teams/fr', 'description': 'French'})
+ self.assertEqual(result[0], {"name": "fr", "href": "/api/v1/teams/fr", "description": "French"})
def test_team(self):
- response = self.client.get(reverse('api-team', args=['fr']))
+ response = self.client.get(reverse("api-team", args=["fr"]))
result = response.json()
- self.assertEqual(result['coordinators'], [{'name': 'John Coordinator'}])
+ self.assertEqual(result["coordinators"], [{"name": "John Coordinator"}])
def test_languages(self):
- response = self.client.get(reverse('api-languages'))
+ response = self.client.get(reverse("api-languages"))
result = response.json()
- self.assertEqual(result[0], {
- 'href': '/api/v1/teams/bem',
- 'locale': 'bem',
- 'name': 'Bemba',
- 'plurals': '',
- 'team__description': None
- })
+ self.assertEqual(
+ result[0],
+ {"href": "/api/v1/teams/bem", "locale": "bem", "name": "Bemba", "plurals": "",
"team__description": None},
+ )
def test_releases(self):
- response = self.client.get(reverse('api-releases'))
+ response = self.client.get(reverse("api-releases"))
result = response.json()
- self.assertEqual(result[0], {
- 'description': 'freedesktop.org (non-GNOME)',
- 'href': '/api/v1/releases/freedesktop-org',
- 'name': 'freedesktop-org'
- })
+ self.assertEqual(
+ result[0],
+ {
+ "description": "freedesktop.org (non-GNOME)",
+ "href": "/api/v1/releases/freedesktop-org",
+ "name": "freedesktop-org",
+ },
+ )
def test_release(self):
- response = self.client.get(reverse('api-release', args=['gnome-3-8']))
+ response = self.client.get(reverse("api-release", args=["gnome-3-8"]))
result = response.json()
- self.assertEqual(result['description'], 'GNOME 3.8 (stable)')
- self.assertEqual(
- result['languages'][0],
- {'href': '/api/v1/releases/gnome-3-8/languages/bem', 'locale': 'bem'}
- )
- self.assertEqual(result['branches'][0], 'gnome-3-8 (zenity)')
- self.assertEqual(result['statistics'], '/api/v1/releases/gnome-3-8/stats')
+ self.assertEqual(result["description"], "GNOME 3.8 (stable)")
+ self.assertEqual(result["languages"][0], {"href": "/api/v1/releases/gnome-3-8/languages/bem",
"locale": "bem"})
+ self.assertEqual(result["branches"][0], "gnome-3-8 (zenity)")
+ self.assertEqual(result["statistics"], "/api/v1/releases/gnome-3-8/stats")
def test_release_stats(self):
- response = self.client.get(reverse('api-release-stats', args=['gnome-3-8']))
+ response = self.client.get(reverse("api-release-stats", args=["gnome-3-8"]))
result = response.json()
- self.assertEqual(result['statistics'][0]['lang_name'], 'French')
+ self.assertEqual(result["statistics"][0]["lang_name"], "French")
self.assertEqual(
- result['statistics'][0]['ui'],
- {'translated_perc': 100, 'untranslated_perc': 0, 'translated': 183,
- 'fuzzy_perc': 0, 'untranslated': 0, 'fuzzy': 0}
+ result["statistics"][0]["ui"],
+ {
+ "translated_perc": 100,
+ "untranslated_perc": 0,
+ "translated": 183,
+ "fuzzy_perc": 0,
+ "untranslated": 0,
+ "fuzzy": 0,
+ },
)
def test_release_language(self):
- response = self.client.get(reverse('api-release-language', args=['gnome-3-8', 'fr']))
+ response = self.client.get(reverse("api-release-language", args=["gnome-3-8", "fr"]))
result = response.json()
- self.assertEqual(len(result['modules']), 4)
+ self.assertEqual(len(result["modules"]), 4)
def test_module_lang_stats(self):
- response = self.client.get(
- reverse('api-module-lang-stats', args=['gnome-hello', 'master', 'po', 'fr'])
- )
+ response = self.client.get(reverse("api-module-lang-stats", args=["gnome-hello", "master", "po",
"fr"]))
result = response.json()
- self.assertEqual(result['statistics'], {'untrans': 0, 'fuzzy': 0, 'trans': 47})
- self.assertEqual(result['pot_file'], '/POT/gnome-hello.master/gnome-hello.master.pot')
- self.assertEqual(result['po_file'], '/POT/gnome-hello.master/gnome-hello.master.fr.po')
+ self.assertEqual(result["statistics"], {"untrans": 0, "fuzzy": 0, "trans": 47})
+ self.assertEqual(result["pot_file"], "/POT/gnome-hello.master/gnome-hello.master.pot")
+ self.assertEqual(result["po_file"], "/POT/gnome-hello.master/gnome-hello.master.fr.po")
def test_reserve_translation(self):
translator = Person.objects.create(
- first_name='John', last_name='Translator',
- email='jt devnull com', username='translator'
+ first_name="John", last_name="Translator", email="jt devnull com", username="translator"
)
- team = Team.objects.get(name='fr')
+ team = Team.objects.get(name="fr")
Role.objects.create(team=team, person=translator)
- url = reverse('api-reserve', args=['gnome-hello', 'master', 'po', 'fr'])
+ url = reverse("api-reserve", args=["gnome-hello", "master", "po", "fr"])
# Test anonymous cannot post
response = self.client.post(url, data={})
- self.assertRedirects(response, reverse('login') + f'?next={url}')
+ self.assertRedirects(response, reverse("login") + f"?next={url}")
self.client.force_login(translator)
response = self.client.post(url, data={})
- self.assertEqual(response.json(), {'result': 'OK'})
+ self.assertEqual(response.json(), {"result": "OK"})
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].recipients(), [team.mailing_list])
state = State.objects.get(person=translator)
- self.assertEqual(state.name, 'Translating')
+ self.assertEqual(state.name, "Translating")
# Test cannot reserve a second time
response = self.client.post(url, data={})
self.assertEqual(response.status_code, 403)
def test_upload_translation(self):
translator = Person.objects.create(
- first_name='John', last_name='Translator',
- email='jt devnull com', username='translator'
- )
- somebody = Person.objects.create(
- email='some devnull com', username='somebody'
+ first_name="John", last_name="Translator", email="jt devnull com", username="translator"
)
- team = Team.objects.get(name='fr')
+ somebody = Person.objects.create(email="some devnull com", username="somebody")
+ team = Team.objects.get(name="fr")
Role.objects.create(team=team, person=translator)
Role.objects.create(team=team, person=somebody)
_, _, state = get_vertimus_state(
- Branch.objects.get(module__name='gnome-hello'),
- Domain.objects.get(module__name='gnome-hello', name='po'),
- Language.objects.get(locale='fr')
+ Branch.objects.get(module__name="gnome-hello"),
+ Domain.objects.get(module__name="gnome-hello", name="po"),
+ Language.objects.get(locale="fr"),
)
- url = reverse('api-upload', args=['gnome-hello', 'master', 'po', 'fr'])
+ url = reverse("api-upload", args=["gnome-hello", "master", "po", "fr"])
test_po = Path(__file__).parent.parent / "stats" / "tests" / "test.po"
# Test anonymous cannot post
- with test_po.open('rb') as fh:
- response = self.client.post(url, data={'file': File(fh)})
- self.assertRedirects(response, reverse('login') + f'?next={url}')
+ with test_po.open("rb") as fh:
+ response = self.client.post(url, data={"file": File(fh)})
+ self.assertRedirects(response, reverse("login") + f"?next={url}")
state.change_state(StateTranslating, person=translator)
# somebody cannot post translation if reserved by translator.
self.client.force_login(somebody)
- with test_po.open('rb') as fh:
- response = self.client.post(url, data={'file': File(fh)})
+ with test_po.open("rb") as fh:
+ response = self.client.post(url, data={"file": File(fh)})
self.assertEqual(response.status_code, 403)
self.client.force_login(translator)
- with test_po.open('rb') as fh:
- response = self.client.post(url, data={'file': File(fh)})
- self.assertEqual(response.json(), {'result': 'OK'})
+ with test_po.open("rb") as fh:
+ response = self.client.post(url, data={"file": File(fh)})
+ self.assertEqual(response.json(), {"result": "OK"})
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].recipients(), [team.mailing_list])
# Test upload with comment
state.change_state(StateTranslating, person=translator)
- with test_po.open('rb') as fh:
+ with test_po.open("rb") as fh:
data = {
- 'file': File(fh),
- 'comment': 'The comment',
+ "file": File(fh),
+ "comment": "The comment",
}
response = self.client.post(url, data=data)
- self.assertEqual(response.json(), {'result': 'OK'})
+ self.assertEqual(response.json(), {"result": "OK"})
action = Action.objects.last()
self.assertEqual(action.comment, "The comment")
diff --git a/api/urls.py b/api/urls.py
index e96f2a48..f1821f51 100644
--- a/api/urls.py
+++ b/api/urls.py
@@ -3,40 +3,36 @@ from django.urls import path
from . import views
complete_translatable_path = (
- 'modules/<name:module_name>/branches/<path:branch_name>/domains/<name:domain_name>'
- '/languages/<locale:lang>'
+ "modules/<name:module_name>/branches/<path:branch_name>/domains/<name:domain_name>"
"/languages/<locale:lang>"
)
urlpatterns = [
- path('', views.APIHomeView.as_view(), name='api-home'),
- path('modules/', views.ModulesView.as_view(), name='api-modules'),
- path('modules/<name:module_name>', views.ModuleView.as_view(), name='api-module'),
+ path("", views.APIHomeView.as_view(), name="api-home"),
+ path("modules/", views.ModulesView.as_view(), name="api-modules"),
+ path("modules/<name:module_name>", views.ModuleView.as_view(), name="api-module"),
path(
- 'modules/<name:module_name>/branch/<path:branch_name>',
+ "modules/<name:module_name>/branch/<path:branch_name>",
views.ModuleBranchView.as_view(),
- name='api-module-branch',
+ name="api-module-branch",
),
- path('teams/', views.TeamsView.as_view(), name='api-teams'),
- path('teams/<locale:team_name>', views.TeamView.as_view(), name='api-team'),
- path('languages/', views.LanguagesView.as_view(), name='api-languages'),
- path('releases/', views.ReleasesView.as_view(), name='api-releases'),
- path('releases/<name:release>', views.ReleaseView.as_view(), name='api-release'),
- path('releases/<name:release>/stats', views.ReleaseStatsView.as_view(), name='api-release-stats'),
+ path("teams/", views.TeamsView.as_view(), name="api-teams"),
+ path("teams/<locale:team_name>", views.TeamView.as_view(), name="api-team"),
+ path("languages/", views.LanguagesView.as_view(), name="api-languages"),
+ path("releases/", views.ReleasesView.as_view(), name="api-releases"),
+ path("releases/<name:release>", views.ReleaseView.as_view(), name="api-release"),
+ path("releases/<name:release>/stats", views.ReleaseStatsView.as_view(), name="api-release-stats"),
path(
- 'releases/<name:release>/languages/<locale:lang>', views.ReleaseLanguageView.as_view(),
- name='api-release-language'
- ),
- path(
- complete_translatable_path, views.ModuleLangStatsView.as_view(), name='api-module-lang-stats'
+ "releases/<name:release>/languages/<locale:lang>",
+ views.ReleaseLanguageView.as_view(),
+ name="api-release-language",
),
+ path(complete_translatable_path, views.ModuleLangStatsView.as_view(), name="api-module-lang-stats"),
# APIs to reserve translation and upload translation, as could be used by an external
# translation tool.
- path(f'{complete_translatable_path}/reserve', views.ReserveTranslationView.as_view(),
name='api-reserve'),
- path(f'{complete_translatable_path}/upload', views.UploadTranslationView.as_view(), name='api-upload'),
-
+ path(f"{complete_translatable_path}/reserve", views.ReserveTranslationView.as_view(),
name="api-reserve"),
+ path(f"{complete_translatable_path}/upload", views.UploadTranslationView.as_view(), name="api-upload"),
# Used by a GitLab webhook to signal a commit for that module/branch
path(
- 'modules/<name:module_name>/branches/<path:branch_name>/ping', views.rebuild_branch,
- name='api-module-rebuild'
+ "modules/<name:module_name>/branches/<path:branch_name>/ping", views.rebuild_branch,
name="api-module-rebuild"
),
]
diff --git a/api/views.py b/api/views.py
index 3e9a8943..9988ac09 100644
--- a/api/views.py
+++ b/api/views.py
@@ -20,13 +20,11 @@ from vertimus.views import get_vertimus_state
class SerializeListView(View):
def get(self, request, *args, **kwargs):
- return JsonResponse(
- self.serialize_qs(self.get_queryset().values(*['pk'] + self.fields)), safe=False
- )
+ return JsonResponse(self.serialize_qs(self.get_queryset().values(*["pk"] + self.fields)), safe=False)
def serialize_obj(self, obj, fields=None):
data = {field: obj[field] for field in fields}
- data['href'] = self.get_href(obj)
+ data["href"] = self.get_href(obj)
return data
def serialize_qs(self, qs):
@@ -43,44 +41,40 @@ class SerializeObjectView(SerializeListView):
class APIHomeView(View):
def get(self, *args, **kwargs):
- return JsonResponse({
- 'modules': reverse('api-modules'),
- 'teams': reverse('api-teams'),
- 'languages': reverse('api-languages'),
- 'releases': reverse('api-releases'),
- })
+ return JsonResponse(
+ {
+ "modules": reverse("api-modules"),
+ "teams": reverse("api-teams"),
+ "languages": reverse("api-languages"),
+ "releases": reverse("api-releases"),
+ }
+ )
class ModulesView(SerializeListView):
- fields = ['name']
+ fields = ["name"]
def get_queryset(self):
return Module.objects.filter(archived=False)
def get_href(self, obj):
- return reverse('api-module', args=[obj['name']])
+ return reverse("api-module", args=[obj["name"]])
class ModuleView(SerializeObjectView):
@property
def fields(self):
- return [
- f.name for f in Module._meta.get_fields()
- if not f.one_to_many and f.name != 'id'
- ]
+ return [f.name for f in Module._meta.get_fields() if not f.one_to_many and f.name != "id"]
def get_object(self):
- return get_object_or_404(Module, name=self.kwargs['module_name'])
+ return get_object_or_404(Module, name=self.kwargs["module_name"])
def serialize_obj(self, obj, **kwargs):
data = serialize_instance(obj, self.fields)
- data['branches'] = [
- serialize_instance(branch, ['name'])
- for branch in obj.get_branches()
- ]
- data['domains'] = [
- serialize_instance(domain, ['name', 'description', 'dtype', 'layout'])
- for domain in obj.domain_set.all().order_by('name')
+ data["branches"] = [serialize_instance(branch, ["name"]) for branch in obj.get_branches()]
+ data["domains"] = [
+ serialize_instance(domain, ["name", "description", "dtype", "layout"])
+ for domain in obj.domain_set.all().order_by("name")
]
return data
@@ -88,181 +82,185 @@ class ModuleView(SerializeObjectView):
class ModuleBranchView(SerializeObjectView):
def get_object(self):
return get_object_or_404(
- Branch.objects.select_related('module'),
- module__name=self.kwargs['module_name'], name=self.kwargs['branch_name']
+ Branch.objects.select_related("module"),
+ module__name=self.kwargs["module_name"],
+ name=self.kwargs["branch_name"],
)
def serialize_obj(self, branch, **kwargs):
data = {
- 'module': branch.module.name,
- 'branch': branch.name,
- 'domains': [],
+ "module": branch.module.name,
+ "branch": branch.name,
+ "domains": [],
}
for dom_name in branch.get_domains().keys():
stats = {}
for stat in Statistics.objects.filter(branch=branch, domain__name=dom_name,
language__isnull=False):
stats[stat.language.locale] = {
- 'trans': stat.translated(),
- 'fuzzy': stat.fuzzy(),
- 'untrans': stat.untranslated(),
+ "trans": stat.translated(),
+ "fuzzy": stat.fuzzy(),
+ "untrans": stat.untranslated(),
}
- data['domains'].append({'name': dom_name, 'statistics': stats})
+ data["domains"].append({"name": dom_name, "statistics": stats})
return data
class TeamsView(SerializeListView):
- fields = ['name', 'description']
+ fields = ["name", "description"]
def get_queryset(self):
- return Team.objects.all().order_by('name')
+ return Team.objects.all().order_by("name")
def get_href(self, obj):
- return reverse('api-team', args=[obj['name']])
+ return reverse("api-team", args=[obj["name"]])
class TeamView(SerializeObjectView):
@property
def fields(self):
- return [
- f.name for f in Team._meta.get_fields()
- if not f.one_to_many and f.name not in ['id', 'members']
- ]
+ return [f.name for f in Team._meta.get_fields() if not f.one_to_many and f.name not in ["id",
"members"]]
def get_object(self):
- return get_object_or_404(Team, name=self.kwargs['team_name'])
+ return get_object_or_404(Team, name=self.kwargs["team_name"])
def serialize_obj(self, obj, **kwargs):
data = serialize_instance(obj, self.fields)
- data['coordinators'] = [
- serialize_instance(coord, ['name'])
- for coord in obj.get_coordinators()
- ]
+ data["coordinators"] = [serialize_instance(coord, ["name"]) for coord in obj.get_coordinators()]
return data
class LanguagesView(SerializeListView):
- fields = ['name', 'locale', 'team__description', 'plurals']
+ fields = ["name", "locale", "team__description", "plurals"]
def get_queryset(self):
- return Language.objects.all().order_by('name')
+ return Language.objects.all().order_by("name")
def get_href(self, obj):
- return reverse('api-team', args=[obj['locale']])
+ return reverse("api-team", args=[obj["locale"]])
class ReleasesView(SerializeListView):
- fields = ['name', 'description']
+ fields = ["name", "description"]
def get_queryset(self):
- return Release.objects.all().order_by('name')
+ return Release.objects.all().order_by("name")
def get_href(self, obj):
- return reverse('api-release', args=[obj['name']])
+ return reverse("api-release", args=[obj["name"]])
class ReleaseView(SerializeObjectView):
- fields = ['name', 'description', 'string_frozen', 'status', 'branches']
+ fields = ["name", "description", "string_frozen", "status", "branches"]
def get_object(self):
- return get_object_or_404(Release, name=self.kwargs['release'])
+ return get_object_or_404(Release, name=self.kwargs["release"])
def serialize_obj(self, obj, **kwargs):
data = serialize_instance(obj, self.fields)
- data['languages'] = [{
- 'locale': lang.locale,
- 'href': reverse('api-release-language', args=[obj.name, lang.locale])
- } for lang in Language.objects.all()]
- data['statistics'] = reverse('api-release-stats', args=[self.kwargs['release']])
+ data["languages"] = [
+ {"locale": lang.locale, "href": reverse("api-release-language", args=[obj.name, lang.locale])}
+ for lang in Language.objects.all()
+ ]
+ data["statistics"] = reverse("api-release-stats", args=[self.kwargs["release"]])
return data
class ReleaseStatsView(View):
def get(self, *args, **kwargs):
- release = get_object_or_404(Release, name=self.kwargs['release'])
+ release = get_object_or_404(Release, name=self.kwargs["release"])
lang_stats = release.get_global_stats()
- return JsonResponse({
- 'release': serialize_instance(release, ['name', 'description', 'status']),
- 'statistics': lang_stats,
- })
+ return JsonResponse(
+ {
+ "release": serialize_instance(release, ["name", "description", "status"]),
+ "statistics": lang_stats,
+ }
+ )
class ReleaseLanguageView(View):
def get(self, *args, **kwargs):
- release = get_object_or_404(Release, name=self.kwargs['release'])
- language = get_object_or_404(Language, locale=self.kwargs['lang'])
+ release = get_object_or_404(Release, name=self.kwargs["release"])
+ language = get_object_or_404(Language, locale=self.kwargs["lang"])
item_list = []
- for branch in release.branches.all().select_related('module'):
+ for branch in release.branches.all().select_related("module"):
for domain in branch.get_domains():
- item_list.append({
- 'module': branch.module.name,
- 'branch': branch.name,
- 'stats': reverse('api-module-lang-stats', kwargs={
- 'module_name': branch.module.name,
- 'branch_name': branch.name,
- 'domain_name': domain,
- 'lang': language.locale,
- }),
- })
- return JsonResponse({
- 'release': release.name,
- 'language': language.locale,
- 'modules': item_list,
- })
+ item_list.append(
+ {
+ "module": branch.module.name,
+ "branch": branch.name,
+ "stats": reverse(
+ "api-module-lang-stats",
+ kwargs={
+ "module_name": branch.module.name,
+ "branch_name": branch.name,
+ "domain_name": domain,
+ "lang": language.locale,
+ },
+ ),
+ }
+ )
+ return JsonResponse(
+ {
+ "release": release.name,
+ "language": language.locale,
+ "modules": item_list,
+ }
+ )
class VertimusPageMixin:
def get_state_from_kwargs(self):
- module = get_object_or_404(Module, name=self.kwargs['module_name'])
- branch = get_object_or_404(module.branch_set, name=self.kwargs['branch_name'])
- domain = branch.get_domains().get(self.kwargs['domain_name'])
+ module = get_object_or_404(Module, name=self.kwargs["module_name"])
+ branch = get_object_or_404(module.branch_set, name=self.kwargs["branch_name"])
+ domain = branch.get_domains().get(self.kwargs["domain_name"])
if not domain:
raise Http404
- language = get_object_or_404(Language, locale=self.kwargs['lang'])
+ language = get_object_or_404(Language, locale=self.kwargs["lang"])
return get_vertimus_state(branch, domain, language)[1:]
class ModuleLangStatsView(VertimusPageMixin, View):
def get(self, *args, **kwargs):
stats, state = self.get_state_from_kwargs()
- return JsonResponse({
- 'module': state.branch.module.name,
- 'branch': state.branch.name,
- 'domain': state.domain.name,
- 'language': state.language.locale,
- 'state': state.name,
- 'statistics': {
- 'trans': stats.translated(), 'fuzzy': stats.fuzzy(), 'untrans': stats.untranslated()
- },
- 'pot_file': stats.pot_url(),
- 'po_file': stats.po_url(),
- })
-
-
-@method_decorator(csrf_exempt, name='dispatch')
+ return JsonResponse(
+ {
+ "module": state.branch.module.name,
+ "branch": state.branch.name,
+ "domain": state.domain.name,
+ "language": state.language.locale,
+ "state": state.name,
+ "statistics": {"trans": stats.translated(), "fuzzy": stats.fuzzy(), "untrans":
stats.untranslated()},
+ "pot_file": stats.pot_url(),
+ "po_file": stats.po_url(),
+ }
+ )
+
+
+@method_decorator(csrf_exempt, name="dispatch")
class ReserveTranslationView(LoginRequiredMixin, VertimusPageMixin, View):
def post(self, request, *args, **kwargs):
stats, state = self.get_state_from_kwargs()
actions = [x.name for x in state.get_available_actions(request.user.person)]
- if 'RT' not in actions:
- return HttpResponseForbidden('This module cannot be reserved')
+ if "RT" not in actions:
+ return HttpResponseForbidden("This module cannot be reserved")
action = ActionRT(person=request.user.person)
- action.apply_on(state, {'comment': '', 'send_to_ml': True})
- return JsonResponse({'result': 'OK'})
+ action.apply_on(state, {"comment": "", "send_to_ml": True})
+ return JsonResponse({"result": "OK"})
-@method_decorator(csrf_exempt, name='dispatch')
+@method_decorator(csrf_exempt, name="dispatch")
class UploadTranslationView(LoginRequiredMixin, VertimusPageMixin, View):
def post(self, request, *args, **kwargs):
stats, state = self.get_state_from_kwargs()
actions = [x.name for x in state.get_available_actions(request.user.person)]
- if 'UT' not in actions:
- return HttpResponseForbidden('You cannot upload a translation to this module')
+ if "UT" not in actions:
+ return HttpResponseForbidden("You cannot upload a translation to this module")
- action = ActionUT(person=request.user.person, file=request.FILES.get('file'))
- comment = request.POST.get('comment', '')
- action.apply_on(state, {'comment': comment, 'send_to_ml': True})
- return JsonResponse({'result': 'OK'})
+ action = ActionUT(person=request.user.person, file=request.FILES.get("file"))
+ comment = request.POST.get("comment", "")
+ action.apply_on(state, {"comment": comment, "send_to_ml": True})
+ return JsonResponse({"result": "OK"})
# CSRF skipped, verification using a secret token.
@@ -276,7 +274,7 @@ def rebuild_branch(request, module_name, branch_name):
# Run effective work in a separate thread to prevent timeouts
thread = Thread(target=rebuild_branch_real, args=(branch,))
thread.start()
- return JsonResponse({'result': 'OK'})
+ return JsonResponse({"result": "OK"})
def rebuild_branch_real(branch):
@@ -291,7 +289,7 @@ def serialize_instance(instance, field_names):
data = {}
for field in field_names:
value = getattr(instance, field)
- if hasattr(value, 'all'):
+ if hasattr(value, "all"):
value = [str(o) for o in value.all()]
data[field] = value
return data
diff --git a/common/backends.py b/common/backends.py
index cdf505c5..9abcc918 100644
--- a/common/backends.py
+++ b/common/backends.py
@@ -5,8 +5,8 @@ from people.models import Person
class TokenBackend(ModelBackend):
def authenticate(self, request, **kwargs):
- auth = request.META.get('HTTP_AUTHENTICATION', '').split()
- if len(auth) != 2 or auth[0] != 'Bearer':
+ auth = request.META.get("HTTP_AUTHENTICATION", "").split()
+ if len(auth) != 2 or auth[0] != "Bearer":
return
try:
user = Person.objects.get(auth_token=auth[1])
diff --git a/common/context_processors.py b/common/context_processors.py
index db9d3fa4..c8eddf1b 100644
--- a/common/context_processors.py
+++ b/common/context_processors.py
@@ -2,4 +2,4 @@ from django.conf import settings
def utils(request):
- return {'bug_url': settings.ENTER_BUG_URL}
+ return {"bug_url": settings.ENTER_BUG_URL}
diff --git a/common/fields.py b/common/fields.py
index ec1fd29c..ee551d03 100644
--- a/common/fields.py
+++ b/common/fields.py
@@ -25,7 +25,7 @@ class DictionaryField(models.Field):
try:
return dict(json.loads(value))
except (ValueError, TypeError):
- raise exceptions.ValidationError(self.error_messages['invalid'])
+ raise exceptions.ValidationError(self.error_messages["invalid"])
if isinstance(value, dict):
return value
@@ -47,13 +47,14 @@ class DictionaryField(models.Field):
return self.get_prep_value(value)
def formfield(self, **kwargs):
- defaults = {'widget': forms.Textarea}
+ defaults = {"widget": forms.Textarea}
defaults.update(kwargs)
return super().formfield(**defaults)
# From https://djangosnippets.org/snippets/1478/
+
class JSONField(models.TextField):
"""JSONField is a generic textfield that neatly serializes/unserializes
JSON objects seamlessly"""
diff --git a/common/middleware.py b/common/middleware.py
index 1708c067..10218de9 100644
--- a/common/middleware.py
+++ b/common/middleware.py
@@ -3,10 +3,10 @@ from django.utils.deprecation import MiddlewareMixin
class TokenAuthenticationMiddleware(MiddlewareMixin):
- backend_path = 'common.backends.TokenBackend'
+ backend_path = "common.backends.TokenBackend"
def process_request(self, request):
- if request.META.get('HTTP_AUTHENTICATION', '') and request.user.is_anonymous:
+ if request.META.get("HTTP_AUTHENTICATION", "") and request.user.is_anonymous:
backend = load_backend(self.backend_path)
user = backend.authenticate(request)
if user:
diff --git a/common/templatetags/list_to_columns.py b/common/templatetags/list_to_columns.py
index 85889f1c..600ba73c 100644
--- a/common/templatetags/list_to_columns.py
+++ b/common/templatetags/list_to_columns.py
@@ -20,7 +20,7 @@ class SplitListNode(template.Node):
def render(self, context):
context[self.new_list] = self.split_seq(context[self.list], int(self.cols))
- return ''
+ return ""
def list_to_columns(parser, token):
@@ -28,7 +28,7 @@ def list_to_columns(parser, token):
bits = token.contents.split()
if len(bits) != 5:
raise template.TemplateSyntaxError("list_to_columns list as new_list 2")
- if bits[2] != 'as':
+ if bits[2] != "as":
raise template.TemplateSyntaxError("second argument to the list_to_columns tag must be 'as'")
return SplitListNode(bits[1], bits[4], bits[3])
diff --git a/common/tests.py b/common/tests.py
index 6a83a9ca..203d4cb8 100644
--- a/common/tests.py
+++ b/common/tests.py
@@ -11,65 +11,72 @@ from django.urls import reverse
from django.utils import timezone, translation
from people.models import Person
-from teams.models import Team, Role
+from teams.models import Role, Team
+
from .utils import lc_sorted, pyicu_present, trans_sort_object_list
class CommonTest(DjangoTestCase):
-
def test_admin_login(self):
- response = self.client.get(reverse('admin:index'))
- self.assertRedirects(response, reverse('admin:login') + '?next=' + reverse('admin:index'))
+ response = self.client.get(reverse("admin:index"))
+ self.assertRedirects(response, reverse("admin:login") + "?next=" + reverse("admin:index"))
def test_help_pages(self):
- response = self.client.get(reverse('help', args=['reduced_po', 1]))
+ response = self.client.get(reverse("help", args=["reduced_po", 1]))
self.assertContains(response, '<div class="modal-body">')
- response = self.client.get(reverse('help', args=['reduced_po', 0]))
+ response = self.client.get(reverse("help", args=["reduced_po", 0]))
self.assertNotContains(response, '<div class="modal-body">')
- response = self.client.get(reverse('help', args=['reduced_po']))
+ response = self.client.get(reverse("help", args=["reduced_po"]))
self.assertNotContains(response, '<div class="modal-body">')
- response = self.client.get(reverse('help', args=['unexisting', 1]))
+ response = self.client.get(reverse("help", args=["unexisting", 1]))
self.assertEqual(response.status_code, 404)
def test_house_keeping(self):
pn = Person.objects.create(
- first_name='John', last_name='Note',
- email='jn devnull com', username='jn', activation_key='a_activation_key'
+ first_name="John",
+ last_name="Note",
+ email="jn devnull com",
+ username="jn",
+ activation_key="a_activation_key",
)
- pn.set_password('password')
+ pn.set_password("password")
# Unactivated person
Person.objects.create(
- first_name='John', last_name='Reviewer',
- username='jr', date_joined=timezone.now() - timedelta(days=11),
- activation_key='non-activated-key'
+ first_name="John",
+ last_name="Reviewer",
+ username="jr",
+ date_joined=timezone.now() - timedelta(days=11),
+ activation_key="non-activated-key",
)
inactive = Person.objects.create(
- first_name='John', last_name='Translator',
- username='jt', last_login=timezone.now() - timedelta(days=30 * 6 + 1),
+ first_name="John",
+ last_name="Translator",
+ username="jt",
+ last_login=timezone.now() - timedelta(days=30 * 6 + 1),
)
- t1 = Team.objects.create(name='fr', description='French')
- t2 = Team.objects.create(name='fr2', description='French')
+ t1 = Team.objects.create(name="fr", description="French")
+ t2 = Team.objects.create(name="fr2", description="French")
Role.objects.create(team=t1, person=inactive)
Role.objects.create(team=t2, person=inactive)
- response = self.client.get('/register/activate/a_activation_key', follow=True)
- self.assertContains(response, 'Your account has been activated.')
+ response = self.client.get("/register/activate/a_activation_key", follow=True)
+ self.assertContains(response, "Your account has been activated.")
- call_command('run-maintenance')
+ call_command("run-maintenance")
# Testing if the non-activated accounts were deleted
- self.assertTrue(Person.objects.filter(last_name='Note').exists())
- self.assertFalse(Person.objects.filter(last_name='Reviewer').exists())
+ self.assertTrue(Person.objects.filter(last_name="Note").exists())
+ self.assertFalse(Person.objects.filter(last_name="Reviewer").exists())
# Testing if the inactive roles were updated
- jt = Person.objects.get(first_name='John', last_name='Translator')
+ jt = Person.objects.get(first_name="John", last_name="Translator")
for role in Role.objects.filter(person=jt):
self.assertFalse(role.is_active)
@@ -79,78 +86,74 @@ class CommonTest(DjangoTestCase):
"ref": "refs/heads/master",
# Gitlab will send a lot more, but we don't care.
}
- with patch('common.views.run_shell_command', return_value=(0, '', '')) as rsc_patched:
- with patch('common.views.call_command') as cc_patched:
- response = self.client.post('/pull_code/', data=push_data)
+ with patch("common.views.run_shell_command", return_value=(0, "", "")) as rsc_patched:
+ with patch("common.views.call_command") as cc_patched:
+ response = self.client.post("/pull_code/", data=push_data)
self.assertEqual(response.status_code, 403)
response = self.client.post(
- '/pull_code/', data=push_data,
- HTTP_X_GITLAB_EVENT='Push Hook',
+ "/pull_code/",
+ data=push_data,
+ HTTP_X_GITLAB_EVENT="Push Hook",
HTTP_X_GITLAB_TOKEN=settings.GITLAB_TOKEN,
)
self.assertEqual(rsc_patched.call_count, 2)
self.assertEqual(cc_patched.call_count, 1)
- self.assertEqual(response.content, b'OK')
+ self.assertEqual(response.content, b"OK")
class LcSortedTest(TestCase):
-
@skipUnless(pyicu_present, "PyICU package is required for this test")
def test_lc_sorted_en_case_insensitive(self):
- with translation.override('en'):
- self.assertEqual(['A', 'b', 'C', 'z'], lc_sorted(['z', 'C', 'b', 'A']))
+ with translation.override("en"):
+ self.assertEqual(["A", "b", "C", "z"], lc_sorted(["z", "C", "b", "A"]))
@skipUnless(pyicu_present, "PyICU package is required for this test")
def test_lc_sorted_fr_eacute_between_d_and_f(self):
- with translation.override('fr'):
- self.assertEqual(['d', 'é', 'f', 'z'], lc_sorted(['é', 'z', 'd', 'f']))
+ with translation.override("fr"):
+ self.assertEqual(["d", "é", "f", "z"], lc_sorted(["é", "z", "d", "f"]))
@skipUnless(pyicu_present, "PyICU package is required for this test")
def test_lc_sorted_sv_ouml_after_z(self):
- with translation.override('sv'):
- self.assertEqual(['a', 'o', 'z', 'ö'], lc_sorted(['z', 'ö', 'o', 'a']))
+ with translation.override("sv"):
+ self.assertEqual(["a", "o", "z", "ö"], lc_sorted(["z", "ö", "o", "a"]))
def test_lc_sorted_with_custom_key(self):
mykey = operator.itemgetter(1)
- with translation.override('fr'):
- self.assertEqual(
- [('z', 'a'), ('a', 'z')],
- lc_sorted([('a', 'z'), ('z', 'a')], key=mykey)
- )
+ with translation.override("fr"):
+ self.assertEqual([("z", "a"), ("a", "z")], lc_sorted([("a", "z"), ("z", "a")], key=mykey))
class TransSortObjectListTest(TestCase):
-
def make_item(self, name):
item = MagicMock(name=name)
item.name = name
return item
- @patch('common.utils._', return_value="bar")
+ @patch("common.utils._", return_value="bar")
def test_translate_object_name(self, gettext):
"""
The given field is translated using gettext and the translation is stored
in translated_name.
"""
item = self.make_item("foo")
- trans_sort_object_list([item], 'name')
+ trans_sort_object_list([item], "name")
self.assertEqual("bar", item.translated_name)
- @patch('common.utils._', side_effect=lambda s: s[::-1])
+ @patch("common.utils._", side_effect=lambda s: s[::-1])
def test_sort_list_according_to_translation(self, _):
"""
The list is sorted according to the computed translated_name
(here gettext reverses the string so foo (oof) comes before bar (rab)).
"""
- foo, bar = self.make_item('foo'), self.make_item('bar')
- actual = trans_sort_object_list([foo, bar], 'name')
+ foo, bar = self.make_item("foo"), self.make_item("bar")
+ actual = trans_sort_object_list([foo, bar], "name")
self.assertEqual([foo, bar], actual, "wrong order")
@skipUnless(pyicu_present, "PyICU package is required for this test")
- @patch('common.utils._', side_effect=lambda x: x)
+ @patch("common.utils._", side_effect=lambda x: x)
def test_uses_localized_ordering(self, _):
"""Consider the current locale when ordering elements."""
- d, eacute, f = self.make_item('d'), self.make_item('é'), self.make_item('f')
- with translation.override('fr'):
- actual = trans_sort_object_list([f, eacute, d], 'name')
+ d, eacute, f = self.make_item("d"), self.make_item("é"), self.make_item("f")
+ with translation.override("fr"):
+ actual = trans_sort_object_list([f, eacute, d], "name")
self.assertEqual([d, eacute, f], actual)
diff --git a/common/utils.py b/common/utils.py
index f008f51f..aac776d4 100644
--- a/common/utils.py
+++ b/common/utils.py
@@ -5,45 +5,47 @@ from subprocess import PIPE, run
from django.conf import settings
from django.core.mail import EmailMessage
-from django.utils.translation import gettext as _, get_language
+from django.utils.translation import get_language
+from django.utils.translation import gettext as _
try:
import icu
+
pyicu_present = True
except ImportError:
pyicu_present = False
-MIME_TYPES = {
- 'json': 'application/json',
- 'xml': 'text/xml'
-}
+MIME_TYPES = {"json": "application/json", "xml": "text/xml"}
STATUS_OK = 0
-def run_shell_command(cmd, input_data=None, raise_on_error=False, env=None,
- cwd=None, **extra_kwargs):
+def run_shell_command(cmd, input_data=None, raise_on_error=False, env=None, cwd=None, **extra_kwargs):
logging.debug(cmd)
if env is not None:
env = dict(os.environ, **env)
shell = not isinstance(cmd, list)
result = run(
- cmd, shell=shell, input=input_data, encoding='utf-8',
- stdout=PIPE, stderr=PIPE, env=env, cwd=cwd, **extra_kwargs,
+ cmd,
+ shell=shell,
+ input=input_data,
+ encoding="utf-8",
+ stdout=PIPE,
+ stderr=PIPE,
+ env=env,
+ cwd=cwd,
+ **extra_kwargs,
)
status = result.returncode
logging.debug(result.stdout + result.stderr)
if raise_on_error and status != STATUS_OK:
- is_git_command = cmd.startswith('git') if isinstance(cmd, str) else cmd[0] == 'git'
+ is_git_command = cmd.startswith("git") if isinstance(cmd, str) else cmd[0] == "git"
if is_git_command and status == -25:
# Try to reset the git repository to an usable state
- run_shell_command(['rm', '.git/index.lock'], cwd=cwd)
- run_shell_command(['git', 'clean', '-dfq'], cwd=cwd)
- run_shell_command(['git', 'reset', '--hard'], cwd=cwd)
- raise OSError(
- status,
- 'Command: "%s", Error: %s' % (cmd, result.stderr or result.stdout)
- )
+ run_shell_command(["rm", ".git/index.lock"], cwd=cwd)
+ run_shell_command(["git", "clean", "-dfq"], cwd=cwd)
+ run_shell_command(["git", "reset", "--hard"], cwd=cwd)
+ raise OSError(status, 'Command: "%s", Error: %s' % (cmd, result.stderr or result.stdout))
return (status, result.stdout, result.stderr)
@@ -54,8 +56,8 @@ def lc_sorted(*args, **kwargs):
"""
if pyicu_present:
collator = icu.Collator.createInstance(icu.Locale(str(get_language())))
- key = kwargs.get('key', lambda x: x)
- kwargs['key'] = lambda x: collator.getSortKey(key(x))
+ key = kwargs.get("key", lambda x: x)
+ kwargs["key"] = lambda x: collator.getSortKey(key(x))
return sorted(*args, **kwargs)
@@ -74,7 +76,7 @@ def get_user_locale(request):
from languages.models import Language # Import here to prevent loop
curlang = Language.get_language_from_ianacode(request.LANGUAGE_CODE)
- if curlang and curlang.locale == 'en':
+ if curlang and curlang.locale == "en":
curlang = None
return curlang
@@ -82,25 +84,24 @@ def get_user_locale(request):
def send_mail(subject, message, **kwargs):
"""Wrapper to Django's send_mail allowing all EmailMessage init arguments."""
if not subject.startswith(settings.EMAIL_SUBJECT_PREFIX):
- subject = '%s%s' % (settings.EMAIL_SUBJECT_PREFIX, subject)
+ subject = "%s%s" % (settings.EMAIL_SUBJECT_PREFIX, subject)
EmailMessage(subject, message, **kwargs).send()
def check_gitlab_request(request):
- remote_ip = request.META.get('HTTP_X_FORWARDED_FOR', request.META.get('REMOTE_ADDR'))
- if ',' in remote_ip:
- remote_ip = remote_ip.split(',')[0].strip()
+ remote_ip = request.META.get("HTTP_X_FORWARDED_FOR", request.META.get("REMOTE_ADDR"))
+ if "," in remote_ip:
+ remote_ip = remote_ip.split(",")[0].strip()
try:
from_host = socket.gethostbyaddr(remote_ip)[0]
except socket.herror:
return False
except Exception:
raise Exception("Unable to get host for address '%s'" % remote_ip)
- return (
- request.method == 'POST' and (
- from_host == 'gitlab.gnome.org' or (
- request.META.get('HTTP_X_GITLAB_EVENT') == 'Push Hook' and
- request.META.get('HTTP_X_GITLAB_TOKEN') == settings.GITLAB_TOKEN
- )
+ return request.method == "POST" and (
+ from_host == "gitlab.gnome.org"
+ or (
+ request.META.get("HTTP_X_GITLAB_EVENT") == "Push Hook"
+ and request.META.get("HTTP_X_GITLAB_TOKEN") == settings.GITLAB_TOKEN
)
)
diff --git a/common/views.py b/common/views.py
index 65fcbd29..43178ded 100644
--- a/common/views.py
+++ b/common/views.py
@@ -2,70 +2,78 @@ from smtplib import SMTPException
from threading import Thread
from django.conf import settings
-from django.contrib.auth.views import LoginView as AuthLoginView
from django.contrib import messages
+from django.contrib.auth.views import LoginView as AuthLoginView
from django.core.management import call_command
-from django.http import HttpResponse, HttpResponseForbidden, HttpResponseRedirect, Http404
+from django.http import (
+ Http404,
+ HttpResponse,
+ HttpResponseForbidden,
+ HttpResponseRedirect,
+)
from django.shortcuts import render
-from django.template.loader import get_template, TemplateDoesNotExist
+from django.template.loader import TemplateDoesNotExist, get_template
from django.urls import reverse
from django.utils.translation import gettext as _
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.debug import sensitive_variables
+from people.forms import LoginForm, RegistrationForm
from people.models import Person, obfuscate_email
from teams.models import Role
-from people.forms import LoginForm, RegistrationForm
+
from .utils import check_gitlab_request, get_user_locale, run_shell_command
def index(request):
- """ Homepage view """
+ """Homepage view"""
curlang = get_user_locale(request)
context = {
- 'pageSection': 'home',
- 'user_language': curlang,
+ "pageSection": "home",
+ "user_language": curlang,
}
- return render(request, 'index.html', context)
+ return render(request, "index.html", context)
def about(request):
translator_credits = _("translator-credits")
if translator_credits == "translator-credits":
- translator_credits = ''
+ translator_credits = ""
else:
- translator_credits = [obfuscate_email(line) for line in translator_credits.split('\n')]
+ translator_credits = [obfuscate_email(line) for line in translator_credits.split("\n")]
context = {
- 'damned_lies_version': settings.VERSION or "unknown",
- 'pageSection': 'home',
- 'translator_credits': translator_credits,
+ "damned_lies_version": settings.VERSION or "unknown",
+ "pageSection": "home",
+ "translator_credits": translator_credits,
}
- return render(request, 'about.html', context)
+ return render(request, "about.html", context)
class LoginView(AuthLoginView):
form_class = LoginForm
- redirect_field_name = 'referer'
+ redirect_field_name = "referer"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
- if not context['referer']:
- context['referer'] = self.request.META.get('HTTP_REFERER', '')
+ if not context["referer"]:
+ context["referer"] = self.request.META.get("HTTP_REFERER", "")
return context
def get_redirect_url(self):
url = super().get_redirect_url()
if not url:
- url = self.request.META.get('HTTP_REFERER', '')
+ url = self.request.META.get("HTTP_REFERER", "")
return url
def form_valid(self, form):
response = super().form_valid(form)
if Role.objects.filter(person__username=self.request.user.username).count() < 1:
- message = _("You have not joined any translation team yet. "
- "You can do it from <a href=\"%(url)s\">your profile</a>.") % {
- 'url': reverse('person_team_join'),
+ message = _(
+ "You have not joined any translation team yet. "
+ 'You can do it from <a href="%(url)s">your profile</a>.'
+ ) % {
+ "url": reverse("person_team_join"),
}
messages.info(self.request, message)
return response
@@ -75,48 +83,51 @@ class LoginView(AuthLoginView):
return super().form_invalid(form)
-@sensitive_variables('password1', 'password2')
+@sensitive_variables("password1", "password2")
def site_register(request):
- if request.method == 'POST':
+ if request.method == "POST":
form = RegistrationForm(data=request.POST)
if form.is_valid():
try:
form.save(request)
except SMTPException as exc:
- messages.error(request, _("An error occurred while sending mail to {email} ({err})".format(
- email=form.cleaned_data['email'], err=str(exc)
- )))
+ messages.error(
+ request,
+ _(
+ "An error occurred while sending mail to {email} ({err})".format(
+ email=form.cleaned_data["email"], err=str(exc)
+ )
+ ),
+ )
else:
- return HttpResponseRedirect(reverse('register_success'))
+ return HttpResponseRedirect(reverse("register_success"))
else:
form = RegistrationForm()
context = {
- 'pageSection': 'home',
- 'form': form,
+ "pageSection": "home",
+ "form": form,
}
- return render(request, 'registration/register.html', context)
+ return render(request, "registration/register.html", context)
def activate_account(request, key):
- """ Activate an account through the link a requestor has received by email """
+ """Activate an account through the link a requestor has received by email"""
try:
person = Person.objects.get(activation_key=key)
except Person.DoesNotExist:
- return render(request, 'error.html', {'error': _("Sorry, the key you provided is not valid.")})
+ return render(request, "error.html", {"error": _("Sorry, the key you provided is not valid.")})
person.activate()
messages.success(request, _("Your account has been activated."))
- return HttpResponseRedirect(reverse('login'))
+ return HttpResponseRedirect(reverse("login"))
def help(request, topic, modal=None):
- template = 'help/%s.html' % topic
+ template = "help/%s.html" % topic
try:
get_template(template)
except TemplateDoesNotExist:
raise Http404
- return render(request, template, {
- 'base': 'base_modal.html' if modal and int(modal) else 'base.html'
- })
+ return render(request, template, {"base": "base_modal.html" if modal and int(modal) else "base.html"})
# CSRF skipped, verification using a secret token.
@@ -130,11 +141,11 @@ def pull_code(request):
# Run effective work in a separate thread to prevent timeouts
thread = Thread(target=pull_code_real)
thread.start()
- return HttpResponse('OK')
+ return HttpResponse("OK")
def pull_code_real():
- cwd = settings.BASE_DIR / 'damnedlies'
- run_shell_command(['git', 'pull', '--rebase'], cwd=cwd)
- call_command('compile-trans', verbosity=0)
- run_shell_command(['touch', 'wsgi.py'], cwd=cwd)
+ cwd = settings.BASE_DIR / "damnedlies"
+ run_shell_command(["git", "pull", "--rebase"], cwd=cwd)
+ call_command("compile-trans", verbosity=0)
+ run_shell_command(["touch", "wsgi.py"], cwd=cwd)
diff --git a/damnedlies/context_processors.py b/damnedlies/context_processors.py
index f612c7d2..af300b2b 100644
--- a/damnedlies/context_processors.py
+++ b/damnedlies/context_processors.py
@@ -1,4 +1,3 @@
-
def running_integration_instance(request):
running_production_system = False
try:
diff --git a/damnedlies/settings.py b/damnedlies/settings.py
index b51d0168..108ce8fe 100644
--- a/damnedlies/settings.py
+++ b/damnedlies/settings.py
@@ -3,6 +3,7 @@ import os
from pathlib import Path
from django.conf import global_settings
+
gettext_noop = lambda s: s
DEBUG = True
@@ -11,64 +12,62 @@ USE_DEBUG_TOOLBAR = False
BASE_DIR = Path(__file__).resolve().parent.parent
-ADMINS = (
- ('Your Name', 'your_address example org'),
-)
+ADMINS = (("Your Name", "your_address example org"),)
DATABASES = {
- 'default': {
- 'ENGINE': 'django.db.backends.sqlite3',
- 'NAME': BASE_DIR / 'database.db',
+ "default": {
+ "ENGINE": "django.db.backends.sqlite3",
+ "NAME": BASE_DIR / "database.db",
}
}
# Please refer to the README file to create an UTF-8 database with MySQL.
-DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
-
-EMAIL_HOST = 'localhost'
-EMAIL_HOST_USER = ''
-EMAIL_HOST_PASSWORD = ''
-EMAIL_SUBJECT_PREFIX = '[Damned Lies] '
-EMAIL_HEADER_NAME = 'X-Vertimus'
-DEFAULT_FROM_EMAIL = 'gnomeweb gnome org'
-SERVER_EMAIL = 'gnomeweb gnome org'
+DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
+
+EMAIL_HOST = "localhost"
+EMAIL_HOST_USER = ""
+EMAIL_HOST_PASSWORD = ""
+EMAIL_SUBJECT_PREFIX = "[Damned Lies] "
+EMAIL_HEADER_NAME = "X-Vertimus"
+DEFAULT_FROM_EMAIL = "gnomeweb gnome org"
+SERVER_EMAIL = "gnomeweb gnome org"
# When in STRINGFREEZE, where to send notifications (gnome-i18n gnome org) on any POT changes
-NOTIFICATIONS_TO = ['gnome-i18n gnome org']
-ENTER_BUG_URL = 'https://gitlab.gnome.org/Infrastructure/damned-lies/issues'
-ENTER_LANGUAGE_BUG_URL = 'https://gitlab.gnome.org/Teams/Translation/%(lang)s/issues/new'
-BROWSE_LANGUAGE_BUG_URL = 'https://gitlab.gnome.org/Teams/Translation/%(lang)s/issues?state=opened'
+NOTIFICATIONS_TO = ["gnome-i18n gnome org"]
+ENTER_BUG_URL = "https://gitlab.gnome.org/Infrastructure/damned-lies/issues"
+ENTER_LANGUAGE_BUG_URL = "https://gitlab.gnome.org/Teams/Translation/%(lang)s/issues/new"
+BROWSE_LANGUAGE_BUG_URL = "https://gitlab.gnome.org/Teams/Translation/%(lang)s/issues?state=opened"
# Local time zone for this installation. Choices can be found here:
# https://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# although not all choices may be available on all operating systems.
# If running in a Windows environment this must be set to the same as your
# system time zone.
-TIME_ZONE = 'Europe/Zurich'
+TIME_ZONE = "Europe/Zurich"
# Language code for this installation. All choices can be found here:
# http://www.i18nguy.com/unicode/language-identifiers.html
-LANGUAGE_CODE = 'en'
+LANGUAGE_CODE = "en"
LANGUAGES = global_settings.LANGUAGES + [
# Add here languages with translations for D-L but not for Django
- ('fur', gettext_noop('Friulian')),
- ('gu', gettext_noop('Gujarati')),
- ('ku', gettext_noop('Kurdish')),
+ ("fur", gettext_noop("Friulian")),
+ ("gu", gettext_noop("Gujarati")),
+ ("ku", gettext_noop("Kurdish")),
]
USE_I18N = True
USE_L10N = True
-LOCALE_PATHS = [BASE_DIR / 'locale']
+LOCALE_PATHS = [BASE_DIR / "locale"]
USE_TZ = True
# Absolute path to the directory that holds media.
-MEDIA_ROOT = BASE_DIR / 'media'
+MEDIA_ROOT = BASE_DIR / "media"
# URL that handles the media served from MEDIA_ROOT.
-MEDIA_URL = '/media/'
+MEDIA_URL = "/media/"
-STATIC_ROOT = BASE_DIR / 'static'
+STATIC_ROOT = BASE_DIR / "static"
-STATIC_URL = '/static/'
+STATIC_URL = "/static/"
# Local directory path for VCS checkout
SCRATCHDIR = Path()
@@ -82,116 +81,113 @@ VCS_HOME_WARNING = gettext_noop(
)
# By default, Django stores files locally, using the MEDIA_ROOT and MEDIA_URL settings
-UPLOAD_DIR = 'upload'
-UPLOAD_ARCHIVED_DIR = 'upload-archived'
+UPLOAD_DIR = "upload"
+UPLOAD_ARCHIVED_DIR = "upload-archived"
FILE_UPLOAD_PERMISSIONS = 0o600
-LOGIN_URL = '/login/'
+LOGIN_URL = "/login/"
# Make this unique, and don't share it with anybody.
# To be filled in local_settings.py
-SECRET_KEY = ''
+SECRET_KEY = ""
TEMPLATES = [
{
- 'BACKEND': 'django.template.backends.django.DjangoTemplates',
- 'DIRS': [BASE_DIR / 'templates'],
- 'APP_DIRS': True,
- 'OPTIONS': {
- 'context_processors': [
+ "BACKEND": "django.template.backends.django.DjangoTemplates",
+ "DIRS": [BASE_DIR / "templates"],
+ "APP_DIRS": True,
+ "OPTIONS": {
+ "context_processors": [
# Default list:
- 'django.contrib.auth.context_processors.auth',
- 'django.template.context_processors.debug',
- 'django.template.context_processors.i18n',
- 'django.template.context_processors.media',
- 'django.template.context_processors.static',
- 'django.template.context_processors.tz',
- 'django.contrib.messages.context_processors.messages',
+ "django.contrib.auth.context_processors.auth",
+ "django.template.context_processors.debug",
+ "django.template.context_processors.i18n",
+ "django.template.context_processors.media",
+ "django.template.context_processors.static",
+ "django.template.context_processors.tz",
+ "django.contrib.messages.context_processors.messages",
# Added processors:
- 'django.template.context_processors.request',
- 'common.context_processors.utils',
- 'damnedlies.context_processors.running_integration_instance',
+ "django.template.context_processors.request",
+ "common.context_processors.utils",
+ "damnedlies.context_processors.running_integration_instance",
],
- 'builtins': [
- 'django.templatetags.i18n',
- 'django.templatetags.static',
+ "builtins": [
+ "django.templatetags.i18n",
+ "django.templatetags.static",
],
},
},
]
MIDDLEWARE = [
- 'django.contrib.sessions.middleware.SessionMiddleware',
- 'django.middleware.locale.LocaleMiddleware',
- 'django.middleware.common.CommonMiddleware',
- 'django.middleware.csrf.CsrfViewMiddleware',
- 'django.contrib.auth.middleware.AuthenticationMiddleware',
- 'common.middleware.TokenAuthenticationMiddleware',
- 'django.contrib.messages.middleware.MessageMiddleware',
- 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+ "django.contrib.sessions.middleware.SessionMiddleware",
+ "django.middleware.locale.LocaleMiddleware",
+ "django.middleware.common.CommonMiddleware",
+ "django.middleware.csrf.CsrfViewMiddleware",
+ "django.contrib.auth.middleware.AuthenticationMiddleware",
+ "common.middleware.TokenAuthenticationMiddleware",
+ "django.contrib.messages.middleware.MessageMiddleware",
+ "django.middleware.clickjacking.XFrameOptionsMiddleware",
]
ATOMIC_REQUESTS = True
-ROOT_URLCONF = 'damnedlies.urls'
+ROOT_URLCONF = "damnedlies.urls"
INSTALLED_APPS = [
- 'django.contrib.auth',
- 'django.contrib.contenttypes',
- 'django.contrib.sessions',
- 'django.contrib.admin',
- 'django.contrib.humanize',
- 'django.contrib.messages',
- 'django.contrib.staticfiles',
- 'common',
- 'languages',
- 'people',
- 'stats',
- 'teams',
- 'vertimus',
- 'feeds',
- 'api',
+ "django.contrib.auth",
+ "django.contrib.contenttypes",
+ "django.contrib.sessions",
+ "django.contrib.admin",
+ "django.contrib.humanize",
+ "django.contrib.messages",
+ "django.contrib.staticfiles",
+ "common",
+ "languages",
+ "people",
+ "stats",
+ "teams",
+ "vertimus",
+ "feeds",
+ "api",
]
-INTERNAL_IPS = ('127.0.0.1',)
+INTERNAL_IPS = ("127.0.0.1",)
-SITE_DOMAIN = 'l10n.gnome.org'
+SITE_DOMAIN = "l10n.gnome.org"
-LOGIN_REDIRECT_URL = '/'
+LOGIN_REDIRECT_URL = "/"
# Members of this group can edit all team's details and change team coordinatorship
-ADMIN_GROUP = ''
+ADMIN_GROUP = ""
# https://www.gnu.org/software/gettext/manual/html_node/Preparing-ITS-Rules.html#Preparing-ITS-Rules
GETTEXT_ITS_DATA = {
- 'gtk': [
- os.path.join('tools', 'gtk4builder.its'),
- os.path.join('tools', 'gtk4builder.loc')
- ],
- 'glib': [os.path.join('gio', 'gschema.its'), os.path.join('gio', 'gschema.loc')],
- 'gnome-control-center': [
- os.path.join('gettext', 'its', 'gnome-keybindings.its'),
- os.path.join('gettext', 'its', 'gnome-keybindings.loc'),
+ "gtk": [os.path.join("tools", "gtk4builder.its"), os.path.join("tools", "gtk4builder.loc")],
+ "glib": [os.path.join("gio", "gschema.its"), os.path.join("gio", "gschema.loc")],
+ "gnome-control-center": [
+ os.path.join("gettext", "its", "gnome-keybindings.its"),
+ os.path.join("gettext", "its", "gnome-keybindings.loc"),
],
- 'polkit': [
- os.path.join('gettext', 'its', 'polkit.its'),
- os.path.join('gettext', 'its', 'polkit.loc'),
+ "polkit": [
+ os.path.join("gettext", "its", "polkit.its"),
+ os.path.join("gettext", "its", "polkit.loc"),
],
- 'shared-mime-info': [
- os.path.join('data', 'its', 'shared-mime-info.its'),
- os.path.join('data', 'its', 'shared-mime-info.loc'),
+ "shared-mime-info": [
+ os.path.join("data", "its", "shared-mime-info.its"),
+ os.path.join("data", "its", "shared-mime-info.loc"),
],
# Copy of https://github.com/ximion/appstream/tree/master/data/its (2020-02-02)
# minus the release/description line (#149).
- 'damned-lies': [
- os.path.join('common', 'static', 'metainfo.its'),
- os.path.join('common', 'static', 'metainfo.loc'),
+ "damned-lies": [
+ os.path.join("common", "static", "metainfo.its"),
+ os.path.join("common", "static", "metainfo.loc"),
],
}
-GITLAB_TOKEN = 'fill_with_real_token'
+GITLAB_TOKEN = "fill_with_real_token"
-LOCK_DIR = Path('/var/lock')
+LOCK_DIR = Path("/var/lock")
try:
from .local_settings import *
@@ -211,14 +207,14 @@ LOGGING = {
"handlers": ["console"],
"level": "ERROR",
},
- }
+ },
}
# When running in development mode, print emails on stdout instead of trying
# to send them
if DEBUG:
- EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
+ EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
if USE_DEBUG_TOOLBAR:
- MIDDLEWARE.insert(0, 'debug_toolbar.middleware.DebugToolbarMiddleware')
- INSTALLED_APPS.append('debug_toolbar')
+ MIDDLEWARE.insert(0, "debug_toolbar.middleware.DebugToolbarMiddleware")
+ INSTALLED_APPS.append("debug_toolbar")
diff --git a/damnedlies/settings_tests.py b/damnedlies/settings_tests.py
index 99eb87c7..880a7f0e 100644
--- a/damnedlies/settings_tests.py
+++ b/damnedlies/settings_tests.py
@@ -2,18 +2,18 @@ import tempfile
from .settings import *
-SECRET_KEY = 'shRc(?sk+sW3Wqn-lBvs=r52a@#hgC9g'
+SECRET_KEY = "shRc(?sk+sW3Wqn-lBvs=r52a@#hgC9g"
-SCRATCHDIR = BASE_DIR / 'scratch'
-POTDIR = SCRATCHDIR / 'POT'
+SCRATCHDIR = BASE_DIR / "scratch"
+POTDIR = SCRATCHDIR / "POT"
GETTEXT_ITS_DATA = {}
LOCK_DIR = Path(tempfile.mkdtemp())
try:
import xmlrunner
+
TEST_RUNNER = "xmlrunner.extra.djangotestrunner.XMLTestRunner"
TEST_OUTPUT_DIR = open("tests-report.xml", "wb")
except ImportError:
pass
-
diff --git a/damnedlies/urls.py b/damnedlies/urls.py
index 8065e56a..0b2ad1b0 100644
--- a/damnedlies/urls.py
+++ b/damnedlies/urls.py
@@ -12,119 +12,89 @@ from stats import views as stats_views
class LocaleConverter(StringConverter):
- regex = '[-_a-zA-Z@]+'
+ regex = "[-_a-zA-Z@]+"
class NameConverter(StringConverter):
"""Converter for module, branch, or domain names."""
- regex = r'[-~\w\+\.]+'
+ regex = r"[-~\w\+\.]+"
-register_converter(LocaleConverter, 'locale')
-register_converter(NameConverter, 'name')
-module_branch_domain = '<name:module_name>/<path:branch_name>/<name:domain_name>'
-urlpatterns = [
- path('',
- common_views.index,
- name='home'),
- path('about/',
- common_views.about,
- name='about'),
- path('login/',
- common_views.LoginView.as_view(template_name='login.html'),
- name='login'),
- path('logout/',
- require_POST(auth_views.LogoutView.as_view()),
- name='logout'),
- path('register/',
- common_views.site_register,
- name='register'),
- re_path(r'^help/(?P<topic>\w+)/(?P<modal>[0-1])?/?$',
- common_views.help,
- name='help'),
- path('register/success/',
- TemplateView.as_view(template_name="registration/register_success.html"),
- name='register_success'),
- path('register/activate/<slug:key>',
- common_views.activate_account,
- name='register_activation'),
- path('password_reset/',
- auth_views.PasswordResetView.as_view(template_name='registration/password_reset_form.html'),
- name='password_reset'),
- path('password_reset/done/',
- auth_views.PasswordResetDoneView.as_view(template_name='registration/password_reset_done.html'),
- name='password_reset_done'),
- path('reset/<slug:uidb64>/<slug:token>/',
- auth_views.PasswordResetConfirmView.as_view(),
- name='password_reset_confirm'),
- path('reset/done/',
- auth_views.PasswordResetCompleteView.as_view(),
- name='password_reset_complete'),
+register_converter(LocaleConverter, "locale")
+register_converter(NameConverter, "name")
+module_branch_domain = "<name:module_name>/<path:branch_name>/<name:domain_name>"
+urlpatterns = [
+ path("", common_views.index, name="home"),
+ path("about/", common_views.about, name="about"),
+ path("login/", common_views.LoginView.as_view(template_name="login.html"), name="login"),
+ path("logout/", require_POST(auth_views.LogoutView.as_view()), name="logout"),
+ path("register/", common_views.site_register, name="register"),
+ re_path(r"^help/(?P<topic>\w+)/(?P<modal>[0-1])?/?$", common_views.help, name="help"),
+ path(
+ "register/success/",
+ TemplateView.as_view(template_name="registration/register_success.html"),
+ name="register_success",
+ ),
+ path("register/activate/<slug:key>", common_views.activate_account, name="register_activation"),
+ path(
+ "password_reset/",
+ auth_views.PasswordResetView.as_view(template_name="registration/password_reset_form.html"),
+ name="password_reset",
+ ),
+ path(
+ "password_reset/done/",
+ auth_views.PasswordResetDoneView.as_view(template_name="registration/password_reset_done.html"),
+ name="password_reset_done",
+ ),
+ path(
+ "reset/<slug:uidb64>/<slug:token>/",
+ auth_views.PasswordResetConfirmView.as_view(),
+ name="password_reset_confirm",
+ ),
+ path("reset/done/", auth_views.PasswordResetCompleteView.as_view(), name="password_reset_complete"),
# Webhook endpoint
- path('pull_code/', common_views.pull_code),
-
- path('teams/', include('teams.urls')),
- path('people/', include('people.urls')),
+ path("pull_code/", common_views.pull_code),
+ path("teams/", include("teams.urls")),
+ path("people/", include("people.urls")),
# users is the hardcoded url in the contrib.auth User class, making it identical to /people
- path('users/', include('people.urls')),
- path('languages/', include('languages.urls')),
- path('vertimus/', include('vertimus.urls')),
- path('i18n/', include('django.conf.urls.i18n')),
- path('admin/', admin.site.urls),
- path('rss/', include('feeds.urls')),
- path('api/v1/', include('api.urls')),
+ path("users/", include("people.urls")),
+ path("languages/", include("languages.urls")),
+ path("vertimus/", include("vertimus.urls")),
+ path("i18n/", include("django.conf.urls.i18n")),
+ path("admin/", admin.site.urls),
+ path("rss/", include("feeds.urls")),
+ path("api/v1/", include("api.urls")),
]
urlpatterns += [
- re_path(r'^module/(?P<format>(html|json|xml))?/?$',
- stats_views.modules,
- name='modules'),
- path('module/po/%s/<filename>' % module_branch_domain,
- stats_views.dynamic_po,
- name='dynamic_po'),
- path('module/<name:module_name>/',
- stats_views.module,
- name='module'),
- path('module/<name:module_name>/edit/branches/',
- stats_views.module_edit_branches,
- name='module_edit_branches'),
- path('module/<name:module_name>/branch/<path:branch_name>/',
- stats_views.module_branch,
- name='module_branch'),
- path('branch/<int:branch_id>/refresh/',
- stats_views.branch_refresh,
- name='branch_refresh'),
- path('module/<name:module_name>/<name:potbase>/<path:branch_name>/<locale:langcode>/images/',
- stats_views.docimages,
- name='docimages'),
- re_path(r'^releases/(?P<format>(html|json|xml))?/?$',
- stats_views.releases,
- name='releases'),
- re_path(r'^releases/(?P<release_name>[\w-]+)/(?P<format>(html|xml))?/?$',
- stats_views.release,
- name='release'),
- path('releases/compare/<dtype>/<path:rels_to_compare>/',
- stats_views.compare_by_releases,
- name='release-compare'),
+ re_path(r"^module/(?P<format>(html|json|xml))?/?$", stats_views.modules, name="modules"),
+ path("module/po/%s/<filename>" % module_branch_domain, stats_views.dynamic_po, name="dynamic_po"),
+ path("module/<name:module_name>/", stats_views.module, name="module"),
+ path("module/<name:module_name>/edit/branches/", stats_views.module_edit_branches,
name="module_edit_branches"),
+ path("module/<name:module_name>/branch/<path:branch_name>/", stats_views.module_branch,
name="module_branch"),
+ path("branch/<int:branch_id>/refresh/", stats_views.branch_refresh, name="branch_refresh"),
+ path(
+ "module/<name:module_name>/<name:potbase>/<path:branch_name>/<locale:langcode>/images/",
+ stats_views.docimages,
+ name="docimages",
+ ),
+ re_path(r"^releases/(?P<format>(html|json|xml))?/?$", stats_views.releases, name="releases"),
+ re_path(r"^releases/(?P<release_name>[\w-]+)/(?P<format>(html|xml))?/?$", stats_views.release,
name="release"),
+ path("releases/compare/<dtype>/<path:rels_to_compare>/", stats_views.compare_by_releases,
name="release-compare"),
]
if settings.USE_DEBUG_TOOLBAR:
import debug_toolbar
+
urlpatterns += [
- path('__debug__/', include(debug_toolbar.urls)),
+ path("__debug__/", include(debug_toolbar.urls)),
]
if settings.STATIC_SERVE:
urlpatterns += [
- path('media/<path:path>',
- serve,
- kwargs={'document_root': settings.MEDIA_ROOT}),
- path('POT/<path:path>',
- serve,
- kwargs={'document_root': settings.POTDIR}),
- path('HTML/<path:path>',
- serve,
- kwargs={'document_root': settings.SCRATCHDIR / 'HTML'}),
+ path("media/<path:path>", serve, kwargs={"document_root": settings.MEDIA_ROOT}),
+ path("POT/<path:path>", serve, kwargs={"document_root": settings.POTDIR}),
+ path("HTML/<path:path>", serve, kwargs={"document_root": settings.SCRATCHDIR / "HTML"}),
]
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 7dd797aa..526bb2be 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -5,41 +5,41 @@ import os
import sys
import django
-from recommonmark.parser import CommonMarkParser
import sphinx_rtd_theme
+from recommonmark.parser import CommonMarkParser
-sys.path.insert(0, os.path.abspath('..'))
-sys.path.insert(1, os.path.abspath('../..'))
+sys.path.insert(0, os.path.abspath(".."))
+sys.path.insert(1, os.path.abspath("../.."))
-os.environ['DJANGO_SETTINGS_MODULE'] = 'damnedlies.settings_tests'
+os.environ["DJANGO_SETTINGS_MODULE"] = "damnedlies.settings_tests"
django.setup()
# -- General configuration ------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
-extensions = ['recommonmark', 'sphinx.ext.autodoc']
+extensions = ["recommonmark", "sphinx.ext.autodoc"]
# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
+templates_path = ["_templates"]
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
-source_parsers = {'.md': CommonMarkParser}
+source_parsers = {".md": CommonMarkParser}
source_suffix = {
- '.rst': 'restructuredtext',
- '.txt': 'markdown',
- '.md': 'markdown',
+ ".rst": "restructuredtext",
+ ".txt": "markdown",
+ ".md": "markdown",
}
# The master toctree document.
-master_doc = 'index'
+master_doc = "index"
# General information about the project.
-project = 'damned-lies'
+project = "damned-lies"
# FIXME: is 2006 correct?
-copyright = '2006 - 2021 − The Damned Lies developers'
-author = 'The Damned Lies developers'
+copyright = "2006 - 2021 − The Damned Lies developers"
+author = "The Damned Lies developers"
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
@@ -56,7 +56,7 @@ release = "master"
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
-language = 'en'
+language = "en"
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
@@ -64,12 +64,12 @@ language = 'en'
exclude_patterns = []
# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
+pygments_style = "sphinx"
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = True
-autoclass_content = 'both'
+autoclass_content = "both"
# -- Options for HTML output ----------------------------------------------
html_theme = "sphinx_rtd_theme"
@@ -77,11 +77,11 @@ html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
html_theme_options = {
# Toc options
- 'collapse_navigation': False,
- 'sticky_navigation': True,
- 'navigation_depth': 4,
- 'includehidden': True,
- 'titles_only': True
+ "collapse_navigation": False,
+ "sticky_navigation": True,
+ "navigation_depth": 4,
+ "includehidden": True,
+ "titles_only": True,
}
# Add any paths that contain custom static files (such as style sheets) here,
@@ -92,4 +92,4 @@ html_static_path = []
# -- Options for HTMLHelp output ------------------------------------------
# Output file base name for HTML help builder.
-htmlhelp_basename = 'damned-lies'
+htmlhelp_basename = "damned-lies"
diff --git a/feeds/urls.py b/feeds/urls.py
index d0053186..1d8a3e3e 100644
--- a/feeds/urls.py
+++ b/feeds/urls.py
@@ -1,8 +1,8 @@
from django.urls import path
-from vertimus.feeds import LatestActionsByLanguage, LatestActionsByTeam
+from vertimus.feeds import LatestActionsByLanguage, LatestActionsByTeam
urlpatterns = [
- path('languages/<locale:locale>/', LatestActionsByLanguage(), name='lang_feed'),
- path('teams/<locale:team_name>/', LatestActionsByTeam(), name='team_feed'),
+ path("languages/<locale:locale>/", LatestActionsByLanguage(), name="lang_feed"),
+ path("teams/<locale:team_name>/", LatestActionsByTeam(), name="team_feed"),
]
diff --git a/languages/admin.py b/languages/admin.py
index d32b6cc3..a5809733 100644
--- a/languages/admin.py
+++ b/languages/admin.py
@@ -1,9 +1,10 @@
from django.contrib import admin
+
from languages.models import Language
class LanguageAdmin(admin.ModelAdmin):
- search_fields = ('name', 'locale')
+ search_fields = ("name", "locale")
admin.site.register(Language, LanguageAdmin)
diff --git a/languages/management/commands/load-plurals.py b/languages/management/commands/load-plurals.py
index 9c66f70e..02e51275 100644
--- a/languages/management/commands/load-plurals.py
+++ b/languages/management/commands/load-plurals.py
@@ -1,4 +1,5 @@
from django.core.management.base import BaseCommand
+
from languages.models import Language
# Shamelessly copied from http://translate.sourceforge.net/wiki/l10n/pluralforms (2009-05-25)
@@ -6,113 +7,113 @@ from languages.models import Language
# pylint: disable=line-too-long
# flake8: noqa
plurals = {
- 'af': "nplurals=2; plural=(n != 1)",
- 'ak': "nplurals=2; plural=(n > 1)",
- 'am': "nplurals=2; plural=(n > 1)",
- 'ar': "nplurals=6; plural= n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 &&
n%100<=99 ? 4 : 5;",
- 'arn': "nplurals=2; plural=(n > 1)",
- 'ay': "nplurals=1; plural=0",
- 'az': "nplurals=2; plural=(n != 1)",
- 'be': "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 || n%100>=20) ? 1
: 2)",
- 'bg': "nplurals=2; plural=(n != 1)",
- 'bn': "nplurals=2; plural=(n != 1)",
- 'bo': "nplurals=1; plural=0",
- 'bs': "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 || n%100>=20) ? 1
: 2)",
- 'ca': "nplurals=2; plural=(n != 1)",
- 'cs': "nplurals=3; plural=(n==1) ? 0 : (n>=2 && n< =4) ? 1 : 2",
- 'csb': "nplurals=3; n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2",
- 'cy': "nplurals=4; plural= (n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3",
- 'da': "nplurals=2; plural=(n != 1)",
- 'de': "nplurals=2; plural=(n != 1)",
- 'dz': "nplurals=1; plural=0",
- 'el': "nplurals=2; plural=(n != 1)",
- 'en': "nplurals=2; plural=(n != 1)",
- 'eo': "nplurals=2; plural=(n != 1)",
- 'es': "nplurals=2; plural=(n != 1)",
- 'es_AR': "nplurals=2; plural=(n != 1)",
- 'et': "nplurals=2; plural=(n != 1)",
- 'eu': "nplurals=2; plural=(n != 1)",
- 'fa': "nplurals=1; plural=0",
- 'fi': "nplurals=2; plural=(n != 1)",
- 'fil': "nplurals=2; plural=n > 1",
- 'fo': "nplurals=2; plural=(n != 1)",
- 'fr': "nplurals=2; plural=(n > 1)",
- 'fur': "nplurals=2; plural=(n != 1)",
- 'fy': "nplurals=2; plural=(n != 1)",
- 'ga': "nplurals=5; plural=n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4",
- 'gl': "nplurals=2; plural=(n != 1)",
- 'gu': "nplurals=2; plural=(n != 1)",
- 'gun': "nplurals=2; plural = (n > 1)",
- 'ha': "nplurals=2; plural=(n != 1)",
- 'he': "nplurals=2; plural=(n != 1)",
- 'hi': "nplurals=2; plural=(n != 1)",
- 'hy': "nplurals=1; plural=0",
- 'hr': "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 || n%100>=20) ? 1
: 2)",
- 'hu': "nplurals=1; plural=0",
- 'id': "nplurals=1; plural=0",
- 'is': "nplurals=2; plural=(n % 100 != 1 && n % 100 != 21 && n % 100 != 31 && n % 100 != 41 && n % 100 !=
51 && n % 100 != 61 && n % 100 != 71 && n % 100 != 81 && n % 100 != 91)",
- 'it': "nplurals=2; plural=(n != 1)",
- 'ja': "nplurals=1; plural=0",
- 'jv': "nplurals=2; plural=n!=0",
- 'ka': "nplurals=1; plural=0",
- 'km': "nplurals=1; plural=0",
- 'kn': "nplurals=2; plural=(n!=1)",
- 'ko': "nplurals=1; plural=0",
- 'ku': "nplurals=2; plural=(n!= 1)",
- 'kw': "nplurals=4; plural= (n==1) ? 0 : (n==2) ? 1 : (n == 3) ? 2 : 3",
- 'ky': "nplurals=1; plural=0",
- 'lb': "nplurals=2; plural=(n != 1)",
- 'ln': "nplurals=2; plural=n>1;",
- 'lo': "nplurals=1; plural=0",
- 'lt': "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2)",
- 'lv': "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2)",
- 'mg': "nplurals=2; plural=(n > 1)",
- 'mi': "nplurals=2; plural=(n > 1)",
- 'mk': "nplurals=2; plural= n==1 || n%10==1 ? 0 : 1",
- 'ml': "nplurals=2; plural=(n != 1)",
- 'mn': "nplurals=2; plural=(n != 1)",
- 'mr': "nplurals=2; plural=(n != 1)",
- 'ms': "nplurals=1; plural=0",
- 'mt': "nplurals=4; plural=(n==1 ? 0 : n==0 || ( n%100>1 && n%100<11) ? 1 : (n%100>10 && n%100<20 ) ? 2 :
3)",
- 'nah': "nplurals=2; plural=(n != 1)",
- 'nap': "nplurals=2; plural=(n != 1)",
- 'nb': "nplurals=2; plural=(n != 1)",
- 'ne': "nplurals=2; plural=(n != 1)",
- 'nl': "nplurals=2; plural=(n != 1)",
- 'nn': "nplurals=2; plural=(n != 1)",
- 'no': "nplurals=2; plural=(n != 1)",
- 'nso': "nplurals=2; plural=(n > 1)",
- 'or': "nplurals=2; plural=(n != 1)",
- 'ps': "nplurals=2; plural=(n != 1)",
- 'pa': "nplurals=2; plural=(n != 1)",
- 'pap': "nplurals=2; plural=(n != 1)",
- 'pl': "nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 || n%100>=20) ? 1 : 2)",
- 'pms': "nplurals=2; plural=(n != 1)",
- 'pt': "nplurals=2; plural=(n != 1)",
- 'pt_BR': "nplurals=2; plural=(n > 1)",
- 'ro': "nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2);",
- 'ru': "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 || n%100>=20) ? 1
: 2)",
- 'sco': "nplurals=2; plural=(n != 1)",
- 'sk': "nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2",
- 'sl': "nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3)",
- 'so': "nplurals=2; plural=n != 1",
- 'sq': "nplurals=2; plural=(n != 1)",
- 'sr': "nplurals=4; plural=n==1? 3 : n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 ||
n%100>=20) ? 1 : 2",
- 'su': "nplurals=1; plural=0",
- 'sv': "nplurals=2; plural=(n != 1)",
- 'ta': "nplurals=2; plural=(n != 1)",
- 'te': "nplurals=2; plural=(n != 1)",
- 'tg': "nplurals=2; plural=(n != 1)",
- 'ti': "nplurals=2; plural=n > 1",
- 'th': "nplurals=1; plural=0",
- 'tk': "nplurals=2; plural=(n != 1)",
- 'tr': "nplurals=1; plural=0",
- 'uk': "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 || n%100>=20) ? 1
: 2)",
- 'ur': "nplurals=2; plural=(n != 1)",
- 'uz': "nplurals=1; plural=0;",
- 'vi': "nplurals=1; plural=0",
- 'wa': "nplurals=2; plural=(n > 1)",
- 'zh': "nplurals=1; plural=0",
+ "af": "nplurals=2; plural=(n != 1)",
+ "ak": "nplurals=2; plural=(n > 1)",
+ "am": "nplurals=2; plural=(n > 1)",
+ "ar": "nplurals=6; plural= n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 &&
n%100<=99 ? 4 : 5;",
+ "arn": "nplurals=2; plural=(n > 1)",
+ "ay": "nplurals=1; plural=0",
+ "az": "nplurals=2; plural=(n != 1)",
+ "be": "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 || n%100>=20) ? 1
: 2)",
+ "bg": "nplurals=2; plural=(n != 1)",
+ "bn": "nplurals=2; plural=(n != 1)",
+ "bo": "nplurals=1; plural=0",
+ "bs": "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 || n%100>=20) ? 1
: 2)",
+ "ca": "nplurals=2; plural=(n != 1)",
+ "cs": "nplurals=3; plural=(n==1) ? 0 : (n>=2 && n< =4) ? 1 : 2",
+ "csb": "nplurals=3; n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2",
+ "cy": "nplurals=4; plural= (n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3",
+ "da": "nplurals=2; plural=(n != 1)",
+ "de": "nplurals=2; plural=(n != 1)",
+ "dz": "nplurals=1; plural=0",
+ "el": "nplurals=2; plural=(n != 1)",
+ "en": "nplurals=2; plural=(n != 1)",
+ "eo": "nplurals=2; plural=(n != 1)",
+ "es": "nplurals=2; plural=(n != 1)",
+ "es_AR": "nplurals=2; plural=(n != 1)",
+ "et": "nplurals=2; plural=(n != 1)",
+ "eu": "nplurals=2; plural=(n != 1)",
+ "fa": "nplurals=1; plural=0",
+ "fi": "nplurals=2; plural=(n != 1)",
+ "fil": "nplurals=2; plural=n > 1",
+ "fo": "nplurals=2; plural=(n != 1)",
+ "fr": "nplurals=2; plural=(n > 1)",
+ "fur": "nplurals=2; plural=(n != 1)",
+ "fy": "nplurals=2; plural=(n != 1)",
+ "ga": "nplurals=5; plural=n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4",
+ "gl": "nplurals=2; plural=(n != 1)",
+ "gu": "nplurals=2; plural=(n != 1)",
+ "gun": "nplurals=2; plural = (n > 1)",
+ "ha": "nplurals=2; plural=(n != 1)",
+ "he": "nplurals=2; plural=(n != 1)",
+ "hi": "nplurals=2; plural=(n != 1)",
+ "hy": "nplurals=1; plural=0",
+ "hr": "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 || n%100>=20) ? 1
: 2)",
+ "hu": "nplurals=1; plural=0",
+ "id": "nplurals=1; plural=0",
+ "is": "nplurals=2; plural=(n % 100 != 1 && n % 100 != 21 && n % 100 != 31 && n % 100 != 41 && n % 100 !=
51 && n % 100 != 61 && n % 100 != 71 && n % 100 != 81 && n % 100 != 91)",
+ "it": "nplurals=2; plural=(n != 1)",
+ "ja": "nplurals=1; plural=0",
+ "jv": "nplurals=2; plural=n!=0",
+ "ka": "nplurals=1; plural=0",
+ "km": "nplurals=1; plural=0",
+ "kn": "nplurals=2; plural=(n!=1)",
+ "ko": "nplurals=1; plural=0",
+ "ku": "nplurals=2; plural=(n!= 1)",
+ "kw": "nplurals=4; plural= (n==1) ? 0 : (n==2) ? 1 : (n == 3) ? 2 : 3",
+ "ky": "nplurals=1; plural=0",
+ "lb": "nplurals=2; plural=(n != 1)",
+ "ln": "nplurals=2; plural=n>1;",
+ "lo": "nplurals=1; plural=0",
+ "lt": "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2)",
+ "lv": "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2)",
+ "mg": "nplurals=2; plural=(n > 1)",
+ "mi": "nplurals=2; plural=(n > 1)",
+ "mk": "nplurals=2; plural= n==1 || n%10==1 ? 0 : 1",
+ "ml": "nplurals=2; plural=(n != 1)",
+ "mn": "nplurals=2; plural=(n != 1)",
+ "mr": "nplurals=2; plural=(n != 1)",
+ "ms": "nplurals=1; plural=0",
+ "mt": "nplurals=4; plural=(n==1 ? 0 : n==0 || ( n%100>1 && n%100<11) ? 1 : (n%100>10 && n%100<20 ) ? 2 :
3)",
+ "nah": "nplurals=2; plural=(n != 1)",
+ "nap": "nplurals=2; plural=(n != 1)",
+ "nb": "nplurals=2; plural=(n != 1)",
+ "ne": "nplurals=2; plural=(n != 1)",
+ "nl": "nplurals=2; plural=(n != 1)",
+ "nn": "nplurals=2; plural=(n != 1)",
+ "no": "nplurals=2; plural=(n != 1)",
+ "nso": "nplurals=2; plural=(n > 1)",
+ "or": "nplurals=2; plural=(n != 1)",
+ "ps": "nplurals=2; plural=(n != 1)",
+ "pa": "nplurals=2; plural=(n != 1)",
+ "pap": "nplurals=2; plural=(n != 1)",
+ "pl": "nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 || n%100>=20) ? 1 : 2)",
+ "pms": "nplurals=2; plural=(n != 1)",
+ "pt": "nplurals=2; plural=(n != 1)",
+ "pt_BR": "nplurals=2; plural=(n > 1)",
+ "ro": "nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2);",
+ "ru": "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 || n%100>=20) ? 1
: 2)",
+ "sco": "nplurals=2; plural=(n != 1)",
+ "sk": "nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2",
+ "sl": "nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3)",
+ "so": "nplurals=2; plural=n != 1",
+ "sq": "nplurals=2; plural=(n != 1)",
+ "sr": "nplurals=4; plural=n==1? 3 : n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 ||
n%100>=20) ? 1 : 2",
+ "su": "nplurals=1; plural=0",
+ "sv": "nplurals=2; plural=(n != 1)",
+ "ta": "nplurals=2; plural=(n != 1)",
+ "te": "nplurals=2; plural=(n != 1)",
+ "tg": "nplurals=2; plural=(n != 1)",
+ "ti": "nplurals=2; plural=n > 1",
+ "th": "nplurals=1; plural=0",
+ "tk": "nplurals=2; plural=(n != 1)",
+ "tr": "nplurals=1; plural=0",
+ "uk": "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 || n%100>=20) ? 1
: 2)",
+ "ur": "nplurals=2; plural=(n != 1)",
+ "uz": "nplurals=1; plural=0;",
+ "vi": "nplurals=1; plural=0",
+ "wa": "nplurals=2; plural=(n > 1)",
+ "zh": "nplurals=1; plural=0",
# 'zh means all districts and all variants of Chinese, such as zh_CN, zh_HK, zh_TW and so on.",
# Note: In rare cases where plural form introduces difference in personal pronoun (such as her vs. they,
we vs. I), the plural form is different:",
# 'zh Chinese nplurals=2; plural=(n > 1)",
@@ -120,9 +121,8 @@ plurals = {
class Command(BaseCommand):
-
def handle(self, *args, **options):
- """ Load known plural forms for existing languages """
+ """Load known plural forms for existing languages"""
for locale in plurals:
try:
diff --git a/languages/migrations/0001_initial.py b/languages/migrations/0001_initial.py
index 1c83fe24..19218ffa 100644
--- a/languages/migrations/0001_initial.py
+++ b/languages/migrations/0001_initial.py
@@ -1,25 +1,28 @@
-from django.db import models, migrations
+from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ('teams', '__first__'),
+ ("teams", "__first__"),
]
operations = [
migrations.CreateModel(
- name='Language',
+ name="Language",
fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True,
primary_key=True)),
- ('name', models.CharField(unique=True, max_length=50)),
- ('locale', models.CharField(unique=True, max_length=15)),
- ('plurals', models.CharField(max_length=200, blank=True)),
- ('team', models.ForeignKey(default=None, blank=True, to='teams.Team', null=True,
on_delete=models.SET_NULL)),
+ ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True,
primary_key=True)),
+ ("name", models.CharField(unique=True, max_length=50)),
+ ("locale", models.CharField(unique=True, max_length=15)),
+ ("plurals", models.CharField(max_length=200, blank=True)),
+ (
+ "team",
+ models.ForeignKey(default=None, blank=True, to="teams.Team", null=True,
on_delete=models.SET_NULL),
+ ),
],
options={
- 'ordering': ('name',),
- 'db_table': 'language',
+ "ordering": ("name",),
+ "db_table": "language",
},
),
]
diff --git a/languages/models.py b/languages/models.py
index 6a6b12ee..323abf93 100644
--- a/languages/models.py
+++ b/languages/models.py
@@ -1,9 +1,10 @@
from django.conf import settings
from django.db import models
from django.db.models import Q
-from django.utils.translation import gettext as _
from django.urls import NoReverseMatch
-from teams.models import Team, FakeTeam
+from django.utils.translation import gettext as _
+
+from teams.models import FakeTeam, Team
class Language(models.Model):
@@ -14,13 +15,13 @@ class Language(models.Model):
# Mapping for code differences between GNOME and Django standards.
lang_mapping = {
- 'zh_CN': 'zh_Hans',
- 'zh_TW': 'zh_Hant',
+ "zh_CN": "zh_Hans",
+ "zh_TW": "zh_Hant",
}
class Meta:
- db_table = 'language'
- ordering = ('name',)
+ db_table = "language"
+ ordering = ("name",)
def __str__(self):
return "%s (%s)" % (self.name, self.locale)
@@ -31,13 +32,13 @@ class Language(models.Model):
@classmethod
def get_language_from_ianacode(cls, ianacode):
- """ Return a matching Language object corresponding to LANGUAGE_CODE (BCP47-formatted)
- This is a sort of BCP47 to ISO639 conversion function """
+ """Return a matching Language object corresponding to LANGUAGE_CODE (BCP47-formatted)
+ This is a sort of BCP47 to ISO639 conversion function"""
iana_splitted = ianacode.split("-", 1)
lang_code = iana_splitted[0]
iana_suffix = iana_splitted[1] if len(iana_splitted) > 1 else ""
- iana_suffix = iana_suffix.replace('Latn', 'latin').replace('Cyrl', 'cyrillic')
- lang_list = cls.objects.filter(Q(locale=lang_code) | Q(locale__regex=r'^%s[@_].+' % lang_code))
+ iana_suffix = iana_suffix.replace("Latn", "latin").replace("Cyrl", "cyrillic")
+ lang_list = cls.objects.filter(Q(locale=lang_code) | Q(locale__regex=r"^%s[@_].+" % lang_code))
if len(lang_list) == 0:
return None
if len(lang_list) > 1:
@@ -58,7 +59,7 @@ class Language(models.Model):
return self.locale
def get_suffix(self):
- splitted = self.locale.replace('@', '_').split('_')
+ splitted = self.locale.replace("@", "_").split("_")
if len(splitted) > 1:
return splitted[-1]
return None
@@ -69,23 +70,23 @@ class Language(models.Model):
def bugs_url_enter(self):
return settings.ENTER_LANGUAGE_BUG_URL % {
- 'lang': self.locale,
+ "lang": self.locale,
}
def bugs_url_show(self):
return settings.BROWSE_LANGUAGE_BUG_URL % {
- 'lang': self.locale,
+ "lang": self.locale,
}
def get_release_stats(self, archives=False):
# FIXME Here be dragons
- """ Get summary stats for all releases """
+ """Get summary stats for all releases"""
from stats.models import Release
if archives:
- releases = Release.objects.all().filter(weight__lt=0).order_by('status', '-weight', '-name')
+ releases = Release.objects.all().filter(weight__lt=0).order_by("status", "-weight", "-name")
else:
- releases = Release.objects.all().filter(weight__gte=0).order_by('status', '-weight', '-name')
+ releases = Release.objects.all().filter(weight__gte=0).order_by("status", "-weight", "-name")
stats = []
for rel in releases:
stats.append(rel.total_for_lang(self))
@@ -97,4 +98,4 @@ class Language(models.Model):
try:
return FakeTeam(self).get_absolute_url()
except NoReverseMatch:
- return ''
+ return ""
diff --git a/languages/tests.py b/languages/tests.py
index ecea8f50..2fb28205 100644
--- a/languages/tests.py
+++ b/languages/tests.py
@@ -1,25 +1,25 @@
-from django.urls import reverse
from django.test import TestCase
+from django.urls import reverse
from languages.models import Language
class LanguageTestCase(TestCase):
- fixtures = ['sample_data.json']
+ fixtures = ["sample_data.json"]
def test_language_release_xml(self):
- response = self.client.get(reverse('language_release_xml', args=['fr', 'gnome-3-8']))
+ response = self.client.get(reverse("language_release_xml", args=["fr", "gnome-3-8"]))
self.assertContains(response, """<stats language="fr" release="gnome-3-8">""")
def test_language_from_ianacode(self):
- Language.objects.create(name='Belarussian', locale='be')
- Language.objects.create(name='French (Belgium)', locale='fr_BE')
- Language.objects.create(name='Chinese (Taiwan)', locale='zh_TW')
+ Language.objects.create(name="Belarussian", locale="be")
+ Language.objects.create(name="French (Belgium)", locale="fr_BE")
+ Language.objects.create(name="Chinese (Taiwan)", locale="zh_TW")
func = Language.get_language_from_ianacode
- self.assertEqual(func('fr-ch').locale, 'fr')
- self.assertEqual(func('fr-be').locale, 'fr_BE')
- self.assertEqual(func('be').locale, 'be')
- self.assertEqual(func('be-latin-RU').locale, 'be')
- self.assertEqual(func('zh-tw').locale, 'zh_TW')
- self.assertEqual(func('xx'), None)
+ self.assertEqual(func("fr-ch").locale, "fr")
+ self.assertEqual(func("fr-be").locale, "fr_BE")
+ self.assertEqual(func("be").locale, "be")
+ self.assertEqual(func("be-latin-RU").locale, "be")
+ self.assertEqual(func("zh-tw").locale, "zh_TW")
+ self.assertEqual(func("xx"), None)
diff --git a/languages/urls.py b/languages/urls.py
index 6b61f7bd..2cb6d9d7 100644
--- a/languages/urls.py
+++ b/languages/urls.py
@@ -3,26 +3,16 @@ from django.urls import path
from languages import views
from teams import views as team_views
-
urlpatterns = [
- path('',
- views.languages,
- name='languages'),
- path('<locale:locale>/all/<slug:dtype>/',
- views.language_all,
- name='language_all'),
- path('<locale:locale>/rel-archives/',
- views.release_archives,
- name='language_release_archives'),
- path('<locale:locale>/<name:release_name>/<slug:dtype>/',
- views.language_release,
- name='language_release'),
- path('<locale:locale>/<name:release_name>/xml',
- views.language_release_xml,
- name='language_release_xml'),
- path('<locale:locale>/<name:release_name>/<slug:dtype>.tar.gz',
- views.language_release_tar,
- name='language_release_tar'),
- path('<locale:team_slug>/',
- team_views.team),
+ path("", views.languages, name="languages"),
+ path("<locale:locale>/all/<slug:dtype>/", views.language_all, name="language_all"),
+ path("<locale:locale>/rel-archives/", views.release_archives, name="language_release_archives"),
+ path("<locale:locale>/<name:release_name>/<slug:dtype>/", views.language_release,
name="language_release"),
+ path("<locale:locale>/<name:release_name>/xml", views.language_release_xml, name="language_release_xml"),
+ path(
+ "<locale:locale>/<name:release_name>/<slug:dtype>.tar.gz",
+ views.language_release_tar,
+ name="language_release_tar",
+ ),
+ path("<locale:team_slug>/", team_views.team),
]
diff --git a/languages/views.py b/languages/views.py
index 9999cca0..35c267e9 100644
--- a/languages/views.py
+++ b/languages/views.py
@@ -4,7 +4,7 @@ from datetime import date, datetime, timezone
from django.conf import settings
from django.http import HttpResponse, HttpResponseRedirect
-from django.shortcuts import render, get_object_or_404
+from django.shortcuts import get_object_or_404, render
from django.utils.translation import gettext as _
from common import utils
@@ -15,62 +15,64 @@ from stats.models import Release, Statistics
def languages(request):
languages = Language.objects.select_related("team").all()
context = {
- 'pageSection': "languages",
- 'languages': utils.trans_sort_object_list(languages, 'name'),
+ "pageSection": "languages",
+ "languages": utils.trans_sort_object_list(languages, "name"),
}
- return render(request, 'languages/language_list.html', context)
+ return render(request, "languages/language_list.html", context)
def language_all(request, locale, dtype):
language = get_object_or_404(Language, locale=locale)
stats = Statistics.get_lang_stats_by_type(language, dtype, release=None)
context = {
- 'pageSection': "languages",
- 'language': language,
- 'stats_title': {
- 'ui': _("UI Translations"),
- 'ui-part': _("UI Translations (reduced)"),
- 'doc': _("Documentation")}.get(dtype),
- 'stats': stats,
- 'scope': dtype.endswith('-part') and 'part' or 'full',
+ "pageSection": "languages",
+ "language": language,
+ "stats_title": {
+ "ui": _("UI Translations"),
+ "ui-part": _("UI Translations (reduced)"),
+ "doc": _("Documentation"),
+ }.get(dtype),
+ "stats": stats,
+ "scope": dtype.endswith("-part") and "part" or "full",
}
- return render(request, 'languages/language_all_modules.html', context)
+ return render(request, "languages/language_all_modules.html", context)
def release_archives(request, locale):
- """ This view is used to display archive release stats through Ajax call
- Only the HTML table is produced
+ """This view is used to display archive release stats through Ajax call
+ Only the HTML table is produced
"""
language = get_object_or_404(Language, locale=locale)
context = {
- 'lang': language,
- 'stats': language.get_release_stats(archives=True),
- 'show_all_modules_line': False,
+ "lang": language,
+ "stats": language.get_release_stats(archives=True),
+ "show_all_modules_line": False,
}
- return render(request, 'languages/language_release_summary.html', context)
+ return render(request, "languages/language_release_summary.html", context)
def language_release(request, locale, release_name, dtype):
- if locale == 'C':
+ if locale == "C":
language = None
else:
language = get_object_or_404(Language, locale=locale)
release = get_object_or_404(Release, name=release_name)
stats = Statistics.get_lang_stats_by_type(language, dtype, release)
context = {
- 'pageSection': "languages",
- 'language': language,
- 'language_name': language and language.get_name() or _("Original strings"),
- 'release': release,
- 'stats_title': {
- 'ui': _("UI Translations"),
- 'ui-part': _("UI Translations (reduced)"),
- 'doc': _("Documentation")}.get(dtype),
- 'stats': stats,
- 'dtype': dtype,
- 'scope': dtype.endswith('-part') and 'part' or 'full',
+ "pageSection": "languages",
+ "language": language,
+ "language_name": language and language.get_name() or _("Original strings"),
+ "release": release,
+ "stats_title": {
+ "ui": _("UI Translations"),
+ "ui-part": _("UI Translations (reduced)"),
+ "doc": _("Documentation"),
+ }.get(dtype),
+ "stats": stats,
+ "dtype": dtype,
+ "scope": dtype.endswith("-part") and "part" or "full",
}
- return render(request, 'languages/language_release.html', context)
+ return render(request, "languages/language_release.html", context)
def language_release_tar(request, locale, release_name, dtype):
@@ -78,17 +80,16 @@ def language_release_tar(request, locale, release_name, dtype):
language = get_object_or_404(Language, locale=locale)
last_modif, file_list = release.get_lang_files(language, dtype)
- tar_filename = '%s.%s.%s.%s.tar.gz' % (release.name, dtype, language.locale, date.today())
- tar_directory = os.path.join(settings.POTDIR, 'tar')
+ tar_filename = "%s.%s.%s.%s.tar.gz" % (release.name, dtype, language.locale, date.today())
+ tar_directory = os.path.join(settings.POTDIR, "tar")
if not os.access(tar_directory, os.R_OK):
os.mkdir(tar_directory)
tar_path = os.path.join(tar_directory, tar_filename)
- if (
- not os.access(tar_path, os.R_OK) or
- last_modif > datetime.fromtimestamp(os.path.getmtime(tar_path), timezone.utc)
+ if not os.access(tar_path, os.R_OK) or last_modif > datetime.fromtimestamp(
+ os.path.getmtime(tar_path), timezone.utc
):
# Create a new tar file
- tar_file = tarfile.open(tar_path, 'w:gz')
+ tar_file = tarfile.open(tar_path, "w:gz")
for f in file_list:
tar_file.add(f, os.path.basename(f))
tar_file.close()
@@ -97,31 +98,31 @@ def language_release_tar(request, locale, release_name, dtype):
def language_release_xml(request, locale, release_name):
- """ This view create the same XML output than the previous Damned-Lies, so as
- apps which depend on it (like Vertimus) don't break.
- This view may be suppressed when Vertimus will be integrated in D-L. """
+ """This view create the same XML output than the previous Damned-Lies, so as
+ apps which depend on it (like Vertimus) don't break.
+ This view may be suppressed when Vertimus will be integrated in D-L."""
language = get_object_or_404(Language, locale=locale)
release = get_object_or_404(Release, name=release_name)
stats = release.get_lang_stats(language)
- content = "<stats language=\"%s\" release=\"%s\">\n" % (locale, release_name)
- for catname, categ in stats['ui']['categs'].items():
- if catname != 'default':
- content += "<category id=\"%s\">" % catname
+ content = '<stats language="%s" release="%s">\n' % (locale, release_name)
+ for catname, categ in stats["ui"]["categs"].items():
+ if catname != "default":
+ content += '<category id="%s">' % catname
# totals for category
- if catname in stats['doc']['categs']:
- content += "<doctranslated>%s</doctranslated>" % stats['doc']['categs'][catname]['cattrans']
- content += "<docfuzzy>%s</docfuzzy>" % stats['doc']['categs'][catname]['catfuzzy']
- content += "<docuntranslated>%s</docuntranslated>" %
stats['doc']['categs'][catname]['catuntrans']
- content += "<translated>%s</translated>" % categ['cattrans']
- content += "<fuzzy>%s</fuzzy>" % categ['catfuzzy']
- content += "<untranslated>%s</untranslated>" % categ['catuntrans']
+ if catname in stats["doc"]["categs"]:
+ content += "<doctranslated>%s</doctranslated>" % stats["doc"]["categs"][catname]["cattrans"]
+ content += "<docfuzzy>%s</docfuzzy>" % stats["doc"]["categs"][catname]["catfuzzy"]
+ content += "<docuntranslated>%s</docuntranslated>" %
stats["doc"]["categs"][catname]["catuntrans"]
+ content += "<translated>%s</translated>" % categ["cattrans"]
+ content += "<fuzzy>%s</fuzzy>" % categ["catfuzzy"]
+ content += "<untranslated>%s</untranslated>" % categ["catuntrans"]
# Modules
- for module, mod_stats in categ['modules'].items():
+ for module, mod_stats in categ["modules"].items():
branch_name, domains = list(mod_stats.items())[0]
- content += "<module id=\"%s\" branch=\"%s\">" % (module.name, branch_name)
+ content += '<module id="%s" branch="%s">' % (module.name, branch_name)
# DOC domains
- if catname in stats['doc']['categs'] and stats['doc']['categs'][catname]['modules']:
- for docmod, data in stats['doc']['categs'][catname]['modules'].items():
+ if catname in stats["doc"]["categs"] and stats["doc"]["categs"][catname]["modules"]:
+ for docmod, data in stats["doc"]["categs"][catname]["modules"].items():
if docmod == module:
content += get_domain_stats(list(data.values())[0], "document")
# UI stats
@@ -129,36 +130,36 @@ def language_release_xml(request, locale, release_name):
content += "</module>"
# Add modules who have no ui counterparts
# FIXME: duplicated code fragment. Replace respectively 'dev-tools' and 'desktop' with the catname
variable
- if catname == 'dev-tools':
+ if catname == "dev-tools":
try:
- mod = [m for m in stats['doc']['categs']['dev-tools']['modules'] if m[0] ==
'gnome-devel-docs'][0][1]
- content += "<module id=\"gnome-devel-docs\" branch=\"%s\">" % mod.keys()[0]
+ mod = [m for m in stats["doc"]["categs"]["dev-tools"]["modules"] if m[0] ==
"gnome-devel-docs"][0][1]
+ content += '<module id="gnome-devel-docs" branch="%s">' % mod.keys()[0]
content += get_domain_stats(mod.values()[0], "document")
content += "</module>"
except Exception: # FIXME: which exception is this catching?
pass
- if catname == 'desktop':
+ if catname == "desktop":
try:
- mod = [m for m in stats['doc']['categs']['desktop']['modules'] if m[0] ==
'gnome-user-docs'][0][1]
- content += "<module id=\"gnome-user-docs\" branch=\"%s\">" % mod.keys()[0]
+ mod = [m for m in stats["doc"]["categs"]["desktop"]["modules"] if m[0] ==
"gnome-user-docs"][0][1]
+ content += '<module id="gnome-user-docs" branch="%s">' % mod.keys()[0]
content += get_domain_stats(mod.values()[0], "document")
content += "</module>"
except Exception: # FIXME: which exception is this catching?
pass
- if catname != 'default':
+ if catname != "default":
content += "</category>"
content += "</stats>"
- return HttpResponse(content, content_type='text/xml')
+ return HttpResponse(content, content_type="text/xml")
def get_domain_stats(mods, node_name):
- """ Iterate module domains to get stats """
+ """Iterate module domains to get stats"""
content = ""
for dom_key, stat in mods:
- if dom_key == ' fake':
+ if dom_key == " fake":
continue
- content += "<%s id=\"%s\">" % (node_name, stat.domain.name)
+ content += '<%s id="%s">' % (node_name, stat.domain.name)
content += "<translated>%s</translated>" % stat.translated()
content += "<fuzzy>%s</fuzzy>" % stat.fuzzy()
content += "<untranslated>%s</untranslated>" % stat.untranslated()
@@ -170,8 +171,8 @@ def get_domain_stats(mods, node_name):
# ********* Utility functions ******************
def clean_tar_files():
- """ Delete outdated tar.gz files generated by the language_release_tar view """
- tar_directory = os.path.join(settings.POTDIR, 'tar')
+ """Delete outdated tar.gz files generated by the language_release_tar view"""
+ tar_directory = os.path.join(settings.POTDIR, "tar")
if not os.path.exists(tar_directory):
return
for tar_file in os.listdir(tar_directory):
diff --git a/people/admin.py b/people/admin.py
index dcfee618..843c3cd9 100644
--- a/people/admin.py
+++ b/people/admin.py
@@ -1,16 +1,17 @@
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
+
from people.models import Person
@admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
- search_fields = ('username', 'first_name', 'last_name', 'email')
- list_display = ('username', 'first_name', 'last_name', 'email', 'webpage_url')
+ search_fields = ("username", "first_name", "last_name", "email")
+ list_display = ("username", "first_name", "last_name", "email", "webpage_url")
-UserAdmin.list_display = ('username', 'email', 'last_name', 'first_name', 'is_active', 'last_login')
+UserAdmin.list_display = ("username", "email", "last_name", "first_name", "is_active", "last_login")
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
diff --git a/people/forms.py b/people/forms.py
index ce6e475c..d682100c 100644
--- a/people/forms.py
+++ b/people/forms.py
@@ -3,17 +3,16 @@ import random
from http.client import InvalidURL
from urllib.request import urlopen
-from PIL import ImageFile
-
from django import forms
from django.conf import settings
-from django.contrib.auth.forms import AuthenticationForm
-from django.contrib.auth.forms import UsernameField
+from django.contrib.auth.forms import AuthenticationForm, UsernameField
from django.contrib.auth.validators import UnicodeUsernameValidator
from django.core.exceptions import ValidationError
from django.urls import reverse
from django.utils.encoding import force_bytes
-from django.utils.translation import gettext_lazy, gettext as _
+from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy
+from PIL import ImageFile
from common.utils import send_mail
from people.models import Person
@@ -23,48 +22,55 @@ username_validator = UnicodeUsernameValidator()
class RegistrationForm(forms.Form):
- """ Form for user registration """
- username = UsernameField(max_length=30,
- label=gettext_lazy('Choose a username:'),
- validators=[username_validator],
- help_text=gettext_lazy('May contain only letters, numbers, and @/./+/-/_
characters.'),
- widget=forms.TextInput(attrs={'class': 'form-control'}))
- email = forms.EmailField(widget=forms.TextInput(attrs={'class': 'form-control'}),
- label=gettext_lazy('Email:'))
- password1 = forms.CharField(widget=forms.PasswordInput(attrs={'class': 'form-control'},
render_value=False),
- label=gettext_lazy('Password:'), required=False, min_length=7,
- help_text=gettext_lazy('At least 7 characters'))
- password2 = forms.CharField(widget=forms.PasswordInput(attrs={'class': 'form-control'},
render_value=False),
- label=gettext_lazy('Confirm password:'), required=False)
+ """Form for user registration"""
+
+ username = UsernameField(
+ max_length=30,
+ label=gettext_lazy("Choose a username:"),
+ validators=[username_validator],
+ help_text=gettext_lazy("May contain only letters, numbers, and @/./+/-/_ characters."),
+ widget=forms.TextInput(attrs={"class": "form-control"}),
+ )
+ email = forms.EmailField(widget=forms.TextInput(attrs={"class": "form-control"}),
label=gettext_lazy("Email:"))
+ password1 = forms.CharField(
+ widget=forms.PasswordInput(attrs={"class": "form-control"}, render_value=False),
+ label=gettext_lazy("Password:"),
+ required=False,
+ min_length=7,
+ help_text=gettext_lazy("At least 7 characters"),
+ )
+ password2 = forms.CharField(
+ widget=forms.PasswordInput(attrs={"class": "form-control"}, render_value=False),
+ label=gettext_lazy("Confirm password:"),
+ required=False,
+ )
def clean_username(self):
- """ Validate the username (correctness and uniqueness)"""
+ """Validate the username (correctness and uniqueness)"""
try:
- Person.objects.get(username__iexact=self.cleaned_data['username'])
+ Person.objects.get(username__iexact=self.cleaned_data["username"])
except Person.DoesNotExist:
- return self.cleaned_data['username']
- raise ValidationError(_('This username is already taken. Please choose another.'))
+ return self.cleaned_data["username"]
+ raise ValidationError(_("This username is already taken. Please choose another."))
def clean(self):
cleaned_data = self.cleaned_data
- password1 = cleaned_data.get('password1')
- password2 = cleaned_data.get('password2')
+ password1 = cleaned_data.get("password1")
+ password2 = cleaned_data.get("password2")
if not password1:
- raise ValidationError(_('You must provide a password'))
+ raise ValidationError(_("You must provide a password"))
if password1 and password1 != password2:
- raise ValidationError(_('The passwords do not match'))
+ raise ValidationError(_("The passwords do not match"))
return cleaned_data
def save(self, request):
- """ Create the user """
- username = self.cleaned_data['username']
- email = self.cleaned_data['email']
- password = self.cleaned_data['password1']
+ """Create the user"""
+ username = self.cleaned_data["username"]
+ email = self.cleaned_data["email"]
+ password = self.cleaned_data["password1"]
- new_user = Person.objects.create_user(
- username=username, email=email, password=password
- )
+ new_user = Person.objects.create_user(username=username, email=email, password=password)
salt = hashlib.sha1(force_bytes(random.random())).hexdigest()[:5]
activation_key = hashlib.sha1(force_bytes(salt + username)).hexdigest()
new_user.activation_key = activation_key
@@ -72,21 +78,24 @@ class RegistrationForm(forms.Form):
new_user.save()
# Send activation email
site_domain = settings.SITE_DOMAIN
- activation_url = str(reverse("register_activation", kwargs={'key': activation_key}))
- message = _(
- "This is a confirmation that your registration on %s succeeded. To activate your account, "
- "please click on the link below or copy and paste it in a browser."
- ) % site_domain
+ activation_url = str(reverse("register_activation", kwargs={"key": activation_key}))
+ message = (
+ _(
+ "This is a confirmation that your registration on %s succeeded. To activate your account, "
+ "please click on the link below or copy and paste it in a browser."
+ )
+ % site_domain
+ )
message += "\n\nhttps://%s%s\n\n" % (site_domain, activation_url)
message += _("Administrators of %s" % site_domain)
- send_mail(_('Account activation'), message, to=[email])
+ send_mail(_("Account activation"), message, to=[email])
return new_user
class LoginForm(AuthenticationForm):
def clean_username(self):
- username = self.cleaned_data['username']
- if '@' in username and not Person.objects.filter(username=username).exists():
+ username = self.cleaned_data["username"]
+ if "@" in username and not Person.objects.filter(username=username).exists():
try:
username = Person.objects.filter(email=username).first().username
except AttributeError:
@@ -97,18 +106,16 @@ class LoginForm(AuthenticationForm):
class DetailForm(forms.ModelForm):
class Meta:
model = Person
- fields = ('username', 'first_name', 'last_name', 'email', 'image', 'avatar_service',
- 'webpage_url', 'irc_nick')
+ fields = ("username", "first_name", "last_name", "email", "image", "avatar_service", "webpage_url",
"irc_nick")
def clean_image(self):
- url = self.cleaned_data['image']
+ url = self.cleaned_data["image"]
if url:
size = get_image_size(url)
if size[0] > 100 or size[1] > 100:
raise ValidationError(
- _("Image too high or too wide (%(width)d×%(height)d, maximum is 100×100 pixels)") % {
- 'width': size[0], 'height': size[1]
- }
+ _("Image too high or too wide (%(width)d×%(height)d, maximum is 100×100 pixels)")
+ % {"width": size[0], "height": size[1]}
)
return url
@@ -122,12 +129,12 @@ class TeamJoinForm(forms.Form):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# FIXME: exclude team to which user is already member
- self.fields['teams'] = TeamChoiceField(queryset=Team.objects.all())
+ self.fields["teams"] = TeamChoiceField(queryset=Team.objects.all())
def get_image_size(url):
- """ Returns width and height (as tuple) of the image poited at by the url
- Code partially copied from http://effbot.org/zone/pil-image-size.htm """
+ """Returns width and height (as tuple) of the image poited at by the url
+ Code partially copied from http://effbot.org/zone/pil-image-size.htm"""
try:
im_file = urlopen(url)
except (IOError, InvalidURL, EOFError, ValueError):
diff --git a/people/migrations/0001_initial.py b/people/migrations/0001_initial.py
index 8eac1ab7..96053779 100644
--- a/people/migrations/0001_initial.py
+++ b/people/migrations/0001_initial.py
@@ -1,34 +1,64 @@
-from django.db import models, migrations
-from django.conf import settings
import django.contrib.auth.models
+from django.conf import settings
+from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ('auth', '0001_initial'),
+ ("auth", "0001_initial"),
]
operations = [
migrations.CreateModel(
- name='Person',
+ name="Person",
fields=[
- ('user_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True,
serialize=False, to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
- ('svn_account', models.SlugField(max_length=20, null=True, blank=True)),
- ('image', models.URLField(help_text='URL to an image file (.jpg, .png, …) of an hackergotchi
(max. 100×100 pixels)', null=True, verbose_name='Image', blank=True)),
- ('use_gravatar', models.BooleanField(default=False, help_text='Display the image of your
gravatar.com account')),
- ('webpage_url', models.URLField(null=True, verbose_name='Web page', blank=True)),
- ('irc_nick', models.SlugField(max_length=20, null=True, verbose_name='IRC nickname',
blank=True)),
- ('bugzilla_account', models.EmailField(help_text="This should be an email address, useful if
not equal to “E-mail address” field", max_length=254, null=True, verbose_name='Bugzilla account',
blank=True)),
- ('activation_key', models.CharField(max_length=40, null=True, blank=True)),
+ (
+ "user_ptr",
+ models.OneToOneField(
+ parent_link=True,
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ to=settings.AUTH_USER_MODEL,
+ on_delete=models.CASCADE,
+ ),
+ ),
+ ("svn_account", models.SlugField(max_length=20, null=True, blank=True)),
+ (
+ "image",
+ models.URLField(
+ help_text="URL to an image file (.jpg, .png, …) of an hackergotchi (max. 100×100
pixels)",
+ null=True,
+ verbose_name="Image",
+ blank=True,
+ ),
+ ),
+ (
+ "use_gravatar",
+ models.BooleanField(default=False, help_text="Display the image of your gravatar.com
account"),
+ ),
+ ("webpage_url", models.URLField(null=True, verbose_name="Web page", blank=True)),
+ ("irc_nick", models.SlugField(max_length=20, null=True, verbose_name="IRC nickname",
blank=True)),
+ (
+ "bugzilla_account",
+ models.EmailField(
+ help_text="This should be an email address, useful if not equal to “E-mail address”
field",
+ max_length=254,
+ null=True,
+ verbose_name="Bugzilla account",
+ blank=True,
+ ),
+ ),
+ ("activation_key", models.CharField(max_length=40, null=True, blank=True)),
],
options={
- 'ordering': ('username',),
- 'db_table': 'person',
+ "ordering": ("username",),
+ "db_table": "person",
},
- bases=('auth.user',),
+ bases=("auth.user",),
managers=[
- ('objects', django.contrib.auth.models.UserManager()),
+ ("objects", django.contrib.auth.models.UserManager()),
],
),
]
diff --git a/people/migrations/0002_set_use_gravatar_verbose_name.py
b/people/migrations/0002_set_use_gravatar_verbose_name.py
index 1e8ee6be..417ae88d 100644
--- a/people/migrations/0002_set_use_gravatar_verbose_name.py
+++ b/people/migrations/0002_set_use_gravatar_verbose_name.py
@@ -4,13 +4,15 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ('people', '0001_initial'),
+ ("people", "0001_initial"),
]
operations = [
migrations.AlterField(
- model_name='person',
- name='use_gravatar',
- field=models.BooleanField(default=False, help_text='Display the image of your gravatar.com
account', verbose_name='Use gravatar'),
+ model_name="person",
+ name="use_gravatar",
+ field=models.BooleanField(
+ default=False, help_text="Display the image of your gravatar.com account", verbose_name="Use
gravatar"
+ ),
),
]
diff --git a/people/migrations/0003_person_avatar_service.py b/people/migrations/0003_person_avatar_service.py
index 0196e94d..52a49c92 100644
--- a/people/migrations/0003_person_avatar_service.py
+++ b/people/migrations/0003_person_avatar_service.py
@@ -5,13 +5,18 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ('people', '0002_set_use_gravatar_verbose_name'),
+ ("people", "0002_set_use_gravatar_verbose_name"),
]
operations = [
migrations.AddField(
- model_name='person',
- name='avatar_service',
- field=models.CharField(blank=True, choices=[('libravatar.org', 'libravatar.org'),
('gravatar.com', 'gravatar.com')], max_length=50, verbose_name='Avatar provider'),
+ model_name="person",
+ name="avatar_service",
+ field=models.CharField(
+ blank=True,
+ choices=[("libravatar.org", "libravatar.org"), ("gravatar.com", "gravatar.com")],
+ max_length=50,
+ verbose_name="Avatar provider",
+ ),
),
]
diff --git a/people/migrations/0004_migrate_use_gravatar.py b/people/migrations/0004_migrate_use_gravatar.py
index 2ee056d7..f654d57a 100644
--- a/people/migrations/0004_migrate_use_gravatar.py
+++ b/people/migrations/0004_migrate_use_gravatar.py
@@ -3,14 +3,13 @@ from django.db import migrations
def migrate_gravatar(apps, schema_editor):
Person = apps.get_model("people", "Person")
- Person.objects.filter(use_gravatar=True).update(avatar_service='gravatar.com')
-
+ Person.objects.filter(use_gravatar=True).update(avatar_service="gravatar.com")
class Migration(migrations.Migration):
dependencies = [
- ('people', '0003_person_avatar_service'),
+ ("people", "0003_person_avatar_service"),
]
operations = [migrations.RunPython(migrate_gravatar)]
diff --git a/people/migrations/0005_remove_person_use_gravatar.py
b/people/migrations/0005_remove_person_use_gravatar.py
index fac67856..40e9b354 100644
--- a/people/migrations/0005_remove_person_use_gravatar.py
+++ b/people/migrations/0005_remove_person_use_gravatar.py
@@ -5,12 +5,12 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
- ('people', '0004_migrate_use_gravatar'),
+ ("people", "0004_migrate_use_gravatar"),
]
operations = [
migrations.RemoveField(
- model_name='person',
- name='use_gravatar',
+ model_name="person",
+ name="use_gravatar",
),
]
diff --git a/people/migrations/0006_remove_person_bugzilla_account.py
b/people/migrations/0006_remove_person_bugzilla_account.py
index a469c8fa..6255af9b 100644
--- a/people/migrations/0006_remove_person_bugzilla_account.py
+++ b/people/migrations/0006_remove_person_bugzilla_account.py
@@ -4,12 +4,12 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
- ('people', '0005_remove_person_use_gravatar'),
+ ("people", "0005_remove_person_use_gravatar"),
]
operations = [
migrations.RemoveField(
- model_name='person',
- name='bugzilla_account',
+ model_name="person",
+ name="bugzilla_account",
),
]
diff --git a/people/migrations/0007_person_auth_token.py b/people/migrations/0007_person_auth_token.py
index e90af768..2fde7241 100644
--- a/people/migrations/0007_person_auth_token.py
+++ b/people/migrations/0007_person_auth_token.py
@@ -4,13 +4,13 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ('people', '0006_remove_person_bugzilla_account'),
+ ("people", "0006_remove_person_bugzilla_account"),
]
operations = [
migrations.AddField(
- model_name='person',
- name='auth_token',
- field=models.CharField(blank=True, max_length=40, verbose_name='Authentication Token'),
+ model_name="person",
+ name="auth_token",
+ field=models.CharField(blank=True, max_length=40, verbose_name="Authentication Token"),
),
]
diff --git a/people/models.py b/people/models.py
index d72414dc..3a733bb3 100644
--- a/people/models.py
+++ b/people/models.py
@@ -13,32 +13,33 @@ from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
AVATAR_SERVICES = {
- 'gravatar.com': 'https://secure.gravatar.com/avatar/{hash}.jpg?{qs}',
+ "gravatar.com": "https://secure.gravatar.com/avatar/{hash}.jpg?{qs}",
# See https://wiki.libravatar.org/api/
- 'libravatar.org': 'https://seccdn.libravatar.org/avatar/{hash}?{qs}',
+ "libravatar.org": "https://seccdn.libravatar.org/avatar/{hash}?{qs}",
}
def obfuscate_email(email):
if email:
# Do not replace the 1st dot in "First M. Name <name dom com>"
- email = re.sub(r'(\w)\.(\w)', r'\1 dot \2', email)
- return mark_safe(escape(email.replace('@', ' at ')).replace(' ', ' '))
- return ''
+ email = re.sub(r"(\w)\.(\w)", r"\1 dot \2", email)
+ return mark_safe(escape(email.replace("@", " at ")).replace(" ", " "))
+ return ""
class Person(User):
- """ The User class of D-L. """
+ """The User class of D-L."""
auth_token = models.CharField(_("Authentication Token"), max_length=40, blank=True)
svn_account = models.SlugField(max_length=20, null=True, blank=True)
image = models.URLField(
- _("Image"), null=True, blank=True,
- help_text=_("URL to an image file (.jpg, .png, …) of an hackergotchi (max. 100×100 pixels)")
+ _("Image"),
+ null=True,
+ blank=True,
+ help_text=_("URL to an image file (.jpg, .png, …) of an hackergotchi (max. 100×100 pixels)"),
)
avatar_service = models.CharField(
- _("Avatar provider"), max_length=50, blank=True,
- choices=((name, name) for name in AVATAR_SERVICES)
+ _("Avatar provider"), max_length=50, blank=True, choices=((name, name) for name in AVATAR_SERVICES)
)
webpage_url = models.URLField(_("Web page"), null=True, blank=True)
irc_nick = models.SlugField(_("IRC nickname"), max_length=20, null=True, blank=True)
@@ -48,38 +49,37 @@ class Person(User):
objects = UserManager()
class Meta:
- db_table = 'person'
- ordering = ('username',)
+ db_table = "person"
+ ordering = ("username",)
@classmethod
def clean_unactivated_accounts(cls):
accounts = cls.objects.filter(
- activation_key__isnull=False,
- date_joined__lt=(timezone.now() - timedelta(days=10))
- ).exclude(activation_key='')
+ activation_key__isnull=False, date_joined__lt=(timezone.now() - timedelta(days=10))
+ ).exclude(activation_key="")
for account in accounts:
account.delete()
@classmethod
def clean_obsolete_accounts(cls):
- """ Remove accounts that:
- - last login is more than 2 years
- - is not coordinator
- - is not module maintainer
- - has no reserved module
+ """Remove accounts that:
+ - last login is more than 2 years
+ - is not coordinator
+ - is not module maintainer
+ - has no reserved module
"""
- accounts = cls.objects.annotate(
- num_modules=models.Count('maintains_modules')
- ).annotate(
- num_states=models.Count('state')
- ).filter(
- models.Q(last_login__lt=(timezone.now() - timedelta(days=730))) |
- models.Q(last_login__isnull=True, date_joined__lt=(timezone.now() - timedelta(days=180)))
- ).exclude(
- role__role='coordinator'
- ).exclude(
- num_modules__gt=0
- ).exclude(num_states__gt=0).order_by()
+ accounts = (
+ cls.objects.annotate(num_modules=models.Count("maintains_modules"))
+ .annotate(num_states=models.Count("state"))
+ .filter(
+ models.Q(last_login__lt=(timezone.now() - timedelta(days=730)))
+ | models.Q(last_login__isnull=True, date_joined__lt=(timezone.now() - timedelta(days=180)))
+ )
+ .exclude(role__role="coordinator")
+ .exclude(num_modules__gt=0)
+ .exclude(num_states__gt=0)
+ .order_by()
+ )
for account in accounts:
account.delete()
@@ -91,7 +91,7 @@ class Person(User):
return user.person
except Person.DoesNotExist:
_dict = user.__dict__.copy()
- del _dict['_state']
+ del _dict["_state"]
user.person = Person(**_dict)
user.person.save()
return user.person
@@ -101,7 +101,7 @@ class Person(User):
if not val:
return None
try:
- return Person.objects.filter(**{key: val}).order_by('-last_login')[0]
+ return Person.objects.filter(**{key: val}).order_by("-last_login")[0]
except IndexError:
return None
@@ -136,12 +136,13 @@ class Person(User):
return "%s <%s>" % (self.name, self.email)
def get_absolute_url(self):
- return reverse('person_detail_username', args=[self.username])
+ return reverse("person_detail_username", args=[self.username])
def coordinates_teams(self):
# Class imported here to avoid cyclic import
# pylint: disable=import-outside-toplevel
from teams.models import Team
+
return Team.objects.filter(role__person__id=self.id).all()
def is_coordinator(self, team):
@@ -149,24 +150,24 @@ class Person(User):
If team is a Team instance, tell if current Person is coordinator of that team.
If team = 'any', tell if current Person is coordinator of at least one team.
"""
- if team == 'any':
- return self.role_set.filter(role='coordinator').exists()
+ if team == "any":
+ return self.role_set.filter(role="coordinator").exists()
try:
- self.role_set.get(team__id=team.id, role='coordinator')
+ self.role_set.get(team__id=team.id, role="coordinator")
return True
except (ObjectDoesNotExist, AttributeError):
return False
def is_committer(self, team):
try:
- self.role_set.get(team__id=team.id, role__in=['committer', 'coordinator'])
+ self.role_set.get(team__id=team.id, role__in=["committer", "coordinator"])
return True
except (ObjectDoesNotExist, AttributeError):
return False
def is_reviewer(self, team):
try:
- self.role_set.get(team__id=team.id, role__in=['reviewer', 'committer', 'coordinator'])
+ self.role_set.get(team__id=team.id, role__in=["reviewer", "committer", "coordinator"])
return True
except (ObjectDoesNotExist, AttributeError):
return False
@@ -182,7 +183,7 @@ class Person(User):
return module in self.maintains_modules.all()
def get_languages(self):
- all_teams = [role.team for role in self.role_set.select_related('team')]
+ all_teams = [role.team for role in self.role_set.select_related("team")]
all_languages = []
for team in all_teams:
all_languages.extend(team.get_languages())
diff --git a/people/templatetags/people.py b/people/templatetags/people.py
index 820be09c..02a3a8d1 100644
--- a/people/templatetags/people.py
+++ b/people/templatetags/people.py
@@ -17,10 +17,7 @@ def people_list(lst):
Format a people list, with clickable person name.
"""
# Translators: string used as separator in person list
- return format_html_join(
- _(", "), '<a href="{}">{}</a>',
- ((pers.get_absolute_url(), pers.name) for pers in lst)
- )
+ return format_html_join(_(", "), '<a href="{}">{}</a>', ((pers.get_absolute_url(), pers.name) for pers
in lst))
@register.filter
@@ -31,7 +28,7 @@ def people_image(person):
url = AVATAR_SERVICES[person.avatar_service].format(
hash=digest,
# Size, default image, rating
- qs=urlencode([('s', '80'), ('d', 'identicon'), ('r', 'g')])
+ qs=urlencode([("s", "80"), ("d", "identicon"), ("r", "g")]),
)
tag = format_html(
'<img class="img-circle" src="{url}" alt="{alt}" crossorigin="anonymous">', url=url,
alt=_("avatar icon")
@@ -39,10 +36,10 @@ def people_image(person):
elif person and person.image:
tag = format_html(
'<img class="img-circle" src="{}" alt="{}" onerror="this.onerror = null; this.src=\'{}\'">',
- person.image, person.name, nobody
+ person.image,
+ person.name,
+ nobody,
)
else:
- tag = format_html(
- '<img class="img-circle" src="{url}" alt="{alt}">', url=nobody, alt=_("generic person icon")
- )
+ tag = format_html('<img class="img-circle" src="{url}" alt="{alt}">', url=nobody, alt=_("generic
person icon"))
return tag
diff --git a/people/tests.py b/people/tests.py
index bcf56959..0b54dcd3 100644
--- a/people/tests.py
+++ b/people/tests.py
@@ -5,7 +5,7 @@ from django.contrib import auth
from django.contrib.staticfiles.finders import find
from django.core import mail
from django.core.exceptions import ValidationError
-from django.test import TestCase, Client
+from django.test import Client, TestCase
from django.urls import reverse
from django.utils import timezone
from django.utils.safestring import SafeData
@@ -17,172 +17,156 @@ from people.models import Person, obfuscate_email
class PeopleTestCase(TestCase):
-
- def _create_person(self, seq='', **kwargs):
- pn = Person(
- first_name='John', last_name='Nothing',
- email='jn%s devnull com' % seq, username='jn%s' % seq
- )
+ def _create_person(self, seq="", **kwargs):
+ pn = Person(first_name="John", last_name="Nothing", email="jn%s devnull com" % seq, username="jn%s"
% seq)
for key, arg in kwargs.items():
setattr(pn, key, arg)
- pn.set_password('password')
+ pn.set_password("password")
pn.save()
return pn
def test_register(self):
- username = 'tèst-01'
+ username = "tèst-01"
response = self.client.post(
- reverse('register'),
- {'username': username, 'password1': '1234567',
- 'password2': '1234567', 'email': 'test01 example org'}
+ reverse("register"),
+ {"username": username, "password1": "1234567", "password2": "1234567", "email": "test01 example
org"},
)
- self.assertRedirects(response, reverse('register_success'))
+ self.assertRedirects(response, reverse("register_success"))
self.assertEqual(Person.objects.filter(username=username).count(), 1)
self.assertEqual(len(mail.outbox), 1)
- self.assertIn(_('Account activation'), mail.outbox[0].subject)
+ self.assertIn(_("Account activation"), mail.outbox[0].subject)
def test_activate_account(self):
# Testing if is_active is False by default
response = self.client.post(
- reverse('register'),
- {'username': 'newuser', 'email': 'newuser example org', 'password1': 'blah012', 'password2':
'blah012'}
+ reverse("register"),
+ {"username": "newuser", "email": "newuser example org", "password1": "blah012", "password2":
"blah012"},
)
- self.newu = Person.objects.get(username='newuser')
+ self.newu = Person.objects.get(username="newuser")
self.assertFalse(self.newu.is_active)
# Testing with a invalid activation key
- response = self.client.get('/register/activate/a_invalid_key')
- self.assertContains(response, 'Sorry, the key you provided is not valid.')
+ response = self.client.get("/register/activate/a_invalid_key")
+ self.assertContains(response, "Sorry, the key you provided is not valid.")
- response = self.client.get('/register/activate/%s' % self.newu.activation_key, follow=True)
- self.assertContains(response, 'Your account has been activated.')
+ response = self.client.get("/register/activate/%s" % self.newu.activation_key, follow=True)
+ self.assertContains(response, "Your account has been activated.")
- self.newu = Person.objects.get(username='newuser')
+ self.newu = Person.objects.get(username="newuser")
self.assertTrue(self.newu.is_active)
def test_login_message(self):
self.pn = self._create_person()
- response = self.client.get(reverse('login'), HTTP_REFERER='http://foo/bar')
- self.assertContains(
- response,
- '<input type="hidden" name="referer" value="http://foo/bar">',
- html=True
- )
- response = self.client.post(
- reverse('login'), data={'username': 'jn', 'password': 'password'}, follow=True
- )
- self.assertContains(
- response,
- 'You have not joined any translation team yet. '
- 'You can do it from <a href="/users/team_join/">your profile</a>.'
- )
- response = self.client.get(reverse('login'))
+ response = self.client.get(reverse("login"), HTTP_REFERER="http://foo/bar")
+ self.assertContains(response, '<input type="hidden" name="referer" value="http://foo/bar">',
html=True)
+ response = self.client.post(reverse("login"), data={"username": "jn", "password": "password"},
follow=True)
self.assertContains(
response,
- 'You are already logged in as jn.'
+ "You have not joined any translation team yet. "
+ 'You can do it from <a href="/users/team_join/">your profile</a>.',
)
+ response = self.client.get(reverse("login"))
+ self.assertContains(response, "You are already logged in as jn.")
self.assertNotContains(response, 'id="login-form"')
def test_login_by_email(self):
self.pn = self._create_person()
- self.client.post(
- reverse('login'), data={'username': 'notexist devnull com', 'password': 'password'}
- )
+ self.client.post(reverse("login"), data={"username": "notexist devnull com", "password": "password"})
user = auth.get_user(self.client)
self.assertFalse(user.is_authenticated)
- self.client.post(
- reverse('login'), data={'username': 'jn devnull com', 'password': 'password'}
- )
+ self.client.post(reverse("login"), data={"username": "jn devnull com", "password": "password"})
user = auth.get_user(self.client)
self.assertTrue(user.is_authenticated)
def test_person_list(self):
self.pn = self._create_person()
- response = self.client.get(reverse('people'))
+ response = self.client.get(reverse("people"))
self.assertContains(response, "GNOME is being developed by following people:")
self.assertContains(response, "John Nothing")
def test_login_link(self):
pn = self._create_person()
self.client.force_login(pn)
- response = self.client.get(reverse('home'))
+ response = self.client.get(reverse("home"))
self.assertContains(
response,
'<a aria-expanded="false" aria-haspopup="true" role="button" data-toggle="dropdown"
class="dropdown-toggle'
' hidden-xs avatar" href="%s"><img class="img-circle" alt="generic person icon"'
- ' src="/static/img/nobody.png"></a>' % (reverse('person_detail_username', args=[pn.username])),
- html=True
+ ' src="/static/img/nobody.png"></a>' % (reverse("person_detail_username", args=[pn.username])),
+ html=True,
)
def test_edit_details(self):
pn = self._create_person()
self.client.force_login(pn)
- response = self.client.get(reverse('person_detail_change'))
+ response = self.client.get(reverse("person_detail_change"))
self.assertContains(response, "First name")
post_data = {
- 'username': 'jn2', 'first_name': "Johnny", 'last_name': "Nothing",
- 'email': 'test02 example org',
- 'image': '', 'webpage_url': "http://www.example.org/"
+ "username": "jn2",
+ "first_name": "Johnny",
+ "last_name": "Nothing",
+ "email": "test02 example org",
+ "image": "",
+ "webpage_url": "http://www.example.org/",
}
- response = self.client.post(reverse('person_detail_change'), post_data)
+ response = self.client.post(reverse("person_detail_change"), post_data)
pn.refresh_from_db()
- self.assertEqual(pn.username, 'jn2')
- self.assertEqual(pn.email, 'test02 example org')
+ self.assertEqual(pn.username, "jn2")
+ self.assertEqual(pn.email, "test02 example org")
# bad image url
- post_data['image'] = "http://http://www.example.org/image.jpg"
+ post_data["image"] = "http://http://www.example.org/image.jpg"
form = forms.DetailForm(post_data, instance=pn)
self.assertFalse(form.is_valid())
- self.assertTrue('image' in form.errors)
+ self.assertTrue("image" in form.errors)
def test_create_token(self):
pn = self._create_person()
- self.assertEqual(pn.auth_token, '')
+ self.assertEqual(pn.auth_token, "")
self.client.force_login(pn)
- response = self.client.post(reverse('person_create_token'))
- self.assertRedirects(response, reverse('person_detail_username', args=[pn.username]))
+ response = self.client.post(reverse("person_create_token"))
+ self.assertRedirects(response, reverse("person_detail_username", args=[pn.username]))
pn.refresh_from_db()
- self.assertNotEqual(pn.auth_token, '')
+ self.assertNotEqual(pn.auth_token, "")
def test_delete_token(self):
- pn = self._create_person(auth_token='askhdaksudakckascjbaskdaiwue')
+ pn = self._create_person(auth_token="askhdaksudakckascjbaskdaiwue")
self.client.force_login(pn)
- response = self.client.post(reverse('person_delete_token'))
- self.assertRedirects(response, reverse('person_detail_username', args=[pn.username]))
+ response = self.client.post(reverse("person_delete_token"))
+ self.assertRedirects(response, reverse("person_detail_username", args=[pn.username]))
pn.refresh_from_db()
- self.assertEqual(pn.auth_token, '')
+ self.assertEqual(pn.auth_token, "")
def test_token_authentication(self):
- pn = self._create_person(auth_token='askhdaksudakckascjbaskdaiwue')
- response = self.client.get(
- reverse('home'), HTTP_AUTHENTICATION='Bearer askhdaksudakckascjbaskdaiwue'
- )
- self.assertFalse(response.context['user'].is_anonymous)
- self.assertEqual(response.context['user'], pn)
+ pn = self._create_person(auth_token="askhdaksudakckascjbaskdaiwue")
+ response = self.client.get(reverse("home"), HTTP_AUTHENTICATION="Bearer
askhdaksudakckascjbaskdaiwue")
+ self.assertFalse(response.context["user"].is_anonymous)
+ self.assertEqual(response.context["user"], pn)
def test_obsolete_accounts(self):
- from teams.models import Team, Role
- from stats.models import Module, Branch, Domain
from languages.models import Language
+ from stats.models import Branch, Domain, Module
+ from teams.models import Role, Team
from vertimus.models import StateTranslating
+
module = Module.objects.create(name="gnome-hello")
Branch.checkout_on_creation = False
- branch = Branch(name='gnome-2-24', module=module)
+ branch = Branch(name="gnome-2-24", module=module)
branch.save(update_statistics=False)
- domain = Domain.objects.create(module=module, name='po', layout='po/{lang}.po')
- team = Team.objects.create(name='fr', description='French', mailing_list='french_ml example org')
- lang = Language.objects.create(name='French', locale='fr', team=team)
+ domain = Domain.objects.create(module=module, name="po", layout="po/{lang}.po")
+ team = Team.objects.create(name="fr", description="French", mailing_list="french_ml example org")
+ lang = Language.objects.create(name="French", locale="fr", team=team)
# Create Users
- p1 = self._create_person(seq='1', last_login=timezone.now() - timedelta(days=700))
- p2 = self._create_person(seq='2', last_login=timezone.now() - timedelta(days=800))
- Role.objects.create(team=team, person=p2, role='coordinator')
- p3 = self._create_person(seq='3', last_login=timezone.now() - timedelta(days=800))
+ p1 = self._create_person(seq="1", last_login=timezone.now() - timedelta(days=700))
+ p2 = self._create_person(seq="2", last_login=timezone.now() - timedelta(days=800))
+ Role.objects.create(team=team, person=p2, role="coordinator")
+ p3 = self._create_person(seq="3", last_login=timezone.now() - timedelta(days=800))
module.maintainers.add(p3)
- p4 = self._create_person(seq='4', last_login=timezone.now() - timedelta(days=800))
+ p4 = self._create_person(seq="4", last_login=timezone.now() - timedelta(days=800))
StateTranslating.objects.create(branch=branch, domain=domain, language=lang, person=p4)
- self._create_person(seq='5', last_login=timezone.now() - timedelta(days=800))
- self._create_person(seq='6', date_joined=timezone.now() - timedelta(days=200), last_login=None)
+ self._create_person(seq="5", last_login=timezone.now() - timedelta(days=800))
+ self._create_person(seq="6", date_joined=timezone.now() - timedelta(days=200), last_login=None)
# Test only p5/p6 should be deleted
self.assertEqual(Person.objects.all().count(), 6)
Person.clean_obsolete_accounts()
@@ -190,20 +174,19 @@ class PeopleTestCase(TestCase):
self.assertEqual(set(Person.objects.all()), set([p1, p2, p3, p4]))
def test_obfuscate_email(self):
- self.assertEqual(obfuscate_email(''), '')
+ self.assertEqual(obfuscate_email(""), "")
self.assertEqual(
- obfuscate_email('me company domain org'),
- 'me dot company at domain dot org'
+ obfuscate_email("me company domain org"),
"me dot company at domain dot org"
)
- self.assertIsInstance(obfuscate_email('me company domain org'), SafeData)
+ self.assertIsInstance(obfuscate_email("me company domain org"), SafeData)
self.assertEqual(
- obfuscate_email('George P. McLain <george domain org'),
- 'George P. McLain <george at domain dot org'
+ obfuscate_email("George P. McLain <george domain org"),
+ "George P. McLain <george at domain dot org",
)
self.assertEqual(
- obfuscate_email('Me <me company domain org>\nYou <some-address example com>'),
- 'Me <me dot company at domain dot org>\n'
- 'You <some-address at example dot com>'
+ obfuscate_email("Me <me company domain org>\nYou <some-address example com>"),
+ "Me <me dot company at domain dot org>\n"
+ "You <some-address at example dot com>",
)
def test_people_image_tag(self):
@@ -211,46 +194,43 @@ class PeopleTestCase(TestCase):
Test the people_image template tag.
"""
from people.templatetags import people
+
pn = self._create_person()
self.assertHTMLEqual(
- people.people_image(pn),
- '<img class="img-circle" src="/static/img/nobody.png" alt="generic person icon">'
+ people.people_image(pn), '<img class="img-circle" src="/static/img/nobody.png" alt="generic
person icon">'
)
- pn.image = 'http://www.example.org/my_image.png'
+ pn.image = "http://www.example.org/my_image.png"
self.assertHTMLEqual(
people.people_image(pn),
'<img class="img-circle" alt="John Nothing" onerror="this.onerror = null; '
- 'this.src=\'/static/img/nobody.png\'" src="http://www.example.org/my_image.png" />'
+ 'this.src=\'/static/img/nobody.png\'" src="http://www.example.org/my_image.png" />',
)
pn.last_name = "<script>Some XSS content</script>"
self.assertIn("<script>Some XSS content</script>", people.people_image(pn))
self.assertHTMLEqual(
people.people_image(pn),
'<img class="img-circle" alt="John <script>Some XSS content</script>" '
- 'onerror="this.onerror = null; this.src=\'/static/img/nobody.png\'" '
- 'src="http://www.example.org/my_image.png" />'
+ "onerror=\"this.onerror = null; this.src='/static/img/nobody.png'\" "
+ 'src="http://www.example.org/my_image.png" />',
)
- pn.avatar_service = 'gravatar.com'
+ pn.avatar_service = "gravatar.com"
self.assertHTMLEqual(
people.people_image(pn),
'<img class="img-circle" alt="avatar icon" crossorigin="anonymous" '
-
'src="https://secure.gravatar.com/avatar/618b8b6c1c973c780ec218242c49cbe7.jpg?s=80&d=identicon&r=g">'
+
'src="https://secure.gravatar.com/avatar/618b8b6c1c973c780ec218242c49cbe7.jpg?s=80&d=identicon&r=g">',
)
- pn.avatar_service = 'libravatar.org'
+ pn.avatar_service = "libravatar.org"
self.assertHTMLEqual(
people.people_image(pn),
'<img class="img-circle" alt="avatar icon" crossorigin="anonymous" '
-
'src="https://seccdn.libravatar.org/avatar/618b8b6c1c973c780ec218242c49cbe7?s=80&d=identicon&r=g">'
+
'src="https://seccdn.libravatar.org/avatar/618b8b6c1c973c780ec218242c49cbe7?s=80&d=identicon&r=g">',
)
- @mock.patch('people.forms.urlopen')
+ @mock.patch("people.forms.urlopen")
def test_get_image_size(self, m_urlopen):
- with open(find('img/nobody.png'), mode='rb') as fh:
+ with open(find("img/nobody.png"), mode="rb") as fh:
m_urlopen.return_value = fh
- self.assertEqual(
- forms.get_image_size("https://l10n.gnome.org/static/img/nobody.png"),
- (48, 48)
- )
+ self.assertEqual(forms.get_image_size("https://l10n.gnome.org/static/img/nobody.png"), (48, 48))
def test_invalid_images(self):
invalid_urls = (
@@ -267,36 +247,29 @@ class PeopleTestCase(TestCase):
The order of the languages should be case insensitive.
This tests that "français" appears before "Gaeilge".
"""
- pn = Person.objects.create(username='jn')
+ pn = Person.objects.create(username="jn")
self.client.force_login(pn)
- url = reverse('person_detail_username', kwargs={'slug': 'jn'})
+ url = reverse("person_detail_username", kwargs={"slug": "jn"})
response = self.client.get(url)
- all_languages = response.context['all_languages']
+ all_languages = response.context["all_languages"]
self.assertGreater(
- all_languages.index(('ga', 'Gaeilge')),
- all_languages.index(('fr', "français")),
- "français is not before Frisian"
+ all_languages.index(("ga", "Gaeilge")),
+ all_languages.index(("fr", "français")),
+ "français is not before Frisian",
)
class PeopleViewsTestCase(TestCase):
-
def setUp(self) -> None:
- self.coordinator = Person.objects.create(
- id=1, username="coordinator"
- )
- self.reviewer = Person.objects.create(
- id=2, username="reviewer"
- )
+ self.coordinator = Person.objects.create(id=1, username="coordinator")
+ self.reviewer = Person.objects.create(id=2, username="reviewer")
self.client = Client()
def test_auth_token_displayed_on_own_page(self):
self.client.force_login(self.coordinator)
auth_token = self.coordinator.auth_token = Person.generate_token()
self.coordinator.save()
- response = self.client.get(reverse(
- "person_detail_username", kwargs={"slug": self.coordinator.username}
- ))
+ response = self.client.get(reverse("person_detail_username", kwargs={"slug":
self.coordinator.username}))
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context["person"].auth_token, auth_token)
self.assertTrue(response.context["on_own_page"])
@@ -306,9 +279,7 @@ class PeopleViewsTestCase(TestCase):
self.client.force_login(self.reviewer)
auth_token = self.coordinator.auth_token = Person.generate_token()
self.coordinator.save()
- response = self.client.get(reverse(
- "person_detail_username", kwargs={"slug": self.coordinator.username}
- ))
+ response = self.client.get(reverse("person_detail_username", kwargs={"slug":
self.coordinator.username}))
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context["person"].auth_token, auth_token)
self.assertFalse(response.context["on_own_page"])
diff --git a/people/urls.py b/people/urls.py
index 24708702..3324fa43 100644
--- a/people/urls.py
+++ b/people/urls.py
@@ -3,34 +3,16 @@ from django.urls import path
from people import views
-
# Path order is really important here
urlpatterns = [
- path('detail_change/',
- login_required(views.PersonEditView.as_view()),
- name='person_detail_change'),
- path('password_change/',
- views.person_password_change,
- name='person_password_change'),
- path('create_token/',
- views.person_create_token,
- name='person_create_token'),
- path('delete_token/',
- views.person_delete_token,
- name='person_delete_token'),
- path('team_join/',
- views.person_team_join,
- name='person_team_join'),
- path('team_leave/<locale:team_slug>/',
- views.person_team_leave,
- name='person_team_leave'),
- path('<int:pk>/',
- views.PersonDetailView.as_view(),
- name='person_detail_id'),
+ path("detail_change/", login_required(views.PersonEditView.as_view()), name="person_detail_change"),
+ path("password_change/", views.person_password_change, name="person_password_change"),
+ path("create_token/", views.person_create_token, name="person_create_token"),
+ path("delete_token/", views.person_delete_token, name="person_delete_token"),
+ path("team_join/", views.person_team_join, name="person_team_join"),
+ path("team_leave/<locale:team_slug>/", views.person_team_leave, name="person_team_leave"),
+ path("<int:pk>/", views.PersonDetailView.as_view(), name="person_detail_id"),
# Equivalent to the previous, but using username instead of user pk
- path('<slug>/',
- views.PersonDetailView.as_view(),
- name='person_detail_username'),
-
- path('', views.PeopleListView.as_view(), name='people'),
+ path("<slug>/", views.PersonDetailView.as_view(), name="person_detail_username"),
+ path("", views.PeopleListView.as_view(), name="people"),
]
diff --git a/people/views.py b/people/views.py
index aeb4f46d..dc2bf096 100644
--- a/people/views.py
+++ b/people/views.py
@@ -2,21 +2,22 @@ from operator import itemgetter
from django.conf import settings
from django.conf.locale import LANG_INFO
+from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import PasswordChangeForm
-from django.contrib import messages
from django.db import IntegrityError, transaction
from django.http import HttpResponseRedirect
-from django.shortcuts import render, get_object_or_404
+from django.shortcuts import get_object_or_404, render
from django.urls import reverse
-from django.utils.translation import gettext_lazy, gettext as _
+from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy
from django.views.decorators.http import require_POST
-from django.views.generic import ListView, DetailView, UpdateView
+from django.views.generic import DetailView, ListView, UpdateView
from common.utils import lc_sorted
+from people.forms import DetailForm, TeamJoinForm
from people.models import Person
-from teams.models import Team, Role
-from people.forms import TeamJoinForm, DetailForm
+from teams.models import Role, Team
from vertimus.models import State
@@ -25,45 +26,48 @@ class PeopleListView(ListView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
- context['pageSection'] = "teams"
+ context["pageSection"] = "teams"
return context
class PersonDetailView(DetailView):
model = Person
- slug_field = 'username'
- context_object_name = 'person'
+ slug_field = "username"
+ context_object_name = "person"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
states = State.objects.filter(action__person=self.object).distinct()
all_languages = [
- (lg[0], LANG_INFO.get(lg[0], {'name_local': lg[1]})['name_local']) for lg in settings.LANGUAGES
+ (lg[0], LANG_INFO.get(lg[0], {"name_local": lg[1]})["name_local"]) for lg in settings.LANGUAGES
]
all_languages = lc_sorted(all_languages, key=itemgetter(1))
- context.update({
- 'pageSection': "teams",
- 'all_languages': all_languages,
- 'on_own_page': self.request.user.is_authenticated and self.object.username ==
self.request.user.username,
- 'states': [(s, s.stats) for s in states],
- })
+ context.update(
+ {
+ "pageSection": "teams",
+ "all_languages": all_languages,
+ "on_own_page": self.request.user.is_authenticated
+ and self.object.username == self.request.user.username,
+ "states": [(s, s.stats) for s in states],
+ }
+ )
return context
class PersonEditView(UpdateView):
model = Person
- slug_field = 'username'
+ slug_field = "username"
form_class = DetailForm
- template_name = 'people/person_detail_change_form.html'
+ template_name = "people/person_detail_change_form.html"
def get_object(self, **kwargs):
- self.kwargs['slug'] = self.request.user.username
+ self.kwargs["slug"] = self.request.user.username
return super().get_object()
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
- context['pageSection'] = "teams"
- context['on_own_page'] = self.object.username == self.request.user.username
+ context["pageSection"] = "teams"
+ context["on_own_page"] = self.object.username == self.request.user.username
return context
def form_invalid(self, form):
@@ -75,10 +79,10 @@ class PersonEditView(UpdateView):
def person_team_join(request):
"""Handle the form to join a team"""
person = get_object_or_404(Person, username=request.user.username)
- if request.method == 'POST':
+ if request.method == "POST":
form = TeamJoinForm(request.POST)
if form.is_valid():
- team = form.cleaned_data['teams']
+ team = form.cleaned_data["teams"]
# Role default to 'translator'
new_role = Role(team=team, person=person)
try:
@@ -88,7 +92,7 @@ def person_team_join(request):
team.send_mail_to_coordinator(
subject=gettext_lazy("A new person joined your team"),
message=gettext_lazy("%(name)s has just joined your translation team on %(site)s"),
- messagekw={'name': person.name, 'site': settings.SITE_DOMAIN}
+ messagekw={"name": person.name, "site": settings.SITE_DOMAIN},
)
except IntegrityError:
messages.info(request, _("You are already member of this team."))
@@ -96,12 +100,12 @@ def person_team_join(request):
form = TeamJoinForm()
context = {
- 'pageSection': "teams",
- 'person': person,
- 'on_own_page': person.username == request.user.username,
- 'form': form,
+ "pageSection": "teams",
+ "person": person,
+ "on_own_page": person.username == request.user.username,
+ "form": form,
}
- return render(request, 'people/person_team_join_form.html', context)
+ return render(request, "people/person_team_join_form.html", context)
@login_required
@@ -117,9 +121,7 @@ def person_team_leave(request, team_slug):
# Message no i18n'ed, should never happen under normal conditions
messages.error(request, _("You are not a member of this team."))
# redirect to normal person detail
- return HttpResponseRedirect(
- reverse('person_detail_username', args=(person.username,))
- )
+ return HttpResponseRedirect(reverse("person_detail_username", args=(person.username,)))
@login_required
@@ -127,7 +129,7 @@ def person_password_change(request):
"""Handle the generic form to change the password"""
person = get_object_or_404(Person, username=request.user.username)
- if request.method == 'POST':
+ if request.method == "POST":
form = PasswordChangeForm(request.user, request.POST)
if form.is_valid():
messages.success(request, _("Your password has been changed."))
@@ -136,12 +138,12 @@ def person_password_change(request):
form = PasswordChangeForm(request.user)
context = {
- 'pageSection': "teams",
- 'person': person,
- 'on_own_page': person.username == request.user.username,
- 'form': form,
+ "pageSection": "teams",
+ "person": person,
+ "on_own_page": person.username == request.user.username,
+ "form": form,
}
- return render(request, 'people/person_password_change_form.html', context)
+ return render(request, "people/person_password_change_form.html", context)
@login_required
@@ -150,13 +152,13 @@ def person_create_token(request):
person = get_object_or_404(Person, username=request.user.username)
person.auth_token = Person.generate_token()
person.save()
- return HttpResponseRedirect(reverse('person_detail_username', args=[person.username]))
+ return HttpResponseRedirect(reverse("person_detail_username", args=[person.username]))
@login_required
@require_POST
def person_delete_token(request):
person = get_object_or_404(Person, username=request.user.username)
- person.auth_token = ''
+ person.auth_token = ""
person.save()
- return HttpResponseRedirect(reverse('person_detail_username', args=[person.username]))
+ return HttpResponseRedirect(reverse("person_detail_username", args=[person.username]))
diff --git a/pyproject.toml b/pyproject.toml
index fa7093a3..7d32a566 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,3 +1,9 @@
[build-system]
requires = ["setuptools>=42"]
-build-backend = "setuptools.build_meta"
\ No newline at end of file
+build-backend = "setuptools.build_meta"
+
+[tool.black]
+line-length = 119
+
+[tool.isort]
+profile = "black"
diff --git a/setup.py b/setup.py
index 080528a1..4a3514f1 100755
--- a/setup.py
+++ b/setup.py
@@ -2,9 +2,8 @@
import os
import re
-from typing import List
-
from datetime import datetime
+from typing import List
from setuptools import setup
@@ -25,7 +24,7 @@ def get_requirements() -> List[str]:
setup(
- version=datetime.utcnow().strftime('%Y%m%d%H%M'),
+ version=datetime.utcnow().strftime("%Y%m%d%H%M"),
packages=["."],
install_requires=get_requirements(),
)
diff --git a/stats/admin.py b/stats/admin.py
index 149a39ef..305112b5 100644
--- a/stats/admin.py
+++ b/stats/admin.py
@@ -1,35 +1,40 @@
from django import forms
-from django.core.exceptions import PermissionDenied
from django.contrib import admin, messages
from django.contrib.admin import helpers
+from django.core.exceptions import PermissionDenied
from django.db import transaction
from django.http import HttpResponseRedirect
from django.shortcuts import render
from stats.doap import update_doap_infos
from stats.models import (
- Statistics, Information, PoFile, Module, Branch, Domain, Category,
- CategoryName, Release,
+ Branch,
+ Category,
+ CategoryName,
+ Domain,
+ Information,
+ Module,
+ PoFile,
+ Release,
+ Statistics,
)
class DomainForm(forms.ModelForm):
class Meta:
model = Module
- fields = '__all__'
+ fields = "__all__"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.pk:
- self.fields['branch_from'].queryset = self.fields['branch_from'].queryset.filter(
- module=self.instance.module
- )
- self.fields['branch_to'].queryset = self.fields['branch_to'].queryset.filter(
+ self.fields["branch_from"].queryset = self.fields["branch_from"].queryset.filter(
module=self.instance.module
)
+ self.fields["branch_to"].queryset =
self.fields["branch_to"].queryset.filter(module=self.instance.module)
else:
- self.fields['branch_from'].queryset = Branch.objects.none()
- self.fields['branch_to'].queryset = Branch.objects.none()
+ self.fields["branch_from"].queryset = Branch.objects.none()
+ self.fields["branch_to"].queryset = Branch.objects.none()
class BranchInline(admin.TabularInline):
@@ -40,15 +45,20 @@ class DomainInline(admin.StackedInline):
model = Domain
form = DomainForm
fieldsets = (
- (None, {
- 'fields': (('name', 'description', 'dtype', 'layout'),)
- }),
- ('Advanced', {
- 'fields': (('pot_method', 'pot_params'),
- 'extra_its_dirs', 'linguas_location', 'red_filter',
- ('branch_from', 'branch_to'),),
- 'classes': ('collapse',),
- }),
+ (None, {"fields": (("name", "description", "dtype", "layout"),)}),
+ (
+ "Advanced",
+ {
+ "fields": (
+ ("pot_method", "pot_params"),
+ "extra_its_dirs",
+ "linguas_location",
+ "red_filter",
+ ("branch_from", "branch_to"),
+ ),
+ "classes": ("collapse",),
+ },
+ ),
)
def get_formset(self, request, obj=None, **kwargs):
@@ -57,27 +67,27 @@ class DomainInline(admin.StackedInline):
return super().get_formset(request, obj, **kwargs)
def formfield_for_dbfield(self, db_field, **kwargs):
- if db_field.name == 'description':
- kwargs['widget'] = forms.Textarea(attrs={'rows': '1', 'cols': '20'})
- elif db_field.name in ('name', 'layout'):
- kwargs['widget'] = forms.TextInput(attrs={'size': '20'})
- elif db_field.name in ('red_filter', 'extra_its_dirs'):
- kwargs['widget'] = forms.Textarea(attrs={'rows': '1', 'cols': '40'})
+ if db_field.name == "description":
+ kwargs["widget"] = forms.Textarea(attrs={"rows": "1", "cols": "20"})
+ elif db_field.name in ("name", "layout"):
+ kwargs["widget"] = forms.TextInput(attrs={"size": "20"})
+ elif db_field.name in ("red_filter", "extra_its_dirs"):
+ kwargs["widget"] = forms.Textarea(attrs={"rows": "1", "cols": "40"})
return super().formfield_for_dbfield(db_field, **kwargs)
def formfield_for_foreignkey(self, db_field, request, **kwargs):
- if db_field.name in ('branch_from', 'branch_to') and hasattr(self, 'parent_obj') and self.parent_obj:
- kwargs['queryset'] = self.parent_obj.branch_set.all()
+ if db_field.name in ("branch_from", "branch_to") and hasattr(self, "parent_obj") and self.parent_obj:
+ kwargs["queryset"] = self.parent_obj.branch_set.all()
return super().formfield_for_foreignkey(db_field, request, **kwargs)
class ModuleForm(forms.ModelForm):
class Meta:
model = Module
- fields = '__all__'
+ fields = "__all__"
def save(self, **kwargs):
- must_renew_checkout = 'vcs_root' in self.changed_data and not self.instance._state.adding and not
self.errors
+ must_renew_checkout = "vcs_root" in self.changed_data and not self.instance._state.adding and not
self.errors
if must_renew_checkout:
old_module = Module.objects.get(pk=self.instance.pk)
# Delete checkout(s)
@@ -94,24 +104,32 @@ class ModuleForm(forms.ModelForm):
class ModuleAdmin(admin.ModelAdmin):
form = ModuleForm
fieldsets = (
- (None, {
- 'fields': (('name', 'description'),
- 'homepage', 'comment', 'archived',
- ('bugs_base',),
- ('vcs_type', 'vcs_root', 'vcs_web'),
- 'ext_platform', 'maintainers')
- }),
+ (
+ None,
+ {
+ "fields": (
+ ("name", "description"),
+ "homepage",
+ "comment",
+ "archived",
+ ("bugs_base",),
+ ("vcs_type", "vcs_root", "vcs_web"),
+ "ext_platform",
+ "maintainers",
+ )
+ },
+ ),
)
inlines = [BranchInline, DomainInline]
- search_fields = ('name', 'homepage', 'comment', 'vcs_web')
- list_filter = ('archived',)
+ search_fields = ("name", "homepage", "comment", "vcs_web")
+ list_filter = ("archived",)
def formfield_for_dbfield(self, db_field, **kwargs):
field = super().formfield_for_dbfield(db_field, **kwargs)
- if db_field.name == 'description':
- field.widget.attrs['rows'] = '1'
- elif db_field.name == 'comment':
- field.widget.attrs['rows'] = '4'
+ if db_field.name == "description":
+ field.widget.attrs["rows"] = "1"
+ elif db_field.name == "comment":
+ field.widget.attrs["rows"] = "4"
return field
@@ -122,45 +140,47 @@ class ModuleAdmin(admin.ModelAdmin):
class BranchAdmin(admin.ModelAdmin):
- search_fields = ('name', 'module__name')
+ search_fields = ("name", "module__name")
class DomainAdmin(admin.ModelAdmin):
form = DomainForm
- list_display = ('__str__', 'layout', 'pot_method')
- list_filter = ('dtype', 'pot_method')
- search_fields = ('name', 'module__name', 'layout', 'pot_method')
+ list_display = ("__str__", "layout", "pot_method")
+ list_filter = ("dtype", "pot_method")
+ search_fields = ("name", "module__name", "layout", "pot_method")
class CategoryInline(admin.TabularInline):
model = Category
- raw_id_fields = ('branch',) # Too costly otherwise
+ raw_id_fields = ("branch",) # Too costly otherwise
extra = 1
class CategoryAdmin(admin.ModelAdmin):
- search_fields = ('name__name', 'branch__module__name')
+ search_fields = ("name__name", "branch__module__name")
class ReleaseAdmin(admin.ModelAdmin):
- list_display = ('name', 'status', 'weight', 'string_frozen')
- list_editable = ('weight',)
+ list_display = ("name", "status", "weight", "string_frozen")
+ list_editable = ("weight",)
inlines = [CategoryInline]
- actions = ['copy_release', 'delete_release']
+ actions = ["copy_release", "delete_release"]
def copy_release(self, request, queryset):
"""Copy an existing release and use master branches"""
if not self.has_add_permission(request):
raise PermissionDenied
if queryset.count() > 1:
- messages.error(request, 'Please copy only one release at a time')
+ messages.error(request, "Please copy only one release at a time")
return HttpResponseRedirect(request.path)
base_rel = queryset.first()
with transaction.atomic():
new_rel = Release.objects.create(
- name=base_rel.name + '-copy', description=base_rel.description + '-copy',
- string_frozen=False, status=base_rel.status
+ name=base_rel.name + "-copy",
+ description=base_rel.description + "-copy",
+ string_frozen=False,
+ status=base_rel.status,
)
branch_seen = set()
@@ -180,10 +200,10 @@ class ReleaseAdmin(admin.ModelAdmin):
copy_release.short_description = "Copy release (and associated branches)"
def delete_release(self, request, queryset):
- """ Admin action to delete releases *with* branches which are not linked to another release """
+ """Admin action to delete releases *with* branches which are not linked to another release"""
if not self.has_delete_permission(request):
raise PermissionDenied
- if request.POST.get('post'):
+ if request.POST.get("post"):
# Already confirmed
for obj in queryset:
self.log_deletion(request, obj, str(obj))
@@ -196,9 +216,14 @@ class ReleaseAdmin(admin.ModelAdmin):
branch.delete()
b += 1
queryset.delete()
- self.message_user(request, "Successfully deleted %(countr)d release(s) and %(countb)d
branch(es)." % {
- "countr": n, "countb": b,
- })
+ self.message_user(
+ request,
+ "Successfully deleted %(countr)d release(s) and %(countb)d branch(es)."
+ % {
+ "countr": n,
+ "countb": b,
+ },
+ )
# Return None to display the change list page again.
return None
context = {
@@ -208,7 +233,7 @@ class ReleaseAdmin(admin.ModelAdmin):
"model_label": self.model._meta.verbose_name_plural,
"action_checkbox_name": helpers.ACTION_CHECKBOX_NAME,
}
- return render(request, 'admin/delete_release_confirmation.html', context)
+ return render(request, "admin/delete_release_confirmation.html", context)
delete_release.short_description = "Delete release (and associated branches)"
@@ -219,13 +244,13 @@ class InformationInline(admin.TabularInline):
class StatisticsAdmin(admin.ModelAdmin):
- search_fields = ('language__name', 'branch__module__name')
- raw_id_fields = ('branch', 'domain', 'language', 'full_po', 'part_po')
+ search_fields = ("language__name", "branch__module__name")
+ raw_id_fields = ("branch", "domain", "language", "full_po", "part_po")
inlines = [InformationInline]
class PoFileAdmin(admin.ModelAdmin):
- search_fields = ('path',)
+ search_fields = ("path",)
admin.site.register(Statistics, StatisticsAdmin)
diff --git a/stats/doap.py b/stats/doap.py
index af20ca68..a88da4fc 100644
--- a/stats/doap.py
+++ b/stats/doap.py
@@ -27,28 +27,28 @@ class DoapParser:
maintainers = []
for maint in maint_tags:
parsed = dict(
- (key, res) for key, res in [
- (attr.split('}')[-1], maint[0].find(attr)) for attr in pers_attrs
- ] if res is not None
+ (key, res)
+ for key, res in [(attr.split("}")[-1], maint[0].find(attr)) for attr in pers_attrs]
+ if res is not None
)
- maint = {'name': parsed['name'].text, 'email': None, 'account': None}
- if 'mbox' in parsed:
- maint['email'] = unquote(parsed['mbox'].items()[0][1].replace("mailto:", ""))
- if 'userid' in parsed:
- maint['account'] = parsed['userid'].text
+ maint = {"name": parsed["name"].text, "email": None, "account": None}
+ if "mbox" in parsed:
+ maint["email"] = unquote(parsed["mbox"].items()[0][1].replace("mailto:", ""))
+ if "userid" in parsed:
+ maint["account"] = parsed["userid"].text
maintainers.append(maint)
return maintainers
def parse_homepage(self):
homepage_tag = self.project_root.find("{http://usefulinc.com/ns/doap#}homepage")
if homepage_tag is not None:
- return homepage_tag.attrib.get('{http://www.w3.org/1999/02/22-rdf-syntax-ns#}resource')
+ return homepage_tag.attrib.get("{http://www.w3.org/1999/02/22-rdf-syntax-ns#}resource")
return None
def update_doap_infos(module):
- """ Should only be called inside an "update-stats" context of a master branch,
- so there is no need for any extra checkout/locking strategy """
+ """Should only be called inside an "update-stats" context of a master branch,
+ so there is no need for any extra checkout/locking strategy"""
doap_path = module.get_head_branch().co_path / ("%s.doap" % module.name)
if not doap_path.exists():
return
@@ -59,25 +59,29 @@ def update_doap_infos(module):
# *********** Update maintainers
def slugify(val):
- value = re.sub(r'[^\w\s-]', '', val).strip().lower()
- return re.sub(r'[-\s]+', '-', value)
+ value = re.sub(r"[^\w\s-]", "", val).strip().lower()
+ return re.sub(r"[-\s]+", "-", value)
+
doap_maintainers = tree.parse_maintainers()
current_maintainers = dict([(m.email or m.username, m) for m in module.maintainers.all()])
# Using email or username as unique identifier
for maintainer in doap_maintainers:
- maintainer_key = maintainer['email'] or maintainer['account'] or slugify(maintainer['name'])
+ maintainer_key = maintainer["email"] or maintainer["account"] or slugify(maintainer["name"])
if maintainer_key in current_maintainers.keys():
del current_maintainers[maintainer_key]
else:
# Add new maintainer
- pers = Person.get_by_attr('email', maintainer['email'])
+ pers = Person.get_by_attr("email", maintainer["email"])
if not pers:
- pers = Person.get_by_attr('username', maintainer['account'] or slugify(maintainer['name']))
+ pers = Person.get_by_attr("username", maintainer["account"] or slugify(maintainer["name"]))
if not pers:
pers = Person(
- username=maintainer['account'] or slugify(maintainer['name']), email=maintainer['email']
or '',
- password='!', svn_account=maintainer['account'], last_name=maintainer['name']
+ username=maintainer["account"] or slugify(maintainer["name"]),
+ email=maintainer["email"] or "",
+ password="!",
+ svn_account=maintainer["account"],
+ last_name=maintainer["name"],
)
pers.save()
module.maintainers.add(pers)
diff --git a/stats/forms.py b/stats/forms.py
index d0190ef6..31e3e42c 100644
--- a/stats/forms.py
+++ b/stats/forms.py
@@ -7,9 +7,9 @@ from stats.models import Branch, Category, CategoryName, Release
class ReleaseField(forms.ModelChoiceField):
def __init__(self, *args, **kwargs):
- kwargs['required'] = False
+ kwargs["required"] = False
super().__init__(*args, **kwargs)
- if 'label' in kwargs:
+ if "label" in kwargs:
self.is_branch = True
@@ -20,35 +20,29 @@ class ModuleBranchForm(forms.Form):
self.branch_fields = []
default_cat_name = None
for branch in module.get_branches(reverse=True):
- categories = list(branch.category_set.order_by('name', 'release__name'))
+ categories = list(branch.category_set.order_by("name", "release__name"))
if len(categories) > 0:
for category in categories:
self.fields[str(category.id)] = ReleaseField(
queryset=Release.objects.all(), label=branch.name, initial=category.release.pk
)
- self.fields[str(category.id) + '_cat'] = forms.ModelChoiceField(
- queryset=CategoryName.objects.all(),
- required=False,
- initial=category.name
+ self.fields[str(category.id) + "_cat"] = forms.ModelChoiceField(
+ queryset=CategoryName.objects.all(), required=False, initial=category.name
)
- self.branch_fields.append((str(category.id), f'{category.id}_cat'))
+ self.branch_fields.append((str(category.id), f"{category.id}_cat"))
default_cat_name = categories[-1].name
else:
# Branch is not linked to any release
- self.fields[branch.name] = ReleaseField(queryset=Release.objects.all(),
- label=branch.name)
- self.fields[branch.name + '_cat'] = forms.ModelChoiceField(
- queryset=CategoryName.objects.all(),
- required=False,
- initial=default_cat_name
+ self.fields[branch.name] = ReleaseField(queryset=Release.objects.all(), label=branch.name)
+ self.fields[branch.name + "_cat"] = forms.ModelChoiceField(
+ queryset=CategoryName.objects.all(), required=False, initial=default_cat_name
)
- self.branch_fields.append((branch.name, branch.name + '_cat'))
+ self.branch_fields.append((branch.name, branch.name + "_cat"))
- self.fields['new_branch'] = forms.CharField(required=False)
- self.fields['new_branch_release'] = ReleaseField(queryset=Release.objects.all())
- self.fields['new_branch_category'] = forms.ModelChoiceField(
- queryset=CategoryName.objects.all(),
- initial=default_cat_name
+ self.fields["new_branch"] = forms.CharField(required=False)
+ self.fields["new_branch_release"] = ReleaseField(queryset=Release.objects.all())
+ self.fields["new_branch_category"] = forms.ModelChoiceField(
+ queryset=CategoryName.objects.all(), initial=default_cat_name
)
def get_branches(self):
@@ -59,31 +53,30 @@ class ModuleBranchForm(forms.Form):
cleaned_data = super().clean()
for field_name in list(cleaned_data.keys()):
if (
- field_name.endswith('_cat')
+ field_name.endswith("_cat")
and cleaned_data[field_name] is None
and cleaned_data[field_name[:-4]] is not None
):
self.add_error(
- field_name,
- ValidationError(_("You have to provide a category when a version is specified."))
+ field_name, ValidationError(_("You have to provide a category when a version is
specified."))
)
- if cleaned_data['new_branch']:
+ if cleaned_data["new_branch"]:
try:
- branch = Branch.objects.get(module=self.module, name=cleaned_data['new_branch'])
+ branch = Branch.objects.get(module=self.module, name=cleaned_data["new_branch"])
except Branch.DoesNotExist:
# The branch will be created later in the view
return cleaned_data
- if not cleaned_data['new_branch_release']:
+ if not cleaned_data["new_branch_release"]:
self.add_error(
- 'new_branch_release',
+ "new_branch_release",
ValidationError(
"There is already an entry for branch %s. Edit that one if "
"you want to remove the release link." % branch
- )
+ ),
)
else:
- rel = Release.objects.get(pk=cleaned_data['new_branch_release'].pk)
- cat = Category(release=rel, branch=branch, name=cleaned_data['new_branch_category'])
+ rel = Release.objects.get(pk=cleaned_data["new_branch_release"].pk)
+ cat = Category(release=rel, branch=branch, name=cleaned_data["new_branch_category"])
cat.full_clean()
- cleaned_data['new_category'] = cat
+ cleaned_data["new_category"] = cat
return cleaned_data
diff --git a/stats/management/commands/archive-release.py b/stats/management/commands/archive-release.py
index 160457d4..b2657fef 100644
--- a/stats/management/commands/archive-release.py
+++ b/stats/management/commands/archive-release.py
@@ -10,32 +10,32 @@ class Command(BaseCommand):
help = "Write statistics of a release in a CSV structure (stdout)"
def add_arguments(self, parser):
- parser.add_argument('release')
+ parser.add_argument("release")
def handle(self, **options):
try:
- release = Release.objects.get(name=options['release'])
+ release = Release.objects.get(name=options["release"])
except Release.DoesNotExist:
- raise CommandError("The release name '%s' is not known.\n" % options['release'])
+ raise CommandError("The release name '%s' is not known.\n" % options["release"])
out = StringIO()
- headers = ['Module', 'Branch', 'Domain', 'Language', 'Translated', 'Fuzzy', 'Untranslated']
+ headers = ["Module", "Branch", "Domain", "Language", "Translated", "Fuzzy", "Untranslated"]
writer = csv.DictWriter(out, headers)
header = dict(zip(headers, headers))
writer.writerow(header)
- stats = Statistics.objects.filter(
- branch__category__release=release, language__isnull=False
- ).select_related('branch__module', 'domain', 'language')
+ stats = Statistics.objects.filter(branch__category__release=release,
language__isnull=False).select_related(
+ "branch__module", "domain", "language"
+ )
for stat in stats:
row = {
- 'Module': stat.branch.module.name,
- 'Branch': stat.branch.name,
- 'Domain': stat.domain.name,
- 'Language': stat.language.locale,
- 'Translated': stat.translated(),
- 'Fuzzy': stat.fuzzy(),
- 'Untranslated': stat.untranslated(),
+ "Module": stat.branch.module.name,
+ "Branch": stat.branch.name,
+ "Domain": stat.domain.name,
+ "Language": stat.language.locale,
+ "Translated": stat.translated(),
+ "Fuzzy": stat.fuzzy(),
+ "Untranslated": stat.untranslated(),
}
writer.writerow(row)
return out.getvalue()
diff --git a/stats/management/commands/compile-trans.py b/stats/management/commands/compile-trans.py
index 7954aec1..c0053d40 100644
--- a/stats/management/commands/compile-trans.py
+++ b/stats/management/commands/compile-trans.py
@@ -13,31 +13,28 @@ class Command(BaseCommand):
def add_arguments(self, parser):
# Copy --locale arg from compilemessages
parser.add_argument(
- '--locale', '-l', action='append', default=[],
- help='Locale(s) to process (e.g. de_AT). Default is to process all. '
- 'Can be used multiple times.',
+ "--locale",
+ "-l",
+ action="append",
+ default=[],
+ help="Locale(s) to process (e.g. de_AT). Default is to process all. " "Can be used multiple
times.",
)
def handle(self, **options):
# Copy all po/ll.po files in locale/ll/LC_MESSAGES/django.po
- podir = settings.BASE_DIR / 'po'
+ podir = settings.BASE_DIR / "po"
for pofile in podir.iterdir():
if pofile.suffix != ".po":
continue
lang_code = pofile.stem
- if options['locale'] and lang_code not in options['locale']:
+ if options["locale"] and lang_code not in options["locale"]:
continue
- localedir = (
- settings.BASE_DIR / 'locale' / Language.django_locale(lang_code) / 'LC_MESSAGES'
- )
+ localedir = settings.BASE_DIR / "locale" / Language.django_locale(lang_code) / "LC_MESSAGES"
if not localedir.is_dir():
localedir.mkdir(parents=True, exist_ok=True)
- if options['verbosity'] > 1:
+ if options["verbosity"] > 1:
self.stdout.write(f"Copying {pofile} to {localedir}")
- shutil.copy(pofile, localedir / 'django.po')
+ shutil.copy(pofile, localedir / "django.po")
# Run compilemessages -l ll
- call_command(
- 'compilemessages',
- locale=[Language.django_locale(locale) for locale in options['locale']]
- )
+ call_command("compilemessages", locale=[Language.django_locale(locale) for locale in
options["locale"]])
diff --git a/stats/management/commands/run-maintenance.py b/stats/management/commands/run-maintenance.py
index 24825ac9..e153fea1 100644
--- a/stats/management/commands/run-maintenance.py
+++ b/stats/management/commands/run-maintenance.py
@@ -1,9 +1,9 @@
from django.core.management.base import BaseCommand
+from languages.views import clean_tar_files
from people.models import Person
from teams.models import Role
from vertimus.models import ActionArchived
-from languages.views import clean_tar_files
class Command(BaseCommand):
diff --git a/stats/management/commands/update-from-doap.py b/stats/management/commands/update-from-doap.py
index 00a47097..732949d1 100644
--- a/stats/management/commands/update-from-doap.py
+++ b/stats/management/commands/update-from-doap.py
@@ -1,18 +1,19 @@
import sys
+
from django.core.management.base import BaseCommand
-from stats.models import Module, ModuleLock
from stats.doap import update_doap_infos
+from stats.models import Module, ModuleLock
class Command(BaseCommand):
help = "Update module information from doap file"
def add_arguments(self, parser):
- parser.add_argument('module', nargs='+')
+ parser.add_argument("module", nargs="+")
def handle(self, **options):
- for mod_name in options['module']:
+ for mod_name in options["module"]:
try:
mod = Module.objects.get(name=mod_name)
except Module.DoesNotExist:
diff --git a/stats/management/commands/update-stats.py b/stats/management/commands/update-stats.py
index fcd241c1..32994457 100644
--- a/stats/management/commands/update-stats.py
+++ b/stats/management/commands/update-stats.py
@@ -1,88 +1,87 @@
import traceback
-from django.core.management.base import BaseCommand, CommandError
from django.core.mail import mail_admins
+from django.core.management.base import BaseCommand, CommandError
from django.db.models import Q
-from stats.models import Module, Branch, Release
+from stats.models import Branch, Module, Release
class Command(BaseCommand):
help = "Update statistics about po files"
def add_arguments(self, parser):
- parser.add_argument('module', nargs='?', default=None)
- parser.add_argument('branch', nargs='*')
- parser.add_argument(
- '--release',
- help="update all modules and branches linked to the specified release name"
- )
- parser.add_argument(
- '--force', action='store_true', default=False,
- help="force statistics generation, even if files didn't change"
- )
+ parser.add_argument("module", nargs="?", default=None)
+ parser.add_argument("branch", nargs="*")
+ parser.add_argument("--release", help="update all modules and branches linked to the specified
release name")
parser.add_argument(
- '--non-gnome', action='store_true', default=False,
- help="generate statistics for non-gnome modules (externally hosted)"
+ "--force",
+ action="store_true",
+ default=False,
+ help="force statistics generation, even if files didn't change",
)
parser.add_argument(
- '--debug', action='store_true', default=False,
- help="activate interactive debug mode"
+ "--non-gnome",
+ action="store_true",
+ default=False,
+ help="generate statistics for non-gnome modules (externally hosted)",
)
+ parser.add_argument("--debug", action="store_true", default=False, help="activate interactive debug
mode")
def handle(self, **options):
- if options['debug']:
+ if options["debug"]:
breakpoint()
- self.force_stats = options['force']
- if options['module'] and options['branch']:
+ self.force_stats = options["force"]
+ if options["module"] and options["branch"]:
# Update the specific branch(es) of a module
# This allows several modules (differently named) to point to the same vcs repo
modules = Module.objects.filter(
- Q(name=options['module']) | Q(vcs_root__endswith='/%s' % options['module'])
+ Q(name=options["module"]) | Q(vcs_root__endswith="/%s" % options["module"])
)
if not modules:
- self.stderr.write("No modules match `%s`." % options['module'])
+ self.stderr.write("No modules match `%s`." % options["module"])
for i, module in enumerate(modules):
if module.archived and not self.force_stats:
self.stderr.write("The module '%s' is archived. Skipping..." % module.name)
continue
branches = []
- for branch_arg in options['branch']:
+ for branch_arg in options["branch"]:
if branch_arg == "trunk":
branch_arg = "HEAD"
try:
branches.append(module.branch_set.get(name=branch_arg))
except Branch.DoesNotExist:
- self.stderr.write("Unable to find branch '%s' for module '%s' in the database." % (
- branch_arg, module.name))
+ self.stderr.write(
+ "Unable to find branch '%s' for module '%s' in the database." % (branch_arg,
module.name)
+ )
continue
self.update_branches(branches, checkout=(i < 1))
- elif options['module']:
+ elif options["module"]:
# Update all branches of a module
try:
- module = Module.objects.get(name=options['module'])
+ module = Module.objects.get(name=options["module"])
except Module.DoesNotExist:
- raise CommandError("Unable to find a module named '%s' in the database" % options['module'])
+ raise CommandError("Unable to find a module named '%s' in the database" % options["module"])
if module.archived and not self.force_stats:
self.stderr.write("The module '%s' is archived. Skipping..." % module.name)
else:
self.update_branches(module.branch_set.all())
- elif options['release']:
- if options['module']:
+ elif options["release"]:
+ if options["module"]:
raise CommandError("The --release option cannot be combined with module/branch parameters.")
try:
- release = Release.objects.get(name=options['release'])
+ release = Release.objects.get(name=options["release"])
except Release.DoesNotExist:
- raise CommandError("Unable to find a release named '%s' in the database" %
options['release'])
+ raise CommandError("Unable to find a release named '%s' in the database" %
options["release"])
self.update_branches(release.branches.all())
else:
# Update all modules
- if options['non_gnome']:
- modules = Module.objects.exclude(vcs_root__contains='.gnome.org')
+ if options["non_gnome"]:
+ modules = Module.objects.exclude(vcs_root__contains=".gnome.org")
else:
modules = Module.objects.all()
if not self.force_stats:
@@ -101,5 +100,4 @@ class Command(BaseCommand):
except Exception as exc:
tbtext = traceback.format_exc()
mail_admins("Error while updating %s %s" % (branch.module.name, branch.name), tbtext)
- self.stderr.write("Error while updating stats for %s %s: %s" % (
- branch.module.name, branch.name, exc))
+ self.stderr.write("Error while updating stats for %s %s: %s" % (branch.module.name,
branch.name, exc))
diff --git a/stats/management/commands/update-trans.py b/stats/management/commands/update-trans.py
index 500e53e4..b2496e30 100644
--- a/stats/management/commands/update-trans.py
+++ b/stats/management/commands/update-trans.py
@@ -1,8 +1,7 @@
+import itertools
import os
import re
import shutil
-
-import itertools
import subprocess
from django.conf import settings
@@ -11,7 +10,7 @@ from django.core.management.base import BaseCommand
from django.db.models import F
from languages.models import Language
-from stats.models import Module, Domain, Release, CategoryName
+from stats.models import CategoryName, Domain, Module, Release
from teams.models import Team
@@ -27,7 +26,7 @@ class Command(BaseCommand):
self.po_dir = settings.BASE_DIR / "po"
self.locale_dir = settings.BASE_DIR / "locale" / self.lang_code / "LC_MESSAGES"
if self.lang_code == "en":
- self.po_file = self.po_dir / 'damned-lies.pot'
+ self.po_file = self.po_dir / "damned-lies.pot"
else:
self.po_file = self.po_dir / f"{self.lang_code}.po"
self._update_django_po_with_code_strings()
diff --git a/stats/migrations/0001_initial.py b/stats/migrations/0001_initial.py
index 5def3d8b..13ab4bcb 100644
--- a/stats/migrations/0001_initial.py
+++ b/stats/migrations/0001_initial.py
@@ -1,208 +1,334 @@
-from django.db import models, migrations
import django.db.models.deletion
+from django.db import migrations, models
+
import common.fields
class Migration(migrations.Migration):
dependencies = [
- ('people', '0001_initial'),
- ('languages', '0001_initial'),
+ ("people", "0001_initial"),
+ ("languages", "0001_initial"),
]
operations = [
migrations.CreateModel(
- name='Branch',
+ name="Branch",
fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True,
primary_key=True)),
- ('name', models.CharField(max_length=50)),
- ('vcs_subpath', models.CharField(max_length=50, null=True, blank=True)),
- ('weight', models.IntegerField(default=0, help_text='Smaller weight is displayed first')),
- ('file_hashes', common.fields.DictionaryField(default='', editable=False, blank=True)),
+ ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True,
primary_key=True)),
+ ("name", models.CharField(max_length=50)),
+ ("vcs_subpath", models.CharField(max_length=50, null=True, blank=True)),
+ ("weight", models.IntegerField(default=0, help_text="Smaller weight is displayed first")),
+ ("file_hashes", common.fields.DictionaryField(default="", editable=False, blank=True)),
],
options={
- 'ordering': ('name',),
- 'db_table': 'branch',
- 'verbose_name_plural': 'branches',
+ "ordering": ("name",),
+ "db_table": "branch",
+ "verbose_name_plural": "branches",
},
),
migrations.CreateModel(
- name='Category',
+ name="Category",
fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True,
primary_key=True)),
- ('name', models.CharField(default='default', max_length=30, choices=[('default', 'Default'),
('admin-tools', 'Administration Tools'), ('dev-tools', 'Development Tools'), ('desktop', 'GNOME Desktop'),
('dev-platform', 'GNOME Developer Platform'), ('proposed', 'New Module Proposals'), ('g3-core', 'Core'),
('g3-utils', 'Utils'), ('g3-apps', 'Apps'), ('g3-a11y', 'Accessibility'), ('g3-games', 'Games'),
('g3-backends', 'Backends'), ('g3-dev-tools', 'Development Tools'), ('g3-core-libs', 'Core Libraries'),
('g3-extra-libs', 'Extra Libraries'), ('g2-legacy', 'Legacy Desktop'), ('stable', 'Stable Branches'), ('dev',
'Development Branches')])),
- ('branch', models.ForeignKey(to='stats.Branch', on_delete=models.CASCADE)),
+ ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True,
primary_key=True)),
+ (
+ "name",
+ models.CharField(
+ default="default",
+ max_length=30,
+ choices=[
+ ("default", "Default"),
+ ("admin-tools", "Administration Tools"),
+ ("dev-tools", "Development Tools"),
+ ("desktop", "GNOME Desktop"),
+ ("dev-platform", "GNOME Developer Platform"),
+ ("proposed", "New Module Proposals"),
+ ("g3-core", "Core"),
+ ("g3-utils", "Utils"),
+ ("g3-apps", "Apps"),
+ ("g3-a11y", "Accessibility"),
+ ("g3-games", "Games"),
+ ("g3-backends", "Backends"),
+ ("g3-dev-tools", "Development Tools"),
+ ("g3-core-libs", "Core Libraries"),
+ ("g3-extra-libs", "Extra Libraries"),
+ ("g2-legacy", "Legacy Desktop"),
+ ("stable", "Stable Branches"),
+ ("dev", "Development Branches"),
+ ],
+ ),
+ ),
+ ("branch", models.ForeignKey(to="stats.Branch", on_delete=models.CASCADE)),
],
options={
- 'db_table': 'category',
- 'verbose_name_plural': 'categories',
+ "db_table": "category",
+ "verbose_name_plural": "categories",
},
),
migrations.CreateModel(
- name='Domain',
+ name="Domain",
fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True,
primary_key=True)),
- ('name', models.CharField(max_length=50)),
- ('description', models.TextField(null=True, blank=True)),
- ('dtype', models.CharField(default='ui', max_length=5, choices=[('ui', 'User Interface'),
('doc', 'Documentation')])),
- ('directory', models.CharField(max_length=50)),
- ('pot_method', models.CharField(help_text="Leave blank for standard method (intltool for UI
and gnome-doc-utils for DOC), or '<gettext>' for the pure xgettext-based extraction", max_length=100,
null=True, blank=True)),
- ('linguas_location', models.CharField(help_text="Use 'no' for no LINGUAS check, or
path/to/file#variable for a non-standard location.\n Leave blank for standard location
(ALL_LINGUAS in LINGUAS/configure.ac/.in for UI and DOC_LINGUAS in Makefile.am for DOC)", max_length=50,
null=True, blank=True)),
- ('red_filter', models.TextField(help_text='pogrep filter to strip po file from unprioritized
strings (format: location|string, "-" for no filter)', null=True, blank=True)),
+ ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True,
primary_key=True)),
+ ("name", models.CharField(max_length=50)),
+ ("description", models.TextField(null=True, blank=True)),
+ (
+ "dtype",
+ models.CharField(
+ default="ui", max_length=5, choices=[("ui", "User Interface"), ("doc",
"Documentation")]
+ ),
+ ),
+ ("directory", models.CharField(max_length=50)),
+ (
+ "pot_method",
+ models.CharField(
+ help_text="Leave blank for standard method (intltool for UI and gnome-doc-utils for
DOC), or '<gettext>' for the pure xgettext-based extraction",
+ max_length=100,
+ null=True,
+ blank=True,
+ ),
+ ),
+ (
+ "linguas_location",
+ models.CharField(
+ help_text="Use 'no' for no LINGUAS check, or path/to/file#variable for a
non-standard location.\n Leave blank for standard location (ALL_LINGUAS in
LINGUAS/configure.ac/.in for UI and DOC_LINGUAS in Makefile.am for DOC)",
+ max_length=50,
+ null=True,
+ blank=True,
+ ),
+ ),
+ (
+ "red_filter",
+ models.TextField(
+ help_text='pogrep filter to strip po file from unprioritized strings (format:
location|string, "-" for no filter)',
+ null=True,
+ blank=True,
+ ),
+ ),
],
options={
- 'ordering': ('-dtype', 'name'),
- 'db_table': 'domain',
+ "ordering": ("-dtype", "name"),
+ "db_table": "domain",
},
),
migrations.CreateModel(
- name='Information',
+ name="Information",
fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True,
primary_key=True)),
- ('type', models.CharField(max_length=10, choices=[('info', 'Information'), ('warn',
'Warning'), ('error', 'Error'), ('warn-ext', 'Warning (external)'), ('error-ext', 'Error (external)')])),
- ('description', models.TextField()),
+ ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True,
primary_key=True)),
+ (
+ "type",
+ models.CharField(
+ max_length=10,
+ choices=[
+ ("info", "Information"),
+ ("warn", "Warning"),
+ ("error", "Error"),
+ ("warn-ext", "Warning (external)"),
+ ("error-ext", "Error (external)"),
+ ],
+ ),
+ ),
+ ("description", models.TextField()),
],
options={
- 'db_table': 'information',
+ "db_table": "information",
},
),
migrations.CreateModel(
- name='InformationArchived',
+ name="InformationArchived",
fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True,
primary_key=True)),
- ('type', models.CharField(max_length=10, choices=[('info', 'Information'), ('warn',
'Warning'), ('error', 'Error'), ('warn-ext', 'Warning (external)'), ('error-ext', 'Error (external)')])),
- ('description', models.TextField()),
+ ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True,
primary_key=True)),
+ (
+ "type",
+ models.CharField(
+ max_length=10,
+ choices=[
+ ("info", "Information"),
+ ("warn", "Warning"),
+ ("error", "Error"),
+ ("warn-ext", "Warning (external)"),
+ ("error-ext", "Error (external)"),
+ ],
+ ),
+ ),
+ ("description", models.TextField()),
],
options={
- 'db_table': 'information_archived',
+ "db_table": "information_archived",
},
),
migrations.CreateModel(
- name='Module',
+ name="Module",
fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True,
primary_key=True)),
- ('name', models.SlugField(unique=True)),
- ('homepage', models.URLField(help_text='Automatically updated if the module contains a doap
file.', null=True, blank=True)),
- ('description', models.TextField(null=True, blank=True)),
- ('comment', models.TextField(null=True, blank=True)),
- ('bugs_base', models.CharField(max_length=50, null=True, blank=True)),
- ('bugs_product', models.CharField(max_length=50, null=True, blank=True)),
- ('bugs_component', models.CharField(max_length=50, null=True, blank=True)),
- ('vcs_type', models.CharField(max_length=5, choices=[('svn', 'Subversion'), ('git', 'Git'),
('hg', 'Mercurial')])),
- ('vcs_root', models.CharField(max_length=200)),
- ('vcs_web', models.URLField()),
- ('ext_platform', models.URLField(help_text='URL to external translation platform, if any',
null=True, blank=True)),
- ('archived', models.BooleanField(default=False)),
- ('maintainers', models.ManyToManyField(help_text='Automatically updated if the module
contains a doap file.', related_name='maintains_modules', db_table='module_maintainer', to='people.Person',
blank=True)),
+ ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True,
primary_key=True)),
+ ("name", models.SlugField(unique=True)),
+ (
+ "homepage",
+ models.URLField(
+ help_text="Automatically updated if the module contains a doap file.", null=True,
blank=True
+ ),
+ ),
+ ("description", models.TextField(null=True, blank=True)),
+ ("comment", models.TextField(null=True, blank=True)),
+ ("bugs_base", models.CharField(max_length=50, null=True, blank=True)),
+ ("bugs_product", models.CharField(max_length=50, null=True, blank=True)),
+ ("bugs_component", models.CharField(max_length=50, null=True, blank=True)),
+ (
+ "vcs_type",
+ models.CharField(
+ max_length=5, choices=[("svn", "Subversion"), ("git", "Git"), ("hg", "Mercurial")]
+ ),
+ ),
+ ("vcs_root", models.CharField(max_length=200)),
+ ("vcs_web", models.URLField()),
+ (
+ "ext_platform",
+ models.URLField(help_text="URL to external translation platform, if any", null=True,
blank=True),
+ ),
+ ("archived", models.BooleanField(default=False)),
+ (
+ "maintainers",
+ models.ManyToManyField(
+ help_text="Automatically updated if the module contains a doap file.",
+ related_name="maintains_modules",
+ db_table="module_maintainer",
+ to="people.Person",
+ blank=True,
+ ),
+ ),
],
options={
- 'ordering': ('name',),
- 'db_table': 'module',
+ "ordering": ("name",),
+ "db_table": "module",
},
),
migrations.CreateModel(
- name='PoFile',
+ name="PoFile",
fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True,
primary_key=True)),
- ('path', models.CharField(max_length=255, null=True, blank=True)),
- ('updated', models.DateTimeField(auto_now_add=True)),
- ('translated', models.IntegerField(default=0)),
- ('fuzzy', models.IntegerField(default=0)),
- ('untranslated', models.IntegerField(default=0)),
- ('figures', common.fields.JSONField(null=True, blank=True)),
- ('translated_words', models.IntegerField(default=0)),
- ('fuzzy_words', models.IntegerField(default=0)),
- ('untranslated_words', models.IntegerField(default=0)),
+ ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True,
primary_key=True)),
+ ("path", models.CharField(max_length=255, null=True, blank=True)),
+ ("updated", models.DateTimeField(auto_now_add=True)),
+ ("translated", models.IntegerField(default=0)),
+ ("fuzzy", models.IntegerField(default=0)),
+ ("untranslated", models.IntegerField(default=0)),
+ ("figures", common.fields.JSONField(null=True, blank=True)),
+ ("translated_words", models.IntegerField(default=0)),
+ ("fuzzy_words", models.IntegerField(default=0)),
+ ("untranslated_words", models.IntegerField(default=0)),
],
options={
- 'db_table': 'pofile',
+ "db_table": "pofile",
},
),
migrations.CreateModel(
- name='Release',
+ name="Release",
fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True,
primary_key=True)),
- ('name', models.SlugField(max_length=20)),
- ('description', models.CharField(max_length=50)),
- ('string_frozen', models.BooleanField(default=False)),
- ('status', models.CharField(max_length=12, choices=[('official', 'Official'), ('unofficial',
'Unofficial'), ('xternal', 'External')])),
- ('weight', models.IntegerField(default=0)),
- ('branches', models.ManyToManyField(related_name='releases', through='stats.Category',
to='stats.Branch')),
+ ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True,
primary_key=True)),
+ ("name", models.SlugField(max_length=20)),
+ ("description", models.CharField(max_length=50)),
+ ("string_frozen", models.BooleanField(default=False)),
+ (
+ "status",
+ models.CharField(
+ max_length=12,
+ choices=[("official", "Official"), ("unofficial", "Unofficial"), ("xternal",
"External")],
+ ),
+ ),
+ ("weight", models.IntegerField(default=0)),
+ (
+ "branches",
+ models.ManyToManyField(related_name="releases", through="stats.Category",
to="stats.Branch"),
+ ),
],
options={
- 'ordering': ('status', '-name'),
- 'db_table': 'release',
+ "ordering": ("status", "-name"),
+ "db_table": "release",
},
),
migrations.CreateModel(
- name='Statistics',
+ name="Statistics",
fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True,
primary_key=True)),
- ('branch', models.ForeignKey(to='stats.Branch', on_delete=models.CASCADE)),
- ('domain', models.ForeignKey(to='stats.Domain', on_delete=models.CASCADE)),
- ('full_po', models.OneToOneField(related_name='stat_f', null=True,
on_delete=django.db.models.deletion.SET_NULL, to='stats.PoFile')),
- ('language', models.ForeignKey(to='languages.Language', null=True,
on_delete=models.CASCADE)),
- ('part_po', models.OneToOneField(related_name='stat_p', null=True,
on_delete=django.db.models.deletion.SET_NULL, to='stats.PoFile')),
+ ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True,
primary_key=True)),
+ ("branch", models.ForeignKey(to="stats.Branch", on_delete=models.CASCADE)),
+ ("domain", models.ForeignKey(to="stats.Domain", on_delete=models.CASCADE)),
+ (
+ "full_po",
+ models.OneToOneField(
+ related_name="stat_f",
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ to="stats.PoFile",
+ ),
+ ),
+ ("language", models.ForeignKey(to="languages.Language", null=True,
on_delete=models.CASCADE)),
+ (
+ "part_po",
+ models.OneToOneField(
+ related_name="stat_p",
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ to="stats.PoFile",
+ ),
+ ),
],
options={
- 'db_table': 'statistics',
- 'verbose_name': 'statistics',
- 'verbose_name_plural': 'statistics',
+ "db_table": "statistics",
+ "verbose_name": "statistics",
+ "verbose_name_plural": "statistics",
},
),
migrations.CreateModel(
- name='StatisticsArchived',
+ name="StatisticsArchived",
fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True,
primary_key=True)),
- ('module', models.TextField()),
- ('type', models.CharField(max_length=3, choices=[('ui', 'User Interface'), ('doc',
'Documentation')])),
- ('domain', models.TextField()),
- ('branch', models.TextField()),
- ('language', models.CharField(max_length=15)),
- ('date', models.DateTimeField()),
- ('translated', models.IntegerField(default=0)),
- ('fuzzy', models.IntegerField(default=0)),
- ('untranslated', models.IntegerField(default=0)),
+ ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True,
primary_key=True)),
+ ("module", models.TextField()),
+ ("type", models.CharField(max_length=3, choices=[("ui", "User Interface"), ("doc",
"Documentation")])),
+ ("domain", models.TextField()),
+ ("branch", models.TextField()),
+ ("language", models.CharField(max_length=15)),
+ ("date", models.DateTimeField()),
+ ("translated", models.IntegerField(default=0)),
+ ("fuzzy", models.IntegerField(default=0)),
+ ("untranslated", models.IntegerField(default=0)),
],
options={
- 'db_table': 'statistics_archived',
+ "db_table": "statistics_archived",
},
),
migrations.AddField(
- model_name='informationarchived',
- name='statistics',
- field=models.ForeignKey(to='stats.StatisticsArchived', on_delete=models.CASCADE),
+ model_name="informationarchived",
+ name="statistics",
+ field=models.ForeignKey(to="stats.StatisticsArchived", on_delete=models.CASCADE),
),
migrations.AddField(
- model_name='information',
- name='statistics',
- field=models.ForeignKey(to='stats.Statistics', on_delete=models.CASCADE),
+ model_name="information",
+ name="statistics",
+ field=models.ForeignKey(to="stats.Statistics", on_delete=models.CASCADE),
),
migrations.AddField(
- model_name='domain',
- name='module',
- field=models.ForeignKey(to='stats.Module', on_delete=models.CASCADE),
+ model_name="domain",
+ name="module",
+ field=models.ForeignKey(to="stats.Module", on_delete=models.CASCADE),
),
migrations.AddField(
- model_name='category',
- name='release',
- field=models.ForeignKey(to='stats.Release', on_delete=models.CASCADE),
+ model_name="category",
+ name="release",
+ field=models.ForeignKey(to="stats.Release", on_delete=models.CASCADE),
),
migrations.AddField(
- model_name='branch',
- name='module',
- field=models.ForeignKey(to='stats.Module', on_delete=models.CASCADE),
+ model_name="branch",
+ name="module",
+ field=models.ForeignKey(to="stats.Module", on_delete=models.CASCADE),
),
migrations.AlterUniqueTogether(
- name='statistics',
- unique_together=set([('branch', 'domain', 'language')]),
+ name="statistics",
+ unique_together=set([("branch", "domain", "language")]),
),
migrations.AlterUniqueTogether(
- name='category',
- unique_together=set([('release', 'branch')]),
+ name="category",
+ unique_together=set([("release", "branch")]),
),
migrations.AlterUniqueTogether(
- name='branch',
- unique_together=set([('name', 'module')]),
+ name="branch",
+ unique_together=set([("name", "module")]),
),
]
diff --git a/stats/migrations/0002_add_category_name.py b/stats/migrations/0002_add_category_name.py
index 2f8bed47..355ebbd8 100644
--- a/stats/migrations/0002_add_category_name.py
+++ b/stats/migrations/0002_add_category_name.py
@@ -1,27 +1,29 @@
-from django.db import models, migrations
import django.db.models.deletion
+from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ('stats', '0001_initial'),
+ ("stats", "0001_initial"),
]
operations = [
migrations.CreateModel(
- name='CategoryName',
+ name="CategoryName",
fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True,
primary_key=True)),
- ('name', models.CharField(unique=True, max_length=30)),
+ ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True,
primary_key=True)),
+ ("name", models.CharField(unique=True, max_length=30)),
],
options={
- 'db_table': 'categoryname',
+ "db_table": "categoryname",
},
),
migrations.AddField(
- model_name='category',
- name='name_id',
- field=models.ForeignKey(db_column='name_id', on_delete=django.db.models.deletion.PROTECT,
to='stats.CategoryName', null=True),
+ model_name="category",
+ name="name_id",
+ field=models.ForeignKey(
+ db_column="name_id", on_delete=django.db.models.deletion.PROTECT, to="stats.CategoryName",
null=True
+ ),
),
]
diff --git a/stats/migrations/0003_migrate_category_names.py b/stats/migrations/0003_migrate_category_names.py
index 8e8f55e9..a4787a8f 100644
--- a/stats/migrations/0003_migrate_category_names.py
+++ b/stats/migrations/0003_migrate_category_names.py
@@ -1,23 +1,23 @@
from django.db import migrations
CATEGORY_CHOICES = (
- ('default', 'Default'),
- ('admin-tools', 'Administration Tools'),
- ('dev-tools', 'Development Tools'),
- ('desktop', 'GNOME Desktop'),
- ('dev-platform', 'GNOME Developer Platform'),
- ('proposed', 'New Module Proposals'),
- ('g3-core', 'Core'),
- ('g3-utils', 'Utils'),
- ('g3-apps', 'Apps'),
- ('g3-a11y', 'Accessibility'),
- ('g3-games', 'Games'),
- ('g3-backends', 'Backends'),
- ('g3-core-libs', 'Core Libraries'),
- ('g3-extra-libs', 'Extra Libraries'),
- ('g2-legacy', 'Legacy Desktop'),
- ('stable', 'Stable Branches'),
- ('dev', 'Development Branches'),
+ ("default", "Default"),
+ ("admin-tools", "Administration Tools"),
+ ("dev-tools", "Development Tools"),
+ ("desktop", "GNOME Desktop"),
+ ("dev-platform", "GNOME Developer Platform"),
+ ("proposed", "New Module Proposals"),
+ ("g3-core", "Core"),
+ ("g3-utils", "Utils"),
+ ("g3-apps", "Apps"),
+ ("g3-a11y", "Accessibility"),
+ ("g3-games", "Games"),
+ ("g3-backends", "Backends"),
+ ("g3-core-libs", "Core Libraries"),
+ ("g3-extra-libs", "Extra Libraries"),
+ ("g2-legacy", "Legacy Desktop"),
+ ("stable", "Stable Branches"),
+ ("dev", "Development Branches"),
)
@@ -28,14 +28,14 @@ def migrate_categs(apps, schema_editor):
for cat_key, cat_name in CATEGORY_CHOICES:
cn = CategoryName.objects.create(name=cat_name)
Category.objects.filter(name=cat_key).update(name_id=cn)
- if cat_name == 'Development Tools':
- Category.objects.filter(name='g3-dev-tools').update(name_id=cn)
+ if cat_name == "Development Tools":
+ Category.objects.filter(name="g3-dev-tools").update(name_id=cn)
class Migration(migrations.Migration):
dependencies = [
- ('stats', '0002_add_category_name'),
+ ("stats", "0002_add_category_name"),
]
operations = [
diff --git a/stats/migrations/0004_remove_old_cat_name.py b/stats/migrations/0004_remove_old_cat_name.py
index ec8e296f..6007606f 100644
--- a/stats/migrations/0004_remove_old_cat_name.py
+++ b/stats/migrations/0004_remove_old_cat_name.py
@@ -1,26 +1,26 @@
-from django.db import models, migrations
import django.db.models.deletion
+from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ('stats', '0003_migrate_category_names'),
+ ("stats", "0003_migrate_category_names"),
]
operations = [
migrations.RemoveField(
- model_name='category',
- name='name',
+ model_name="category",
+ name="name",
),
migrations.RenameField(
- model_name='category',
- old_name='name_id',
- new_name='name',
+ model_name="category",
+ old_name="name_id",
+ new_name="name",
),
migrations.AlterField(
- model_name='category',
- name='name',
- field=models.ForeignKey(to='stats.CategoryName', on_delete=django.db.models.deletion.PROTECT),
+ model_name="category",
+ name="name",
+ field=models.ForeignKey(to="stats.CategoryName", on_delete=django.db.models.deletion.PROTECT),
),
]
diff --git a/stats/migrations/0005_update_module_name_field.py
b/stats/migrations/0005_update_module_name_field.py
index 19234fa2..40439ad7 100644
--- a/stats/migrations/0005_update_module_name_field.py
+++ b/stats/migrations/0005_update_module_name_field.py
@@ -1,18 +1,29 @@
-from django.db import models, migrations
import re
+
import django.core.validators
+from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ('stats', '0004_remove_old_cat_name'),
+ ("stats", "0004_remove_old_cat_name"),
]
operations = [
migrations.AlterField(
- model_name='module',
- name='name',
- field=models.CharField(unique=True, max_length=50,
validators=[django.core.validators.RegexValidator(re.compile('^[-\\+a-zA-Z0-9_]+\\Z'), "Enter a valid 'slug'
consisting of letters, numbers, underscores, hyphens or plus signs.", 'invalid')]),
+ model_name="module",
+ name="name",
+ field=models.CharField(
+ unique=True,
+ max_length=50,
+ validators=[
+ django.core.validators.RegexValidator(
+ re.compile("^[-\\+a-zA-Z0-9_]+\\Z"),
+ "Enter a valid 'slug' consisting of letters, numbers, underscores, hyphens or plus
signs.",
+ "invalid",
+ )
+ ],
+ ),
),
]
diff --git a/stats/migrations/0006_add_domain_branch_from_to.py
b/stats/migrations/0006_add_domain_branch_from_to.py
index 05f6ece9..5d62e056 100644
--- a/stats/migrations/0006_add_domain_branch_from_to.py
+++ b/stats/migrations/0006_add_domain_branch_from_to.py
@@ -1,27 +1,36 @@
-from django.db import models, migrations
import django.db.models.deletion
+from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ('stats', '0005_update_module_name_field'),
+ ("stats", "0005_update_module_name_field"),
]
operations = [
migrations.AddField(
- model_name='domain',
- name='branch_from',
- field=models.ForeignKey(related_name='+', on_delete=django.db.models.deletion.PROTECT,
blank=True, to='stats.Branch', null=True),
+ model_name="domain",
+ name="branch_from",
+ field=models.ForeignKey(
+ related_name="+", on_delete=django.db.models.deletion.PROTECT, blank=True,
to="stats.Branch", null=True
+ ),
),
migrations.AddField(
- model_name='domain',
- name='branch_to',
- field=models.ForeignKey(related_name='+', on_delete=django.db.models.deletion.PROTECT,
blank=True, to='stats.Branch', null=True),
+ model_name="domain",
+ name="branch_to",
+ field=models.ForeignKey(
+ related_name="+", on_delete=django.db.models.deletion.PROTECT, blank=True,
to="stats.Branch", null=True
+ ),
),
migrations.AlterField(
- model_name='domain',
- name='pot_method',
- field=models.CharField(help_text="Leave blank for standard method (intltool for UI and
gnome-doc-utils for DOC), or '<gettext>' for the pure xgettext-based extraction", max_length=100,
null=True, blank=True),
+ model_name="domain",
+ name="pot_method",
+ field=models.CharField(
+ help_text="Leave blank for standard method (intltool for UI and gnome-doc-utils for DOC), or
'<gettext>' for the pure xgettext-based extraction",
+ max_length=100,
+ null=True,
+ blank=True,
+ ),
),
]
diff --git a/stats/migrations/0007_extend_bugs_base.py b/stats/migrations/0007_extend_bugs_base.py
index b233d829..a462bddb 100644
--- a/stats/migrations/0007_extend_bugs_base.py
+++ b/stats/migrations/0007_extend_bugs_base.py
@@ -4,13 +4,13 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ('stats', '0006_add_domain_branch_from_to'),
+ ("stats", "0006_add_domain_branch_from_to"),
]
operations = [
migrations.AlterField(
- model_name='module',
- name='bugs_base',
+ model_name="module",
+ name="bugs_base",
field=models.CharField(max_length=250, null=True, blank=True),
),
]
diff --git a/stats/migrations/0008_domain_extra_its_dirs.py b/stats/migrations/0008_domain_extra_its_dirs.py
index a04bf14c..9085ca0c 100644
--- a/stats/migrations/0008_domain_extra_its_dirs.py
+++ b/stats/migrations/0008_domain_extra_its_dirs.py
@@ -4,13 +4,15 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ('stats', '0007_extend_bugs_base'),
+ ("stats", "0007_extend_bugs_base"),
]
operations = [
migrations.AddField(
- model_name='domain',
- name='extra_its_dirs',
- field=models.TextField(help_text='colon-separated directories containing extra .its/.loc files
for gettext', blank=True),
+ model_name="domain",
+ name="extra_its_dirs",
+ field=models.TextField(
+ help_text="colon-separated directories containing extra .its/.loc files for gettext",
blank=True
+ ),
),
]
diff --git a/stats/migrations/0009_remove_null_on_text_fields.py
b/stats/migrations/0009_remove_null_on_text_fields.py
index 5e8740e2..a0968b84 100644
--- a/stats/migrations/0009_remove_null_on_text_fields.py
+++ b/stats/migrations/0009_remove_null_on_text_fields.py
@@ -4,86 +4,102 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ('stats', '0008_domain_extra_its_dirs'),
+ ("stats", "0008_domain_extra_its_dirs"),
]
operations = [
migrations.AlterField(
- model_name='branch',
- name='vcs_subpath',
- field=models.CharField(blank=True, default='', max_length=50),
+ model_name="branch",
+ name="vcs_subpath",
+ field=models.CharField(blank=True, default="", max_length=50),
preserve_default=False,
),
migrations.AlterField(
- model_name='domain',
- name='description',
- field=models.TextField(blank=True, default=''),
+ model_name="domain",
+ name="description",
+ field=models.TextField(blank=True, default=""),
preserve_default=False,
),
migrations.AlterField(
- model_name='domain',
- name='linguas_location',
- field=models.CharField(blank=True, default='', help_text="Use 'no' for no LINGUAS check, or
path/to/file#variable for a non-standard location.\nLeave blank for standard location (ALL_LINGUAS in
LINGUAS/configure.ac/.in for UI and DOC_LINGUAS in Makefile.am for DOC)", max_length=50),
+ model_name="domain",
+ name="linguas_location",
+ field=models.CharField(
+ blank=True,
+ default="",
+ help_text="Use 'no' for no LINGUAS check, or path/to/file#variable for a non-standard
location.\nLeave blank for standard location (ALL_LINGUAS in LINGUAS/configure.ac/.in for UI and DOC_LINGUAS
in Makefile.am for DOC)",
+ max_length=50,
+ ),
preserve_default=False,
),
migrations.AlterField(
- model_name='domain',
- name='pot_method',
- field=models.CharField(blank=True, default='', help_text="Leave blank for autodetected method
(intltool/gettext/gnome-doc-utils), or '<gettext>', '<intltool>', or real pot command to force
the tool chain", max_length=100),
+ model_name="domain",
+ name="pot_method",
+ field=models.CharField(
+ blank=True,
+ default="",
+ help_text="Leave blank for autodetected method (intltool/gettext/gnome-doc-utils), or
'<gettext>', '<intltool>', or real pot command to force the tool chain",
+ max_length=100,
+ ),
preserve_default=False,
),
migrations.AlterField(
- model_name='domain',
- name='red_filter',
- field=models.TextField(blank=True, default='', help_text='pogrep filter to strip po file from
unprioritized strings (format: location|string, “-” for no filter)'),
+ model_name="domain",
+ name="red_filter",
+ field=models.TextField(
+ blank=True,
+ default="",
+ help_text="pogrep filter to strip po file from unprioritized strings (format:
location|string, “-” for no filter)",
+ ),
preserve_default=False,
),
migrations.AlterField(
- model_name='module',
- name='bugs_base',
- field=models.CharField(blank=True, default='', max_length=250),
+ model_name="module",
+ name="bugs_base",
+ field=models.CharField(blank=True, default="", max_length=250),
preserve_default=False,
),
migrations.AlterField(
- model_name='module',
- name='bugs_component',
- field=models.CharField(blank=True, default='', max_length=50),
+ model_name="module",
+ name="bugs_component",
+ field=models.CharField(blank=True, default="", max_length=50),
preserve_default=False,
),
migrations.AlterField(
- model_name='module',
- name='bugs_product',
- field=models.CharField(blank=True, default='', max_length=50),
+ model_name="module",
+ name="bugs_product",
+ field=models.CharField(blank=True, default="", max_length=50),
preserve_default=False,
),
migrations.AlterField(
- model_name='module',
- name='comment',
- field=models.TextField(blank=True, default=''),
+ model_name="module",
+ name="comment",
+ field=models.TextField(blank=True, default=""),
preserve_default=False,
),
migrations.AlterField(
- model_name='module',
- name='description',
- field=models.TextField(blank=True, default=''),
+ model_name="module",
+ name="description",
+ field=models.TextField(blank=True, default=""),
preserve_default=False,
),
migrations.AlterField(
- model_name='module',
- name='ext_platform',
- field=models.URLField(blank=True, default='', help_text='URL to external translation platform,
if any'),
+ model_name="module",
+ name="ext_platform",
+ field=models.URLField(blank=True, default="", help_text="URL to external translation platform,
if any"),
preserve_default=False,
),
migrations.AlterField(
- model_name='module',
- name='homepage',
- field=models.URLField(blank=True, default='', help_text='Automatically updated if the module
contains a doap file.'),
+ model_name="module",
+ name="homepage",
+ field=models.URLField(
+ blank=True, default="", help_text="Automatically updated if the module contains a doap file."
+ ),
preserve_default=False,
),
migrations.AlterField(
- model_name='pofile',
- name='path',
- field=models.CharField(blank=True, default='', max_length=255),
+ model_name="pofile",
+ name="path",
+ field=models.CharField(blank=True, default="", max_length=255),
preserve_default=False,
),
]
diff --git a/stats/migrations/0010_pot_method.py b/stats/migrations/0010_pot_method.py
index 2e05f01a..14b73f4f 100644
--- a/stats/migrations/0010_pot_method.py
+++ b/stats/migrations/0010_pot_method.py
@@ -4,23 +4,39 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ('stats', '0009_remove_null_on_text_fields'),
+ ("stats", "0009_remove_null_on_text_fields"),
]
operations = [
migrations.RenameField(
- model_name='domain',
- old_name='pot_method',
- new_name='pot_method_old',
+ model_name="domain",
+ old_name="pot_method",
+ new_name="pot_method_old",
),
migrations.AddField(
- model_name='domain',
- name='pot_method',
- field=models.CharField(choices=[('auto', 'auto detected'), ('gettext', 'gettext'), ('intltool',
'intltool'), ('shell', 'shell command'), ('url', 'URL'), ('in_repo', '.pot in repository'), ('subtitles',
'subtitles')], default='auto', max_length=20),
+ model_name="domain",
+ name="pot_method",
+ field=models.CharField(
+ choices=[
+ ("auto", "auto detected"),
+ ("gettext", "gettext"),
+ ("intltool", "intltool"),
+ ("shell", "shell command"),
+ ("url", "URL"),
+ ("in_repo", ".pot in repository"),
+ ("subtitles", "subtitles"),
+ ],
+ default="auto",
+ max_length=20,
+ ),
),
migrations.AddField(
- model_name='domain',
- name='pot_params',
- field=models.CharField(blank=True, help_text="pot_method='url': URL, pot_method='shell': shell
command, pot_method='gettext': optional params", max_length=100),
+ model_name="domain",
+ name="pot_params",
+ field=models.CharField(
+ blank=True,
+ help_text="pot_method='url': URL, pot_method='shell': shell command, pot_method='gettext':
optional params",
+ max_length=100,
+ ),
),
]
diff --git a/stats/migrations/0011_migrate_pot_method.py b/stats/migrations/0011_migrate_pot_method.py
index e424e079..fd06fe78 100644
--- a/stats/migrations/0011_migrate_pot_method.py
+++ b/stats/migrations/0011_migrate_pot_method.py
@@ -3,18 +3,18 @@ from django.db import migrations
def migrate_pot_method(apps, schema_editor):
Domain = apps.get_model("stats", "Domain")
- for domain in Domain.objects.exclude(pot_method_old=''):
- if domain.pot_method_old == ':':
- domain.pot_method = 'in_repo'
- elif domain.pot_method_old == '<gettext>':
- domain.pot_method = 'gettext'
- elif domain.pot_method_old == '<intltool>':
- domain.pot_method = 'intltool'
- elif domain.pot_method_old.startswith('http'):
- domain.pot_method = 'url'
+ for domain in Domain.objects.exclude(pot_method_old=""):
+ if domain.pot_method_old == ":":
+ domain.pot_method = "in_repo"
+ elif domain.pot_method_old == "<gettext>":
+ domain.pot_method = "gettext"
+ elif domain.pot_method_old == "<intltool>":
+ domain.pot_method = "intltool"
+ elif domain.pot_method_old.startswith("http"):
+ domain.pot_method = "url"
domain.pot_params = domain.pot_method_old
else:
- domain.pot_method = 'shell'
+ domain.pot_method = "shell"
domain.pot_params = domain.pot_method_old
domain.save()
@@ -22,7 +22,7 @@ def migrate_pot_method(apps, schema_editor):
class Migration(migrations.Migration):
dependencies = [
- ('stats', '0010_pot_method'),
+ ("stats", "0010_pot_method"),
]
operations = [migrations.RunPython(migrate_pot_method)]
diff --git a/stats/migrations/0012_remove_domain_pot_method_old.py
b/stats/migrations/0012_remove_domain_pot_method_old.py
index 86f5d94a..427450d6 100644
--- a/stats/migrations/0012_remove_domain_pot_method_old.py
+++ b/stats/migrations/0012_remove_domain_pot_method_old.py
@@ -6,12 +6,12 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
- ('stats', '0011_migrate_pot_method'),
+ ("stats", "0011_migrate_pot_method"),
]
operations = [
migrations.RemoveField(
- model_name='domain',
- name='pot_method_old',
+ model_name="domain",
+ name="pot_method_old",
),
]
diff --git a/stats/migrations/0013_domain_layout.py b/stats/migrations/0013_domain_layout.py
index cf07eeed..28fb5e8a 100644
--- a/stats/migrations/0013_domain_layout.py
+++ b/stats/migrations/0013_domain_layout.py
@@ -4,14 +4,18 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ('stats', '0012_remove_domain_pot_method_old'),
+ ("stats", "0012_remove_domain_pot_method_old"),
]
operations = [
migrations.AddField(
- model_name='domain',
- name='layout',
- field=models.CharField(default='', help_text="UI standard is 'po/{lang}.po', doc standard is
'po/{lang}/{lang}.po'", max_length=100),
+ model_name="domain",
+ name="layout",
+ field=models.CharField(
+ default="",
+ help_text="UI standard is 'po/{lang}.po', doc standard is 'po/{lang}/{lang}.po'",
+ max_length=100,
+ ),
preserve_default=False,
),
]
diff --git a/stats/migrations/0014_migrate_dir_to_layout.py b/stats/migrations/0014_migrate_dir_to_layout.py
index 002c8645..792c58d7 100644
--- a/stats/migrations/0014_migrate_dir_to_layout.py
+++ b/stats/migrations/0014_migrate_dir_to_layout.py
@@ -5,17 +5,17 @@ def migrate_layout(apps, schema_editor):
Domain = apps.get_model("stats", "Domain")
for dom in Domain.objects.all():
# This rule is valid for 95% of cases. The rest will need manual tweak.
- if dom.dtype == 'ui':
- dom.layout = '%s/{lang}.po' % dom.directory
- elif dom.dtype == 'doc':
- dom.layout = '%s/{lang}/{lang}.po' % dom.directory
+ if dom.dtype == "ui":
+ dom.layout = "%s/{lang}.po" % dom.directory
+ elif dom.dtype == "doc":
+ dom.layout = "%s/{lang}/{lang}.po" % dom.directory
dom.save()
class Migration(migrations.Migration):
dependencies = [
- ('stats', '0013_domain_layout'),
+ ("stats", "0013_domain_layout"),
]
operations = [migrations.RunPython(migrate_layout, migrations.RunPython.noop)]
diff --git a/stats/migrations/0015_remove_domain_directory.py
b/stats/migrations/0015_remove_domain_directory.py
index 79e23a1b..09d2676e 100644
--- a/stats/migrations/0015_remove_domain_directory.py
+++ b/stats/migrations/0015_remove_domain_directory.py
@@ -4,12 +4,12 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
- ('stats', '0014_migrate_dir_to_layout'),
+ ("stats", "0014_migrate_dir_to_layout"),
]
operations = [
migrations.RemoveField(
- model_name='domain',
- name='directory',
+ model_name="domain",
+ name="directory",
),
]
diff --git a/stats/migrations/0016_removed_bugs_fields.py b/stats/migrations/0016_removed_bugs_fields.py
index 03a29708..6672e219 100644
--- a/stats/migrations/0016_removed_bugs_fields.py
+++ b/stats/migrations/0016_removed_bugs_fields.py
@@ -4,16 +4,16 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
- ('stats', '0015_remove_domain_directory'),
+ ("stats", "0015_remove_domain_directory"),
]
operations = [
migrations.RemoveField(
- model_name='module',
- name='bugs_component',
+ model_name="module",
+ name="bugs_component",
),
migrations.RemoveField(
- model_name='module',
- name='bugs_product',
+ model_name="module",
+ name="bugs_product",
),
]
diff --git a/stats/migrations/0017_pofile_path_relative.py b/stats/migrations/0017_pofile_path_relative.py
index 1a10aad3..84207ff2 100644
--- a/stats/migrations/0017_pofile_path_relative.py
+++ b/stats/migrations/0017_pofile_path_relative.py
@@ -1,4 +1,5 @@
import os
+
from django.conf import settings
from django.db import migrations
@@ -9,22 +10,21 @@ def strip_path_prefix(apps, schema_editor):
def strip_path(stat):
old_path = stat.full_po.path
- stat.full_po.path = old_path.split('/' + scratch_dir + '/')[1]
+ stat.full_po.path = old_path.split("/" + scratch_dir + "/")[1]
stat.full_po.save()
- if stat.part_po is not None and stat.part_po.path.startswith('/'):
+ if stat.part_po is not None and stat.part_po.path.startswith("/"):
old_path = stat.part_po.path
- stat.part_po.path = old_path.split('/' + scratch_dir + '/')[1]
+ stat.part_po.path = old_path.split("/" + scratch_dir + "/")[1]
stat.part_po.save()
-
-
- for stat in Statistics.objects.filter(full_po__isnull=False, full_po__path__startswith='/'):
+
+ for stat in Statistics.objects.filter(full_po__isnull=False, full_po__path__startswith="/"):
strip_path(stat)
class Migration(migrations.Migration):
dependencies = [
- ('stats', '0016_removed_bugs_fields'),
+ ("stats", "0016_removed_bugs_fields"),
]
operations = [migrations.RunPython(strip_path_prefix, migrations.RunPython.noop)]
diff --git a/stats/migrations/0018_new_jsonfields.py b/stats/migrations/0018_new_jsonfields.py
index a263addc..a142c95b 100644
--- a/stats/migrations/0018_new_jsonfields.py
+++ b/stats/migrations/0018_new_jsonfields.py
@@ -4,28 +4,28 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ('stats', '0017_pofile_path_relative'),
+ ("stats", "0017_pofile_path_relative"),
]
operations = [
migrations.RenameField(
- model_name='pofile',
- old_name='figures',
- new_name='figures_old',
+ model_name="pofile",
+ old_name="figures",
+ new_name="figures_old",
),
migrations.RenameField(
- model_name='branch',
- old_name='file_hashes',
- new_name='file_hashes_old',
+ model_name="branch",
+ old_name="file_hashes",
+ new_name="file_hashes_old",
),
migrations.AddField(
- model_name='pofile',
- name='figures',
+ model_name="pofile",
+ name="figures",
field=models.JSONField(blank=True, null=True),
),
migrations.AddField(
- model_name='branch',
- name='file_hashes',
+ model_name="branch",
+ name="file_hashes",
field=models.JSONField(blank=True, null=True, editable=False),
),
]
diff --git a/stats/migrations/0019_migrate_old_custom_files.py
b/stats/migrations/0019_migrate_old_custom_files.py
index 79207d18..415752c3 100644
--- a/stats/migrations/0019_migrate_old_custom_files.py
+++ b/stats/migrations/0019_migrate_old_custom_files.py
@@ -7,7 +7,7 @@ def migrate_figures(apps, schema_editor):
for file_ in PoFile.objects.exclude(figures_old__isnull=False):
file_.figures = file_.figures_old
file_.save()
- for branch in Branch.objects.exclude(file_hashes_old=''):
+ for branch in Branch.objects.exclude(file_hashes_old=""):
branch.file_hashes = branch.file_hashes_old
branch.save()
@@ -15,7 +15,7 @@ def migrate_figures(apps, schema_editor):
class Migration(migrations.Migration):
dependencies = [
- ('stats', '0018_new_jsonfields'),
+ ("stats", "0018_new_jsonfields"),
]
operations = [
diff --git a/stats/migrations/0020_remove_pofile_figures_old.py
b/stats/migrations/0020_remove_pofile_figures_old.py
index d9edf09f..da6ee6b8 100644
--- a/stats/migrations/0020_remove_pofile_figures_old.py
+++ b/stats/migrations/0020_remove_pofile_figures_old.py
@@ -4,16 +4,16 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
- ('stats', '0019_migrate_old_custom_files'),
+ ("stats", "0019_migrate_old_custom_files"),
]
operations = [
migrations.RemoveField(
- model_name='pofile',
- name='figures_old',
+ model_name="pofile",
+ name="figures_old",
),
migrations.RemoveField(
- model_name='branch',
- name='file_hashes_old',
+ model_name="branch",
+ name="file_hashes_old",
),
]
diff --git a/stats/migrations/0021_release_name_unique.py b/stats/migrations/0021_release_name_unique.py
index 347d7cda..c12a3ca6 100644
--- a/stats/migrations/0021_release_name_unique.py
+++ b/stats/migrations/0021_release_name_unique.py
@@ -4,13 +4,13 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ('stats', '0020_remove_pofile_figures_old'),
+ ("stats", "0020_remove_pofile_figures_old"),
]
operations = [
migrations.AlterField(
- model_name='release',
- name='name',
+ model_name="release",
+ name="name",
field=models.SlugField(max_length=20, unique=True),
),
]
diff --git a/stats/models.py b/stats/models.py
index b7eefdb7..c5af2309 100644
--- a/stats/models.py
+++ b/stats/models.py
@@ -1,8 +1,8 @@
import logging
import os
-import sys
import re
import shutil
+import sys
import threading
from collections import Counter, OrderedDict
from datetime import datetime
@@ -24,58 +24,56 @@ from django.urls import reverse
from django.utils import dateformat, timezone
from django.utils.encoding import force_str
from django.utils.functional import cached_property
-from django.utils.translation import ngettext, gettext as _, gettext_noop
+from django.utils.translation import gettext as _
+from django.utils.translation import gettext_noop, ngettext
from common.utils import is_site_admin, run_shell_command
+from languages.models import Language
+from people.models import Person
from stats import repos, signals, utils
from stats.doap import update_doap_infos
-from people.models import Person
-from languages.models import Language
logger = logging.getLogger(__name__)
# These args should be kept in sync with
# https://github.com/mesonbuild/meson/blob/master/mesonbuild/modules/i18n.py#L25
GLIB_PRESET = (
- '--keyword=_',
- '--keyword=N_',
- '--keyword=C_:1c,2',
- '--keyword=NC_:1c,2',
- '--keyword=g_dcgettext:2',
- '--keyword=g_dngettext:2,3',
- '--keyword=g_dpgettext2:2c,3',
-
- '--flag=N_:1:pass-c-format',
- '--flag=C_:2:pass-c-format',
- '--flag=NC_:2:pass-c-format',
- '--flag=g_dngettext:2:pass-c-format',
- '--flag=g_strdup_printf:1:c-format',
- '--flag=g_string_printf:2:c-format',
- '--flag=g_string_append_printf:2:c-format',
- '--flag=g_error_new:3:c-format',
- '--flag=g_set_error:4:c-format',
+ "--keyword=_",
+ "--keyword=N_",
+ "--keyword=C_:1c,2",
+ "--keyword=NC_:1c,2",
+ "--keyword=g_dcgettext:2",
+ "--keyword=g_dngettext:2,3",
+ "--keyword=g_dpgettext2:2c,3",
+ "--flag=N_:1:pass-c-format",
+ "--flag=C_:2:pass-c-format",
+ "--flag=NC_:2:pass-c-format",
+ "--flag=g_dngettext:2:pass-c-format",
+ "--flag=g_strdup_printf:1:c-format",
+ "--flag=g_string_printf:2:c-format",
+ "--flag=g_string_append_printf:2:c-format",
+ "--flag=g_error_new:3:c-format",
+ "--flag=g_set_error:4:c-format",
)
# Standard Django slug validation but also accept '+' (for gtk+)
-slug_re = re.compile(r'^[-\+a-zA-Z0-9_]+\Z')
+slug_re = re.compile(r"^[-\+a-zA-Z0-9_]+\Z")
validate_slug = RegexValidator(
- slug_re,
- "Enter a valid 'slug' consisting of letters, numbers, underscores, hyphens or plus signs.",
- 'invalid'
+ slug_re, "Enter a valid 'slug' consisting of letters, numbers, underscores, hyphens or plus signs.",
"invalid"
)
VCS_TYPE_CHOICES = (
- ('svn', 'Subversion'),
- ('git', 'Git'),
- ('hg', 'Mercurial'),
+ ("svn", "Subversion"),
+ ("git", "Git"),
+ ("hg", "Mercurial"),
)
BRANCH_HEAD_NAMES = (
- 'HEAD',
- 'trunk',
- 'main',
- 'mainline',
- 'master',
+ "HEAD",
+ "trunk",
+ "main",
+ "mainline",
+ "master",
)
@@ -85,10 +83,7 @@ class UnableToCommit(Exception):
class Module(models.Model):
name = models.CharField(max_length=50, unique=True, validators=[validate_slug])
- homepage = models.URLField(
- blank=True,
- help_text="Automatically updated if the module contains a doap file."
- )
+ homepage = models.URLField(blank=True, help_text="Automatically updated if the module contains a doap
file.")
description = models.TextField(blank=True)
comment = models.TextField(blank=True)
bugs_base = models.CharField(max_length=250, blank=True)
@@ -96,23 +91,20 @@ class Module(models.Model):
# URLField is too restrictive for vcs_root
vcs_root = models.CharField(max_length=200)
vcs_web = models.URLField()
- ext_platform = models.URLField(
- blank=True,
- help_text="URL to external translation platform, if any"
- )
+ ext_platform = models.URLField(blank=True, help_text="URL to external translation platform, if any")
archived = models.BooleanField(default=False)
maintainers = models.ManyToManyField(
Person,
- db_table='module_maintainer',
- related_name='maintains_modules',
+ db_table="module_maintainer",
+ related_name="maintains_modules",
blank=True,
- help_text="Automatically updated if the module contains a doap file."
+ help_text="Automatically updated if the module contains a doap file.",
)
class Meta:
- db_table = 'module'
- ordering = ('name',)
+ db_table = "module"
+ ordering = ("name",)
def __str__(self):
return self.name
@@ -121,11 +113,11 @@ class Module(models.Model):
return self.name < other.name
def get_absolute_url(self):
- return reverse('module', args=[self.name])
+ return reverse("module", args=[self.name])
def get_description(self):
if self.description:
- return '{name} — {description}'.format(name=self.name, description=_(self.description))
+ return "{name} — {description}".format(name=self.name, description=_(self.description))
return self.name
def get_comment(self):
@@ -137,41 +129,42 @@ class Module(models.Model):
comment,
_(
'Translations for this module are externally hosted. Please go to the <a
href="%(link)s">'
- 'external platform</a> to see how you can submit your translation.'
- ) % {'link': self.ext_platform}
+ "external platform</a> to see how you can submit your translation."
+ )
+ % {"link": self.ext_platform},
)
return comment
def has_standard_vcs(self):
- """ This function checks if the module is hosted in the standard VCS of the project """
+ """This function checks if the module is hosted in the standard VCS of the project"""
return re.search(settings.VCS_HOME_REGEX, self.vcs_root) is not None
def get_bugs_i18n_url(self, content=None):
- if 'gitlab' in self.bugs_base:
- link = utils.url_join(self.bugs_base, '?state=opened&label_name[]=8.%20Translation')
+ if "gitlab" in self.bugs_base:
+ link = utils.url_join(self.bugs_base, "?state=opened&label_name[]=8.%20Translation")
if content:
- link += "&search=%s" % content.replace(' ', '+')
+ link += "&search=%s" % content.replace(" ", "+")
return link
return None
def get_bugs_enter_url(self):
- if 'gitlab' in self.bugs_base:
- return utils.url_join(self.bugs_base, 'new')
+ if "gitlab" in self.bugs_base:
+ return utils.url_join(self.bugs_base, "new")
return self.bugs_base
def get_branches(self, reverse=False):
- """ Return module branches, in ascending order by default (descending order if reverse == True) """
+ """Return module branches, in ascending order by default (descending order if reverse == True)"""
return sorted(self.branch_set.all(), reverse=reverse)
def get_head_branch(self):
- """ Returns the HEAD (trunk, master, ...) branch of the module """
+ """Returns the HEAD (trunk, master, ...) branch of the module"""
branch = self.branch_set.filter(name__in=BRANCH_HEAD_NAMES).first()
if not branch:
branch = self.get_branches(reverse=False)[0]
return branch
def can_edit_branches(self, user):
- """ Returns True for superusers, users with adequate permissions or maintainers of the module """
+ """Returns True for superusers, users with adequate permissions or maintainers of the module"""
if is_site_admin(user) or user.username in [p.username for p in self.maintainers.all()]:
return True
return False
@@ -188,10 +181,11 @@ class Module(models.Model):
class ModuleLock:
- """ Weird things happen when multiple updates run in parallel for the same module
- We use filesystem directories creation/deletion to act as global lock mecanism
+ """Weird things happen when multiple updates run in parallel for the same module
+ We use filesystem directories creation/deletion to act as global lock mecanism
"""
- dir_prefix = 'updating-'
+
+ dir_prefix = "updating-"
def __init__(self, mod):
self.module = mod
@@ -234,7 +228,8 @@ class ModuleLock:
@total_ordering
class Branch(models.Model):
- """ Branch of a module """
+ """Branch of a module"""
+
name = models.CharField(max_length=50)
vcs_subpath = models.CharField(max_length=50, blank=True)
module = models.ForeignKey(Module, on_delete=models.CASCADE)
@@ -246,10 +241,10 @@ class Branch(models.Model):
checkout_on_creation = True
class Meta:
- db_table = 'branch'
- verbose_name_plural = 'branches'
- ordering = ('name',)
- unique_together = ('name', 'module')
+ db_table = "branch"
+ verbose_name_plural = "branches"
+ ordering = ("name",)
+ unique_together = ("name", "module")
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -262,7 +257,7 @@ class Branch(models.Model):
@property
def name_escaped(self):
"""Branch name suitable for including in file system paths."""
- return self.name.replace('/', '_')
+ return self.name.replace("/", "_")
@cached_property
def _repo(self):
@@ -287,7 +282,7 @@ class Branch(models.Model):
self._repo.rename(old_name, self.name)
if update_statistics and not self.module.archived:
# The update command is launched asynchronously in a separate thread
- upd_thread = threading.Thread(target=self.update_stats, kwargs={'force': True})
+ upd_thread = threading.Thread(target=self.update_stats, kwargs={"force": True})
upd_thread.start()
def delete(self):
@@ -298,8 +293,8 @@ class Branch(models.Model):
# Remove the repo checkout
self._repo.remove()
# Remove the pot/po generated files
- if os.access(str(self.output_dir('ui')), os.W_OK):
- shutil.rmtree(str(self.output_dir('ui')))
+ if os.access(str(self.output_dir("ui")), os.W_OK):
+ shutil.rmtree(str(self.output_dir("ui")))
def __hash__(self):
return hash(self.pk)
@@ -319,11 +314,11 @@ class Branch(models.Model):
@property
def uses_meson(self):
- return Path(self.co_path, 'meson.build').exists()
+ return Path(self.co_path, "meson.build").exists()
@property
def img_url_prefix(self):
- return 'raw' if self.module.vcs_type == 'git' else ''
+ return "raw" if self.module.vcs_type == "git" else ""
def is_head(self):
return self.name in BRANCH_HEAD_NAMES
@@ -334,8 +329,8 @@ class Branch(models.Model):
return ""
def file_changed(self, rel_path):
- """ This method determine if some file has changed based on its hash
- Always returns true if this is the first time the path is checked
+ """This method determine if some file has changed based on its hash
+ Always returns true if this is the first time the path is checked
"""
full_path = self.co_path / rel_path
if not full_path.exists():
@@ -351,18 +346,18 @@ class Branch(models.Model):
return True
def has_string_frozen(self):
- """ Returns true if the branch is contained in at least one string frozen release """
+ """Returns true if the branch is contained in at least one string frozen release"""
return bool(self.releases.filter(string_frozen=True).count())
def is_archive_only(self):
- """ Return True if the branch only appears in 'archived' releases """
+ """Return True if the branch only appears in 'archived' releases"""
return bool(self.releases.filter(weight__gt=0).count())
def is_vcs_readonly(self):
- return 'ssh://' not in self.module.vcs_root and not self.module.vcs_root.startswith('git@')
+ return "ssh://" not in self.module.vcs_root and not self.module.vcs_root.startswith("git@")
def get_vcs_web_url(self):
- if self.module.vcs_type in ('hg', 'git'):
+ if self.module.vcs_type in ("hg", "git"):
return self.module.vcs_web
if self.vcs_subpath:
return utils.url_join(self.module.vcs_web, self.vcs_subpath)
@@ -371,16 +366,16 @@ class Branch(models.Model):
return utils.url_join(self.module.vcs_web, "branches", self.name)
def get_vcs_web_log_url(self):
- """ Link to browsable commit log """
- if self.module.vcs_type == 'git':
- return utils.url_join(self.module.vcs_web, 'commits', self.name)
+ """Link to browsable commit log"""
+ if self.module.vcs_type == "git":
+ return utils.url_join(self.module.vcs_web, "commits", self.name)
# Not implemented for other VCS
return ""
@cached_property
def co_path(self):
- """ Returns the path of the local checkout for the branch """
- if self.module.vcs_type in ('hg', 'git'):
+ """Returns the path of the local checkout for the branch"""
+ if self.module.vcs_type in ("hg", "git"):
branch_dir = self.module.name
else:
branch_dir = self.module.name + "." + self.name_escaped
@@ -391,7 +386,7 @@ class Branch(models.Model):
Return all domains that this branch applies to.
"""
domains = OrderedDict()
- for dom in Domain.objects.filter(module=self.module).select_related('branch_from',
'branch_to').all():
+ for dom in Domain.objects.filter(module=self.module).select_related("branch_from",
"branch_to").all():
if self.has_domain(dom):
domains[dom.name] = dom
return domains
@@ -407,9 +402,9 @@ class Branch(models.Model):
return self.co_path / domain.base_dir
def output_dir(self, dom_type):
- """ Directory where generated pot and po files are written on local system """
- subdir = {'ui': '', 'doc': 'docs'}[dom_type]
- dirname = settings.POTDIR / f'{self.module.name}.{self.name_escaped}' / subdir
+ """Directory where generated pot and po files are written on local system"""
+ subdir = {"ui": "", "doc": "docs"}[dom_type]
+ dirname = settings.POTDIR / f"{self.module.name}.{self.name_escaped}" / subdir
dirname.mkdir(parents=True, exist_ok=True)
return dirname
@@ -423,17 +418,15 @@ class Branch(models.Model):
stats = OrderedDict()
stats_langs = {}
domain_pks = [d.pk for d in self.get_domains().values() if d.dtype == typ]
- pot_stats = Statistics.objects.select_related(
- "language", "domain", "branch", "full_po"
- ).filter(
- branch=self, language__isnull=True, domain__pk__in=domain_pks
- ).order_by('domain__name')
+ pot_stats = (
+ Statistics.objects.select_related("language", "domain", "branch", "full_po")
+ .filter(branch=self, language__isnull=True, domain__pk__in=domain_pks)
+ .order_by("domain__name")
+ )
for stat in pot_stats.all():
stats[stat.domain.name] = [stat]
stats_langs[stat.domain.name] = []
- tr_stats = Statistics.objects.select_related(
- "language", "domain", "branch", "full_po"
- ).filter(
+ tr_stats = Statistics.objects.select_related("language", "domain", "branch", "full_po").filter(
branch=self, language__isnull=False, domain__pk__in=domain_pks
)
for stat in tr_stats.all():
@@ -452,16 +445,16 @@ class Branch(models.Model):
def get_doc_stats(self, mandatory_langs=()):
if not self._doc_stats:
- self._doc_stats = self.get_stats('doc', mandatory_langs)
+ self._doc_stats = self.get_stats("doc", mandatory_langs)
return self._doc_stats
def get_ui_stats(self, mandatory_langs=()):
if not self._ui_stats:
- self._ui_stats = self.get_stats('ui', mandatory_langs)
+ self._ui_stats = self.get_stats("ui", mandatory_langs)
return self._ui_stats
def update_stats(self, force, checkout=True, domain=None):
- """ Update statistics for all po files from the branch """
+ """Update statistics for all po files from the branch"""
with ModuleLock(self.module):
checkout_errors = []
if checkout:
@@ -487,8 +480,9 @@ class Branch(models.Model):
# 2. Pre-check, if available (e.g. intltool-update -m)
# **************************
- if (dom.dtype == 'ui' and
- (dom.pot_method == 'auto' or (dom.pot_method == 'gettext' and dom.layout.count('/')
< 2))):
+ if dom.dtype == "ui" and (
+ dom.pot_method == "auto" or (dom.pot_method == "gettext" and dom.layout.count("/") < 2)
+ ):
# Run intltool-update -m to check for some errors
errors.extend(utils.check_potfiles(domain_path))
@@ -497,8 +491,8 @@ class Branch(models.Model):
potfile, errs = dom.generate_pot_file(self)
errors.extend(errs)
linguas = dom.get_linguas(self)
- if linguas['langs'] is None and linguas['error']:
- errors.append(("warn", linguas['error']))
+ if linguas["langs"] is None and linguas["error"]:
+ errors.append(("warn", linguas["error"]))
# Prepare statistics object
try:
@@ -511,15 +505,15 @@ class Branch(models.Model):
# 4. Compare with old pot files, various checks
# *****************************
- previous_pot = self.output_dir(dom.dtype) / ('%s.%s.pot' % (dom.potbase(),
self.name_escaped))
+ previous_pot = self.output_dir(dom.dtype) / ("%s.%s.pot" % (dom.potbase(),
self.name_escaped))
if not potfile:
logger.error("Can’t generate POT file for %s/%s.", self.module.name, dom.name)
if previous_pot.exists():
# Use old POT file
potfile = previous_pot
- pot_stat.set_error('error', gettext_noop("Can’t generate POT file, using old one."))
+ pot_stat.set_error("error", gettext_noop("Can’t generate POT file, using old one."))
else:
- pot_stat.set_error('error', gettext_noop("Can’t generate POT file, statistics
aborted."))
+ pot_stat.set_error("error", gettext_noop("Can’t generate POT file, statistics
aborted."))
continue
# 5. Check if pot changed
@@ -529,7 +523,7 @@ class Branch(models.Model):
if previous_pot.exists():
# Compare old and new POT
changed_status, diff = utils.pot_diff_status(previous_pot, potfile)
- if string_frozen and dom.dtype == 'ui' and changed_status ==
utils.CHANGED_WITH_ADDITIONS:
+ if string_frozen and dom.dtype == "ui" and changed_status ==
utils.CHANGED_WITH_ADDITIONS:
utils.notify_list(self, diff)
# 6. Generate pot stats and update DB
@@ -540,7 +534,7 @@ class Branch(models.Model):
try:
shutil.copyfile(str(potfile), str(previous_pot))
except Exception: # FIXME: too broad exception
- pot_stat.set_error('error', gettext_noop("Can’t copy new POT file to public
location."))
+ pot_stat.set_error("error", gettext_noop("Can’t copy new POT file to public
location."))
# Send pot_has_changed signal
if previous_pot.exists() and changed_status != utils.NOT_CHANGED:
@@ -549,24 +543,23 @@ class Branch(models.Model):
# 7. Update language po files and update DB
# *****************************************
stats_with_ext_errors = Statistics.objects.filter(
- branch=self, domain=dom, information__type__endswith='-ext'
+ branch=self, domain=dom, information__type__endswith="-ext"
)
langs_with_ext_errors = [stat.language.locale for stat in stats_with_ext_errors]
dom_langs = dom.get_lang_files(self.co_path)
for lang, pofile in dom_langs:
- outpo = self.output_dir(dom.dtype) / (
- '%s.%s.%s.po' % (dom.potbase(), self.name_escaped, lang)
- )
- if (not force and changed_status in (utils.NOT_CHANGED, utils.CHANGED_ONLY_FORMATTING)
- and outpo.exists()
- and pofile.stat().st_mtime < outpo.stat().st_mtime
- and lang not in langs_with_ext_errors):
+ outpo = self.output_dir(dom.dtype) / ("%s.%s.%s.po" % (dom.potbase(), self.name_escaped,
lang))
+ if (
+ not force
+ and changed_status in (utils.NOT_CHANGED, utils.CHANGED_ONLY_FORMATTING)
+ and outpo.exists()
+ and pofile.stat().st_mtime < outpo.stat().st_mtime
+ and lang not in langs_with_ext_errors
+ ):
continue
# msgmerge po with recent pot
- run_shell_command([
- 'msgmerge', '--previous', '-o', str(outpo), str(pofile), str(potfile)
- ])
+ run_shell_command(["msgmerge", "--previous", "-o", str(outpo), str(pofile),
str(potfile)])
# Get Statistics object
try:
@@ -589,13 +582,11 @@ class Branch(models.Model):
if not errs:
stat.update_stats(outpo)
- if linguas['langs'] is not None and lang not in linguas['langs']:
- stat.set_error('warn-ext', linguas['error'])
+ if linguas["langs"] is not None and lang not in linguas["langs"]:
+ stat.set_error("warn-ext", linguas["error"])
# Delete stats for unexisting langs
- Statistics.objects.filter(
- branch=self, domain=dom
- ).exclude(
+ Statistics.objects.filter(branch=self, domain=dom).exclude(
models.Q(language__isnull=True) | models.Q(language__locale__in=[dl[0] for dl in
dom_langs])
).delete()
# Check if doap file changed
@@ -603,7 +594,7 @@ class Branch(models.Model):
update_doap_infos(self.module)
def checkout(self):
- """ Do a checkout or an update of the VCS files """
+ """Do a checkout or an update of the VCS files"""
logger.debug("Checking “%s.%s” out to “%s”…", self.module.name, self.name, self.co_path)
self._repo.checkout()
@@ -617,7 +608,7 @@ class Branch(models.Model):
self._repo.update()
def commit_po(self, po_file, domain, language, author):
- """ Commit the file 'po_file' in the branch VCS repository """
+ """Commit the file 'po_file' in the branch VCS repository"""
with ModuleLock(self.module):
self.update_repo()
dest_full_path, existing, linguas_path = domain.commit_info(self, language)
@@ -661,73 +652,70 @@ class Branch(models.Model):
class Domain(models.Model):
- DOMAIN_TYPE_CHOICES = (
- ('ui', 'User Interface'),
- ('doc', 'Documentation')
- )
+ DOMAIN_TYPE_CHOICES = (("ui", "User Interface"), ("doc", "Documentation"))
POT_METHOD_CHOICES = (
- ('auto', 'auto detected'),
- ('gettext', 'gettext'),
- ('intltool', 'intltool'),
- ('shell', 'shell command'),
- ('url', 'URL'),
- ('in_repo', '.pot in repository'),
- ('subtitles', 'subtitles'),
+ ("auto", "auto detected"),
+ ("gettext", "gettext"),
+ ("intltool", "intltool"),
+ ("shell", "shell command"),
+ ("url", "URL"),
+ ("in_repo", ".pot in repository"),
+ ("subtitles", "subtitles"),
)
module = models.ForeignKey(Module, on_delete=models.CASCADE)
name = models.CharField(max_length=50)
description = models.TextField(blank=True)
- dtype = models.CharField(max_length=5, choices=DOMAIN_TYPE_CHOICES, default='ui')
+ dtype = models.CharField(max_length=5, choices=DOMAIN_TYPE_CHOICES, default="ui")
layout = models.CharField(
- max_length=100,
- help_text="UI standard is 'po/{lang}.po', doc standard is 'po/{lang}/{lang}.po'"
+ max_length=100, help_text="UI standard is 'po/{lang}.po', doc standard is 'po/{lang}/{lang}.po'"
)
- pot_method = models.CharField(max_length=20, choices=POT_METHOD_CHOICES, default='auto')
+ pot_method = models.CharField(max_length=20, choices=POT_METHOD_CHOICES, default="auto")
pot_params = models.CharField(
- max_length=100, blank=True,
- help_text="pot_method='url': URL, pot_method='shell': shell command, pot_method='gettext': optional
params"
+ max_length=100,
+ blank=True,
+ help_text="pot_method='url': URL, pot_method='shell': shell command, pot_method='gettext': optional
params",
)
extra_its_dirs = models.TextField(
- blank=True,
- help_text="colon-separated directories containing extra .its/.loc files for gettext"
+ blank=True, help_text="colon-separated directories containing extra .its/.loc files for gettext"
)
linguas_location = models.CharField(
- max_length=50, blank=True,
+ max_length=50,
+ blank=True,
help_text=(
"Use 'no' for no LINGUAS check, or path/to/file#variable for a non-standard location.\n"
"Leave blank for standard location (ALL_LINGUAS in LINGUAS/configure.ac/.in for UI and
DOC_LINGUAS "
"in Makefile.am for DOC)"
- )
+ ),
)
red_filter = models.TextField(
blank=True,
help_text="pogrep filter to strip po file from unprioritized strings (format: location|string,"
- " “-” for no filter)"
+ " “-” for no filter)",
)
# Allow to specify the branches to which this domain applies
- branch_from = models.ForeignKey(Branch, null=True, blank=True, on_delete=models.PROTECT,
related_name='+')
- branch_to = models.ForeignKey(Branch, null=True, blank=True, on_delete=models.PROTECT, related_name='+')
+ branch_from = models.ForeignKey(Branch, null=True, blank=True, on_delete=models.PROTECT,
related_name="+")
+ branch_to = models.ForeignKey(Branch, null=True, blank=True, on_delete=models.PROTECT, related_name="+")
class Meta:
- db_table = 'domain'
- ordering = ('-dtype', 'name')
+ db_table = "domain"
+ ordering = ("-dtype", "name")
def __str__(self):
return "%s (%s/%s)" % (self.name, self.module.name, self.get_dtype_display())
@property
def base_dir(self):
- return self.layout[:self.layout.find('{lang}')]
+ return self.layout[: self.layout.find("{lang}")]
def potbase(self):
"""
Return the name of the generated pot file, without extension.
(--default-domain for xgettext, --gettext-package for intltool)
"""
- if self.name[:2] == 'po':
+ if self.name[:2] == "po":
# e.g. replace po by gimp (for ui), or po-plugins by gimp-plugins
return self.module.name + self.name[2:]
- if self.name == 'help':
+ if self.name == "help":
return "%s-help" % self.module.name
return self.name
@@ -737,12 +725,13 @@ class Domain(models.Model):
return self.potbase()
def has_standard_location(self):
- return (self.dtype == 'ui' and self.layout == 'po/{lang}.po') or (
- self.dtype == 'doc' and self.layout == 'help/{lang}/{lang}.po')
+ return (self.dtype == "ui" and self.layout == "po/{lang}.po") or (
+ self.dtype == "doc" and self.layout == "help/{lang}/{lang}.po"
+ )
def can_build_docs(self, branch):
try:
- return self.dtype == 'doc' and self.doc_format(branch)
+ return self.dtype == "doc" and self.doc_format(branch)
except utils.UndetectableDocFormat:
return False
@@ -768,15 +757,15 @@ class Domain(models.Model):
def extract_lang(path):
path_str = str(path)
- start = len(str(base_path)) + self.layout.find('{lang}') + 1
- return re.split(r'\.|/', path_str[start:])[0]
+ start = len(str(base_path)) + self.layout.find("{lang}") + 1
+ return re.split(r"\.|/", path_str[start:])[0]
- for item in base_path.glob(self.layout.replace('{lang}', '*')):
+ for item in base_path.glob(self.layout.replace("{lang}", "*")):
flist.append((extract_lang(item), item))
return flist
def generate_pot_file(self, current_branch):
- """ Return the pot file generated (in the checkout tree), and the error if any """
+ """Return the pot file generated (in the checkout tree), and the error if any"""
vcs_path = current_branch.co_path / self.base_dir
env = None
@@ -784,10 +773,10 @@ class Domain(models.Model):
pot_command = None
errors = []
- if self.pot_method == 'auto':
- if self.dtype == 'ui':
+ if self.pot_method == "auto":
+ if self.dtype == "ui":
# Fallback is intltool, except for meson modules.
- pot_method = 'gettext' if current_branch.uses_meson else 'intltool'
+ pot_method = "gettext" if current_branch.uses_meson else "intltool"
else:
# Standard pot generation
potfile, errs = utils.generate_doc_pot_file(current_branch, self)
@@ -796,76 +785,76 @@ class Domain(models.Model):
else:
pot_method = self.pot_method
- if self.module.name == 'damned-lies':
+ if self.module.name == "damned-lies":
# Special case for d-l, pot file should be generated from running instance dir
- call_command('update-trans', 'en', verbosity=0)
- potfile = Path('./po') / 'damned-lies.pot'
+ call_command("update-trans", "en", verbosity=0)
+ potfile = Path("./po") / "damned-lies.pot"
- elif pot_method == 'url':
+ elif pot_method == "url":
# Get POT from URL and save file locally
req = request.Request(self.pot_params)
try:
handle = request.urlopen(req)
except URLError:
return "", (("error", gettext_noop("Error retrieving pot file from URL.")),)
- with potfile.open('wb') as f:
+ with potfile.open("wb") as f:
f.write(handle.read())
- elif pot_method == 'gettext':
+ elif pot_method == "gettext":
try:
pot_command, env = self.get_xgettext_command(current_branch)
except Exception as err:
errors.append(str(err))
- elif pot_method == 'intltool':
- pot_command = ['intltool-update', '-g', self.potbase(), '-p']
+ elif pot_method == "intltool":
+ pot_command = ["intltool-update", "-g", self.potbase(), "-p"]
if self.module.bugs_base:
- env = {"XGETTEXT_ARGS": "\"--msgid-bugs-address=%s\"" % self.module.bugs_base}
+ env = {"XGETTEXT_ARGS": '"--msgid-bugs-address=%s"' % self.module.bugs_base}
- elif pot_method == 'shell':
+ elif pot_method == "shell":
pot_command = self.pot_params
- elif pot_method == 'in_repo':
+ elif pot_method == "in_repo":
pass # Nothing to do, pot exists
- elif pot_method == 'subtitles':
+ elif pot_method == "subtitles":
from translate.convert import sub2po
- srt_files = [
- p for p in vcs_path.iterdir() if p.is_file() and p.name.endswith('.srt')
- ]
+
+ srt_files = [p for p in vcs_path.iterdir() if p.is_file() and p.name.endswith(".srt")]
if not srt_files:
# Try once more at parent level
- srt_files = [
- p for p in vcs_path.parent.iterdir() if p.is_file() and p.name.endswith('.srt')
- ]
+ srt_files = [p for p in vcs_path.parent.iterdir() if p.is_file() and p.name.endswith(".srt")]
if not srt_files:
return "", (("error", gettext_noop("No subtitle files found.")),)
- with srt_files[0].open(mode='r') as po_fh, potfile.open(mode='wb') as pot_fh:
+ with srt_files[0].open(mode="r") as po_fh, potfile.open(mode="wb") as pot_fh:
sub2po.convertsub(po_fh, pot_fh)
if pot_command:
status, output, errs = run_shell_command(pot_command, env=env, cwd=vcs_path)
if status != utils.STATUS_OK:
return "", (
- ("error",
- gettext_noop("Error regenerating POT file for
%(file)s:\n<pre>%(cmd)s\n%(output)s</pre>") % {
- 'file': self.potbase(),
- 'cmd': ' '.join(pot_command) if isinstance(pot_command, list) else pot_command,
- 'output': force_str(errs)
- }),
+ (
+ "error",
+ gettext_noop("Error regenerating POT file for
%(file)s:\n<pre>%(cmd)s\n%(output)s</pre>")
+ % {
+ "file": self.potbase(),
+ "cmd": " ".join(pot_command) if isinstance(pot_command, list) else pot_command,
+ "output": force_str(errs),
+ },
+ ),
)
if not potfile.exists():
# Try to get POT file from command output, with path relative to checkout root
- m = re.search(r'([\w/-]*\.pot)', output)
+ m = re.search(r"([\w/-]*\.pot)", output)
if m:
potfile = current_branch.co_path / m.group(0)
else:
# Try to find .pot file in /po dir
for file_ in vcs_path.iterdir():
- if file_.match('*.pot'):
+ if file_.match("*.pot"):
potfile = file_
break
- elif pot_method == 'gettext':
+ elif pot_method == "gettext":
# Filter out strings NOT to be translated, typically icon file names.
utils.exclude_untrans_messages(potfile)
@@ -882,53 +871,51 @@ class Domain(models.Model):
if self.pot_params:
xgettext_args.extend(self.pot_params.split())
for opt, value in [
- ('--directory', str(branch.co_path)),
- ('--from-code', 'utf-8'),
- ('--add-comments', ''),
- ('--output', f'{self.potbase()}.pot'),
+ ("--directory", str(branch.co_path)),
+ ("--from-code", "utf-8"),
+ ("--add-comments", ""),
+ ("--output", f"{self.potbase()}.pot"),
]:
if opt not in xgettext_args:
- xgettext_args.extend([opt, value] if value != '' else [opt])
- if (vcs_path / 'POTFILES.in').exists():
- xgettext_args = ['--files-from', 'POTFILES.in'] + xgettext_args
- elif (vcs_path / 'POTFILES').exists():
- xgettext_args = ['--files-from', 'POTFILES'] + xgettext_args
+ xgettext_args.extend([opt, value] if value != "" else [opt])
+ if (vcs_path / "POTFILES.in").exists():
+ xgettext_args = ["--files-from", "POTFILES.in"] + xgettext_args
+ elif (vcs_path / "POTFILES").exists():
+ xgettext_args = ["--files-from", "POTFILES"] + xgettext_args
else:
raise RuntimeError(f"No POTFILES file found in {self.base_dir}")
if not os.path.exists(utils.ITS_DATA_DIR):
utils.collect_its_data()
- env = {'GETTEXTDATADIRS': os.path.dirname(utils.ITS_DATA_DIR)}
+ env = {"GETTEXTDATADIRS": os.path.dirname(utils.ITS_DATA_DIR)}
if self.extra_its_dirs:
- env['GETTEXTDATADIRS'] = ':'.join(
- [env['GETTEXTDATADIRS']] + [str(branch.co_path / path)
- for path in self.extra_its_dirs.split(':')]
+ env["GETTEXTDATADIRS"] = ":".join(
+ [env["GETTEXTDATADIRS"]] + [str(branch.co_path / path) for path in
self.extra_its_dirs.split(":")]
)
# Parse and use content from: "XGETTEXT_OPTIONS = --keyword=_ --keyword=N_"
- makefile = utils.MakefileWrapper.find_file(branch, [vcs_path], file_name='Makevars')
+ makefile = utils.MakefileWrapper.find_file(branch, [vcs_path], file_name="Makevars")
if makefile:
- kwds_vars = makefile.read_variable('XGETTEXT_OPTIONS')
+ kwds_vars = makefile.read_variable("XGETTEXT_OPTIONS")
if kwds_vars:
xgettext_args.extend(kwds_vars.split())
else:
- makefile = utils.MakefileWrapper.find_file(branch, [vcs_path], file_name='meson.build')
+ makefile = utils.MakefileWrapper.find_file(branch, [vcs_path], file_name="meson.build")
if makefile:
- if makefile.read_variable('gettext.preset') == 'glib' or not makefile.readable:
+ if makefile.read_variable("gettext.preset") == "glib" or not makefile.readable:
xgettext_args.extend(GLIB_PRESET)
- extra_args = makefile.read_variable('gettext.args')
+ extra_args = makefile.read_variable("gettext.args")
if extra_args:
xgettext_args.extend([extra_args] if isinstance(extra_args, str) else extra_args)
- datadirs = makefile.read_variable('gettext.data_dirs')
+ datadirs = makefile.read_variable("gettext.data_dirs")
if datadirs:
- env['GETTEXTDATADIRS'] = ':'.join(
- [env['GETTEXTDATADIRS']] +
- [str(branch.co_path / path) for path in datadirs]
+ env["GETTEXTDATADIRS"] = ":".join(
+ [env["GETTEXTDATADIRS"]] + [str(branch.co_path / path) for path in datadirs]
)
# Added last as some chars in it may disturb CLI parsing
if self.module.bugs_base:
- xgettext_args.extend(['--msgid-bugs-address', self.module.bugs_base])
- return ['xgettext'] + xgettext_args, env
+ xgettext_args.extend(["--msgid-bugs-address", self.module.bugs_base])
+ return ["xgettext"] + xgettext_args, env
def commit_info(self, branch, language):
"""
@@ -945,8 +932,8 @@ class Domain(models.Model):
abs_po_path = branch.co_path / self.get_po_path(language.locale)
existing = abs_po_path.exists()
linguas = None
- if not existing and self.linguas_location != 'no':
- linguas = branch.co_path / self.base_dir / 'LINGUAS'
+ if not existing and self.linguas_location != "no":
+ linguas = branch.co_path / self.base_dir / "LINGUAS"
if not linguas.exists():
raise UnableToCommit(
_("Sorry, adding new translations when the LINGUAS file is not known is not supported.")
@@ -954,13 +941,13 @@ class Domain(models.Model):
return abs_po_path, existing, linguas
def get_linguas(self, branch):
- """ Return a linguas dict like this: {'langs':['lang1', lang2], 'error':"Error"} """
+ """Return a linguas dict like this: {'langs':['lang1', lang2], 'error':"Error"}"""
base_path = branch.co_path
if self.linguas_location:
# Custom (or no) linguas location
- if self.linguas_location == 'no':
- return {'langs': None, 'error': ''}
- elif self.linguas_location.split('/')[-1] == "LINGUAS":
+ if self.linguas_location == "no":
+ return {"langs": None, "error": ""}
+ elif self.linguas_location.split("/")[-1] == "LINGUAS":
return utils.read_linguas_file(base_path / self.linguas_location)
else:
variable = "ALL_LINGUAS"
@@ -971,26 +958,21 @@ class Domain(models.Model):
linguas_file = utils.MakefileWrapper(branch, base_path / file_path)
langs = linguas_file.read_variable(variable)
return {
- 'langs': langs.split() if langs is not None else None,
- 'error': gettext_noop(
- "Entry for this language is not present in %(var)s variable in %(file)s file." % {
- 'var': variable, 'file': file_path
- }
- )
+ "langs": langs.split() if langs is not None else None,
+ "error": gettext_noop(
+ "Entry for this language is not present in %(var)s variable in %(file)s file."
+ % {"var": variable, "file": file_path}
+ ),
}
# Standard linguas location
- if self.dtype == 'ui':
+ if self.dtype == "ui":
return utils.get_ui_linguas(branch, self.base_dir)
- if self.dtype == 'doc':
+ if self.dtype == "doc":
return utils.get_doc_linguas(branch, self.base_dir)
raise ValueError("Domain dtype should be one of 'ui', 'doc'")
-RELEASE_STATUS_CHOICES = (
- ('official', 'Official'),
- ('unofficial', 'Unofficial'),
- ('xternal', 'External')
-)
+RELEASE_STATUS_CHOICES = (("official", "Official"), ("unofficial", "Unofficial"), ("xternal", "External"))
class Release(models.Model):
@@ -1000,11 +982,11 @@ class Release(models.Model):
status = models.CharField(max_length=12, choices=RELEASE_STATUS_CHOICES)
# weight is used to sort releases, higher on top, below 0 in archives
weight = models.IntegerField(default=0)
- branches = models.ManyToManyField(Branch, through='Category', related_name='releases')
+ branches = models.ManyToManyField(Branch, through="Category", related_name="releases")
class Meta:
- db_table = 'release'
- ordering = ('status', '-name')
+ db_table = "release"
+ ordering = ("status", "-name")
def __str__(self):
return self.description
@@ -1016,97 +998,98 @@ class Release(models.Model):
def excluded_domains(self):
# Compute domains which doesn't apply for this release due to limited domain
# (by branch_from/branch_to).
- limited_stats = Statistics.objects.select_related(
- 'branch', 'domain'
- ).filter(
- branch__releases=self
- ).filter(
- language__isnull=True
- ).filter(models.Q(domain__branch_from__isnull=False) | models.Q(domain__branch_to__isnull=False))
+ limited_stats = (
+ Statistics.objects.select_related("branch", "domain")
+ .filter(branch__releases=self)
+ .filter(language__isnull=True)
+ .filter(models.Q(domain__branch_from__isnull=False) | models.Q(domain__branch_to__isnull=False))
+ )
return {st.domain for st in limited_stats if not st.branch.has_domain(st.domain)}
@classmethod
def total_by_releases(cls, dtype, releases):
- """ Get summary stats for all languages and 'releases', and return a 'stats' dict with
- each language locale as the key:
- stats{
- 'll': {'lang': <language object>,
- 'stats': [percentage for release 1, percentage for release 2, ...],
- 'diff': difference in % between first and last release,
- }
- 'll': ...
- }
+ """Get summary stats for all languages and 'releases', and return a 'stats' dict with
+ each language locale as the key:
+ stats{
+ 'll': {'lang': <language object>,
+ 'stats': [percentage for release 1, percentage for release 2, ...],
+ 'diff': difference in % between first and last release,
+ }
+ 'll': ...
+ }
"""
stats = {}
totals = [0] * len(releases)
lang_dict = dict((lang.locale, lang) for lang in Language.objects.all())
for rel in releases:
- query = Statistics.objects.filter(
- domain__dtype=dtype, branch__releases=rel
- ).exclude(
- domain__in=rel.excluded_domains
- ).values(
- 'language__locale'
- ).annotate(
- trans=models.Sum('full_po__translated'), fuzzy=models.Sum('full_po__fuzzy'),
- untrans=models.Sum('full_po__untranslated')
- ).order_by('language__name')
+ query = (
+ Statistics.objects.filter(domain__dtype=dtype, branch__releases=rel)
+ .exclude(domain__in=rel.excluded_domains)
+ .values("language__locale")
+ .annotate(
+ trans=models.Sum("full_po__translated"),
+ fuzzy=models.Sum("full_po__fuzzy"),
+ untrans=models.Sum("full_po__untranslated"),
+ )
+ .order_by("language__name")
+ )
for line in query:
- locale = line['language__locale']
+ locale = line["language__locale"]
if locale and locale not in stats:
- stats[locale] = {
- 'lang': lang_dict[locale],
- 'stats': [0] * len(releases)
- }
+ stats[locale] = {"lang": lang_dict[locale], "stats": [0] * len(releases)}
if locale is None: # POT stats
- totals[releases.index(rel)] = line['untrans']
+ totals[releases.index(rel)] = line["untrans"]
else:
- stats[locale]['stats'][releases.index(rel)] = line['trans']
+ stats[locale]["stats"][releases.index(rel)] = line["trans"]
# Compute percentages
def compute_percentage(x, y):
return int(x / y * 100)
for k in stats:
- stats[k]['stats'] = list(map(compute_percentage, stats[k]['stats'], totals))
- stats[k]['diff'] = stats[k]['stats'][-1] - stats[k]['stats'][0]
+ stats[k]["stats"] = list(map(compute_percentage, stats[k]["stats"], totals))
+ stats[k]["diff"] = stats[k]["stats"][-1] - stats[k]["stats"][0]
return stats
def total_strings(self):
- """ Returns the total number of strings in the release as a tuple (doc_total, ui_total) """
+ """Returns the total number of strings in the release as a tuple (doc_total, ui_total)"""
# Use pot stats to compute total sum
- qs = Statistics.objects.filter(
- branch__category__release=self, language__isnull=True
- ).exclude(
- domain__in=self.excluded_domains
- ).values(
- 'domain__dtype'
- ).annotate(untrans=models.Sum('full_po__untranslated'))
+ qs = (
+ Statistics.objects.filter(branch__category__release=self, language__isnull=True)
+ .exclude(domain__in=self.excluded_domains)
+ .values("domain__dtype")
+ .annotate(untrans=models.Sum("full_po__untranslated"))
+ )
totals = Counter()
for line in qs:
- totals[line['domain__dtype']] += line['untrans']
- return totals['doc'], totals['ui']
+ totals[line["domain__dtype"]] += line["untrans"]
+ return totals["doc"], totals["ui"]
def total_part_for_all_langs(self):
- """ Return total partial UI strings for each language """
+ """Return total partial UI strings for each language"""
total_part_ui_strings = {}
- all_ui_pots = Statistics.objects.select_related(
- 'part_po'
- ).exclude(
- domain__in=self.excluded_domains
- ).filter(language__isnull=True, branch__releases=self, domain__dtype='ui')
- all_ui_stats = Statistics.objects.select_related(
- 'part_po', 'language'
- ).exclude(
- domain__in=self.excluded_domains
- ).filter(
- language__isnull=False, branch__releases=self, domain__dtype='ui'
- ).values('branch_id', 'domain_id', 'language__locale', 'part_po__translated',
- 'part_po__fuzzy', 'part_po__untranslated')
+ all_ui_pots = (
+ Statistics.objects.select_related("part_po")
+ .exclude(domain__in=self.excluded_domains)
+ .filter(language__isnull=True, branch__releases=self, domain__dtype="ui")
+ )
+ all_ui_stats = (
+ Statistics.objects.select_related("part_po", "language")
+ .exclude(domain__in=self.excluded_domains)
+ .filter(language__isnull=False, branch__releases=self, domain__dtype="ui")
+ .values(
+ "branch_id",
+ "domain_id",
+ "language__locale",
+ "part_po__translated",
+ "part_po__fuzzy",
+ "part_po__untranslated",
+ )
+ )
stats_d = {
- f"{st['branch_id']}-{st['domain_id']}-{st['language__locale']}": sum([
- st['part_po__translated'] or 0, st['part_po__fuzzy'] or 0, st['part_po__untranslated'] or 0
- ])
+ f"{st['branch_id']}-{st['domain_id']}-{st['language__locale']}": sum(
+ [st["part_po__translated"] or 0, st["part_po__fuzzy"] or 0, st["part_po__untranslated"] or 0]
+ )
for st in all_ui_stats
}
for lang in Language.objects.all():
@@ -1114,29 +1097,34 @@ class Release(models.Model):
return total_part_ui_strings
def total_part_for_lang(self, lang, all_pots=None, all_stats_d=None):
- """ For partial UI stats, the total number can differ from lang to lang, so we
- are bound to iterate each stats to sum it """
+ """For partial UI stats, the total number can differ from lang to lang, so we
+ are bound to iterate each stats to sum it"""
if all_pots is None:
- all_pots = Statistics.objects.select_related(
- 'part_po'
- ).exclude(
- domain__in=self.excluded_domains
- ).filter(language__isnull=True, branch__releases=self, domain__dtype='ui')
+ all_pots = (
+ Statistics.objects.select_related("part_po")
+ .exclude(domain__in=self.excluded_domains)
+ .filter(language__isnull=True, branch__releases=self, domain__dtype="ui")
+ )
if all_stats_d is None:
- all_stats = Statistics.objects.select_related(
- 'part_po', 'language'
- ).exclude(
- domain__in=self.excluded_domains
- ).filter(
- language=lang, branch__releases=self, domain__dtype='ui'
- ).values(
- 'branch_id', 'domain_id', 'language__locale', 'part_po__translated',
- 'part_po__fuzzy', 'part_po__untranslated'
+ all_stats = (
+ Statistics.objects.select_related("part_po", "language")
+ .exclude(domain__in=self.excluded_domains)
+ .filter(language=lang, branch__releases=self, domain__dtype="ui")
+ .values(
+ "branch_id",
+ "domain_id",
+ "language__locale",
+ "part_po__translated",
+ "part_po__fuzzy",
+ "part_po__untranslated",
+ )
)
all_stats_d = {
- "%d-%d-%s" % (st['branch_id'], st['domain_id'], st['language__locale']): sum(filter(
- None, [st['part_po__translated'], st['part_po__fuzzy'], st['part_po__untranslated']]
- )) for st in all_stats
+ "%d-%d-%s"
+ % (st["branch_id"], st["domain_id"], st["language__locale"]): sum(
+ filter(None, [st["part_po__translated"], st["part_po__fuzzy"],
st["part_po__untranslated"]])
+ )
+ for st in all_stats
}
total = 0
for stat in all_pots:
@@ -1145,164 +1133,192 @@ class Release(models.Model):
return total
def total_for_lang(self, lang):
- """ Returns total translated/fuzzy/untranslated strings for a specific
- language """
+ """Returns total translated/fuzzy/untranslated strings for a specific
+ language"""
total_doc, total_ui = self.total_strings()
total_ui_part = self.total_part_for_lang(lang)
- query = Statistics.objects.filter(
- language=lang, branch__releases=self
- ).exclude(
- domain__in=self.excluded_domains
- ).values(
- 'domain__dtype'
- ).annotate(
- trans=Coalesce(models.Sum('full_po__translated'), models.Value(0)),
- fuzzy=Coalesce(models.Sum('full_po__fuzzy'), models.Value(0)),
- trans_p=Coalesce(models.Sum('part_po__translated'), models.Value(0)),
- fuzzy_p=Coalesce(models.Sum('part_po__fuzzy'), models.Value(0))
+ query = (
+ Statistics.objects.filter(language=lang, branch__releases=self)
+ .exclude(domain__in=self.excluded_domains)
+ .values("domain__dtype")
+ .annotate(
+ trans=Coalesce(models.Sum("full_po__translated"), models.Value(0)),
+ fuzzy=Coalesce(models.Sum("full_po__fuzzy"), models.Value(0)),
+ trans_p=Coalesce(models.Sum("part_po__translated"), models.Value(0)),
+ fuzzy_p=Coalesce(models.Sum("part_po__fuzzy"), models.Value(0)),
+ )
)
stats = {
- 'id': self.id, 'name': self.name, 'description': _(self.description),
- 'ui': {
- 'translated': 0, 'fuzzy': 0, 'total': total_ui,
- 'translated_perc': 0, 'fuzzy_perc': 0, 'untranslated_perc': 0,
+ "id": self.id,
+ "name": self.name,
+ "description": _(self.description),
+ "ui": {
+ "translated": 0,
+ "fuzzy": 0,
+ "total": total_ui,
+ "translated_perc": 0,
+ "fuzzy_perc": 0,
+ "untranslated_perc": 0,
},
- 'ui_part': {
- 'translated': 0, 'fuzzy': 0, 'total': total_ui_part,
- 'translated_perc': 0, 'fuzzy_perc': 0, 'untranslated_perc': 0,
+ "ui_part": {
+ "translated": 0,
+ "fuzzy": 0,
+ "total": total_ui_part,
+ "translated_perc": 0,
+ "fuzzy_perc": 0,
+ "untranslated_perc": 0,
},
- 'doc': {
- 'translated': 0, 'fuzzy': 0, 'total': total_doc,
- 'translated_perc': 0, 'fuzzy_perc': 0, 'untranslated_perc': 0
+ "doc": {
+ "translated": 0,
+ "fuzzy": 0,
+ "total": total_doc,
+ "translated_perc": 0,
+ "fuzzy_perc": 0,
+ "untranslated_perc": 0,
},
}
for res in query:
- if res['domain__dtype'] == 'ui':
- stats['ui']['translated'] = res['trans']
- stats['ui']['fuzzy'] = res['fuzzy']
- stats['ui_part']['translated'] = res['trans_p']
- stats['ui_part']['fuzzy'] = res['fuzzy_p']
- if res['domain__dtype'] == 'doc':
- stats['doc']['translated'] = res['trans']
- stats['doc']['fuzzy'] = res['fuzzy']
- stats['ui']['untranslated'] = total_ui - (stats['ui']['translated'] + stats['ui']['fuzzy'])
- stats['ui_part']['untranslated'] = total_ui_part - (stats['ui_part']['translated'] +
stats['ui_part']['fuzzy'])
+ if res["domain__dtype"] == "ui":
+ stats["ui"]["translated"] = res["trans"]
+ stats["ui"]["fuzzy"] = res["fuzzy"]
+ stats["ui_part"]["translated"] = res["trans_p"]
+ stats["ui_part"]["fuzzy"] = res["fuzzy_p"]
+ if res["domain__dtype"] == "doc":
+ stats["doc"]["translated"] = res["trans"]
+ stats["doc"]["fuzzy"] = res["fuzzy"]
+ stats["ui"]["untranslated"] = total_ui - (stats["ui"]["translated"] + stats["ui"]["fuzzy"])
+ stats["ui_part"]["untranslated"] = total_ui_part - (stats["ui_part"]["translated"] +
stats["ui_part"]["fuzzy"])
if total_ui > 0:
- stats['ui']['translated_perc'] = int(100 * stats['ui']['translated'] / total_ui)
- stats['ui']['fuzzy_perc'] = int(100 * stats['ui']['fuzzy'] / total_ui)
- stats['ui']['untranslated_perc'] = int(100 * stats['ui']['untranslated'] / total_ui)
+ stats["ui"]["translated_perc"] = int(100 * stats["ui"]["translated"] / total_ui)
+ stats["ui"]["fuzzy_perc"] = int(100 * stats["ui"]["fuzzy"] / total_ui)
+ stats["ui"]["untranslated_perc"] = int(100 * stats["ui"]["untranslated"] / total_ui)
if total_ui_part > 0:
- stats['ui_part']['translated_perc'] = int(100 * stats['ui_part']['translated'] / total_ui_part)
- stats['ui_part']['fuzzy_perc'] = int(100 * stats['ui_part']['fuzzy'] / total_ui_part)
- stats['ui_part']['untranslated_perc'] = int(100 * stats['ui_part']['untranslated'] /
total_ui_part)
- stats['doc']['untranslated'] = total_doc - (stats['doc']['translated'] + stats['doc']['fuzzy'])
+ stats["ui_part"]["translated_perc"] = int(100 * stats["ui_part"]["translated"] / total_ui_part)
+ stats["ui_part"]["fuzzy_perc"] = int(100 * stats["ui_part"]["fuzzy"] / total_ui_part)
+ stats["ui_part"]["untranslated_perc"] = int(100 * stats["ui_part"]["untranslated"] /
total_ui_part)
+ stats["doc"]["untranslated"] = total_doc - (stats["doc"]["translated"] + stats["doc"]["fuzzy"])
if total_doc > 0:
- stats['doc']['translated_perc'] = int(100 * stats['doc']['translated'] / total_doc)
- stats['doc']['fuzzy_perc'] = int(100 * stats['doc']['fuzzy'] / total_doc)
- stats['doc']['untranslated_perc'] = int(100 * stats['doc']['untranslated'] / total_doc)
+ stats["doc"]["translated_perc"] = int(100 * stats["doc"]["translated"] / total_doc)
+ stats["doc"]["fuzzy_perc"] = int(100 * stats["doc"]["fuzzy"] / total_doc)
+ stats["doc"]["untranslated_perc"] = int(100 * stats["doc"]["untranslated"] / total_doc)
return stats
def get_global_stats(self):
- """ Get statistics for all languages in a release, grouped by language
- Returns a sorted list: (language name and locale, ui, ui-part and doc stats dictionaries) """
-
- query = Statistics.objects.filter(
- language__isnull=False, branch__releases=self
- ).exclude(
- domain__in=self.excluded_domains
- ).values(
- 'domain__dtype', 'language'
- ).annotate(
- trans=Coalesce(models.Sum('full_po__translated'), models.Value(0)),
- fuzzy=Coalesce(models.Sum('full_po__fuzzy'), models.Value(0)),
- trans_p=Coalesce(models.Sum('part_po__translated'), models.Value(0)),
- fuzzy_p=Coalesce(models.Sum('part_po__fuzzy'), models.Value(0)),
- locale=models.Min('language__locale'), lang_name=models.Min('language__name')
- ).order_by('domain__dtype', 'trans')
+ """Get statistics for all languages in a release, grouped by language
+ Returns a sorted list: (language name and locale, ui, ui-part and doc stats dictionaries)"""
+
+ query = (
+ Statistics.objects.filter(language__isnull=False, branch__releases=self)
+ .exclude(domain__in=self.excluded_domains)
+ .values("domain__dtype", "language")
+ .annotate(
+ trans=Coalesce(models.Sum("full_po__translated"), models.Value(0)),
+ fuzzy=Coalesce(models.Sum("full_po__fuzzy"), models.Value(0)),
+ trans_p=Coalesce(models.Sum("part_po__translated"), models.Value(0)),
+ fuzzy_p=Coalesce(models.Sum("part_po__fuzzy"), models.Value(0)),
+ locale=models.Min("language__locale"),
+ lang_name=models.Min("language__name"),
+ )
+ .order_by("domain__dtype", "trans")
+ )
stats = {}
total_docstrings, total_uistrings = self.total_strings()
total_uistrings_part = self.total_part_for_all_langs()
for row in query:
- locale = row['locale']
+ locale = row["locale"]
if locale not in stats:
# Initialize stats dict
stats[locale] = {
- 'lang_name': row['lang_name'], 'lang_locale': locale,
- 'ui': {
- 'translated': 0, 'fuzzy': 0, 'untranslated': total_uistrings,
- 'translated_perc': 0, 'fuzzy_perc': 0, 'untranslated_perc': 100
+ "lang_name": row["lang_name"],
+ "lang_locale": locale,
+ "ui": {
+ "translated": 0,
+ "fuzzy": 0,
+ "untranslated": total_uistrings,
+ "translated_perc": 0,
+ "fuzzy_perc": 0,
+ "untranslated_perc": 100,
},
- 'ui_part': {
- 'translated': 0, 'fuzzy': 0, 'untranslated': total_uistrings_part[locale],
- 'translated_perc': 0, 'fuzzy_perc': 0, 'untranslated_perc': 100
+ "ui_part": {
+ "translated": 0,
+ "fuzzy": 0,
+ "untranslated": total_uistrings_part[locale],
+ "translated_perc": 0,
+ "fuzzy_perc": 0,
+ "untranslated_perc": 100,
},
- 'doc': {
- 'translated': 0, 'fuzzy': 0, 'untranslated': total_docstrings,
- 'translated_perc': 0, 'fuzzy_perc': 0, 'untranslated_perc': 100
+ "doc": {
+ "translated": 0,
+ "fuzzy": 0,
+ "untranslated": total_docstrings,
+ "translated_perc": 0,
+ "fuzzy_perc": 0,
+ "untranslated_perc": 100,
},
}
- if row['domain__dtype'] == 'doc':
- stats[locale]['doc']['translated'] = row['trans']
- stats[locale]['doc']['fuzzy'] = row['fuzzy']
- stats[locale]['doc']['untranslated'] = total_docstrings - (row['trans'] + row['fuzzy'])
+ if row["domain__dtype"] == "doc":
+ stats[locale]["doc"]["translated"] = row["trans"]
+ stats[locale]["doc"]["fuzzy"] = row["fuzzy"]
+ stats[locale]["doc"]["untranslated"] = total_docstrings - (row["trans"] + row["fuzzy"])
if total_docstrings > 0:
- stats[locale]['doc']['translated_perc'] = int(100 * row['trans'] / total_docstrings)
- stats[locale]['doc']['fuzzy_perc'] = int(100 * row['fuzzy'] / total_docstrings)
- stats[locale]['doc']['untranslated_perc'] = int(
- 100 * stats[locale]['doc']['untranslated'] / total_docstrings
+ stats[locale]["doc"]["translated_perc"] = int(100 * row["trans"] / total_docstrings)
+ stats[locale]["doc"]["fuzzy_perc"] = int(100 * row["fuzzy"] / total_docstrings)
+ stats[locale]["doc"]["untranslated_perc"] = int(
+ 100 * stats[locale]["doc"]["untranslated"] / total_docstrings
)
- if row['domain__dtype'] == 'ui':
- stats[locale]['ui']['translated'] = row['trans']
- stats[locale]['ui']['fuzzy'] = row['fuzzy']
- stats[locale]['ui']['untranslated'] = total_uistrings - (row['trans'] + row['fuzzy'])
- stats[locale]['ui_part']['translated'] = row['trans_p']
- stats[locale]['ui_part']['fuzzy'] = row['fuzzy_p']
- stats[locale]['ui_part']['untranslated'] = total_uistrings_part[locale] - (
- row['trans_p'] + row['fuzzy_p']
+ if row["domain__dtype"] == "ui":
+ stats[locale]["ui"]["translated"] = row["trans"]
+ stats[locale]["ui"]["fuzzy"] = row["fuzzy"]
+ stats[locale]["ui"]["untranslated"] = total_uistrings - (row["trans"] + row["fuzzy"])
+ stats[locale]["ui_part"]["translated"] = row["trans_p"]
+ stats[locale]["ui_part"]["fuzzy"] = row["fuzzy_p"]
+ stats[locale]["ui_part"]["untranslated"] = total_uistrings_part[locale] - (
+ row["trans_p"] + row["fuzzy_p"]
)
if total_uistrings > 0:
- stats[locale]['ui']['translated_perc'] = int(100 * row['trans'] / total_uistrings)
- stats[locale]['ui']['fuzzy_perc'] = int(100 * row['fuzzy'] / total_uistrings)
- stats[locale]['ui']['untranslated_perc'] = int(
- 100 * stats[locale]['ui']['untranslated'] / total_uistrings)
- if total_uistrings_part.get(locale, 0) > 0:
- stats[locale]['ui_part']['translated_perc'] = int(
- 100 * row['trans_p'] / total_uistrings_part[locale]
+ stats[locale]["ui"]["translated_perc"] = int(100 * row["trans"] / total_uistrings)
+ stats[locale]["ui"]["fuzzy_perc"] = int(100 * row["fuzzy"] / total_uistrings)
+ stats[locale]["ui"]["untranslated_perc"] = int(
+ 100 * stats[locale]["ui"]["untranslated"] / total_uistrings
)
- stats[locale]['ui_part']['fuzzy_perc'] = int(
- 100 * row['fuzzy_p'] / total_uistrings_part[locale]
+ if total_uistrings_part.get(locale, 0) > 0:
+ stats[locale]["ui_part"]["translated_perc"] = int(
+ 100 * row["trans_p"] / total_uistrings_part[locale]
)
- stats[locale]['ui_part']['untranslated_perc'] = int(
- 100 * stats[locale]['ui_part']['untranslated'] / total_uistrings_part[locale]
+ stats[locale]["ui_part"]["fuzzy_perc"] = int(100 * row["fuzzy_p"] /
total_uistrings_part[locale])
+ stats[locale]["ui_part"]["untranslated_perc"] = int(
+ 100 * stats[locale]["ui_part"]["untranslated"] / total_uistrings_part[locale]
)
results = list(stats.values())
# Sort by most translated first
- results.sort(key=lambda st: (-st['ui']['translated'], -st['doc']['translated'], st['lang_name']))
+ results.sort(key=lambda st: (-st["ui"]["translated"], -st["doc"]["translated"], st["lang_name"]))
return results
def get_lang_stats(self, lang):
- """ Get statistics for a specific language, producing the stats data structure
- Used for displaying the language-release template """
+ """Get statistics for a specific language, producing the stats data structure
+ Used for displaying the language-release template"""
stats = {
- 'doc': Statistics.get_lang_stats_by_type(lang, 'doc', self),
- 'ui': Statistics.get_lang_stats_by_type(lang, 'ui', self),
+ "doc": Statistics.get_lang_stats_by_type(lang, "doc", self),
+ "ui": Statistics.get_lang_stats_by_type(lang, "ui", self),
}
return stats
def get_lang_files(self, lang, dtype):
- """ Return a list of all po files of a lang for this release, preceded by the more recent
modification date
- It uses the POT file if there is no po for a module """
+ """Return a list of all po files of a lang for this release, preceded by the more recent
modification date
+ It uses the POT file if there is no po for a module"""
partial = False
if dtype == "ui-part":
dtype, partial = "ui", True
- pot_stats = Statistics.objects.exclude(
- domain__in=self.excluded_domains
- ).filter(language=None, branch__releases=self, domain__dtype=dtype, full_po__isnull=False)
- po_stats = {f"{st.branch_id}-{st.domain_id}": st
- for st in Statistics.objects.filter(language=lang, branch__releases=self,
domain__dtype=dtype)}
+ pot_stats = Statistics.objects.exclude(domain__in=self.excluded_domains).filter(
+ language=None, branch__releases=self, domain__dtype=dtype, full_po__isnull=False
+ )
+ po_stats = {
+ f"{st.branch_id}-{st.domain_id}": st
+ for st in Statistics.objects.filter(language=lang, branch__releases=self, domain__dtype=dtype)
+ }
lang_files = []
last_modif_date = datetime(1970, 1, 1, tzinfo=timezone.utc)
# Create list of files
@@ -1321,7 +1337,7 @@ class CategoryName(models.Model):
name = models.CharField(max_length=30, unique=True)
class Meta:
- db_table = 'categoryname'
+ db_table = "categoryname"
def __str__(self):
return self.name
@@ -1333,9 +1349,9 @@ class Category(models.Model):
name = models.ForeignKey(CategoryName, on_delete=models.PROTECT)
class Meta:
- db_table = 'category'
- verbose_name_plural = 'categories'
- unique_together = ('release', 'branch')
+ db_table = "category"
+ verbose_name_plural = "categories"
+ unique_together = ("release", "branch")
def __str__(self):
return "%s (%s, %s)" % (self.name, self.release, self.branch)
@@ -1356,7 +1372,7 @@ class PoFile(models.Model):
untranslated_words = models.IntegerField(default=0)
class Meta:
- db_table = 'pofile'
+ db_table = "pofile"
def __str__(self):
return "%s (%s/%s/%s)" % (self.path, self.translated, self.fuzzy, self.untranslated)
@@ -1371,7 +1387,7 @@ class PoFile(models.Model):
@property
def full_path(self):
- return self.prefix / self.path.lstrip('/')
+ return self.prefix / self.path.lstrip("/")
def filename(self):
return os.path.basename(self.path)
@@ -1382,7 +1398,7 @@ class PoFile(models.Model):
return self.translated + self.fuzzy + self.untranslated
def fig_count(self):
- """ If stat of a document type, get the number of figures in the document """
+ """If stat of a document type, get the number of figures in the document"""
return len(self.figures) if self.figures else 0
def tr_percentage(self):
@@ -1423,13 +1439,13 @@ class PoFile(models.Model):
def update_stats(self):
stats = utils.po_file_stats(Path(self.full_path))
- self.translated = stats['translated']
- self.fuzzy = stats['fuzzy']
- self.untranslated = stats['untranslated']
+ self.translated = stats["translated"]
+ self.fuzzy = stats["fuzzy"]
+ self.untranslated = stats["untranslated"]
- self.translated_words = stats['translated_words']
- self.fuzzy_words = stats['fuzzy_words']
- self.untranslated_words = stats['untranslated_words']
+ self.translated_words = stats["translated_words"]
+ self.fuzzy_words = stats["fuzzy_words"]
+ self.untranslated_words = stats["untranslated_words"]
self.save()
@@ -1439,14 +1455,14 @@ class Statistics(models.Model):
domain = models.ForeignKey(Domain, on_delete=models.CASCADE)
language = models.ForeignKey(Language, null=True, on_delete=models.CASCADE)
- full_po = models.OneToOneField(PoFile, null=True, related_name='stat_f', on_delete=models.SET_NULL)
- part_po = models.OneToOneField(PoFile, null=True, related_name='stat_p', on_delete=models.SET_NULL)
+ full_po = models.OneToOneField(PoFile, null=True, related_name="stat_f", on_delete=models.SET_NULL)
+ part_po = models.OneToOneField(PoFile, null=True, related_name="stat_p", on_delete=models.SET_NULL)
class Meta:
- db_table = 'statistics'
+ db_table = "statistics"
verbose_name = "statistics"
verbose_name_plural = verbose_name
- unique_together = ('branch', 'domain', 'language')
+ unique_together = ("branch", "domain", "language")
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -1454,27 +1470,32 @@ class Statistics(models.Model):
self.info_list = []
def __str__(self):
- """ String representation of the object """
- return "%s (%s-%s) %s (%s)" % (self.branch.module.name, self.domain.dtype, self.domain.name,
- self.branch.name, self.get_lang())
+ """String representation of the object"""
+ return "%s (%s-%s) %s (%s)" % (
+ self.branch.module.name,
+ self.domain.dtype,
+ self.domain.name,
+ self.branch.name,
+ self.get_lang(),
+ )
- def translated(self, scope='full'):
- return getattr(self.part_po if scope == 'part' else self.full_po, 'translated', 0)
+ def translated(self, scope="full"):
+ return getattr(self.part_po if scope == "part" else self.full_po, "translated", 0)
- def translated_words(self, scope='full'):
- return getattr(self.part_po if scope == 'part' else self.full_po, 'translated_words', 0)
+ def translated_words(self, scope="full"):
+ return getattr(self.part_po if scope == "part" else self.full_po, "translated_words", 0)
- def fuzzy(self, scope='full'):
- return getattr(self.part_po if scope == 'part' else self.full_po, 'fuzzy', 0)
+ def fuzzy(self, scope="full"):
+ return getattr(self.part_po if scope == "part" else self.full_po, "fuzzy", 0)
- def fuzzy_words(self, scope='full'):
- return getattr(self.part_po if scope == 'part' else self.full_po, 'fuzzy_words', 0)
+ def fuzzy_words(self, scope="full"):
+ return getattr(self.part_po if scope == "part" else self.full_po, "fuzzy_words", 0)
- def untranslated(self, scope='full'):
- return getattr(self.part_po if scope == 'part' else self.full_po, 'untranslated', 0)
+ def untranslated(self, scope="full"):
+ return getattr(self.part_po if scope == "part" else self.full_po, "untranslated", 0)
- def untranslated_words(self, scope='full'):
- return getattr(self.part_po if scope == 'part' else self.full_po, 'untranslated_words', 0)
+ def untranslated_words(self, scope="full"):
+ return getattr(self.part_po if scope == "part" else self.full_po, "untranslated_words", 0)
def is_fake(self):
return False
@@ -1483,58 +1504,58 @@ class Statistics(models.Model):
return self.language_id is None
def get_type(self):
- """ Returns the type of the domain (ui, docbook, mallard) """
+ """Returns the type of the domain (ui, docbook, mallard)"""
if self.domain.dtype == "ui":
return "ui"
return self.domain.doc_format(self.branch).format
- def tr_percentage(self, scope='full'):
- if scope == 'full' and self.full_po:
+ def tr_percentage(self, scope="full"):
+ if scope == "full" and self.full_po:
return self.full_po.tr_percentage()
- if scope == 'part' and self.part_po:
+ if scope == "part" and self.part_po:
return self.part_po.tr_percentage()
return 0
- def tr_word_percentage(self, scope='full'):
- if scope == 'full' and self.full_po:
+ def tr_word_percentage(self, scope="full"):
+ if scope == "full" and self.full_po:
return self.full_po.tr_word_percentage()
- if scope == 'part' and self.part_po:
+ if scope == "part" and self.part_po:
return self.part_po.tr_word_percentage()
return 0
- def fu_percentage(self, scope='full'):
- if scope == 'full' and self.full_po:
+ def fu_percentage(self, scope="full"):
+ if scope == "full" and self.full_po:
return self.full_po.fu_percentage()
- if scope == 'part' and self.part_po:
+ if scope == "part" and self.part_po:
return self.part_po.fu_percentage()
return 0
- def fu_word_percentage(self, scope='full'):
- if scope == 'full' and self.full_po:
+ def fu_word_percentage(self, scope="full"):
+ if scope == "full" and self.full_po:
return self.full_po.fu_word_percentage()
- if scope == 'part' and self.part_po:
+ if scope == "part" and self.part_po:
return self.part_po.fu_word_percentage()
return 0
- def un_percentage(self, scope='full'):
- if scope == 'full' and self.full_po:
+ def un_percentage(self, scope="full"):
+ if scope == "full" and self.full_po:
return self.full_po.un_percentage()
- if scope == 'part' and self.part_po:
+ if scope == "part" and self.part_po:
return self.part_po.un_percentage()
return 0
- def un_word_percentage(self, scope='full'):
- if scope == 'full' and self.full_po:
+ def un_word_percentage(self, scope="full"):
+ if scope == "full" and self.full_po:
return self.full_po.un_word_percentage()
- if scope == 'part' and self.part_po:
+ if scope == "part" and self.part_po:
return self.part_po.un_word_percentage()
return 0
def get_lang(self):
if not self.is_pot_stats():
return _("%(lang_name)s (%(lang_locale)s)") % {
- 'lang_name': _(self.language.name),
- 'lang_locale': self.language.locale
+ "lang_name": _(self.language.name),
+ "lang_locale": self.language.locale,
}
return "pot file"
@@ -1552,7 +1573,10 @@ class Statistics(models.Model):
def filename(self, potfile=False, reduced=False):
if not self.is_pot_stats() and not potfile:
return "%s.%s.%s.%spo" % (
- self.domain.potbase(), self.branch.name_escaped, self.language.locale, reduced and
"reduced." or ""
+ self.domain.potbase(),
+ self.branch.name_escaped,
+ self.language.locale,
+ reduced and "reduced." or "",
)
return "%s.%s.%spot" % (self.domain.potbase(), self.branch.name_escaped, reduced and "reduced." or
"")
@@ -1563,20 +1587,25 @@ class Statistics(models.Model):
pot_words_size = self.full_po.pot_size(words=True)
fig_count = self.full_po.fig_count()
# Return stat table header: 'POT file (n messages) - updated on ??-??-???? tz'
- msg_text = ngettext("%(count)s message", "%(count)s messages", pot_size) % {'count': pot_size}
+ msg_text = ngettext("%(count)s message", "%(count)s messages", pot_size) % {"count": pot_size}
upd_text = _("updated on %(date)s") % {
# Date format syntax is similar to PHP http://www.php.net/date
- 'date': dateformat.format(self.full_po.updated, _("Y-m-d g:i a O"))
+ "date": dateformat.format(self.full_po.updated, _("Y-m-d g:i a O"))
}
- words_text = ngettext("%(count)s word", "%(count)s words", pot_words_size) % {'count':
pot_words_size}
+ words_text = ngettext("%(count)s word", "%(count)s words", pot_words_size) % {"count":
pot_words_size}
if fig_count:
- fig_text = ngettext("%(count)s figure", "%(count)s figures", fig_count) % {'count': fig_count}
+ fig_text = ngettext("%(count)s figure", "%(count)s figures", fig_count) % {"count": fig_count}
text = _("POT file (%(messages)s — %(words)s, %(figures)s) — %(updated)s") % {
- 'messages': msg_text, 'figures': fig_text, 'updated': upd_text, 'words': words_text
+ "messages": msg_text,
+ "figures": fig_text,
+ "updated": upd_text,
+ "words": words_text,
}
else:
text = _("POT file (%(messages)s — %(words)s) — %(updated)s") % {
- 'messages': msg_text, 'updated': upd_text, 'words': words_text
+ "messages": msg_text,
+ "updated": upd_text,
+ "words": words_text,
}
return text
@@ -1584,62 +1613,69 @@ class Statistics(models.Model):
return bool(self.full_po and self.full_po.figures)
def get_figures(self):
- """ Return an enriched list of figure dicts (used in module_images.html):
- [{'path':, 'hash':, 'fuzzy':, 'translated':, 'translated_file':}, ...] """
+ """Return an enriched list of figure dicts (used in module_images.html):
+ [{'path':, 'hash':, 'fuzzy':, 'translated':, 'translated_file':}, ...]"""
figures = []
- if self.full_po and self.domain.dtype == 'doc' and self.full_po.figures:
+ if self.full_po and self.domain.dtype == "doc" and self.full_po.figures:
# something like: "https://gitlab.gnome.org/GNOME/vinagre / raw / master / help / %s / %s"
url_model = utils.url_join(
- self.branch.get_vcs_web_url(), self.branch.img_url_prefix,
- self.branch.name, self.domain.base_dir, '%s', '%s'
+ self.branch.get_vcs_web_url(),
+ self.branch.img_url_prefix,
+ self.branch.name,
+ self.domain.base_dir,
+ "%s",
+ "%s",
)
for fig in self.full_po.figures:
fig2 = fig.copy()
- fig2['orig_remote_url'] = url_model % ('C', fig['path'])
- fig2['translated_file'] = False
+ fig2["orig_remote_url"] = url_model % ("C", fig["path"])
+ fig2["translated_file"] = False
# Check if a translated figure really exists or if the English one is used
- if (self.language and
- (self.branch.co_path / self.domain.base_dir / self.language.locale
- / fig['path']).exists()):
- fig2['trans_remote_url'] = url_model % (self.language.locale, fig['path'])
- fig2['translated_file'] = True
+ if (
+ self.language
+ and (self.branch.co_path / self.domain.base_dir / self.language.locale /
fig["path"]).exists()
+ ):
+ fig2["trans_remote_url"] = url_model % (self.language.locale, fig["path"])
+ fig2["translated_file"] = True
figures.append(fig2)
return figures
def fig_stats(self):
- stats = {'fuzzy': 0, 'translated': 0, 'total': 0, 'prc': 0}
+ stats = {"fuzzy": 0, "translated": 0, "total": 0, "prc": 0}
if self.full_po and self.full_po.figures:
for fig in self.full_po.figures:
- stats['total'] += 1
- if fig.get('fuzzy', 0):
- stats['fuzzy'] += 1
+ stats["total"] += 1
+ if fig.get("fuzzy", 0):
+ stats["fuzzy"] += 1
else:
- if fig.get('translated', 0):
- stats['translated'] += 1
- stats['untranslated'] = stats['total'] - (stats['translated'] + stats['fuzzy'])
- if stats['total'] > 0:
- stats['prc'] = 100 * stats['translated'] / stats['total']
+ if fig.get("translated", 0):
+ stats["translated"] += 1
+ stats["untranslated"] = stats["total"] - (stats["translated"] + stats["fuzzy"])
+ if stats["total"] > 0:
+ stats["prc"] = 100 * stats["translated"] / stats["total"]
return stats
def vcs_web_path(self):
- """ Return the Web interface path of file on remote vcs """
+ """Return the Web interface path of file on remote vcs"""
return utils.url_join(self.branch.get_vcs_web_url(), self.domain.base_dir)
def po_path(self, potfile=False, reduced=False):
- """ Return path of (merged) po file on local filesystem """
+ """Return path of (merged) po file on local filesystem"""
subdir = ""
if self.domain.dtype == "doc":
subdir = "docs"
path = (
- settings.POTDIR / f"{self.module_name}.{self.branch.name_escaped}" /
- subdir / self.filename(potfile, reduced)
+ settings.POTDIR
+ / f"{self.module_name}.{self.branch.name_escaped}"
+ / subdir
+ / self.filename(potfile, reduced)
)
if reduced and not path.exists():
path = self.po_path(potfile=potfile, reduced=False)
return path
def po_url(self, potfile=False, reduced=False):
- """ Return URL of (merged) po file, e.g. for downloading the file """
+ """Return URL of (merged) po file, e.g. for downloading the file"""
subdir = ""
if self.domain.dtype == "doc":
subdir = "docs/"
@@ -1659,20 +1695,23 @@ class Statistics(models.Model):
if file_path is None and self.full_po:
file_path = self.full_po.full_path
stats = utils.po_file_stats(file_path)
- for err in stats['errors']:
+ for err in stats["errors"]:
self.set_error(*err)
# If stats are 100%, compare the total number of strings between
# committed po (pofile) and merged po (outpo).
# Until https://savannah.gnu.org/bugs/index.php?50910 is fixed.
- if self.language and stats['fuzzy'] + stats['untranslated'] == 0: # Fully translated po file
+ if self.language and stats["fuzzy"] + stats["untranslated"] == 0: # Fully translated po file
abs_po_path = self.branch.co_path / self.domain.get_po_path(self.language.locale)
if abs_po_path.exists():
git_stats = utils.po_file_stats(abs_po_path)
- if stats['translated'] > git_stats['translated']:
- self.set_error('warn', gettext_noop(
- "The currently committed file has less translated strings. "
- "You should probably commit this file."
- ))
+ if stats["translated"] > git_stats["translated"]:
+ self.set_error(
+ "warn",
+ gettext_noop(
+ "The currently committed file has less translated strings. "
+ "You should probably commit this file."
+ ),
+ )
try:
df = self.domain.doc_format(self.branch)
except Exception:
@@ -1684,12 +1723,12 @@ class Statistics(models.Model):
self.full_po = PoFile.objects.create(path=relpath)
self.save()
self.full_po.path = relpath
- self.full_po.translated = int(stats['translated'])
- self.full_po.fuzzy = int(stats['fuzzy'])
- self.full_po.untranslated = int(stats['untranslated'])
- self.full_po.translated_words = int(stats['translated_words'])
- self.full_po.fuzzy_words = int(stats['fuzzy_words'])
- self.full_po.untranslated_words = int(stats['untranslated_words'])
+ self.full_po.translated = int(stats["translated"])
+ self.full_po.fuzzy = int(stats["fuzzy"])
+ self.full_po.untranslated = int(stats["untranslated"])
+ self.full_po.translated_words = int(stats["translated_words"])
+ self.full_po.fuzzy_words = int(stats["fuzzy_words"])
+ self.full_po.untranslated_words = int(stats["untranslated_words"])
self.full_po.figures = fig_stats
self.full_po.updated = timezone.now()
self.full_po.save()
@@ -1705,18 +1744,22 @@ class Statistics(models.Model):
self.save()
# Try to compute a reduced po file
- if ((self.full_po.fuzzy + self.full_po.untranslated) > 0
- and not self.branch.is_archive_only()
- and self.domain.red_filter != '-'):
+ if (
+ (self.full_po.fuzzy + self.full_po.untranslated) > 0
+ and not self.branch.is_archive_only()
+ and self.domain.red_filter != "-"
+ ):
# Generate partial_po and store partial stats
- if self.full_po.path.endswith('.pot'):
+ if self.full_po.path.endswith(".pot"):
part_po_path = self.full_po.full_path.with_suffix(".reduced.pot")
else:
part_po_path = self.full_po.full_path.with_suffix(".reduced.po")
utils.po_grep(self.full_po.full_path, str(part_po_path), self.domain.red_filter)
part_stats = utils.po_file_stats(part_po_path)
- if (part_stats['translated'] + part_stats['fuzzy'] + part_stats['untranslated'] ==
- stats['translated'] + stats['fuzzy'] + stats['untranslated']):
+ if (
+ part_stats["translated"] + part_stats["fuzzy"] + part_stats["untranslated"]
+ == stats["translated"] + stats["fuzzy"] + stats["untranslated"]
+ ):
# No possible gain, set part_po = full_po so it is possible to
# compute complete stats at database level
part_po_equals_full_po()
@@ -1728,12 +1771,12 @@ class Statistics(models.Model):
self.part_po = PoFile.objects.create(path=relpath)
self.save()
self.part_po.path = relpath
- self.part_po.translated = part_stats['translated']
- self.part_po.fuzzy = part_stats['fuzzy']
- self.part_po.untranslated = part_stats['untranslated']
- self.part_po.translated_words = part_stats['translated_words']
- self.part_po.fuzzy_words = part_stats['fuzzy_words']
- self.part_po.untranslated_words = part_stats['untranslated_words']
+ self.part_po.translated = part_stats["translated"]
+ self.part_po.fuzzy = part_stats["fuzzy"]
+ self.part_po.untranslated = part_stats["untranslated"]
+ self.part_po.translated_words = part_stats["translated_words"]
+ self.part_po.fuzzy_words = part_stats["fuzzy_words"]
+ self.part_po.untranslated_words = part_stats["untranslated_words"]
self.part_po.updated = timezone.now()
self.part_po.save()
else:
@@ -1741,7 +1784,8 @@ class Statistics(models.Model):
elif self.domain.dtype == "doc" and self.language is not None:
fig_errors = utils.check_identical_figures(
- fig_stats, self.branch.domain_path(self.domain), self.language.locale)
+ fig_stats, self.branch.domain_path(self.domain), self.language.locale
+ )
for err in fig_errors:
self.set_error(*err)
@@ -1751,90 +1795,98 @@ class Statistics(models.Model):
Information.objects.create(statistics=self, type=tp, description=description)
def most_important_message(self):
- """ Return a message of type 1.'error', or 2.'warn, or 3.'warn """
+ """Return a message of type 1.'error', or 2.'warn, or 3.'warn"""
error = None
for e in self.information_set.all():
- if (not error
- or e.type in ('error', 'error-ext')
- or (e.type in ('warn', 'warn-ext') and error.type == 'info')):
+ if (
+ not error
+ or e.type in ("error", "error-ext")
+ or (e.type in ("warn", "warn-ext") and error.type == "info")
+ ):
error = e
return error
@classmethod
def get_lang_stats_by_type(cls, lang, dtype, release):
- """ Cook statistics for an entire release, a domain type dtype and the language lang.
- Structure of the resulting stats dictionary is as follows:
- stats = {
- 'dtype':dtype, # 'ui' or 'doc'
- 'total': 0,
- 'totaltrans': 0,
- 'totalfuzzy': 0,
- 'totaluntrans': 0,
- 'totaltransperc': 0,
- 'totalfuzzyperc': 0,
- 'totaluntransperc': 0,
- 'categs': { # OrderedDict
- <categkey>: {
- 'catname': <catname>, # translated category name
- 'cattotal': 0,
- 'cattrans': 0,
- 'catfuzzy': 0,
- 'catuntrans': 0,
- 'cattransperc': 0,
- 'modules': { # OrderedDict
- <module>: {
- <branchname>:
- [(<domname>, <stat>), ...], # List of tuples (domain name, Statistics
object)
- # First element is a placeholder for a FakeSummaryStatistics
object
- # only used for summary if module has more than 1 domain
- }
+ """Cook statistics for an entire release, a domain type dtype and the language lang.
+ Structure of the resulting stats dictionary is as follows:
+ stats = {
+ 'dtype':dtype, # 'ui' or 'doc'
+ 'total': 0,
+ 'totaltrans': 0,
+ 'totalfuzzy': 0,
+ 'totaluntrans': 0,
+ 'totaltransperc': 0,
+ 'totalfuzzyperc': 0,
+ 'totaluntransperc': 0,
+ 'categs': { # OrderedDict
+ <categkey>: {
+ 'catname': <catname>, # translated category name
+ 'cattotal': 0,
+ 'cattrans': 0,
+ 'catfuzzy': 0,
+ 'catuntrans': 0,
+ 'cattransperc': 0,
+ 'modules': { # OrderedDict
+ <module>: {
+ <branchname>:
+ [(<domname>, <stat>), ...], # List of tuples (domain name, Statistics object)
+ # First element is a placeholder for a FakeSummaryStatistics object
+ # only used for summary if module has more than 1 domain
}
}
}
- },
- 'all_errors':[]
- }
+ }
+ },
+ 'all_errors':[]
+ }
"""
# Import here to prevent a circular dependency
# pylint: disable=import-outside-toplevel
- from vertimus.models import State, Action
+ from vertimus.models import Action, State
scope = "full"
- if dtype.endswith('-part'):
+ if dtype.endswith("-part"):
dtype = dtype[:-5]
scope = "part"
stats = {
- 'dtype': dtype, 'totaltrans': 0, 'totalfuzzy': 0, 'totaluntrans': 0,
- 'totaltransperc': 0, 'totalfuzzyperc': 0, 'totaluntransperc': 0,
- 'categs': OrderedDict(), 'all_errors': [],
+ "dtype": dtype,
+ "totaltrans": 0,
+ "totalfuzzy": 0,
+ "totaluntrans": 0,
+ "totaltransperc": 0,
+ "totalfuzzyperc": 0,
+ "totaluntransperc": 0,
+ "categs": OrderedDict(),
+ "all_errors": [],
}
# Sorted by module to allow grouping ('fake' stats)
- pot_stats = Statistics.objects.select_related('domain', 'branch__module', 'full_po', 'part_po')
+ pot_stats = Statistics.objects.select_related("domain", "branch__module", "full_po", "part_po")
if release:
- pot_stats = pot_stats.filter(
- language=None, branch__releases=release, domain__dtype=dtype
- ).order_by('branch__module__name')
+ pot_stats = pot_stats.filter(language=None, branch__releases=release,
domain__dtype=dtype).order_by(
+ "branch__module__name"
+ )
categ_names = {
cat.branch_id: cat.name.name
- for cat in Category.objects.select_related('branch', 'name').filter(release=release)
+ for cat in Category.objects.select_related("branch", "name").filter(release=release)
}
else:
- pot_stats = pot_stats.filter(language=None, domain__dtype=dtype).order_by('branch__module__name')
+ pot_stats = pot_stats.filter(language=None, domain__dtype=dtype).order_by("branch__module__name")
- tr_stats = Statistics.objects.select_related('domain', 'language', 'branch__module', 'full_po',
'part_po')
+ tr_stats = Statistics.objects.select_related("domain", "language", "branch__module", "full_po",
"part_po")
if release:
- tr_stats = tr_stats.filter(
- language=lang, branch__releases=release, domain__dtype=dtype
- ).order_by('branch__module__id')
+ tr_stats = tr_stats.filter(language=lang, branch__releases=release,
domain__dtype=dtype).order_by(
+ "branch__module__id"
+ )
else:
- tr_stats = tr_stats.filter(language=lang, domain__dtype=dtype).order_by('branch__module__id')
+ tr_stats = tr_stats.filter(language=lang, domain__dtype=dtype).order_by("branch__module__id")
tr_stats_dict = {f"{st.branch_id}-{st.domain_id}": st for st in tr_stats}
infos_dict = Information.get_info_dict(lang)
# Prepare State objects in a dict (with "branch_id-domain_id" as key), to save database queries later
- vt_states = State.objects.select_related('branch', 'domain')
+ vt_states = State.objects.select_related("branch", "domain")
if release:
vt_states = vt_states.filter(language=lang, branch__releases=release, domain__dtype=dtype)
else:
@@ -1842,7 +1894,7 @@ class Statistics(models.Model):
vt_states_dict = {f"{vt.branch_id}-{vt.domain_id}": vt for vt in vt_states}
# Get comments from last action of State objects
- actions = Action.objects.filter(state_db__in=vt_states, comment__isnull=False).order_by('created')
+ actions = Action.objects.filter(state_db__in=vt_states, comment__isnull=False).order_by("created")
actions_dict = {action.state_db_id: action for action in actions}
for vt_state in vt_states_dict.values():
if vt_state.id in actions_dict:
@@ -1857,11 +1909,14 @@ class Statistics(models.Model):
domname = _(stat.domain.description) if stat.domain.description else ""
branchname = stat.branch.name
module = stat.branch.module
- if categ_key not in stats['categs']:
- stats['categs'][categ_key] = {
- 'cattrans': 0, 'catfuzzy': 0, 'catuntrans': 0, 'cattransperc': 0,
- 'catname': _(categ_key),
- 'modules': OrderedDict(),
+ if categ_key not in stats["categs"]:
+ stats["categs"][categ_key] = {
+ "cattrans": 0,
+ "catfuzzy": 0,
+ "catuntrans": 0,
+ "cattransperc": 0,
+ "catname": _(categ_key),
+ "modules": OrderedDict(),
}
# Try to get translated stat, else stick with POT stat
br_dom_key = "%d-%d" % (stat.branch.id, stat.domain.id)
@@ -1872,61 +1927,62 @@ class Statistics(models.Model):
# Match stat with error list
if stat.id in infos_dict:
stat.info_list = infos_dict[stat.id]
- stats['all_errors'].extend(stat.info_list)
+ stats["all_errors"].extend(stat.info_list)
# Search if a state exists for this statistic
if br_dom_key in vt_states_dict:
stat.state = vt_states_dict[br_dom_key]
- if not stat.active and stat.state.name != 'None':
+ if not stat.active and stat.state.name != "None":
stat.active = True
- stats['totaltrans'] += stat.translated(scope)
- stats['totalfuzzy'] += stat.fuzzy(scope)
- stats['totaluntrans'] += stat.untranslated(scope)
- stats['categs'][categ_key]['cattrans'] += stat.translated(scope)
- stats['categs'][categ_key]['catfuzzy'] += stat.fuzzy(scope)
- stats['categs'][categ_key]['catuntrans'] += stat.untranslated(scope)
- if module not in stats['categs'][categ_key]['modules']:
+ stats["totaltrans"] += stat.translated(scope)
+ stats["totalfuzzy"] += stat.fuzzy(scope)
+ stats["totaluntrans"] += stat.untranslated(scope)
+ stats["categs"][categ_key]["cattrans"] += stat.translated(scope)
+ stats["categs"][categ_key]["catfuzzy"] += stat.fuzzy(scope)
+ stats["categs"][categ_key]["catuntrans"] += stat.untranslated(scope)
+ if module not in stats["categs"][categ_key]["modules"]:
# first element is a placeholder for a fake stat
- stats['categs'][categ_key]['modules'][module] = {branchname: [[' fake', None], [domname,
stat]]}
- elif branchname not in stats['categs'][categ_key]['modules'][module]:
+ stats["categs"][categ_key]["modules"][module] = {branchname: [[" fake", None], [domname,
stat]]}
+ elif branchname not in stats["categs"][categ_key]["modules"][module]:
# first element is a placeholder for a fake stat
- stats['categs'][categ_key]['modules'][module][branchname] = [[' fake', None], [domname,
stat]]
+ stats["categs"][categ_key]["modules"][module][branchname] = [[" fake", None], [domname,
stat]]
else:
# Here we add the 2nd or more stat to the same module-branch
- if len(stats['categs'][categ_key]['modules'][module][branchname]) == 2:
+ if len(stats["categs"][categ_key]["modules"][module][branchname]) == 2:
# Create a fake statistics object for module summary
- stats['categs'][categ_key]['modules'][module][branchname][0][1] = FakeSummaryStatistics(
+ stats["categs"][categ_key]["modules"][module][branchname][0][1] = FakeSummaryStatistics(
stat.domain.module, stat.branch, dtype
)
- stats['categs'][categ_key]['modules'][module][branchname][0][1].trans(
- stats['categs'][categ_key]['modules'][module][branchname][1][1]
+ stats["categs"][categ_key]["modules"][module][branchname][0][1].trans(
+ stats["categs"][categ_key]["modules"][module][branchname][1][1]
)
- stats['categs'][categ_key]['modules'][module][branchname].append([domname, stat])
- stats['categs'][categ_key]['modules'][module][branchname][0][1].trans(stat)
+ stats["categs"][categ_key]["modules"][module][branchname].append([domname, stat])
+ stats["categs"][categ_key]["modules"][module][branchname][0][1].trans(stat)
# Compute percentages and sorting
- stats['total'] = stats['totaltrans'] + stats['totalfuzzy'] + stats['totaluntrans']
- if stats['total'] > 0:
- stats['totaltransperc'] = int(100 * stats['totaltrans'] / stats['total'])
- stats['totalfuzzyperc'] = int(100 * stats['totalfuzzy'] / stats['total'])
- stats['totaluntransperc'] = int(100 * stats['totaluntrans'] / stats['total'])
- stats['categs'] = OrderedDict(sorted(stats['categs'].items(), key=lambda t: t[0]))
- for categ in stats['categs'].values():
- categ['cattotal'] = categ['cattrans'] + categ['catfuzzy'] + categ['catuntrans']
- if categ['cattotal'] > 0:
- categ['cattransperc'] = int(100 * categ['cattrans'] / categ['cattotal'])
+ stats["total"] = stats["totaltrans"] + stats["totalfuzzy"] + stats["totaluntrans"]
+ if stats["total"] > 0:
+ stats["totaltransperc"] = int(100 * stats["totaltrans"] / stats["total"])
+ stats["totalfuzzyperc"] = int(100 * stats["totalfuzzy"] / stats["total"])
+ stats["totaluntransperc"] = int(100 * stats["totaluntrans"] / stats["total"])
+ stats["categs"] = OrderedDict(sorted(stats["categs"].items(), key=lambda t: t[0]))
+ for categ in stats["categs"].values():
+ categ["cattotal"] = categ["cattrans"] + categ["catfuzzy"] + categ["catuntrans"]
+ if categ["cattotal"] > 0:
+ categ["cattransperc"] = int(100 * categ["cattrans"] / categ["cattotal"])
# Sort domains
- for mod in categ['modules'].values():
+ for mod in categ["modules"].values():
for doms in mod.values():
doms.sort(key=itemgetter(0))
# Sort errors
- stats['all_errors'].sort()
+ stats["all_errors"].sort()
return stats
class FakeLangStatistics:
- """ Statistics class for a non existing lang stats """
+ """Statistics class for a non existing lang stats"""
+
is_fake = True
def __init__(self, pot_stat, lang):
@@ -1939,8 +1995,8 @@ class FakeLangStatistics:
def get_lang(self):
return _("%(lang_name)s (%(lang_locale)s)") % {
- 'lang_name': _(self.language.name),
- 'lang_locale': self.language.locale
+ "lang_name": _(self.language.name),
+ "lang_locale": self.language.locale,
}
def po_url(self, potfile=False, reduced=False):
@@ -1948,16 +2004,20 @@ class FakeLangStatistics:
locale = "%s-reduced" % self.language.locale
else:
locale = self.language.locale
- return reverse('dynamic_po', kwargs={
- 'module_name': self.branch.module.name,
- 'domain_name': self.domain.name,
- 'branch_name': self.branch.name,
- 'filename': "%s.po" % locale,
- })
+ return reverse(
+ "dynamic_po",
+ kwargs={
+ "module_name": self.branch.module.name,
+ "domain_name": self.domain.name,
+ "branch_name": self.branch.name,
+ "filename": "%s.po" % locale,
+ },
+ )
class FakeSummaryStatistics:
- """ Statistics class that sums up an entire module stats """
+ """Statistics class that sums up an entire module stats"""
+
is_fake = True
def __init__(self, module, branch, dtype):
@@ -1999,17 +2059,17 @@ class FakeSummaryStatistics:
def pot_size(self):
return int(self._translated) + int(self._fuzzy) + int(self._untranslated)
- def tr_percentage(self, scope='full'):
+ def tr_percentage(self, scope="full"):
if self.pot_size() == 0:
return 0
return int(100 * self._translated / self.pot_size())
- def fu_percentage(self, scope='full'):
+ def fu_percentage(self, scope="full"):
if self.pot_size() == 0:
return 0
return int(100 * self._fuzzy / self.pot_size())
- def un_percentage(self, scope='full'):
+ def un_percentage(self, scope="full"):
if self.pot_size() == 0:
return 0
return int(100 * self._untranslated / self.pot_size())
@@ -2027,33 +2087,33 @@ class StatisticsArchived(models.Model):
untranslated = models.IntegerField(default=0)
class Meta:
- db_table = 'statistics_archived'
+ db_table = "statistics_archived"
INFORMATION_TYPE_CHOICES = (
- ('info', 'Information'),
- ('warn', 'Warning'),
- ('error', 'Error'),
+ ("info", "Information"),
+ ("warn", "Warning"),
+ ("error", "Error"),
# Type of warning/error external to po file itself (LINGUAS, images, etc.)
# po files containing these are always rechecked
- ('warn-ext', 'Warning (external)'),
- ('error-ext', 'Error (external)')
+ ("warn-ext", "Warning (external)"),
+ ("error-ext", "Error (external)"),
)
class Information(models.Model):
- statistics = models.ForeignKey('Statistics', on_delete=models.CASCADE)
+ statistics = models.ForeignKey("Statistics", on_delete=models.CASCADE)
# Priority of a stats message
type = models.CharField(max_length=10, choices=INFORMATION_TYPE_CHOICES)
description = models.TextField()
class Meta:
- db_table = 'information'
+ db_table = "information"
@classmethod
def get_info_dict(cls, lang):
- """ Return a dict (of lists) with all Information objects for a lang, with statistics_id as the key
- Used for caching and preventing db access when requesting these objects for a long list of stats
"""
+ """Return a dict (of lists) with all Information objects for a lang, with statistics_id as the key
+ Used for caching and preventing db access when requesting these objects for a long list of stats"""
info_dict = {}
for info in Information.objects.filter(statistics__language=lang):
if info.statistics_id in info_dict:
@@ -2070,34 +2130,34 @@ class Information(models.Model):
def get_description(self):
text = self.description
- matches = re.findall('###([^#]*)###', text)
+ matches = re.findall("###([^#]*)###", text)
if matches:
- text = re.sub('###([^#]*)###', '%s', text)
+ text = re.sub("###([^#]*)###", "%s", text)
text = _(text)
# FIXME: if multiple substitutions, works only if order of %s is unchanged in translated string
for match in matches:
- text = text.replace('%s', match, 1)
+ text = text.replace("%s", match, 1)
return text
def report_bug_url(self):
link = self.statistics.branch.module.get_bugs_enter_url()
link += "&short_desc=%(short)s&content=%(short)s&comment=%(long)s" % {
- 'short': "Error regenerating POT file",
- 'long': utils.ellipsize(utils.stripHTML(self.get_description()), 1600),
+ "short": "Error regenerating POT file",
+ "long": utils.ellipsize(utils.stripHTML(self.get_description()), 1600),
}
return link
class InformationArchived(models.Model):
- statistics = models.ForeignKey('StatisticsArchived', on_delete=models.CASCADE)
+ statistics = models.ForeignKey("StatisticsArchived", on_delete=models.CASCADE)
# Priority of a stats message
type = models.CharField(max_length=10, choices=INFORMATION_TYPE_CHOICES)
description = models.TextField()
class Meta:
- db_table = 'information_archived'
+ db_table = "information_archived"
# Utilities to properly sort branch names like gnome-3-2 < gnome-3-10
diff --git a/stats/potdiff.py b/stats/potdiff.py
index 63875b92..386186cb 100644
--- a/stats/potdiff.py
+++ b/stats/potdiff.py
@@ -37,6 +37,7 @@ def diff(pota, potb):
return result_all, result_add_only
else:
import difflib
+
d = difflib.Differ()
result = list(d.compare(res1, res2))
@@ -76,10 +77,10 @@ def _parse_contents(contents):
onemsg = ""
if msgctxt:
- onemsg += ('"' + msgctxt + '"::')
- onemsg += ('"' + msgid + '"')
+ onemsg += '"' + msgctxt + '"::'
+ onemsg += '"' + msgid + '"'
if plural:
- onemsg += ('/"' + plural + '"')
+ onemsg += '/"' + plural + '"'
result.append(onemsg)
@@ -95,7 +96,7 @@ def _parse_contents(contents):
plural = ""
in_msgid_plural = 0
- elif line[0] == "\"" and line[-1] == "\"":
+ elif line[0] == '"' and line[-1] == '"':
if in_msgid:
if in_msgid_plural:
plural += line[1:-1]
diff --git a/stats/repos.py b/stats/repos.py
index 1434b303..31224a23 100644
--- a/stats/repos.py
+++ b/stats/repos.py
@@ -8,7 +8,7 @@ from stats.utils import STATUS_OK, url_join
class RepoBase:
@classmethod
def repo_class_by_type(cls, tp):
- return {'svn': SVNRepo, 'git': GitRepo, 'hg': MercurialRepo}.get(tp)
+ return {"svn": SVNRepo, "git": GitRepo, "hg": MercurialRepo}.get(tp)
def __init__(self, branch):
self.branch = branch
@@ -47,28 +47,28 @@ class GitRepo(RepoBase):
if self.branch.is_head():
# We are assuming here that this is the first branch created
commands = [
- ['git', 'clone', self.branch.module.vcs_root, str(self.branch.co_path)],
- ['git', 'remote', 'update'],
- ['git', 'checkout', self.branch.name],
+ ["git", "clone", self.branch.module.vcs_root, str(self.branch.co_path)],
+ ["git", "remote", "update"],
+ ["git", "checkout", self.branch.name],
]
else:
commands = [
- ['git', 'pull'],
- ['git', 'checkout', '--track', '-b', self.branch.name, 'origin/%s' % self.branch.name],
+ ["git", "pull"],
+ ["git", "checkout", "--track", "-b", self.branch.name, "origin/%s" % self.branch.name],
]
# check if there are any submodules and init & update them
commands.append("if [ -e .gitmodules ]; then git submodule update --init; fi")
for cmd in commands:
- working_dir = self.branch.co_path.parent if 'clone' in cmd else self.branch.co_path
+ working_dir = self.branch.co_path.parent if "clone" in cmd else self.branch.co_path
run_shell_command(cmd, raise_on_error=True, cwd=working_dir)
def update(self):
# test "git checkout %(branch)s && git clean -dfq && git pull origin/%(branch)s"?
commands = [
- ['git', 'checkout', '-f', self.branch.name],
- ['git', 'fetch'],
- ['git', 'reset', '--hard', 'origin/%s' % self.branch.name],
- ['git', 'clean', '-dfq'],
+ ["git", "checkout", "-f", self.branch.name],
+ ["git", "fetch"],
+ ["git", "reset", "--hard", "origin/%s" % self.branch.name],
+ ["git", "clean", "-dfq"],
# check if there are any submodules and init & update them
"if [ -e .gitmodules ]; then git submodule update --init; fi",
]
@@ -78,45 +78,41 @@ class GitRepo(RepoBase):
def commit_files(self, files, message, author=None):
base_path = self.branch.co_path
for _file in files:
- run_shell_command(['git', 'add', str(_file)], raise_on_error=True, cwd=base_path)
- commit_cmd = ['git', 'commit', '-m', message]
+ run_shell_command(["git", "add", str(_file)], raise_on_error=True, cwd=base_path)
+ commit_cmd = ["git", "commit", "-m", message]
if author:
- commit_cmd.extend(['--author', author])
+ commit_cmd.extend(["--author", author])
run_shell_command(commit_cmd, raise_on_error=True, cwd=base_path)
# git push
try:
- run_shell_command(
- ['git', 'push', 'origin', self.branch.name], raise_on_error=True, cwd=base_path
- )
+ run_shell_command(["git", "push", "origin", self.branch.name], raise_on_error=True,
cwd=base_path)
except OSError:
# Revert the commit
- run_shell_command(['git', 'reset', '--hard', 'origin/%s' % self.branch.name], cwd=base_path)
+ run_shell_command(["git", "reset", "--hard", "origin/%s" % self.branch.name], cwd=base_path)
raise
else:
- _, out, _ = run_shell_command(['git', 'log', '-n1', '--format=oneline'], cwd=base_path)
- commit_hash = out.split()[0] if out else ''
+ _, out, _ = run_shell_command(["git", "log", "-n1", "--format=oneline"], cwd=base_path)
+ commit_hash = out.split()[0] if out else ""
return commit_hash
def cherry_pick(self, commit_hash):
commit_dir = self.branch.co_path
- result = run_shell_command(['git', 'cherry-pick', '-x', commit_hash], cwd=commit_dir)
+ result = run_shell_command(["git", "cherry-pick", "-x", commit_hash], cwd=commit_dir)
if result[0] == STATUS_OK:
- run_shell_command(
- ['git', 'push', 'origin', self.branch.name], raise_on_error=True, cwd=commit_dir
- )
+ run_shell_command(["git", "push", "origin", self.branch.name], raise_on_error=True,
cwd=commit_dir)
return True
else:
# Revert
- run_shell_command(['git', 'cherry-pick', '--abort'], cwd=commit_dir)
+ run_shell_command(["git", "cherry-pick", "--abort"], cwd=commit_dir)
return False
def rename(self, old_name, new_name):
commit_dir = self.branch.co_path
- run_shell_command(['git', 'checkout', old_name], cwd=commit_dir)
- run_shell_command(['git', 'branch', '-m', old_name, new_name], cwd=commit_dir)
- run_shell_command(['git', 'fetch'], cwd=commit_dir)
- run_shell_command(['git', 'branch', '--unset-upstream'], cwd=commit_dir)
- run_shell_command(['git', 'branch', '--set-upstream-to', f'origin/{new_name}'], cwd=commit_dir)
+ run_shell_command(["git", "checkout", old_name], cwd=commit_dir)
+ run_shell_command(["git", "branch", "-m", old_name, new_name], cwd=commit_dir)
+ run_shell_command(["git", "fetch"], cwd=commit_dir)
+ run_shell_command(["git", "branch", "--unset-upstream"], cwd=commit_dir)
+ run_shell_command(["git", "branch", "--set-upstream-to", f"origin/{new_name}"], cwd=commit_dir)
def remove(self):
wdir = str(self.branch.co_path)
@@ -125,11 +121,8 @@ class GitRepo(RepoBase):
shutil.rmtree(wdir)
else:
# get_branches()[0] because some modules have no main branch setup (release-notes)
- run_shell_command(
- ['git', 'checkout', self.branch.module.get_branches()[0].name],
- cwd=wdir
- )
- run_shell_command(['git', 'branch', '-D', self.branch.name], cwd=wdir)
+ run_shell_command(["git", "checkout", self.branch.module.get_branches()[0].name], cwd=wdir)
+ run_shell_command(["git", "branch", "-D", self.branch.name], cwd=wdir)
class SVNRepo(RepoBase):
@@ -140,12 +133,10 @@ class SVNRepo(RepoBase):
else:
co_url = url_join(root, self.branch.module.name, "branches", self.branch.name)
- run_shell_command([
- 'svn', 'co', '--non-interactive', co_url, str(self.branch.co_path)
- ], raise_on_error=True)
+ run_shell_command(["svn", "co", "--non-interactive", co_url, str(self.branch.co_path)],
raise_on_error=True)
def update(self):
- run_shell_command(['svn', 'up', '--non-interactive'], raise_on_error=True, cwd=self.branch.co_path)
+ run_shell_command(["svn", "up", "--non-interactive"], raise_on_error=True, cwd=self.branch.co_path)
def remove(self):
if os.access(str(self.branch.co_path), os.W_OK):
@@ -158,11 +149,8 @@ class MercurialRepo(RepoBase):
def init_checkout(self):
base_path = self.branch.co_path
- run_shell_command(
- ['hg', 'clone', base_path, self.module.name],
- cwd=base_path.parent
- )
- run_shell_command(['hg', 'update', self.branch.name], cwd=base_path)
+ run_shell_command(["hg", "clone", base_path, self.module.name], cwd=base_path.parent)
+ run_shell_command(["hg", "update", self.branch.name], cwd=base_path)
def update(self):
- run_shell_command(['hg', 'revert', '--all'], raise_on_error=True, cwd=self.branch.co_path)
+ run_shell_command(["hg", "revert", "--all"], raise_on_error=True, cwd=self.branch.co_path)
diff --git a/stats/templatetags/stats_extras.py b/stats/templatetags/stats_extras.py
index 9e07044a..16368423 100644
--- a/stats/templatetags/stats_extras.py
+++ b/stats/templatetags/stats_extras.py
@@ -4,21 +4,17 @@ from django.utils.html import format_html
from django.utils.safestring import mark_safe
from django.utils.translation import get_language_bidi
-from stats.models import (PoFile,
- Statistics,
- FakeLangStatistics,
- FakeSummaryStatistics, )
-
+from stats.models import FakeLangStatistics, FakeSummaryStatistics, PoFile, Statistics
register = template.Library()
STATISTICS_FULL = (
'<pre class="stats">'
- '<b>{prc:>3}%</b>'
+ "<b>{prc:>3}%</b>"
'<span class="num1"> {translated:>6}</span>'
'<span class="num2"> {fuzzy:>5}</span>'
'<span class="num3"> {untranslated:>5}</span>'
- '</pre>'
+ "</pre>"
)
STATISTICS_SHORT = (
@@ -36,15 +32,15 @@ PROGRESS_BAR = (
@register.filter
def linked_with(value, arg):
- """ This filter returns an object (passed in value) enclosed with its
- absolute url arg is the linked text """
+ """This filter returns an object (passed in value) enclosed with its
+ absolute url arg is the linked text"""
return "<a href='%s'>%s</a>" % (value.get_absolute_url(), arg)
@register.filter
def support_class(value):
- """ Returns a class depending on the coverage of the translation stats.
- Value is a translation percentage """
+ """Returns a class depending on the coverage of the translation stats.
+ Value is a translation percentage"""
if value >= 80:
return "supported"
elif value >= 50:
@@ -54,10 +50,10 @@ def support_class(value):
@register.filter
def support_class_total(stats):
- """ Retuns a class depending on the trend. """
- actual = stats['stats'][-1]
+ """Retuns a class depending on the trend."""
+ actual = stats["stats"][-1]
- if stats['diff'] >= 0:
+ if stats["diff"] >= 0:
return support_class(actual)
else:
if actual >= 80:
@@ -69,7 +65,7 @@ def support_class_total(stats):
@register.filter
def escapeat(value):
"""Replace '@' with '__', accepted sequence in JS ids."""
- return value.replace('@', '__')
+ return value.replace("@", "__")
@register.filter
@@ -78,80 +74,76 @@ def browse_bugs(module, content):
@register.filter
-def num_stats(stat, scope='full'):
+def num_stats(stat, scope="full"):
return num_stats_helper(stat, scope=scope, strings=True)
@register.filter
-def num_word_stats(stat, scope='full'):
+def num_word_stats(stat, scope="full"):
return num_stats_helper(stat, scope=scope, strings=False)
-def num_stats_helper(stat, scope='full', strings=True):
+def num_stats_helper(stat, scope="full", strings=True):
"""
Produce stat numbers as in: 85% (1265/162/85)
0s are hidden unless scope ends with ',zeros'
"""
- if ',' in scope:
- scope, zeros = scope.split(',', 1)
- show_zeros = zeros == 'zeros'
+ if "," in scope:
+ scope, zeros = scope.split(",", 1)
+ show_zeros = zeros == "zeros"
else:
show_zeros = False
- if isinstance(stat, (Statistics,
- FakeLangStatistics,
- FakeSummaryStatistics)):
+ if isinstance(stat, (Statistics, FakeLangStatistics, FakeSummaryStatistics)):
if strings:
stats = {
- 'prc': stat.tr_percentage(scope),
- 'translated': stat.translated(scope),
- 'fuzzy': stat.fuzzy(scope),
- 'untranslated': stat.untranslated(scope),
+ "prc": stat.tr_percentage(scope),
+ "translated": stat.translated(scope),
+ "fuzzy": stat.fuzzy(scope),
+ "untranslated": stat.untranslated(scope),
}
else:
stats = {
- 'prc': stat.tr_word_percentage(scope),
- 'translated': stat.translated_words(scope),
- 'fuzzy': stat.fuzzy_words(scope),
- 'untranslated': stat.untranslated_words(scope),
+ "prc": stat.tr_word_percentage(scope),
+ "translated": stat.translated_words(scope),
+ "fuzzy": stat.fuzzy_words(scope),
+ "untranslated": stat.untranslated_words(scope),
}
elif isinstance(stat, PoFile):
if strings:
stats = {
- 'translated': stat.translated,
- 'fuzzy': stat.fuzzy,
- 'untranslated': stat.untranslated,
+ "translated": stat.translated,
+ "fuzzy": stat.fuzzy,
+ "untranslated": stat.untranslated,
}
else:
stats = {
- 'translated': stat.translated_words,
- 'fuzzy': stat.fuzzy_words,
- 'untranslated': stat.untranslated_words,
+ "translated": stat.translated_words,
+ "fuzzy": stat.fuzzy_words,
+ "untranslated": stat.untranslated_words,
}
- if scope != 'short':
+ if scope != "short":
if strings:
- stats['prc'] = stat.tr_percentage()
+ stats["prc"] = stat.tr_percentage()
else:
- stats['prc'] = stat.tr_word_percentage()
+ stats["prc"] = stat.tr_word_percentage()
else:
stats = stat
- if 'translated_perc' in stats:
- stats['prc'] = stats['translated_perc']
- if 'prc' in stats:
+ if "translated_perc" in stats:
+ stats["prc"] = stats["translated_perc"]
+ if "prc" in stats:
template = STATISTICS_FULL
if not show_zeros:
- stats = {k: (' ' if v == 0 and k != 'prc' else v) for k, v in stats.items()}
+ stats = {k: (" " if v == 0 and k != "prc" else v) for k, v in stats.items()}
else:
template = STATISTICS_SHORT
return format_html(template, **stats)
@register.filter
-def vis_stats(stat, scope='full'):
- """ Produce visual stats with green/red bar """
+def vis_stats(stat, scope="full"):
+ """Produce visual stats with green/red bar"""
bidi = get_language_bidi() and "right" or "left"
- if isinstance(stat, (Statistics,
- FakeLangStatistics,
- FakeSummaryStatistics)):
+ if isinstance(stat, (Statistics, FakeLangStatistics, FakeSummaryStatistics)):
trans = stat.tr_percentage(scope)
fuzzy = stat.fu_percentage(scope)
untrans = stat.un_percentage(scope)
@@ -160,28 +152,29 @@ def vis_stats(stat, scope='full'):
fuzzy = stat.fu_percentage()
untrans = stat.un_percentage()
elif isinstance(stat, dict):
- trans = stat['translated_perc']
- fuzzy = stat['fuzzy_perc']
- untrans = stat['untranslated_perc']
+ trans = stat["translated_perc"]
+ fuzzy = stat["fuzzy_perc"]
+ untrans = stat["untranslated_perc"]
else:
text = '<div class="untranslated" style="{dir}: 0px; width: 100px;"></div>'
return format_html(text, dir=bidi)
- return format_html(PROGRESS_BAR, **{
- 'dir': bidi,
- 'trans': trans,
- 'fuzzy': fuzzy,
- 'tr_fu': trans + fuzzy,
- 'untrans': untrans,
- })
+ return format_html(
+ PROGRESS_BAR,
+ **{
+ "dir": bidi,
+ "trans": trans,
+ "fuzzy": fuzzy,
+ "tr_fu": trans + fuzzy,
+ "untrans": untrans,
+ }
+ )
@register.filter
-def vis_word_stats(stat, scope='full'):
- """ Produce visual stats with green/red bar """
- if isinstance(stat, (Statistics,
- FakeLangStatistics,
- FakeSummaryStatistics)):
+def vis_word_stats(stat, scope="full"):
+ """Produce visual stats with green/red bar"""
+ if isinstance(stat, (Statistics, FakeLangStatistics, FakeSummaryStatistics)):
trans = stat.tr_word_percentage(scope)
fuzzy = stat.fu_word_percentage(scope)
untrans = stat.un_word_percentage(scope)
@@ -190,26 +183,29 @@ def vis_word_stats(stat, scope='full'):
fuzzy = stat.fu_word_percentage()
untrans = stat.un_word_percentage()
else:
- trans = stat['translated_perc']
- fuzzy = stat['fuzzy_perc']
- untrans = stat['untranslated_perc']
-
- return format_html(PROGRESS_BAR, **{
- 'dir': get_language_bidi() and "right" or "left",
- 'trans': trans,
- 'fuzzy': fuzzy,
- 'tr_fu': trans + fuzzy,
- 'untrans': untrans,
- })
+ trans = stat["translated_perc"]
+ fuzzy = stat["fuzzy_perc"]
+ untrans = stat["untranslated_perc"]
+
+ return format_html(
+ PROGRESS_BAR,
+ **{
+ "dir": get_language_bidi() and "right" or "left",
+ "trans": trans,
+ "fuzzy": fuzzy,
+ "tr_fu": trans + fuzzy,
+ "untrans": untrans,
+ }
+ )
@register.filter
def is_video(fig):
- return fig['path'].endswith('.ogv')
+ return fig["path"].endswith(".ogv")
@register.filter(is_safe=True)
-def markdown(value, arg=''):
+def markdown(value, arg=""):
"""
Copy of deprecated django.contrib.markup
Runs Markdown over a given value, optionally using various
diff --git a/stats/tests/fixture_factory.py b/stats/tests/fixture_factory.py
index 0827de38..205eca62 100644
--- a/stats/tests/fixture_factory.py
+++ b/stats/tests/fixture_factory.py
@@ -4,12 +4,19 @@ from django.core.management import call_command
from django.test import TestCase
from languages.models import Language
-from teams.models import Team, Role
from people.models import Person
from stats.models import (
- Branch, Category, CategoryName, Domain, Information, Module, PoFile,
- Release, Statistics,
+ Branch,
+ Category,
+ CategoryName,
+ Domain,
+ Information,
+ Module,
+ PoFile,
+ Release,
+ Statistics,
)
+from teams.models import Role, Team
class FixtureFactory(TestCase):
@@ -22,48 +29,48 @@ class FixtureFactory(TestCase):
def make_fixture(self):
# Creating models: Teams
t1 = Team.objects.create(
- name='fr',
+ name="fr",
description="French",
webpage_url="http://gnomefr.traduc.org/",
mailing_list="gnomefr traduc org",
mailing_list_subscribe="http://www.traduc.org/mailman/listinfo/gnomefr",
presentation="Here can come any custom description for a team",
)
- t2 = Team.objects.create(name='it', description="Italian", webpage_url="http://www.it.gnome.org/")
+ t2 = Team.objects.create(name="it", description="Italian", webpage_url="http://www.it.gnome.org/")
# Creating models: Languages
- Language.objects.create(name='en', locale='en', plurals="nplurals=2; plural=(n != 1)")
- l_fr = Language.objects.create(name='French', locale='fr', plurals="nplurals=2; plural=(n > 1)",
team=t1)
- l_it = Language.objects.create(name='Italian', locale='it', plurals="nplurals=2; plural=(n != 1)",
team=t2)
+ Language.objects.create(name="en", locale="en", plurals="nplurals=2; plural=(n != 1)")
+ l_fr = Language.objects.create(name="French", locale="fr", plurals="nplurals=2; plural=(n > 1)",
team=t1)
+ l_it = Language.objects.create(name="Italian", locale="it", plurals="nplurals=2; plural=(n != 1)",
team=t2)
# Lang with no team and no stats
- Language.objects.create(name='Bemba', locale='bem')
+ Language.objects.create(name="Bemba", locale="bem")
# Creating models: Persons/Roles
- p0 = Person.objects.create(username='admin1') # Fake person (deleted below), just not to use pk=1
for user
+ p0 = Person.objects.create(username="admin1") # Fake person (deleted below), just not to use pk=1
for user
p1 = Person.objects.create(
- first_name='Robert',
- last_name='Translator',
- email='bob example org',
- username='bob',
- irc_nick='bobby',
- svn_account='bob1',
- )
- p1.set_password('bob')
- Role.objects.create(team=t1, person=p1, role='translator')
+ first_name="Robert",
+ last_name="Translator",
+ email="bob example org",
+ username="bob",
+ irc_nick="bobby",
+ svn_account="bob1",
+ )
+ p1.set_password("bob")
+ Role.objects.create(team=t1, person=p1, role="translator")
p2 = Person.objects.create(
- first_name='John',
- last_name='Coordinator',
- email='coord example org',
- username='coord',
- svn_account='coord_fr',
- )
- p2.set_password('coord')
- Role.objects.create(team=t1, person=p2, role='coordinator')
+ first_name="John",
+ last_name="Coordinator",
+ email="coord example org",
+ username="coord",
+ svn_account="coord_fr",
+ )
+ p2.set_password("coord")
+ Role.objects.create(team=t1, person=p2, role="coordinator")
p3 = Person.objects.create(
- first_name='Alessio', last_name='Reviewer', email='alessio example org', username='alessio'
+ first_name="Alessio", last_name="Reviewer", email="alessio example org", username="alessio"
)
- p1.set_password('alessio')
- Role.objects.create(team=t2, person=p3, role='reviewer')
+ p1.set_password("alessio")
+ Role.objects.create(team=t2, person=p3, role="reviewer")
p0.delete()
# Creating models: Modules
@@ -89,42 +96,42 @@ class FixtureFactory(TestCase):
vcs_web="https://gitlab.freedesktop.org/xdg/shared-mime-info",
bugs_base="https://gitlab.freedesktop.org/xdg/shared-mime-info/issues",
comment="This is not a GNOME-specific module. Please submit your translation "
- "through the <a href=\"https://www.transifex.com/freedesktop/shared-mime-info/\">Transifex
platform</a>.",
+ 'through the <a href="https://www.transifex.com/freedesktop/shared-mime-info/">Transifex
platform</a>.',
)
# Creating models: Domains
dom = {}
for mod in (gnome_hello, zenity, s_m_i):
- dom['%s-ui' % mod.name] = Domain.objects.create(
- module=mod, name='po', description='UI Translations', dtype='ui', layout='po/{lang}.po'
+ dom["%s-ui" % mod.name] = Domain.objects.create(
+ module=mod, name="po", description="UI Translations", dtype="ui", layout="po/{lang}.po"
)
- dom['%s-doc' % mod.name] = Domain.objects.create(
- module=mod, name='help', description='User Guide', dtype='doc',
layout='help/{lang}/{lang}.po'
+ dom["%s-doc" % mod.name] = Domain.objects.create(
+ module=mod, name="help", description="User Guide", dtype="doc",
layout="help/{lang}/{lang}.po"
)
# Creating models: Branches
Branch.checkout_on_creation = False
- b1 = Branch(name='master', module=gnome_hello)
+ b1 = Branch(name="master", module=gnome_hello)
b1.save(update_statistics=False)
- b2 = Branch(name='gnome-3-8', module=zenity)
+ b2 = Branch(name="gnome-3-8", module=zenity)
b2.save(update_statistics=False)
- b3 = Branch(name='master', module=zenity)
+ b3 = Branch(name="master", module=zenity)
b3.save(update_statistics=False)
- b4 = Branch(name='master', module=s_m_i)
+ b4 = Branch(name="master", module=s_m_i)
b4.save(update_statistics=False)
# Creating models: Releases/Categories
rel1 = Release.objects.create(
- name='gnome-3-8', status='official', description='GNOME 3.8 (stable)', string_frozen=True
+ name="gnome-3-8", status="official", description="GNOME 3.8 (stable)", string_frozen=True
)
rel2 = Release.objects.create(
- name='gnome-dev', status='official', description='GNOME in Development', string_frozen=False
+ name="gnome-dev", status="official", description="GNOME in Development", string_frozen=False
)
rel3 = Release.objects.create(
- name='freedesktop-org', status='xternal', description='freedesktop.org (non-GNOME)',
string_frozen=False
+ name="freedesktop-org", status="xternal", description="freedesktop.org (non-GNOME)",
string_frozen=False
)
- cat_name = CategoryName.objects.create(name='Desktop')
+ cat_name = CategoryName.objects.create(name="Desktop")
Category.objects.create(release=rel1, branch=b1, name=cat_name)
Category.objects.create(release=rel2, branch=b1, name=cat_name)
Category.objects.create(release=rel1, branch=b2, name=cat_name)
@@ -134,15 +141,15 @@ class FixtureFactory(TestCase):
# gnome-hello ui, gnome-hello doc (POT, fr, it)
pofile = PoFile.objects.create(untranslated=47)
Statistics.objects.create(
- branch=b1, domain=dom['gnome-hello-ui'], language=None, full_po=pofile, part_po=pofile
+ branch=b1, domain=dom["gnome-hello-ui"], language=None, full_po=pofile, part_po=pofile
)
pofile = PoFile.objects.create(translated=47)
Statistics.objects.create(
- branch=b1, domain=dom['gnome-hello-ui'], language=l_fr, full_po=pofile, part_po=pofile
+ branch=b1, domain=dom["gnome-hello-ui"], language=l_fr, full_po=pofile, part_po=pofile
)
pofile = PoFile.objects.create(translated=30, fuzzy=10, untranslated=7)
Statistics.objects.create(
- branch=b1, domain=dom['gnome-hello-ui'], language=l_it, full_po=pofile, part_po=pofile
+ branch=b1, domain=dom["gnome-hello-ui"], language=l_it, full_po=pofile, part_po=pofile
)
pofile = PoFile.objects.create(
untranslated=20,
@@ -152,7 +159,7 @@ class FixtureFactory(TestCase):
],
)
Statistics.objects.create(
- branch=b1, domain=dom['gnome-hello-doc'], language=None, full_po=pofile, part_po=pofile
+ branch=b1, domain=dom["gnome-hello-doc"], language=None, full_po=pofile, part_po=pofile
)
pofile = PoFile.objects.create(
translated=20,
@@ -172,63 +179,63 @@ class FixtureFactory(TestCase):
],
)
Statistics.objects.create(
- branch=b1, domain=dom['gnome-hello-doc'], language=l_fr, full_po=pofile, part_po=pofile
+ branch=b1, domain=dom["gnome-hello-doc"], language=l_fr, full_po=pofile, part_po=pofile
)
pofile = PoFile.objects.create(translated=20)
Statistics.objects.create(
- branch=b1, domain=dom['gnome-hello-doc'], language=l_it, full_po=pofile, part_po=pofile
+ branch=b1, domain=dom["gnome-hello-doc"], language=l_it, full_po=pofile, part_po=pofile
)
# zenity ui 3.8, zenity doc 3.8, zenity ui master, zenity doc master (POT, fr, it)
pofile = PoFile.objects.create(untranslated=136)
part_pofile = PoFile.objects.create(untranslated=128)
Statistics.objects.create(
- branch=b2, domain=dom['zenity-ui'], language=None, full_po=pofile, part_po=part_pofile
+ branch=b2, domain=dom["zenity-ui"], language=None, full_po=pofile, part_po=part_pofile
)
pofile = PoFile.objects.create(translated=136)
- Statistics.objects.create(branch=b2, domain=dom['zenity-ui'], language=l_fr, full_po=pofile,
part_po=pofile)
+ Statistics.objects.create(branch=b2, domain=dom["zenity-ui"], language=l_fr, full_po=pofile,
part_po=pofile)
pofile = PoFile.objects.create(translated=130, untranslated=6)
part_pofile = PoFile.objects.create(translated=100, untranslated=28)
Statistics.objects.create(
- branch=b2, domain=dom['zenity-ui'], language=l_it, full_po=pofile, part_po=part_pofile
+ branch=b2, domain=dom["zenity-ui"], language=l_it, full_po=pofile, part_po=part_pofile
)
pofile = PoFile.objects.create(untranslated=259)
- Statistics.objects.create(branch=b2, domain=dom['zenity-doc'], language=None, full_po=pofile,
part_po=pofile)
+ Statistics.objects.create(branch=b2, domain=dom["zenity-doc"], language=None, full_po=pofile,
part_po=pofile)
pofile = PoFile.objects.create(untranslated=259)
- Statistics.objects.create(branch=b2, domain=dom['zenity-doc'], language=l_fr, full_po=pofile,
part_po=pofile)
+ Statistics.objects.create(branch=b2, domain=dom["zenity-doc"], language=l_fr, full_po=pofile,
part_po=pofile)
pofile = PoFile.objects.create(translated=259)
- Statistics.objects.create(branch=b2, domain=dom['zenity-doc'], language=l_it, full_po=pofile,
part_po=pofile)
+ Statistics.objects.create(branch=b2, domain=dom["zenity-doc"], language=l_it, full_po=pofile,
part_po=pofile)
pofile = PoFile.objects.create(untranslated=149)
stat1 = Statistics.objects.create(
- branch=b3, domain=dom['zenity-ui'], language=None, full_po=pofile, part_po=pofile
+ branch=b3, domain=dom["zenity-ui"], language=None, full_po=pofile, part_po=pofile
)
pofile = PoFile.objects.create(translated=255, fuzzy=4)
- Statistics.objects.create(branch=b3, domain=dom['zenity-ui'], language=l_fr, full_po=pofile,
part_po=pofile)
+ Statistics.objects.create(branch=b3, domain=dom["zenity-ui"], language=l_fr, full_po=pofile,
part_po=pofile)
pofile = PoFile.objects.create(translated=259)
- Statistics.objects.create(branch=b3, domain=dom['zenity-ui'], language=l_it, full_po=pofile,
part_po=pofile)
+ Statistics.objects.create(branch=b3, domain=dom["zenity-ui"], language=l_it, full_po=pofile,
part_po=pofile)
pofile = PoFile.objects.create(untranslated=259)
- Statistics.objects.create(branch=b3, domain=dom['zenity-doc'], language=None, full_po=pofile,
part_po=pofile)
+ Statistics.objects.create(branch=b3, domain=dom["zenity-doc"], language=None, full_po=pofile,
part_po=pofile)
pofile = PoFile.objects.create(untranslated=259)
- Statistics.objects.create(branch=b3, domain=dom['zenity-doc'], language=l_fr, full_po=pofile,
part_po=pofile)
+ Statistics.objects.create(branch=b3, domain=dom["zenity-doc"], language=l_fr, full_po=pofile,
part_po=pofile)
pofile = PoFile.objects.create(translated=259)
- Statistics.objects.create(branch=b3, domain=dom['zenity-doc'], language=l_it, full_po=pofile,
part_po=pofile)
+ Statistics.objects.create(branch=b3, domain=dom["zenity-doc"], language=l_it, full_po=pofile,
part_po=pofile)
# shared-mime-info ui (POT, fr, it)
pofile = PoFile.objects.create(untranslated=626)
Statistics.objects.create(
- branch=b4, domain=dom['shared-mime-info-ui'], language=None, full_po=pofile, part_po=pofile
+ branch=b4, domain=dom["shared-mime-info-ui"], language=None, full_po=pofile, part_po=pofile
)
pofile = PoFile.objects.create(translated=598, fuzzy=20, untranslated=2)
Statistics.objects.create(
- branch=b4, domain=dom['shared-mime-info-ui'], language=l_fr, full_po=pofile, part_po=pofile
+ branch=b4, domain=dom["shared-mime-info-ui"], language=l_fr, full_po=pofile, part_po=pofile
)
pofile = PoFile.objects.create(translated=620, fuzzy=6)
Statistics.objects.create(
- branch=b4, domain=dom['shared-mime-info-ui'], language=l_it, full_po=pofile, part_po=pofile
+ branch=b4, domain=dom["shared-mime-info-ui"], language=l_it, full_po=pofile, part_po=pofile
)
# Example of error
stat1.information_set.add(
Information(
- type='error',
+ type="error",
description=(
"Error regenerating POT file for zenity:\n<pre>intltool-update -g 'zenity' -p\nERROR:"
" xgettext failed to generate PO template file.</pre>"
@@ -239,10 +246,10 @@ class FixtureFactory(TestCase):
# Output fixture
out_file = NamedTemporaryFile(suffix=".json", dir=".", delete=False)
call_command(
- 'dumpdata',
- *['auth.User', 'people', 'teams', 'languages', 'stats'],
+ "dumpdata",
+ *["auth.User", "people", "teams", "languages", "stats"],
indent=1,
- format='json',
+ format="json",
stdout=out_file
)
out_file.close()
diff --git a/stats/tests/tests.py b/stats/tests/tests.py
index d9670caa..6ee4b4f5 100644
--- a/stats/tests/tests.py
+++ b/stats/tests/tests.py
@@ -21,9 +21,20 @@ from languages.models import Language
from people.models import Person
from stats import utils
from stats.models import (
- GLIB_PRESET, Branch, CategoryName, Domain, FakeLangStatistics, Information,
- Module, ModuleLock, PoFile, Release, Statistics, UnableToCommit
+ GLIB_PRESET,
+ Branch,
+ CategoryName,
+ Domain,
+ FakeLangStatistics,
+ Information,
+ Module,
+ ModuleLock,
+ PoFile,
+ Release,
+ Statistics,
+ UnableToCommit,
)
+
from .utils import PatchShellCommand, test_scratchdir
try:
@@ -35,10 +46,10 @@ except ImportError:
class ModuleTestCase(TestCase):
- fixtures = ['sample_data.json']
+ fixtures = ["sample_data.json"]
SYS_DEPENDENCIES = (
- ('gettext', 'msgfmt'),
- ('intltool', 'intltool-update'),
+ ("gettext", "msgfmt"),
+ ("intltool", "intltool-update"),
)
@classmethod
@@ -54,15 +65,15 @@ class ModuleTestCase(TestCase):
self.branch = self.mod.branch_set.get(name="master")
def test_module_methods(self):
- self.assertEqual(self.mod.get_description(), 'gnome-hello')
+ self.assertEqual(self.mod.get_description(), "gnome-hello")
def test_module_comparable(self):
- modules = [Module.objects.get(name='zenity'), Module.objects.get(name='gnome-hello')]
- self.assertEqual(sorted(modules)[0].name, 'gnome-hello')
+ modules = [Module.objects.get(name="zenity"), Module.objects.get(name="gnome-hello")]
+ self.assertEqual(sorted(modules)[0].name, "gnome-hello")
def test_clean_module_locks(self):
class CustomModuleLock(ModuleLock):
- dir_prefix = 'updating-test-'
+ dir_prefix = "updating-test-"
module = Module.objects.get(name="gnome-hello")
with CustomModuleLock(module) as lock:
@@ -79,87 +90,77 @@ class ModuleTestCase(TestCase):
mocked_sleep.assert_called_once()
def test_modules_list(self):
- response = self.client.get(reverse('modules'))
+ response = self.client.get(reverse("modules"))
self.assertContains(response, '<li><a href="/module/zenity/">zenity</a></li>')
# Also test JSON output
- response = self.client.get(reverse('modules', args=['json']))
- content = json.loads(response.content.decode('utf-8'))
+ response = self.client.get(reverse("modules", args=["json"]))
+ content = json.loads(response.content.decode("utf-8"))
self.assertEqual(len(content), Module.objects.count())
- self.assertEqual(
- content[-1]["fields"]["vcs_root"],
- 'https://gitlab.gnome.org/GNOME/zenity.git'
- )
+ self.assertEqual(content[-1]["fields"]["vcs_root"], "https://gitlab.gnome.org/GNOME/zenity.git")
def test_module_branch(self):
- response = self.client.get(reverse('module_branch', args=['gnome-hello', 'master']))
+ response = self.client.get(reverse("module_branch", args=["gnome-hello", "master"]))
self.assertContains(response, '<table class="stats table">')
# Missing branch
- with self.assertLogs('django.request', level='WARNING'):
- response = self.client.get(reverse('module_branch', args=['gnome-hello', 'missing']))
+ with self.assertLogs("django.request", level="WARNING"):
+ response = self.client.get(reverse("module_branch", args=["gnome-hello", "missing"]))
self.assertEqual(response.status_code, 404)
# Branch name with slash is reverse-able
- with self.assertLogs('django.request', level='WARNING'):
- response = self.client.get(reverse('module_branch', args=['gnome-hello', 'with/slash']))
+ with self.assertLogs("django.request", level="WARNING"):
+ response = self.client.get(reverse("module_branch", args=["gnome-hello", "with/slash"]))
self.assertEqual(response.status_code, 404)
def test_module_bugs_reporting(self):
self.assertEqual(
self.mod.get_bugs_i18n_url(),
- 'https://gitlab.gnome.org/GNOME/gnome-hello/issues/?state=opened&label_name[]=8.%20Translation'
+ "https://gitlab.gnome.org/GNOME/gnome-hello/issues/?state=opened&label_name[]=8.%20Translation",
)
self.assertEqual(
- self.mod.get_bugs_i18n_url('pot error'),
- 'https://gitlab.gnome.org/GNOME/gnome-hello/issues/?state=opened&label_name[]'
- '=8.%20Translation&search=pot+error'
+ self.mod.get_bugs_i18n_url("pot error"),
+ "https://gitlab.gnome.org/GNOME/gnome-hello/issues/?state=opened&label_name[]"
+ "=8.%20Translation&search=pot+error",
)
- response = self.client.get(reverse('module', args=[self.mod.name]))
+ response = self.client.get(reverse("module", args=[self.mod.name]))
self.assertContains(
response,
'<li><a href="https://gitlab.gnome.org/GNOME/gnome-hello/issues/new">Report a bug</a></li>',
- html=True
+ html=True,
)
- self.mod.bugs_base = ''
+ self.mod.bugs_base = ""
self.mod.save()
- response = self.client.get(reverse('module', args=[self.mod.name]))
+ response = self.client.get(reverse("module", args=[self.mod.name]))
self.assertContains(response, "Sorry, no known locations to report bugs for this module.")
@test_scratchdir
def test_first_branch_creation(self):
- mod = Module.objects.create(name='eog', vcs_type='git',
vcs_root='https://gitlab.gnome.org/GNOME/eog.git')
- br = Branch(module=mod, name='master')
+ mod = Module.objects.create(name="eog", vcs_type="git",
vcs_root="https://gitlab.gnome.org/GNOME/eog.git")
+ br = Branch(module=mod, name="master")
with PatchShellCommand() as cmds:
br.checkout()
self.assertTrue(
- 'git clone https://gitlab.gnome.org/GNOME/eog.git %s/git/eog' % settings.SCRATCHDIR in cmds,
- "%s is not the expected 'git clone' command." % cmds[0]
+ "git clone https://gitlab.gnome.org/GNOME/eog.git %s/git/eog" % settings.SCRATCHDIR in cmds,
+ "%s is not the expected 'git clone' command." % cmds[0],
)
def test_head_branch(self):
- self.assertEqual(self.mod.get_head_branch().name, 'master')
- Branch.objects.filter(module=self.mod, name='master').delete()
- Branch.objects.bulk_create([
- Branch(module=self.mod, name='gnome-3-18'),
- Branch(module=self.mod, name='gnome-41'),
- Branch(module=self.mod, name='gnome-2-1'),
- ])
- self.assertEqual(self.mod.get_head_branch().name, 'gnome-41')
+ self.assertEqual(self.mod.get_head_branch().name, "master")
+ Branch.objects.filter(module=self.mod, name="master").delete()
+ Branch.objects.bulk_create(
+ [
+ Branch(module=self.mod, name="gnome-3-18"),
+ Branch(module=self.mod, name="gnome-41"),
+ Branch(module=self.mod, name="gnome-2-1"),
+ ]
+ )
+ self.assertEqual(self.mod.get_head_branch().name, "gnome-41")
def test_branch_methods(self):
- branch = Branch(module=self.mod, name='master')
+ branch = Branch(module=self.mod, name="master")
self.assertTrue(branch.is_head())
- self.assertEqual(
- branch.get_vcs_web_url(),
- "https://gitlab.gnome.org/GNOME/gnome-hello/"
- )
- self.assertEqual(
- branch.get_vcs_web_log_url(),
- 'https://gitlab.gnome.org/GNOME/gnome-hello/commits/master'
- )
- branch.name = 'gnome-3-30'
- self.assertEqual(
- branch.get_vcs_web_log_url(),
- 'https://gitlab.gnome.org/GNOME/gnome-hello/commits/gnome-3-30'
- )
+ self.assertEqual(branch.get_vcs_web_url(), "https://gitlab.gnome.org/GNOME/gnome-hello/")
+ self.assertEqual(branch.get_vcs_web_log_url(),
"https://gitlab.gnome.org/GNOME/gnome-hello/commits/master")
+ branch.name = "gnome-3-30"
+ self.assertEqual(branch.get_vcs_web_log_url(),
"https://gitlab.gnome.org/GNOME/gnome-hello/commits/gnome-3-30")
def test_branch_domains(self):
"""
@@ -167,37 +168,37 @@ class ModuleTestCase(TestCase):
based on the domain branch_form/branch_to fields.
"""
domains = self.branch.get_domains()
- self.assertEqual(list(domains.keys()), ['po', 'help'])
+ self.assertEqual(list(domains.keys()), ["po", "help"])
- b3 = Branch(name='gnome-3-3', module=self.mod)
+ b3 = Branch(name="gnome-3-3", module=self.mod)
b3.save(update_statistics=False)
- b5 = Branch(name='gnome-3-5', module=self.mod)
+ b5 = Branch(name="gnome-3-5", module=self.mod)
b5.save(update_statistics=False)
- b7 = Branch(name='gnome-3-7', module=self.mod)
+ b7 = Branch(name="gnome-3-7", module=self.mod)
b7.save(update_statistics=False)
- help_domain = domains['help']
+ help_domain = domains["help"]
help_domain.branch_from = b5
help_domain.save()
- self.assertEqual(list(b3.get_domains().keys()), ['po'])
+ self.assertEqual(list(b3.get_domains().keys()), ["po"])
self.assertFalse(b3.has_domain(help_domain))
- self.assertTrue(b3.has_domain(domains['po']))
- self.assertEqual(list(b5.get_domains().keys()), ['po', 'help'])
+ self.assertTrue(b3.has_domain(domains["po"]))
+ self.assertEqual(list(b5.get_domains().keys()), ["po", "help"])
help_domain.branch_to = b7
help_domain.save()
- self.assertEqual(list(self.branch.get_domains().keys()), ['po'])
+ self.assertEqual(list(self.branch.get_domains().keys()), ["po"])
@test_scratchdir
def test_branch_stats(self):
- lang = Language.objects.create(name='xxx', locale='xxx')
+ lang = Language.objects.create(name="xxx", locale="xxx")
ghost_stat = Statistics.objects.create(
- branch=self.branch, domain=self.mod.domain_set.get(name='po'), language=lang
+ branch=self.branch, domain=self.mod.domain_set.get(name="po"), language=lang
)
# Check stats
- with PatchShellCommand(only=['git ']):
+ with PatchShellCommand(only=["git "]):
self.branch.update_stats(force=True)
- fr_po_stat = Statistics.objects.get(branch=self.branch, domain__name='po', language__locale='fr')
+ fr_po_stat = Statistics.objects.get(branch=self.branch, domain__name="po", language__locale="fr")
self.assertEqual(fr_po_stat.translated(), 44)
- self.assertEqual(fr_po_stat.translated(scope='part'), 44)
+ self.assertEqual(fr_po_stat.translated(scope="part"), 44)
self.assertEqual(fr_po_stat.translated_words(), 131)
self.assertEqual(fr_po_stat.full_po, fr_po_stat.part_po)
self.assertEqual(fr_po_stat.pot_url(), "/POT/gnome-hello.master/gnome-hello.master.pot")
@@ -205,21 +206,23 @@ class ModuleTestCase(TestCase):
self.assertEqual(fr_po_stat.po_url(reduced=True),
"/POT/gnome-hello.master/gnome-hello.master.fr.reduced.po")
# Partial translated file
- it_po_stat = Statistics.objects.get(branch=self.branch, domain__name='po', language__locale='it')
+ it_po_stat = Statistics.objects.get(branch=self.branch, domain__name="po", language__locale="it")
self.assertEqual(it_po_stat.translated(), 9)
self.assertEqual(it_po_stat.full_po, it_po_stat.part_po)
# Committed file with less strings that merged one.
- de_po_stat = Statistics.objects.get(branch=self.branch, domain__name='po', language__locale='de')
+ de_po_stat = Statistics.objects.get(branch=self.branch, domain__name="po", language__locale="de")
self.assertEqual(de_po_stat.information_set.count(), 1)
- self.assertTrue(de_po_stat.information_set.first().description.startswith(
- 'The currently committed file has less translated strings'
- ))
+ self.assertTrue(
+ de_po_stat.information_set.first().description.startswith(
+ "The currently committed file has less translated strings"
+ )
+ )
- pot_doc_stat = Statistics.objects.get(branch=self.branch, domain__name='help', language=None)
+ pot_doc_stat = Statistics.objects.get(branch=self.branch, domain__name="help", language=None)
self.assertEqual(pot_doc_stat.untranslated(), 43)
self.assertEqual(len(pot_doc_stat.full_po.figures), 2)
- fr_doc_stat = Statistics.objects.get(branch=self.branch, domain__name='help', language__locale='fr')
+ fr_doc_stat = Statistics.objects.get(branch=self.branch, domain__name="help", language__locale="fr")
# Depending on the itstool version, the "GNOME Hello" string may or may
# not have a msgctxt marker. Allowing both here.
self.assertIn(fr_doc_stat.fuzzy(), [1, 2])
@@ -229,32 +232,32 @@ class ModuleTestCase(TestCase):
with self.assertRaises(Statistics.DoesNotExist):
Statistics.objects.get(pk=ghost_stat.pk)
- stats = self.branch.get_stats('doc', mandatory_langs=[lang])
- self.assertIn('help', stats)
- self.assertTrue(stats['help'][0].is_pot_stats()) # POT first
- self.assertEqual(stats['help'][-1].language.locale, 'xxx') # Fake stat last
+ stats = self.branch.get_stats("doc", mandatory_langs=[lang])
+ self.assertIn("help", stats)
+ self.assertTrue(stats["help"][0].is_pot_stats()) # POT first
+ self.assertEqual(stats["help"][-1].language.locale, "xxx") # Fake stat last
@test_scratchdir
def test_branch_exists(self):
- branch = Branch.objects.get(name='master', module__name='zenity')
+ branch = Branch.objects.get(name="master", module__name="zenity")
self.assertFalse(branch._repo.exists())
- branch = Branch.objects.get(name='master', module__name='gnome-hello')
+ branch = Branch.objects.get(name="master", module__name="gnome-hello")
self.assertTrue(branch._repo.exists())
@test_scratchdir
def test_rename_branch(self):
- branch = Branch.objects.get(name='master', module__name='gnome-hello')
- _, out, _ = run_shell_command(['git', 'branch', '--list'], cwd=branch.co_path)
- self.assertNotIn('gnome-hello-1-4', out)
- branch.name = 'gnome-hello-1-4'
+ branch = Branch.objects.get(name="master", module__name="gnome-hello")
+ _, out, _ = run_shell_command(["git", "branch", "--list"], cwd=branch.co_path)
+ self.assertNotIn("gnome-hello-1-4", out)
+ branch.name = "gnome-hello-1-4"
branch.save(update_statistics=False)
self.branch.co_path
- _, out, _ = run_shell_command(['git', 'branch', '--list'], cwd=branch.co_path)
- self.assertIn('gnome-hello-1-4', out)
+ _, out, _ = run_shell_command(["git", "branch", "--list"], cwd=branch.co_path)
+ self.assertIn("gnome-hello-1-4", out)
@test_scratchdir
def test_delete_branch(self):
- """ Deleting the master branch of a git module deletes the checkout dir """
+ """Deleting the master branch of a git module deletes the checkout dir"""
checkout_path = self.branch.co_path
branch = Branch(module=self.mod, name="gnome-hello-1-4")
branch.save(update_statistics=False)
@@ -266,216 +269,202 @@ class ModuleTestCase(TestCase):
def test_create_unexisting_branch(self):
"""Creating a non-existing branch should raise a ValidationError."""
Branch.checkout_on_creation = True
- with patch('stats.tests.tests.Branch.checkout', side_effect=OSError()):
+ with patch("stats.tests.tests.Branch.checkout", side_effect=OSError()):
branch = Branch(name="trunk2", module=self.mod)
self.assertRaises(ValidationError, branch.clean)
def test_edit_branches_form(self):
- coord = User.objects.get(username='coord')
- coord.set_password('coord')
+ coord = User.objects.get(username="coord")
+ coord.set_password("coord")
coord.save()
- self.client.login(username='coord', password='coord')
- response = self.client.get(reverse('module_edit_branches', args=[self.mod]), follow=True)
+ self.client.login(username="coord", password="coord")
+ response = self.client.get(reverse("module_edit_branches", args=[self.mod]), follow=True)
self.assertContains(response, "you're not allowed to edit")
# Test now with appropriate permissions
coord.is_superuser = True
coord.save()
# Duplicate data
- cat_name = CategoryName.objects.get(name='Desktop')
+ cat_name = CategoryName.objects.get(name="Desktop")
response = self.client.post(
- reverse('module_edit_branches', args=[self.mod]), {
- '1': '1', '1_cat': str(cat_name.pk),
- '2': '2', '2_cat': str(cat_name.pk),
- 'new_branch': 'master', 'new_branch_release': '2', 'new_branch_category': str(cat_name.pk)
- }
+ reverse("module_edit_branches", args=[self.mod]),
+ {
+ "1": "1",
+ "1_cat": str(cat_name.pk),
+ "2": "2",
+ "2_cat": str(cat_name.pk),
+ "new_branch": "master",
+ "new_branch_release": "2",
+ "new_branch_category": str(cat_name.pk),
+ },
)
self.assertContains(response, "the form is not valid")
def test_branch_sorting(self):
- b1 = Branch(name='a-branch', module=self.mod)
+ b1 = Branch(name="a-branch", module=self.mod)
b1.save(update_statistics=False)
- b2 = Branch(name='p-branch', module=self.mod)
+ b2 = Branch(name="p-branch", module=self.mod)
b2.save(update_statistics=False)
- self.assertEqual(
- [b.name for b in sorted(self.mod.branch_set.all())],
- ['master', 'p-branch', 'a-branch']
- )
+ self.assertEqual([b.name for b in sorted(self.mod.branch_set.all())], ["master", "p-branch",
"a-branch"])
b1.weight = -1
b1.save(update_statistics=False)
- self.assertEqual(
- [b.name for b in sorted(self.mod.branch_set.all())],
- ['master', 'a-branch', 'p-branch']
- )
- self.assertEqual(
- [b.name for b in self.mod.get_branches(reverse=True)],
- ['p-branch', 'a-branch', 'master']
- )
+ self.assertEqual([b.name for b in sorted(self.mod.branch_set.all())], ["master", "a-branch",
"p-branch"])
+ self.assertEqual([b.name for b in self.mod.get_branches(reverse=True)], ["p-branch", "a-branch",
"master"])
# "Clever" sorting of alphanumeric names
- b3 = Branch(name='gnome-3-2', module=self.mod)
+ b3 = Branch(name="gnome-3-2", module=self.mod)
b3.save(update_statistics=False)
- b4 = Branch(name='gnome-3-10', module=self.mod)
+ b4 = Branch(name="gnome-3-10", module=self.mod)
b4.save(update_statistics=False)
- b5 = Branch(name='gnome-3-20', module=self.mod)
+ b5 = Branch(name="gnome-3-20", module=self.mod)
b5.save(update_statistics=False)
self.assertEqual(
[b.name for b in sorted(self.mod.branch_set.all())],
- ['master', 'a-branch', 'p-branch', 'gnome-3-20', 'gnome-3-10', 'gnome-3-2']
+ ["master", "a-branch", "p-branch", "gnome-3-20", "gnome-3-10", "gnome-3-2"],
)
@test_scratchdir
def test_string_frozen_mail(self):
- """ String change for a module of a string_frozen release should generate a message """
+ """String change for a module of a string_frozen release should generate a message"""
mail.outbox = []
- with PatchShellCommand(only=['git ']):
+ with PatchShellCommand(only=["git "]):
self.branch.update_stats(force=False)
# Create a new file with translation
new_file_path = self.branch.co_path / "dummy_file.py"
new_string = "Dummy string for D-L tests"
- with new_file_path.open('w') as fh:
+ with new_file_path.open("w") as fh:
fh.write("a = _('%s')\n" % new_string)
# Add the new file to POTFILES.in
- with (self.branch.co_path / "po" / "POTFILES.in").open('a') as fh:
+ with (self.branch.co_path / "po" / "POTFILES.in").open("a") as fh:
fh.write("dummy_file.py\n")
# Regenerate stats (mail should be sent)
self.branch.update_stats(force=False, checkout=False)
# Assertions
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].subject, "[Damned Lies] String additions to 'gnome-hello.master'")
- self.assertEqual(mail.outbox[0].extra_headers, {settings.EMAIL_HEADER_NAME: 'stringchange'})
+ self.assertEqual(mail.outbox[0].extra_headers, {settings.EMAIL_HEADER_NAME: "stringchange"})
self.assertIn('"%s"' % new_string, mail.outbox[0].body)
@test_scratchdir
def test_dynamic_po(self):
- """ Test the creation of a blank po file for a new language """
+ """Test the creation of a blank po file for a new language"""
tamil = Language.objects.create(name="Tamil", locale="ta")
- with PatchShellCommand(only=['git ']):
+ with PatchShellCommand(only=["git "]):
self.branch.update_stats(force=False) # At least POT stats needed
- pot_stats = Statistics.objects.get(
- branch=self.branch, domain__name='po', language__isnull=True
- )
+ pot_stats = Statistics.objects.get(branch=self.branch, domain__name="po", language__isnull=True)
dyn_url = FakeLangStatistics(pot_stats, tamil).po_url()
response = self.client.get(dyn_url)
- self.assertEqual(
- response['content-disposition'],
- 'inline; filename=gnome-hello.master.ta.po'
- )
- self.assertContains(response, """# Tamil translation for gnome-hello.
+ self.assertEqual(response["content-disposition"], "inline; filename=gnome-hello.master.ta.po")
+ self.assertContains(
+ response,
+ """# Tamil translation for gnome-hello.
# Copyright (C) %s gnome-hello's COPYRIGHT HOLDER
# This file is distributed under the same license as the gnome-hello package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.""" % date.today().year)
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR."""
+ % date.today().year,
+ )
self.assertContains(response, "Language-Team: Tamil <ta li org>")
dyn_url = FakeLangStatistics(pot_stats, tamil).po_url(reduced=True)
response = self.client.get(dyn_url)
- self.assertEqual(
- response['content-disposition'],
- 'inline; filename=gnome-hello.master.ta-reduced.po'
- )
- self.assertContains(response, '# Tamil translation for gnome-hello.')
+ self.assertEqual(response["content-disposition"], "inline;
filename=gnome-hello.master.ta-reduced.po")
+ self.assertContains(response, "# Tamil translation for gnome-hello.")
@test_scratchdir
def test_dynamic_po_msginit_error(self):
tamil = Language.objects.create(name="Tamil", locale="ta")
- with PatchShellCommand(only=['git ']):
+ with PatchShellCommand(only=["git "]):
self.branch.update_stats(force=False) # At least POT stats needed
- pot_stats = Statistics.objects.get(
- branch=self.branch, domain__name='po', language__isnull=True
- )
+ pot_stats = Statistics.objects.get(branch=self.branch, domain__name="po", language__isnull=True)
dyn_url = FakeLangStatistics(pot_stats, tamil).po_url()
- with patch('stats.views.run_shell_command', side_effect=OSError()):
+ with patch("stats.views.run_shell_command", side_effect=OSError()):
response = self.client.get(dyn_url)
- self.assertContains(
- response,
- "Unable to produce a new .po file for language ta"
- )
+ self.assertContains(response, "Unable to produce a new .po file for language ta")
@test_scratchdir
def test_commit_ability(self):
branch = self.mod.get_head_branch()
- domain = self.mod.domain_set.get(name='po')
- fr_lang = Language.objects.get(locale='fr')
+ domain = self.mod.domain_set.get(name="po")
+ fr_lang = Language.objects.get(locale="fr")
with self.assertRaises(UnableToCommit):
# read-only VCS
domain.commit_info(branch, fr_lang)
# Setup as a writable repo
- self.mod.vcs_root = 'git gitlab gnome org:GNOME/%s.git' % self.mod.name
- linguas = branch.co_path / 'po' / 'LINGUAS'
+ self.mod.vcs_root = "git gitlab gnome org:GNOME/%s.git" % self.mod.name
+ linguas = branch.co_path / "po" / "LINGUAS"
os.remove(str(linguas))
- bem_lang = Language.objects.get(locale='bem')
+ bem_lang = Language.objects.get(locale="bem")
with self.assertRaises(UnableToCommit):
# unaccessible LINGUAS file for a new lang
domain.commit_info(branch, bem_lang)
- domain.linguas_location = 'no'
- self.assertEqual(
- domain.commit_info(branch, bem_lang),
- (branch.co_path / 'po' / 'bem.po', False, None)
- )
+ domain.linguas_location = "no"
+ self.assertEqual(domain.commit_info(branch, bem_lang), (branch.co_path / "po" / "bem.po", False,
None))
- self.assertEqual(
- domain.commit_info(branch, fr_lang),
- (branch.co_path / 'po' / 'fr.po', True, None)
- )
+ self.assertEqual(domain.commit_info(branch, fr_lang), (branch.co_path / "po" / "fr.po", True, None))
@test_scratchdir
def test_commit_po(self):
branch = self.mod.get_head_branch()
- po_file = Path(__file__).parent / 'test.po'
- domain = self.mod.domain_set.get(name='po')
- fr_lang = Language.objects.get(locale='fr')
+ po_file = Path(__file__).parent / "test.po"
+ domain = self.mod.domain_set.get(name="po")
+ fr_lang = Language.objects.get(locale="fr")
with PatchShellCommand():
- with self.assertRaisesRegex(UnableToCommit, 'read only'):
- with self.assertLogs('stats.models', level='ERROR'):
- branch.commit_po(po_file, domain, fr_lang, 'Author <someone example org>')
+ with self.assertRaisesRegex(UnableToCommit, "read only"):
+ with self.assertLogs("stats.models", level="ERROR"):
+ branch.commit_po(po_file, domain, fr_lang, "Author <someone example org>")
# Setup as a writable repo
- self.mod.vcs_root = 'git gitlab gnome org:GNOME/%s.git' % self.mod.name
+ self.mod.vcs_root = "git gitlab gnome org:GNOME/%s.git" % self.mod.name
self.mod.save()
update_repo_sequence = (
- 'git checkout -f master', 'git fetch', 'git reset --hard origin/master',
- 'git clean -dfq', 'if [ -e .gitmodules ]; then git submodule update --init; fi',
+ "git checkout -f master",
+ "git fetch",
+ "git reset --hard origin/master",
+ "git clean -dfq",
+ "if [ -e .gitmodules ]; then git submodule update --init; fi",
)
# User interface (existing language)
git_ops = update_repo_sequence + (
- 'git add po/fr.po',
+ "git add po/fr.po",
# Quoting is done at the Popen level
- 'git commit -m Update French translation --author Author <someone example org>',
- 'git push origin master', 'git log -n1 --format=oneline',
+ "git commit -m Update French translation --author Author <someone example org>",
+ "git push origin master",
+ "git log -n1 --format=oneline",
)
with PatchShellCommand() as cmds:
- branch.commit_po(po_file, domain, fr_lang, 'Author <someone example org>')
+ branch.commit_po(po_file, domain, fr_lang, "Author <someone example org>")
for idx, cmd in enumerate(git_ops):
self.assertIn(cmd, cmds[idx])
# User interface (new language)
- bem_lang = Language.objects.get(locale='bem')
+ bem_lang = Language.objects.get(locale="bem")
git_ops = update_repo_sequence + (
- 'git add po/bem.po', 'git add po/LINGUAS',
- 'git commit -m Add Bemba translation --author Author <someone example org>',
- 'git push origin master'
+ "git add po/bem.po",
+ "git add po/LINGUAS",
+ "git commit -m Add Bemba translation --author Author <someone example org>",
+ "git push origin master",
)
with PatchShellCommand() as cmds:
- with self.assertLogs('stats.models', level='ERROR'):
- branch.commit_po(po_file, domain, bem_lang, 'Author <someone example org>')
+ with self.assertLogs("stats.models", level="ERROR"):
+ branch.commit_po(po_file, domain, bem_lang, "Author <someone example org>")
for idx, cmd in enumerate(git_ops):
self.assertIn(cmd, cmds[idx])
- self.assertIn('intltool-update -g', cmds[-1])
+ self.assertIn("intltool-update -g", cmds[-1])
# Check lang has been inserted in LINGUAS file, before 'de'
- self.assertIn('bem\nde', (branch.domain_path(domain) / 'LINGUAS').read_text())
+ self.assertIn("bem\nde", (branch.domain_path(domain) / "LINGUAS").read_text())
# Documentation
- domain = self.mod.domain_set.get(name='help')
+ domain = self.mod.domain_set.get(name="help")
git_ops = update_repo_sequence + (
- 'git add help/fr/fr.po',
- 'git commit -m Update French translation --author Author <someone example org>',
- 'git push origin master'
+ "git add help/fr/fr.po",
+ "git commit -m Update French translation --author Author <someone example org>",
+ "git push origin master",
)
with PatchShellCommand() as cmds:
- branch.commit_po(po_file, domain, fr_lang, 'Author <someone example org>')
+ branch.commit_po(po_file, domain, fr_lang, "Author <someone example org>")
for idx, cmd in enumerate(git_ops):
self.assertIn(cmd, cmds[idx])
@@ -486,42 +475,47 @@ class ModuleTestCase(TestCase):
cherry-pick the branch commit on the master branch.
"""
# Setup as a writable repo
- self.mod.vcs_root = 'git gitlab gnome org:GNOME/%s.git' % self.mod.name
+ self.mod.vcs_root = "git gitlab gnome org:GNOME/%s.git" % self.mod.name
self.mod.save()
- domain = self.mod.domain_set.get(name='po')
+ domain = self.mod.domain_set.get(name="po")
commit_dir = self.branch.co_path
run_shell_command(
- ['git', 'config', 'user.name', 'John Doe'], cwd=commit_dir,
+ ["git", "config", "user.name", "John Doe"],
+ cwd=commit_dir,
)
run_shell_command(
- ['git', 'config', 'user.email', 'john example org'], cwd=commit_dir,
+ ["git", "config", "user.email", "john example org"],
+ cwd=commit_dir,
)
run_shell_command(
- ['git', 'checkout', '-b', 'gnome-3-18', 'origin/master'], cwd=commit_dir, raise_on_error=True
+ ["git", "checkout", "-b", "gnome-3-18", "origin/master"], cwd=commit_dir, raise_on_error=True
)
branch = Branch(module=self.mod, name="gnome-3-18")
branch.save(update_statistics=False)
- fr_lang = Language.objects.get(locale='fr')
+ fr_lang = Language.objects.get(locale="fr")
# Copy stats from master
stat = Statistics.objects.get(language=fr_lang, branch=self.branch, domain=domain)
stat.pk, stat.full_po, stat.part_po = None, None, None
stat.branch = branch
stat.save()
- po_file = Path(__file__).parent / 'test.po'
- self.mod.vcs_root = self.mod.vcs_root.replace('git://', 'ssh://')
+ po_file = Path(__file__).parent / "test.po"
+ self.mod.vcs_root = self.mod.vcs_root.replace("git://", "ssh://")
self.mod.save()
- with PatchShellCommand(only=['git push', 'git fetch', 'git reset']) as cmds:
- commit_hash = branch.commit_po(po_file, domain, fr_lang, 'Piotr Drąg <piotr example org>')
+ with PatchShellCommand(only=["git push", "git fetch", "git reset"]) as cmds:
+ commit_hash = branch.commit_po(po_file, domain, fr_lang, "Piotr Drąg <piotr example org>")
update_repo_sequence = (
- 'git checkout -f master', 'git fetch', 'git reset --hard origin/master',
- 'git clean -dfq', 'if [ -e .gitmodules ]; then git submodule update --init; fi',
+ "git checkout -f master",
+ "git fetch",
+ "git reset --hard origin/master",
+ "git clean -dfq",
+ "if [ -e .gitmodules ]; then git submodule update --init; fi",
)
git_ops = update_repo_sequence + (
- 'git cherry-pick -x',
- 'git push origin master',
+ "git cherry-pick -x",
+ "git push origin master",
)
- with PatchShellCommand(only=['git push', 'git fetch', 'git reset']) as cmds:
+ with PatchShellCommand(only=["git push", "git fetch", "git reset"]) as cmds:
self.branch.cherrypick_commit(commit_hash, domain)
for idx, cmd in enumerate(git_ops):
self.assertIn(cmd, cmds[idx])
@@ -534,20 +528,22 @@ class ModuleTestCase(TestCase):
@test_scratchdir
def test_update_maintainers_from_doap_file(self):
- from stats.doap import update_doap_infos
from people.models import Person
+ from stats.doap import update_doap_infos
+
# Add a maintainer which will be removed
pers = Person(username="toto")
pers.save()
self.mod.maintainers.add(pers)
update_doap_infos(self.mod)
self.assertEqual(self.mod.maintainers.count(), 3)
- claude = self.mod.maintainers.get(email='claude 2xlibre net')
- self.assertEqual(claude.username, 'claudep')
+ claude = self.mod.maintainers.get(email="claude 2xlibre net")
+ self.assertEqual(claude.username, "claudep")
@test_scratchdir
def test_update_doap_infos(self):
from stats.doap import update_doap_infos
+
update_doap_infos(self.mod)
self.assertEqual(self.mod.homepage, "http://git.gnome.org/browse/gnome-hello")
@@ -559,15 +555,16 @@ class TestModuleBase(TestCase):
super().setUpTestData()
Branch.checkout_on_creation = False
cls.mod = Module.objects.create(
- name="testmodule", vcs_type='git',
- bugs_base='https://gitlab.gnome.org/GNOME/testmodule/issues',
+ name="testmodule",
+ vcs_type="git",
+ bugs_base="https://gitlab.gnome.org/GNOME/testmodule/issues",
)
cls.branch = Branch(module=cls.mod, name="master")
cls.branch.save(update_statistics=False)
@classmethod
def tearDownClass(cls):
- html_dir = settings.SCRATCHDIR / 'HTML'
+ html_dir = settings.SCRATCHDIR / "HTML"
if html_dir.exists():
shutil.rmtree(str(html_dir))
super().tearDownClass()
@@ -580,56 +577,67 @@ class DomainTests(TestModuleBase):
def test_get_po_path(self):
# Standard UI
- dom = Domain(module=self.mod, dtype='ui', layout='po/{lang}.po')
- self.assertEqual(dom.get_po_path('fr'), 'po/fr.po')
+ dom = Domain(module=self.mod, dtype="ui", layout="po/{lang}.po")
+ self.assertEqual(dom.get_po_path("fr"), "po/fr.po")
# Standard Doc
- dom.dtype = 'doc'
- dom.layout = 'help/{lang}/{lang}.po'
- self.assertEqual(dom.get_po_path('fr'), 'help/fr/fr.po')
+ dom.dtype = "doc"
+ dom.layout = "help/{lang}/{lang}.po"
+ self.assertEqual(dom.get_po_path("fr"), "help/fr/fr.po")
def test_domain_linguas(self):
- help_domain = Domain(
- module=self.mod, name='help', dtype='doc', layout='help_mallard/{lang}/{lang}.po'
- )
+ help_domain = Domain(module=self.mod, name="help", dtype="doc",
layout="help_mallard/{lang}/{lang}.po")
help_domain.linguas_location = "help_mallard/Makefile.am"
- self.assertEqual(help_domain.get_linguas(self.branch), {
- 'langs': None,
- 'error': 'Entry for this language is not present in ALL_LINGUAS variable in '
- 'help_mallard/Makefile.am file.',
- })
+ self.assertEqual(
+ help_domain.get_linguas(self.branch),
+ {
+ "langs": None,
+ "error": "Entry for this language is not present in ALL_LINGUAS variable in "
+ "help_mallard/Makefile.am file.",
+ },
+ )
help_domain.linguas_location = "help_mallard/Makefile.am#HELP_LINGUAS"
- self.assertEqual(help_domain.get_linguas(self.branch), {
- 'langs': ['es', 'fr'],
- 'error': 'Entry for this language is not present in HELP_LINGUAS variable in '
- 'help_mallard/Makefile.am file.',
- })
- help_domain.linguas_location = ''
- self.assertEqual(help_domain.get_linguas(self.branch), {
- 'langs': ['es', 'fr'],
- 'error': 'DOC_LINGUAS list doesn’t include this language.',
- })
+ self.assertEqual(
+ help_domain.get_linguas(self.branch),
+ {
+ "langs": ["es", "fr"],
+ "error": "Entry for this language is not present in HELP_LINGUAS variable in "
+ "help_mallard/Makefile.am file.",
+ },
+ )
+ help_domain.linguas_location = ""
+ self.assertEqual(
+ help_domain.get_linguas(self.branch),
+ {
+ "langs": ["es", "fr"],
+ "error": "DOC_LINGUAS list doesn’t include this language.",
+ },
+ )
def test_domain_xgettext_command(self):
- domain = Domain(
- module=self.mod, name='po', dtype='ui', layout='po/{lang}.po'
- )
+ domain = Domain(module=self.mod, name="po", dtype="ui", layout="po/{lang}.po")
self.assertEqual(
domain.get_xgettext_command(self.branch),
(
[
- 'xgettext', '--files-from', 'POTFILES.in', '--directory',
- str(settings.SCRATCHDIR / 'git' / 'testmodule'),
- '--from-code', 'utf-8',
- '--add-comments', '--output', 'testmodule.pot'
+ "xgettext",
+ "--files-from",
+ "POTFILES.in",
+ "--directory",
+ str(settings.SCRATCHDIR / "git" / "testmodule"),
+ "--from-code",
+ "utf-8",
+ "--add-comments",
+ "--output",
+ "testmodule.pot",
]
- + list(GLIB_PRESET) +
- [
- '--keyword=Description',
- '--msgid-bugs-address',
- 'https://gitlab.gnome.org/GNOME/testmodule/issues'
+ + list(GLIB_PRESET)
+ + [
+ "--keyword=Description",
+ "--msgid-bugs-address",
+ "https://gitlab.gnome.org/GNOME/testmodule/issues",
],
- {'GETTEXTDATADIRS': os.path.dirname(utils.ITS_DATA_DIR)}
- )
+ {"GETTEXTDATADIRS": os.path.dirname(utils.ITS_DATA_DIR)},
+ ),
)
def test_doc_domain_docbook(self):
@@ -637,50 +645,50 @@ class DomainTests(TestModuleBase):
Test Docbook-style help
"""
domain = Domain.objects.create(
- module=self.mod, name='release-notes', dtype='doc',
- layout='help_docbook/{lang}/{lang}.po'
+ module=self.mod, name="release-notes", dtype="doc", layout="help_docbook/{lang}/{lang}.po"
)
potfile, errs = utils.generate_doc_pot_file(self.branch, domain)
self.assertEqual(errs, [])
doc_format = domain.doc_format(self.branch)
- self.assertEqual(doc_format.format, 'docbook')
+ self.assertEqual(doc_format.format, "docbook")
pot_path = self.branch.domain_path(domain) / "C" / "release-notes.pot"
self.assertTrue(pot_path.exists())
self.addCleanup(os.remove, str(pot_path))
- self.assertIn('#: C/rnlookingforward.xml:11', pot_path.read_text())
+ self.assertIn("#: C/rnlookingforward.xml:11", pot_path.read_text())
res = utils.get_fig_stats(pot_path, doc_format=doc_format)
self.assertEqual(len(res), 1)
- self.assertEqual(res[0]['path'], "figures/rnusers.nautilus.png")
+ self.assertEqual(res[0]["path"], "figures/rnusers.nautilus.png")
def test_doc_domain_mallard(self):
"""
Test Mallard-style help (with itstool)
"""
domain = Domain.objects.create(
- module=self.mod, name='release-notes', dtype='doc',
- layout='help_mallard/{lang}/{lang}.po'
+ module=self.mod, name="release-notes", dtype="doc", layout="help_mallard/{lang}/{lang}.po"
)
potfile, errs = utils.generate_doc_pot_file(self.branch, domain)
self.assertEqual(errs, [])
doc_format = domain.doc_format(self.branch)
- self.assertEqual(potfile.name, 'release-notes.pot')
- self.assertEqual(doc_format.tool, 'itstool')
- self.assertEqual(doc_format.format, 'mallard')
+ self.assertEqual(potfile.name, "release-notes.pot")
+ self.assertEqual(doc_format.tool, "itstool")
+ self.assertEqual(doc_format.format, "mallard")
self.addCleanup(os.remove, str(potfile))
- self.assertIn('#: C/what-is.page:7', potfile.read_text())
+ self.assertIn("#: C/what-is.page:7", potfile.read_text())
res = utils.get_fig_stats(potfile, doc_format=doc_format)
self.assertEqual(len(res), 1)
- self.assertEqual(res[0]['path'], "figures/gnome-hello-logo.png")
- self.assertEqual(res[0]['hash'], "1ae47b7a7c4fbeb1f6bb72c0cf18d389")
+ self.assertEqual(res[0]["path"], "figures/gnome-hello-logo.png")
+ self.assertEqual(res[0]["hash"], "1ae47b7a7c4fbeb1f6bb72c0cf18d389")
def test_http_pot(self):
dom = Domain.objects.create(
- module=self.mod, name='http-po', dtype='ui',
- pot_method='url',
- pot_params='https://l10n.gnome.org/POT/damned-lies.master/damned-lies.master.pot'
+ module=self.mod,
+ name="http-po",
+ dtype="ui",
+ pot_method="url",
+ pot_params="https://l10n.gnome.org/POT/damned-lies.master/damned-lies.master.pot",
)
- with patch('stats.models.request.urlopen') as mock_urlopen:
- with open(Path(__file__).parent / 'empty.pot', mode='rb') as fh:
+ with patch("stats.models.request.urlopen") as mock_urlopen:
+ with open(Path(__file__).parent / "empty.pot", mode="rb") as fh:
mock_urlopen.return_value = fh
potfile, errs = dom.generate_pot_file(self.branch)
self.addCleanup(os.remove, str(potfile))
@@ -688,9 +696,7 @@ class DomainTests(TestModuleBase):
def test_shell_pot(self):
dom = Domain.objects.create(
- module=self.mod, name='shell-po', dtype='ui',
- pot_method='shell',
- pot_params='touch some.pot'
+ module=self.mod, name="shell-po", dtype="ui", pot_method="shell", pot_params="touch some.pot"
)
potfile, errs = dom.generate_pot_file(self.branch)
self.addCleanup(os.remove, str(potfile))
@@ -699,21 +705,27 @@ class DomainTests(TestModuleBase):
@skipUnless(has_translate_subtitle_support, "This test needs translate-toolkit subtitles support.")
def test_subtitles_pot(self):
dom = Domain.objects.create(
- module=self.mod, name='po', dtype='ui', layout='subtitles/po/{lang}.po',
- pot_method='subtitles',
+ module=self.mod,
+ name="po",
+ dtype="ui",
+ layout="subtitles/po/{lang}.po",
+ pot_method="subtitles",
)
potfile, errs = dom.generate_pot_file(self.branch)
self.addCleanup(os.remove, str(potfile))
self.assertTrue(potfile.exists())
self.assertEqual(
dom.get_lang_files(self.branch.co_path),
- [('es', Path('%s/git/testmodule/subtitles/po/es.po' % settings.SCRATCHDIR))]
+ [("es", Path("%s/git/testmodule/subtitles/po/es.po" % settings.SCRATCHDIR))],
)
def test_repository_pot(self):
dom = Domain.objects.create(
- module=self.mod, name='po', dtype='ui', layout='inrepo/{lang}.po',
- pot_method='in_repo',
+ module=self.mod,
+ name="po",
+ dtype="ui",
+ layout="inrepo/{lang}.po",
+ pot_method="in_repo",
)
potfile, errs = dom.generate_pot_file(self.branch)
self.assertEqual(errs, ())
@@ -721,41 +733,40 @@ class DomainTests(TestModuleBase):
class StatisticsTests(TestCase):
- fixtures = ['sample_data.json']
+ fixtures = ["sample_data.json"]
def test_get_global_stats(self):
rel = Release.objects.get(name="gnome-3-8")
stats = rel.get_global_stats()
self.assertEqual(len(stats), 2) # data for French and Italian
fr_stats = stats[0]
- self.assertEqual(fr_stats['lang_name'], 'French')
- self.assertEqual(fr_stats['ui']['translated'], 183)
- self.assertEqual(fr_stats['doc']['untranslated'], 259)
+ self.assertEqual(fr_stats["lang_name"], "French")
+ self.assertEqual(fr_stats["ui"]["translated"], 183)
+ self.assertEqual(fr_stats["doc"]["untranslated"], 259)
def test_total_stats_for_lang(self):
rel = Release.objects.get(name="gnome-3-8")
- total_for_lang = rel.total_for_lang(Language.objects.get(locale='fr'))
- self.assertEqual(total_for_lang['ui']['total'], total_for_lang['ui_part']['total'])
- self.assertTrue(total_for_lang['ui']['untranslated'] == total_for_lang['ui_part']['untranslated'] ==
0)
- total_for_lang = rel.total_for_lang(Language.objects.get(locale='bem'))
- self.assertEqual(total_for_lang['ui']['total'] - 8, total_for_lang['ui_part']['total'])
- self.assertEqual(total_for_lang['ui']['untranslated'], 183)
- self.assertEqual(total_for_lang['ui_part']['untranslated'], 175)
+ total_for_lang = rel.total_for_lang(Language.objects.get(locale="fr"))
+ self.assertEqual(total_for_lang["ui"]["total"], total_for_lang["ui_part"]["total"])
+ self.assertTrue(total_for_lang["ui"]["untranslated"] == total_for_lang["ui_part"]["untranslated"] ==
0)
+ total_for_lang = rel.total_for_lang(Language.objects.get(locale="bem"))
+ self.assertEqual(total_for_lang["ui"]["total"] - 8, total_for_lang["ui_part"]["total"])
+ self.assertEqual(total_for_lang["ui"]["untranslated"], 183)
+ self.assertEqual(total_for_lang["ui_part"]["untranslated"], 175)
# Test that excluded domains are taken into account
- zenity_po = Domain.objects.get(module__name='zenity', name='po')
- zenity_po.branch_from = Branch.objects.get(module__name='zenity', name='master')
+ zenity_po = Domain.objects.get(module__name="zenity", name="po")
+ zenity_po.branch_from = Branch.objects.get(module__name="zenity", name="master")
zenity_po.save()
rel = Release.objects.get(name="gnome-3-8")
- total_for_lang = rel.total_for_lang(Language.objects.get(locale='bem'))
- self.assertLess(total_for_lang['ui']['untranslated'], 183)
+ total_for_lang = rel.total_for_lang(Language.objects.get(locale="bem"))
+ self.assertLess(total_for_lang["ui"]["untranslated"], 183)
@test_scratchdir
def test_update_po_file(self):
stats = Statistics.objects.get(
- branch__module__name='zenity', branch__name='gnome-3-8',
- domain__name='po', language__locale='it'
+ branch__module__name="zenity", branch__name="gnome-3-8", domain__name="po", language__locale="it"
)
- stats.full_po = PoFile.objects.create(path='git/gnome-hello/po/it.po')
+ stats.full_po = PoFile.objects.create(path="git/gnome-hello/po/it.po")
stats.save()
stats.full_po.update_stats()
self.assertEqual(stats.full_po.translated, 20)
@@ -763,57 +774,56 @@ class StatisticsTests(TestCase):
def test_stats_links(self):
pot_stats = Statistics.objects.get(
- branch__module__name='zenity', branch__name='gnome-3-8',
- domain__name='po', language__isnull=True)
+ branch__module__name="zenity", branch__name="gnome-3-8", domain__name="po", language__isnull=True
+ )
self.assertEqual(pot_stats.po_url(), "/POT/zenity.gnome-3-8/zenity.gnome-3-8.pot")
stats = Statistics.objects.get(
- branch__module__name='zenity', branch__name='gnome-3-8',
- domain__name='po', language__locale='it')
+ branch__module__name="zenity", branch__name="gnome-3-8", domain__name="po", language__locale="it"
+ )
self.assertEqual(stats.po_url(), "/POT/zenity.gnome-3-8/zenity.gnome-3-8.it.po")
self.assertEqual(stats.po_url(reduced=True), "/POT/zenity.gnome-3-8/zenity.gnome-3-8.it.reduced.po")
# Same for a fake stats
- stats = FakeLangStatistics(pot_stats, Language.objects.get(locale='bem'))
+ stats = FakeLangStatistics(pot_stats, Language.objects.get(locale="bem"))
self.assertEqual(stats.po_url(), "/module/po/zenity/gnome-3-8/po/bem.po")
self.assertEqual(stats.po_url(reduced=True), "/module/po/zenity/gnome-3-8/po/bem-reduced.po")
def test_total_by_releases(self):
- releases = list(Release.objects.filter(name__in=('gnome-3-8', 'gnome-dev')))
- stats = Release.total_by_releases('ui', releases)
- self.assertEqual(set(stats.keys()), {'fr', 'it'})
- self.assertEqual(stats['fr']['diff'], 0)
- self.assertEqual(stats['fr']['stats'], [100, 100])
- self.assertEqual(stats['it']['diff'], 24)
- self.assertEqual(stats['it']['stats'], [63, 87])
+ releases = list(Release.objects.filter(name__in=("gnome-3-8", "gnome-dev")))
+ stats = Release.total_by_releases("ui", releases)
+ self.assertEqual(set(stats.keys()), {"fr", "it"})
+ self.assertEqual(stats["fr"]["diff"], 0)
+ self.assertEqual(stats["fr"]["stats"], [100, 100])
+ self.assertEqual(stats["it"]["diff"], 24)
+ self.assertEqual(stats["it"]["stats"], [63, 87])
def test_get_lang_stats_by_type(self):
- mod = Module.objects.get(name='zenity')
- lang = Language.objects.get(locale='fr')
- dom = Domain.objects.create(
- module=mod, name='po2', dtype='ui', description='UI Translations'
- )
- branch = mod.branch_set.get(name='gnome-3-8')
+ mod = Module.objects.get(name="zenity")
+ lang = Language.objects.get(locale="fr")
+ dom = Domain.objects.create(module=mod, name="po2", dtype="ui", description="UI Translations")
+ branch = mod.branch_set.get(name="gnome-3-8")
# Add one more stat so as comparison happens on two real stat objects
- Statistics.objects.create(
- branch=branch,
- domain=dom,
- language=None
- )
- stats = Statistics.get_lang_stats_by_type(
- lang, 'ui', Release.objects.get(name="gnome-3-8")
- )
+ Statistics.objects.create(branch=branch, domain=dom, language=None)
+ stats = Statistics.get_lang_stats_by_type(lang, "ui", Release.objects.get(name="gnome-3-8"))
for key, value in {
- 'total': 183, 'totaltrans': 183, 'totalfuzzy': 0, 'totaluntrans': 0,
- 'totaltransperc': 100, 'totalfuzzyperc': 0, 'totaluntransperc': 0,
- 'dtype': 'ui', 'all_errors': []
+ "total": 183,
+ "totaltrans": 183,
+ "totalfuzzy": 0,
+ "totaluntrans": 0,
+ "totaltransperc": 100,
+ "totalfuzzyperc": 0,
+ "totaluntransperc": 0,
+ "dtype": "ui",
+ "all_errors": [],
}.items():
self.assertEqual(stats[key], value)
def _test_update_statistics(self):
# Temporarily deactivated, since update_stats cannot receive stats any more
from vertimus.models import State, StateTranslating
+
# Get a stat that has full_po and part_po
stat = Statistics.objects.get(
- branch__module__name='zenity', branch__name='gnome-2-30', language__locale='it',
domain__dtype='ui'
+ branch__module__name="zenity", branch__name="gnome-2-30", language__locale="it",
domain__dtype="ui"
)
pers = Person.objects.create(username="toto")
state = StateTranslating.objects.create(
@@ -831,27 +841,25 @@ class StatisticsTests(TestCase):
def test_information_comparable(self):
infos = [
Information.objects.create(
- statistics=Statistics.objects.filter(branch__module__name='zenity').first(),
- type='info',
+ statistics=Statistics.objects.filter(branch__module__name="zenity").first(),
+ type="info",
description="Some information",
),
Information.objects.create(
- statistics=Statistics.objects.filter(branch__module__name='gnome-hello').first(),
- type='info',
+ statistics=Statistics.objects.filter(branch__module__name="gnome-hello").first(),
+ type="info",
description="Other information",
),
]
- self.assertEqual(sorted(infos)[0].statistics.branch.module.name, 'gnome-hello')
+ self.assertEqual(sorted(infos)[0].statistics.branch.module.name, "gnome-hello")
@test_scratchdir
def test_empty_pot(self):
- pot_file = Path(__file__).parent / 'empty.pot'
- shutil.copyfile(pot_file, settings.SCRATCHDIR / 'POT' / 'zenity.master')
- mod = Module.objects.get(name='zenity')
- dom = Domain.objects.create(
- module=mod, name='po2', dtype='ui', description='UI Translations'
- )
- branch = mod.branch_set.get(name='gnome-3-8')
+ pot_file = Path(__file__).parent / "empty.pot"
+ shutil.copyfile(pot_file, settings.SCRATCHDIR / "POT" / "zenity.master")
+ mod = Module.objects.get(name="zenity")
+ dom = Domain.objects.create(module=mod, name="po2", dtype="ui", description="UI Translations")
+ branch = mod.branch_set.get(name="gnome-3-8")
stat = Statistics.objects.create(branch=branch, domain=dom, language=None)
stat.update_stats(pot_file)
self.assertEqual(stat.full_po, stat.part_po)
@@ -860,57 +868,56 @@ class StatisticsTests(TestCase):
self.assertEqual(stat.untranslated(), 0)
def test_compare_releases(self):
- response = self.client.get(reverse('release-compare', args=['ui', '3-8/dev']))
+ response = self.client.get(reverse("release-compare", args=["ui", "3-8/dev"]))
self.assertContains(response, f'<h1>{_("Releases Comparison")}</h1>')
- self.assertEqual(len(response.context['releases']), 2)
+ self.assertEqual(len(response.context["releases"]), 2)
# wrong domain type
- with self.assertLogs('django.request', 'WARNING'):
- response = self.client.get(reverse('release-compare', args=['whatever', '3-8/dev']))
+ with self.assertLogs("django.request", "WARNING"):
+ response = self.client.get(reverse("release-compare", args=["whatever", "3-8/dev"]))
self.assertEqual(response.status_code, 404)
# wrong release names
- with self.assertLogs('django.request', 'WARNING'):
- response = self.client.get(reverse('release-compare', args=['ui', 'whatever1/whatever2']))
+ with self.assertLogs("django.request", "WARNING"):
+ response = self.client.get(reverse("release-compare", args=["ui", "whatever1/whatever2"]))
self.assertEqual(response.status_code, 404)
class FigureTests(TestCase):
- fixtures = ['sample_data.json']
+ fixtures = ["sample_data.json"]
def test_figure_view(self):
- url = reverse('docimages', args=['gnome-hello', 'help', 'master', 'fr'])
+ url = reverse("docimages", args=["gnome-hello", "help", "master", "fr"])
response = self.client.get(url)
self.assertContains(response, "gnome-hello-new.png")
# Same for a non-existing language
- Language.objects.create(name='Afrikaans', locale='af')
- url = reverse('docimages', args=['gnome-hello', 'help', 'master', 'af'])
+ Language.objects.create(name="Afrikaans", locale="af")
+ url = reverse("docimages", args=["gnome-hello", "help", "master", "af"])
response = self.client.get(url)
self.assertContains(response, "gnome-hello-new.png")
def test_figure_urls(self):
- """ Test if figure urls are properly constructed """
+ """Test if figure urls are properly constructed"""
stat = Statistics.objects.get(
- branch__module__name='gnome-hello', branch__name='master', domain__dtype='doc',
language__locale='fr'
+ branch__module__name="gnome-hello", branch__name="master", domain__dtype="doc",
language__locale="fr"
)
figs = stat.get_figures()
self.assertEqual(
- figs[0]['orig_remote_url'],
- 'https://gitlab.gnome.org/GNOME/gnome-hello/raw/master/help/C/figures/gnome-hello-new.png'
+ figs[0]["orig_remote_url"],
+ "https://gitlab.gnome.org/GNOME/gnome-hello/raw/master/help/C/figures/gnome-hello-new.png",
)
- self.assertFalse('trans_remote_url' in figs[0])
+ self.assertFalse("trans_remote_url" in figs[0])
@test_scratchdir
def test_identical_figure_warning(self):
- """ Detect warning if translated figure is identical to original figure """
- branch = Branch.objects.get(module__name='gnome-hello', name='master')
- pot_stat = Statistics.objects.get(branch=branch, domain__name='help', language__isnull=True)
- fig_path = str(
- pot_stat.branch.co_path / pot_stat.domain.base_dir / 'C' /
- pot_stat.get_figures()[0]['path']
- )
- shutil.copyfile(fig_path, fig_path.replace('/C/', '/fr/'))
- doc_stat = Statistics.objects.get(branch=branch, domain__name='help', language__locale='fr')
+ """Detect warning if translated figure is identical to original figure"""
+ branch = Branch.objects.get(module__name="gnome-hello", name="master")
+ pot_stat = Statistics.objects.get(branch=branch, domain__name="help", language__isnull=True)
+ fig_path = str(pot_stat.branch.co_path / pot_stat.domain.base_dir / "C" /
pot_stat.get_figures()[0]["path"])
+ shutil.copyfile(fig_path, fig_path.replace("/C/", "/fr/"))
+ doc_stat = Statistics.objects.get(branch=branch, domain__name="help", language__locale="fr")
errs = utils.check_identical_figures(
- doc_stat.get_figures(), branch.co_path / 'help', 'fr',
+ doc_stat.get_figures(),
+ branch.co_path / "help",
+ "fr",
)
self.assertEqual(len(errs), 1)
self.assertTrue(errs[0][1].startswith("Figures should not be copied"))
@@ -919,143 +926,137 @@ class FigureTests(TestCase):
class UtilsTests(TestModuleBase):
def test_read_makefile_variable(self):
domain = Domain.objects.create(
- module=self.mod, name='help', dtype='doc', layout='help_docbook/{lang}/{lang}.po'
+ module=self.mod, name="help", dtype="doc", layout="help_docbook/{lang}/{lang}.po"
)
- makefile = utils.MakefileWrapper.find_file(
- self.branch, [self.branch.domain_path(domain)])
+ makefile = utils.MakefileWrapper.find_file(self.branch, [self.branch.domain_path(domain)])
var_content = makefile.read_variable("DOC_INCLUDES")
- self.assertEqual(var_content.split(), ['rnusers.xml', 'rnlookingforward.xml', '$(NULL)'])
+ self.assertEqual(var_content.split(), ["rnusers.xml", "rnlookingforward.xml", "$(NULL)"])
var_content = makefile.read_variable("HELP_FILES")
- self.assertEqual(var_content.split(), ['rnusers.xml', 'rnlookingforward.xml'])
+ self.assertEqual(var_content.split(), ["rnusers.xml", "rnlookingforward.xml"])
# Test $(top_srcdir) replacement
var_content = makefile.read_variable("SOME_VAR")
- self.assertEqual(var_content, str(self.branch.co_path) + '/some_path.po')
+ self.assertEqual(var_content, str(self.branch.co_path) + "/some_path.po")
def test_read_meson_variables(self):
domain = Domain.objects.create(
- module=self.mod, name='help', dtype='doc',
- layout='help_mallard_meson/{lang}/{lang}.po'
+ module=self.mod, name="help", dtype="doc", layout="help_mallard_meson/{lang}/{lang}.po"
)
meson_file = utils.MakefileWrapper.find_file(self.branch, [self.branch.domain_path(domain)])
- self.assertEqual(meson_file.read_variable('yelp.languages'), ['es', 'fr'])
- self.assertEqual(
- meson_file.read_variable('yelp.sources'),
- ['index.page', 'what-is.page', 'legal.xml']
- )
+ self.assertEqual(meson_file.read_variable("yelp.languages"), ["es", "fr"])
+ self.assertEqual(meson_file.read_variable("yelp.sources"), ["index.page", "what-is.page",
"legal.xml"])
# UI meson file
path = Path(__file__).parent / "meson-ui.build"
meson_file = utils.MesonfileWrapper(self.branch, path)
- self.assertEqual(meson_file.read_variable('gettext.preset'), 'glib')
- self.assertEqual(meson_file.read_variable('gettext.args'), ['--keyword=Description'])
+ self.assertEqual(meson_file.read_variable("gettext.preset"), "glib")
+ self.assertEqual(meson_file.read_variable("gettext.args"), ["--keyword=Description"])
# UI meson file (with data_dirs)
path = Path(__file__).parent / "meson-ui2.build"
meson_file = utils.MesonfileWrapper(self.branch, path)
self.assertEqual(
- meson_file.read_variable('gettext.data_dirs'),
- [str(self.branch.co_path / 'src/gstyle/data'),
- str(self.branch.co_path / 'data/style-schemes')]
+ meson_file.read_variable("gettext.data_dirs"),
+ [str(self.branch.co_path / "src/gstyle/data"), str(self.branch.co_path / "data/style-schemes")],
)
def test_read_cmake_variables(self):
domain = Domain.objects.create(
- module=self.mod, name='help', dtype='doc', layout='help_mallard/{lang}/{lang}.po'
+ module=self.mod, name="help", dtype="doc", layout="help_mallard/{lang}/{lang}.po"
)
cmake_file = utils.MakefileWrapper.find_file(
- self.branch, [self.branch.domain_path(domain)], file_name='CMakeLists.txt')
- self.assertEqual(
- cmake_file.read_variable('HELP_FILES').split(),
- ['index.page', 'what-is.page', 'legal.xml']
+ self.branch, [self.branch.domain_path(domain)], file_name="CMakeLists.txt"
)
+ self.assertEqual(cmake_file.read_variable("HELP_FILES").split(), ["index.page", "what-is.page",
"legal.xml"])
def test_doc_format_source_files(self):
domain = Domain.objects.create(
- module=self.mod, name='help', dtype='doc', layout='help_mallard_meson/{lang}/{lang}.po'
+ module=self.mod, name="help", dtype="doc", layout="help_mallard_meson/{lang}/{lang}.po"
)
df = utils.DocFormat(domain, self.branch)
- self.assertEqual(
- df.source_files(),
- [Path('C/index.page'), Path('C/what-is.page'), Path('C/legal.xml')]
- )
+ self.assertEqual(df.source_files(), [Path("C/index.page"), Path("C/what-is.page"),
Path("C/legal.xml")])
@test_scratchdir
def test_insert_locale_in_linguas(self):
- linguas_path = settings.SCRATCHDIR / 'git' / 'gnome-hello' / 'po' / 'LINGUAS'
- utils.insert_locale_in_linguas(linguas_path, 'xx')
- self.assertTrue(linguas_path.read_text().endswith('\nxx\n'))
+ linguas_path = settings.SCRATCHDIR / "git" / "gnome-hello" / "po" / "LINGUAS"
+ utils.insert_locale_in_linguas(linguas_path, "xx")
+ self.assertTrue(linguas_path.read_text().endswith("\nxx\n"))
# Try with file without ending new line
- with linguas_path.open('rb+') as fh:
+ with linguas_path.open("rb+") as fh:
fh.seek(-1, os.SEEK_END)
fh.truncate()
- utils.insert_locale_in_linguas(linguas_path, 'xy')
- self.assertTrue(linguas_path.read_text().endswith('\nxy\n'))
+ utils.insert_locale_in_linguas(linguas_path, "xy")
+ self.assertTrue(linguas_path.read_text().endswith("\nxy\n"))
# Also works if file is initially empty
os.truncate(linguas_path, 0)
- utils.insert_locale_in_linguas(linguas_path, 'xz')
- self.assertEqual(linguas_path.read_text(), 'xz\n')
+ utils.insert_locale_in_linguas(linguas_path, "xz")
+ self.assertEqual(linguas_path.read_text(), "xz\n")
def test_exclude_messages_from_potfile(self):
- pot_file = Path(__file__).parent / 'donottranslate.pot'
- with tempfile.NamedTemporaryFile(suffix='.pot') as temppot:
+ pot_file = Path(__file__).parent / "donottranslate.pot"
+ with tempfile.NamedTemporaryFile(suffix=".pot") as temppot:
shutil.copyfile(str(pot_file), temppot.name)
utils.exclude_untrans_messages(temppot.name)
stats = utils.po_file_stats(Path(temppot.name))
- self.assertEqual(stats['untranslated'], 2)
+ self.assertEqual(stats["untranslated"], 2)
@test_scratchdir
def test_po_grep(self):
# Take it.po, because desktop.in.h strings are not yet translated
- it_path = settings.SCRATCHDIR / 'git' / 'gnome-hello' / 'po' / 'it.po'
+ it_path = settings.SCRATCHDIR / "git" / "gnome-hello" / "po" / "it.po"
temp_file = tempfile.NamedTemporaryFile(delete=False)
self.addCleanup(os.remove, temp_file.name)
- utils.po_grep(it_path, temp_file.name, 'locations|desktop.in.h')
- with open(temp_file.name, 'r') as fh:
+ utils.po_grep(it_path, temp_file.name, "locations|desktop.in.h")
+ with open(temp_file.name, "r") as fh:
content = fh.read()
self.assertGreater(len(content), 0)
- self.assertNotIn('desktop.in.h', content)
+ self.assertNotIn("desktop.in.h", content)
def test_po_quality(self):
valid_path = Path(__file__).parent / "xml_valid.po"
- result = utils.check_po_quality(str(valid_path), ['xmltags'])
- self.assertEqual(result, '')
+ result = utils.check_po_quality(str(valid_path), ["xmltags"])
+ self.assertEqual(result, "")
error_path = Path(__file__).parent / "xml_error.po"
- result = utils.check_po_quality(str(error_path), ['xmltags'])
- self.assertIn('xmltags: Different XML tags', result)
+ result = utils.check_po_quality(str(error_path), ["xmltags"])
+ self.assertIn("xmltags: Different XML tags", result)
- result = utils.check_po_quality('unexisting/path.po', ['xmltags'])
+ result = utils.check_po_quality("unexisting/path.po", ["xmltags"])
self.assertEqual(result, "The file “unexisting/path.po” does not exist")
class OtherTests(TestCase):
def test_release_list(self):
- Release.objects.bulk_create([
- Release(name='gnome-3-18', description='GNOME 3.18', status='official', weight=0),
- Release(name='gnome-2-32', description='GNOME 2.32', status='official', weight=-10),
- ])
- response = self.client.get(reverse('releases'))
+ Release.objects.bulk_create(
+ [
+ Release(name="gnome-3-18", description="GNOME 3.18", status="official", weight=0),
+ Release(name="gnome-2-32", description="GNOME 2.32", status="official", weight=-10),
+ ]
+ )
+ response = self.client.get(reverse("releases"))
contents = [
- '<h1>GNOME Releases</h1>', '<h2>Older Releases</h2>',
- '<a href="%s">GNOME 3.18</a>' % reverse('release', args=['gnome-3-18']),
+ "<h1>GNOME Releases</h1>",
+ "<h2>Older Releases</h2>",
+ '<a href="%s">GNOME 3.18</a>' % reverse("release", args=["gnome-3-18"]),
]
for item in contents:
self.assertContains(response, item)
# Also test JSON output
- response = self.client.get(reverse('releases', args=['json']))
- content = json.loads(response.content.decode('utf-8'))
+ response = self.client.get(reverse("releases", args=["json"]))
+ content = json.loads(response.content.decode("utf-8"))
self.assertEqual(content[0]["fields"]["name"], "gnome-3-18")
def test_svn_checkout_cmd(self):
from ..repos import SVNRepo
- mod = Module.objects.create(
- name='webkit', vcs_type='svn', vcs_root='https://svn.webkit.org/repository'
- )
- repo = SVNRepo(Branch(name='HEAD', module=mod, vcs_subpath='trunk'))
+
+ mod = Module.objects.create(name="webkit", vcs_type="svn",
vcs_root="https://svn.webkit.org/repository")
+ repo = SVNRepo(Branch(name="HEAD", module=mod, vcs_subpath="trunk"))
with PatchShellCommand() as cmds:
repo.init_checkout()
- self.assertEqual(cmds, [
- 'svn co --non-interactive https://svn.webkit.org/repository/webkit/trunk '
- '%s/svn/webkit.HEAD' % settings.SCRATCHDIR
- ])
+ self.assertEqual(
+ cmds,
+ [
+ "svn co --non-interactive https://svn.webkit.org/repository/webkit/trunk "
+ "%s/svn/webkit.HEAD" % settings.SCRATCHDIR
+ ],
+ )
diff --git a/stats/tests/utils.py b/stats/tests/utils.py
index ccf48300..1b3b34e3 100644
--- a/stats/tests/utils.py
+++ b/stats/tests/utils.py
@@ -14,16 +14,17 @@ class PatchShellCommand:
Mock common.utils.run_shell_commands and gather all passed commands.
`only` is an optional list of commands to limit mocking to (empty -> all).
"""
+
def __init__(self, only=None):
self.only = only
def __enter__(self):
self.cmds = []
- self.patcher1 = patch('stats.models.run_shell_command', side_effect=self.mocked_run_shell_command)
+ self.patcher1 = patch("stats.models.run_shell_command", side_effect=self.mocked_run_shell_command)
self.patcher1.start()
- self.patcher2 = patch('stats.utils.run_shell_command', side_effect=self.mocked_run_shell_command)
+ self.patcher2 = patch("stats.utils.run_shell_command", side_effect=self.mocked_run_shell_command)
self.patcher2.start()
- self.patcher3 = patch('stats.repos.run_shell_command', side_effect=self.mocked_run_shell_command)
+ self.patcher3 = patch("stats.repos.run_shell_command", side_effect=self.mocked_run_shell_command)
self.patcher3.start()
return self.cmds
@@ -33,10 +34,11 @@ class PatchShellCommand:
if self.only is not None and not any(needle in cmd_str for needle in self.only):
# Pass the command to the real utils.run_shell_command
from common.utils import run_shell_command
+
return run_shell_command(cmd, *args, **kwargs)
else:
# Pretend the command was successfull
- return 0, '', ''
+ return 0, "", ""
def __exit__(self, *args):
self.patcher1.stop()
@@ -45,21 +47,23 @@ class PatchShellCommand:
def test_scratchdir(test_func):
- """ Decorator to temporarily use the scratchdir inside the test directory """
+ """Decorator to temporarily use the scratchdir inside the test directory"""
+
@wraps(test_func)
def decorator(self):
old_scratchdir = settings.SCRATCHDIR
old_potdir = settings.POTDIR
- settings.SCRATCHDIR = Path(tempfile.mkdtemp()) / 'scratch'
+ settings.SCRATCHDIR = Path(tempfile.mkdtemp()) / "scratch"
settings.POTDIR = settings.SCRATCHDIR / "POT"
settings.POTDIR.mkdir(parents=True)
- tar_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'gnome-hello.tar.gz')
+ tar_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "gnome-hello.tar.gz")
with tarfile.open(tar_path) as gnome_hello_tar:
- gnome_hello_tar.extractall(settings.SCRATCHDIR / 'git')
+ gnome_hello_tar.extractall(settings.SCRATCHDIR / "git")
try:
test_func(self)
finally:
shutil.rmtree(settings.SCRATCHDIR)
settings.SCRATCHDIR = old_scratchdir
settings.POTDIR = old_potdir
+
return decorator
diff --git a/stats/utils.py b/stats/utils.py
index d7495330..ec6acfa4 100644
--- a/stats/utils.py
+++ b/stats/utils.py
@@ -11,12 +11,13 @@ from django.conf import settings
from django.core.files.base import File
from django.template.loader import get_template
from django.utils.functional import cached_property
-from django.utils.translation import gettext as _, gettext_noop
-
+from django.utils.translation import gettext as _
+from django.utils.translation import gettext_noop
from translate.storage.base import TranslationStore
-from translate.tools import pogrep, pocount
+from translate.tools import pocount, pogrep
from common.utils import run_shell_command, send_mail
+
from . import potdiff
STATUS_OK = 0
@@ -27,8 +28,8 @@ CHANGED_ONLY_FORMATTING = 1
CHANGED_WITH_ADDITIONS = 2
CHANGED_NO_ADDITIONS = 3
-ITSTOOL_PATH = getattr(settings, 'ITSTOOL_PATH', '')
-ITS_DATA_DIR = settings.SCRATCHDIR / 'data' / 'its'
+ITSTOOL_PATH = getattr(settings, "ITSTOOL_PATH", "")
+ITS_DATA_DIR = settings.SCRATCHDIR / "data" / "its"
# monkey-patch ttk (https://github.com/translate/translate/issues/2129)
orig_addunit = TranslationStore.addunit
@@ -51,19 +52,17 @@ class UndetectableDocFormat(Exception):
class DocFormat:
- itstool_regex = re.compile("^msgid \"external ref=\'(?P<path>[^\']*)\' md5=\'(?P<hash>[^\']*)\'\"")
+ itstool_regex = re.compile("^msgid \"external ref='(?P<path>[^']*)' md5='(?P<hash>[^']*)'\"")
def __init__(self, domain, branch):
self.branch = branch
self.vcs_path = branch.domain_path(domain)
self.makefile = MakefileWrapper.find_file(branch, [self.vcs_path])
if self.makefile is None:
- raise UndetectableDocFormat(
- gettext_noop("Unable to find a makefile for module %s") % branch.module.name
- )
- has_page_files = any(f.suffix == '.page' for f in self.list_C_files())
- self.format = 'mallard' if has_page_files else 'docbook'
- self.tool = 'itstool'
+ raise UndetectableDocFormat(gettext_noop("Unable to find a makefile for module %s") %
branch.module.name)
+ has_page_files = any(f.suffix == ".page" for f in self.list_C_files())
+ self.format = "mallard" if has_page_files else "docbook"
+ self.tool = "itstool"
def __repr__(self):
return "%s format=%s, tool=%s>" % (self.__class__, self.format, self.tool)
@@ -73,7 +72,7 @@ class DocFormat:
return isinstance(self.makefile, MesonfileWrapper)
def list_C_files(self):
- return [p.relative_to(self.vcs_path) for p in (self.vcs_path / 'C').iterdir() if p.is_file()]
+ return [p.relative_to(self.vcs_path) for p in (self.vcs_path / "C").iterdir() if p.is_file()]
def source_files(self):
"""Return help source files, with path relative to help base dir."""
@@ -89,29 +88,25 @@ class DocFormat:
break
if not sources:
# Last try: only one xml/docbook file in C/...
- xml_files = [
- f for f in self.list_C_files() if f.suffix in ('.xml', '.docbook')
- ]
+ xml_files = [f for f in self.list_C_files() if f.suffix in (".xml", ".docbook")]
if len(xml_files) == 1:
sources.append(xml_files[0].name)
else:
- raise Exception(
- gettext_noop("Unable to find doc source files for this module.")
- )
+ raise Exception(gettext_noop("Unable to find doc source files for this module."))
source_list = self.makefile.read_variable(*self.include_var)
if not source_list:
- suffix = ['.page'] if self.format == 'mallard' else ['.xml', '.docbook']
+ suffix = [".page"] if self.format == "mallard" else [".xml", ".docbook"]
# Fallback to directory listing
return [f for f in self.list_C_files() if f.suffix in suffix]
if isinstance(source_list, str):
sources += source_list.split()
elif source_list:
sources += source_list
- return [Path('C', src) for src in sources if src not in ('', '$(NULL)')]
+ return [Path("C", src) for src in sources if src not in ("", "$(NULL)")]
def command(self, potfile, files):
- cmd = ['%sitstool' % ITSTOOL_PATH, '-o', str(potfile)]
+ cmd = ["%sitstool" % ITSTOOL_PATH, "-o", str(potfile)]
cmd.extend([str(f) for f in files])
return cmd
@@ -129,7 +124,7 @@ class DocFormat:
@property
def img_grep(self):
- return "^msgid \"external ref="
+ return '^msgid "external ref='
@property
def bef_line(self):
@@ -142,7 +137,7 @@ class DocFormat:
class MakefileWrapper:
- default_makefiles = ['Makefile.am', 'meson.build', 'CMakeLists.txt']
+ default_makefiles = ["Makefile.am", "meson.build", "CMakeLists.txt"]
@classmethod
def find_file(cls, branch, vcs_paths, file_name=None):
@@ -152,9 +147,9 @@ class MakefileWrapper:
for file_name in names:
file_path = path / file_name
if file_path.exists():
- if file_name == 'meson.build':
+ if file_name == "meson.build":
return MesonfileWrapper(branch, file_path)
- elif file_name == 'CMakeLists.txt':
+ elif file_name == "CMakeLists.txt":
return CMakefileWrapper(branch, file_path)
else:
return MakefileWrapper(branch, file_path)
@@ -166,7 +161,7 @@ class MakefileWrapper:
@cached_property
def content(self):
try:
- content = self.path.read_text(encoding='utf-8')
+ content = self.path.read_text(encoding="utf-8")
except IOError:
return None
return content
@@ -178,7 +173,7 @@ class MakefileWrapper:
found = None
for line in self.content.splitlines():
line = line.strip()
- if line.startswith('#'):
+ if line.startswith("#"):
continue
if non_terminated_content:
line = non_terminated_content + " " + line
@@ -202,19 +197,19 @@ class MakefileWrapper:
# Try to expand '$(var)' variables
if found:
for element in found.split():
- if element.startswith('$(') and element.endswith(')') and element != '$(NULL)':
+ if element.startswith("$(") and element.endswith(")") and element != "$(NULL)":
result = self.read_variable(element[2:-1])
if result:
found = found.replace(element, result)
- if '$(top_srcdir)' in found:
- found = found.replace('$(top_srcdir)', str(self.branch.co_path))
+ if "$(top_srcdir)" in found:
+ found = found.replace("$(top_srcdir)", str(self.branch.co_path))
return found
class MesonfileWrapper(MakefileWrapper):
- i18n_gettext_kwargs = {'po_dir', 'data_dirs', 'type', 'languages', 'args', 'preset'}
- gnome_yelp_kwargs = {'sources', 'media', 'symlink_media', 'languages'}
- ignorable_kwargs = {'install_dir'}
+ i18n_gettext_kwargs = {"po_dir", "data_dirs", "type", "languages", "args", "preset"}
+ gnome_yelp_kwargs = {"sources", "media", "symlink_media", "languages"}
+ ignorable_kwargs = {"install_dir"}
readable = True
@cached_property
@@ -222,21 +217,14 @@ class MesonfileWrapper(MakefileWrapper):
content = super().content
# Here be dragons: Try to make meson content look like Python
possible_kwargs = list(self.i18n_gettext_kwargs | self.gnome_yelp_kwargs | self.ignorable_kwargs)
- content = re.sub(
- r'(' + '|'.join(possible_kwargs) + ') ?:',
- r'\1=',
- content
- )
+ content = re.sub(r"(" + "|".join(possible_kwargs) + ") ?:", r"\1=", content)
# ignore if/endif sections
- content = re.sub(r"^if .*endif$", '', content, flags=re.M | re.S)
- content = content.replace(
- 'true', 'True'
- ).replace(
- 'false', 'False'
- ).replace(
- "i18n = import('i18n')", ''
- ).replace(
- "gnome = import('gnome')", ''
+ content = re.sub(r"^if .*endif$", "", content, flags=re.M | re.S)
+ content = (
+ content.replace("true", "True")
+ .replace("false", "False")
+ .replace("i18n = import('i18n')", "")
+ .replace("gnome = import('gnome')", "")
)
return content
@@ -248,16 +236,16 @@ class MesonfileWrapper(MakefileWrapper):
class VarCatcher:
def yelp(self, *args, **kwargs):
- catched['yelp.project_id'] = args[0]
- for var_name in {'sources', 'languages'}:
+ catched["yelp.project_id"] = args[0]
+ for var_name in {"sources", "languages"}:
if var_name in kwargs:
- catched['yelp.%s' % var_name] = kwargs[var_name]
+ catched["yelp.%s" % var_name] = kwargs[var_name]
def gettext(self, *args, **kwargs):
- catched['gettext.project_id'] = args[0]
+ catched["gettext.project_id"] = args[0]
for var_name in MesonfileWrapper.i18n_gettext_kwargs:
if var_name in kwargs:
- catched['gettext.%s' % var_name] = kwargs[var_name]
+ catched["gettext.%s" % var_name] = kwargs[var_name]
catcher = VarCatcher()
this_instance = self
@@ -270,10 +258,13 @@ class MesonfileWrapper(MakefileWrapper):
return str(this_instance.branch.co_path)
meson_locals = {
- 'gnome': catcher, 'i18n': catcher,
- 'GNOME': catcher, 'I18N': catcher,
- 'install_data': MagicMock(), 'meson': MesonMock(),
- 'join_paths': lambda *args: os.sep.join(args),
+ "gnome": catcher,
+ "i18n": catcher,
+ "GNOME": catcher,
+ "I18N": catcher,
+ "install_data": MagicMock(),
+ "meson": MesonMock(),
+ "join_paths": lambda *args: os.sep.join(args),
}
while True:
try:
@@ -321,13 +312,13 @@ class CMakefileWrapper(MakefileWrapper):
if non_terminated_content:
line = non_terminated_content + " " + line
# Test if line is non terminated
- if line.count('(') > line.count(')'):
+ if line.count("(") > line.count(")"):
non_terminated_content = line
else:
non_terminated_content = ""
# Search for variable
for var in variables:
- match = re.search(r'set\s*\(%s\s*([^\)]*)\)' % var, line)
+ match = re.search(r"set\s*\(%s\s*([^\)]*)\)" % var, line)
if match:
found = match.group(1).strip()
break
@@ -337,7 +328,7 @@ class CMakefileWrapper(MakefileWrapper):
def sort_object_list(lst, sort_meth):
- """ Sort an object list with sort_meth (which should return a translated string) """
+ """Sort an object list with sort_meth (which should return a translated string)"""
templist = [(getattr(obj_, sort_meth)().lower(), obj_) for obj_ in lst]
templist.sort()
return [obj_ for (key1, obj_) in templist]
@@ -345,7 +336,7 @@ def sort_object_list(lst, sort_meth):
def multiple_replace(dct, text):
regex = re.compile("(%s)" % "|".join(map(re.escape, dct.keys())))
- return regex.sub(lambda mo: dct[mo.string[mo.start():mo.end()]], text)
+ return regex.sub(lambda mo: dct[mo.string[mo.start() : mo.end()]], text)
def stripHTML(string):
@@ -360,8 +351,8 @@ def ellipsize(val, length):
def check_program_presence(prog_name):
- """ Test if prog_name is an available command on the system """
- status, output, err = run_shell_command(['which', prog_name])
+ """Test if prog_name is an available command on the system"""
+ status, output, err = run_shell_command(["which", prog_name])
return status == 0
@@ -395,11 +386,11 @@ def po_grep(in_file, out_file, filter_):
def check_potfiles(po_path):
"""Check if there were any problems regenerating a POT file (intltool-update -m).
- Return a list of errors """
+ Return a list of errors"""
errors = []
- run_shell_command(['rm', '-f', 'missing', 'notexist'], cwd=po_path)
- status, output, errs = run_shell_command(['intltool-update', '-m'], cwd=po_path)
+ run_shell_command(["rm", "-f", "missing", "notexist"], cwd=po_path)
+ status, output, errs = run_shell_command(["intltool-update", "-m"], cwd=po_path)
if status != STATUS_OK:
errors.append(("error", gettext_noop("Errors while running “intltool-update -m” check.")))
@@ -410,10 +401,9 @@ def check_potfiles(po_path):
errors.append(
(
"warn",
- gettext_noop(
- "There are some missing files from POTFILES.in: %s"
- ) % ("<ul><li>" + "</li>\n<li>".join(fh.readlines()) + "</li>\n</ul>")
- )
+ gettext_noop("There are some missing files from POTFILES.in: %s")
+ % ("<ul><li>" + "</li>\n<li>".join(fh.readlines()) + "</li>\n</ul>"),
+ )
)
notexist = po_path / "notexist"
@@ -425,14 +415,15 @@ def check_potfiles(po_path):
gettext_noop(
"Following files are referenced in either POTFILES.in or POTFILES.skip, "
"yet they don’t exist: %s"
- ) % ("<ul><li>" + "</li>\n<li>".join(fh.readlines()) + "</li>\n</ul>")
+ )
+ % ("<ul><li>" + "</li>\n<li>".join(fh.readlines()) + "</li>\n</ul>"),
)
)
return errors
def generate_doc_pot_file(branch, domain):
- """ Return the pot file for a document-type domain, and the error if any """
+ """Return the pot file for a document-type domain, and the error if any"""
try:
doc_format = DocFormat(domain, branch)
files = doc_format.source_files()
@@ -445,13 +436,17 @@ def generate_doc_pot_file(branch, domain):
status, output, errs = run_shell_command(command, cwd=doc_format.vcs_path)
if status != STATUS_OK:
- return "", [(
- "error",
- gettext_noop("Error regenerating POT file for document
%(file)s:\n<pre>%(cmd)s\n%(output)s</pre>") % {
- 'file': potbase,
- 'cmd': " ".join([c.replace(str(settings.SCRATCHDIR), "<scratchdir>") for c in
command]),
- 'output': errs
- })]
+ return "", [
+ (
+ "error",
+ gettext_noop("Error regenerating POT file for document
%(file)s:\n<pre>%(cmd)s\n%(output)s</pre>")
+ % {
+ "file": potbase,
+ "cmd": " ".join([c.replace(str(settings.SCRATCHDIR), "<scratchdir>") for c in
command]),
+ "output": errs,
+ },
+ )
+ ]
if not potfile.exists():
return "", []
@@ -482,38 +477,28 @@ def check_po_conformity(pofile):
input_data = pofile.read()
if isinstance(input_data, bytes):
try:
- input_data = input_data.decode('utf-8')
+ input_data = input_data.decode("utf-8")
except UnicodeDecodeError:
- return [(
- "warn",
- gettext_noop("PO file “%s” is not UTF-8 encoded.") % pofile.name
- )]
+ return [("warn", gettext_noop("PO file “%s” is not UTF-8 encoded.") % pofile.name)]
input_file = "-"
else:
input_data = None
input_file = str(pofile)
- command = ['msgfmt', '-cv', '-o', '/dev/null', input_file]
+ command = ["msgfmt", "-cv", "-o", "/dev/null", input_file]
status, _, _ = run_shell_command(command, env=C_ENV, input_data=input_data)
if status != STATUS_OK:
- errors.append((
- "error",
- gettext_noop("PO file “%s” doesn’t pass msgfmt check.") % pofile.name
- ))
+ errors.append(("error", gettext_noop("PO file “%s” doesn’t pass msgfmt check.") % pofile.name))
- if input_file != '-' and os.access(str(pofile), os.X_OK):
+ if input_file != "-" and os.access(str(pofile), os.X_OK):
errors.append(("warn", gettext_noop("This PO file has an executable bit set.")))
# Check if PO file is in UTF-8
if input_file != "-":
- command = "msgconv -t UTF-8 \"%s\" | diff -i -I '^#~' -u \"%s\" - >/dev/null" % (
- pofile, pofile)
+ command = 'msgconv -t UTF-8 "%s" | diff -i -I \'^#~\' -u "%s" - >/dev/null' % (pofile, pofile)
status, _, _ = run_shell_command(command, env=C_ENV)
if status != STATUS_OK:
- errors.append((
- "warn",
- gettext_noop("PO file “%s” is not UTF-8 encoded.") % pofile.name
- ))
+ errors.append(("warn", gettext_noop("PO file “%s” is not UTF-8 encoded.") % pofile.name))
return errors
@@ -524,11 +509,7 @@ def check_po_quality(pofile, filters):
"""
if not os.path.exists(pofile):
return _("The file “%s” does not exist") % pofile
- status, out, errs = run_shell_command(
- ['pofilter', '--progress=none', '--gnome', '-t'] +
- list(filters) +
- [pofile]
- )
+ status, out, errs = run_shell_command(["pofilter", "--progress=none", "--gnome", "-t"] + list(filters) +
[pofile])
if status == STATUS_OK:
return out
else:
@@ -538,20 +519,17 @@ def check_po_quality(pofile, filters):
def po_file_stats(pofile):
"""Compute pofile translation statistics."""
res = {
- 'translated': 0,
- 'fuzzy': 0,
- 'untranslated': 0,
- 'translated_words': 0,
- 'fuzzy_words': 0,
- 'untranslated_words': 0,
- 'errors': [],
+ "translated": 0,
+ "fuzzy": 0,
+ "untranslated": 0,
+ "translated_words": 0,
+ "fuzzy_words": 0,
+ "untranslated_words": 0,
+ "errors": [],
}
if not pofile.exists():
- res['errors'].append((
- "error",
- gettext_noop("PO file “%s” does not exist or cannot be read.") % pofile.name
- ))
+ res["errors"].append(("error", gettext_noop("PO file “%s” does not exist or cannot be read.") %
pofile.name))
return res
try:
@@ -560,44 +538,41 @@ def po_file_stats(pofile):
# From translate-toolkit 3.3
status = pocount.calcstats(str(pofile))
if not status:
- res['errors'].append((
- "error",
- gettext_noop("Can’t get statistics for POT file “%s”.") % pofile.name
- ))
+ res["errors"].append(("error", gettext_noop("Can’t get statistics for POT file “%s”.") %
pofile.name))
else:
- res['fuzzy'] = status['fuzzy']
- res['translated'] = status['translated']
- res['untranslated'] = status['untranslated']
- res['fuzzy_words'] = status['fuzzysourcewords']
- res['translated_words'] = status['translatedsourcewords']
- res['untranslated_words'] = status['untranslatedsourcewords']
+ res["fuzzy"] = status["fuzzy"]
+ res["translated"] = status["translated"]
+ res["untranslated"] = status["untranslated"]
+ res["fuzzy_words"] = status["fuzzysourcewords"]
+ res["translated_words"] = status["translatedsourcewords"]
+ res["untranslated_words"] = status["untranslatedsourcewords"]
return res
def read_linguas_file(full_path):
- """ Read a LINGUAS file (each language code on a line by itself) """
+ """Read a LINGUAS file (each language code on a line by itself)"""
langs = []
with full_path.open("r") as fh:
- [langs.extend(line.split()) for line in fh if line[:1] != '#']
+ [langs.extend(line.split()) for line in fh if line[:1] != "#"]
return {
- 'langs': langs,
- 'error': gettext_noop("Entry for this language is not present in LINGUAS file."),
+ "langs": langs,
+ "error": gettext_noop("Entry for this language is not present in LINGUAS file."),
}
def insert_locale_in_linguas(linguas_path, locale):
temp_linguas = linguas_path.parent / (linguas_path.name + "~")
- with linguas_path.open('r') as fin, temp_linguas.open('w') as fout:
+ with linguas_path.open("r") as fin, temp_linguas.open("w") as fout:
lang_written = False
- line = '\n'
+ line = "\n"
for line in fin:
if not lang_written and line[0] != "#" and line[:5] > locale[:5]:
fout.write(locale + "\n")
lang_written = True
fout.write(line)
if not lang_written:
- fout.write(('\n' if not line.endswith('\n') else '') + locale + "\n")
+ fout.write(("\n" if not line.endswith("\n") else "") + locale + "\n")
temp_linguas.replace(linguas_path)
@@ -614,22 +589,25 @@ def get_ui_linguas(branch, subdir):
# AS_ALL_LINGUAS is a macro that takes all po files by default
status, output, errs = run_shell_command("grep -qs AS_ALL_LINGUAS %s%sconfigure.*" % (branch.co_path,
os.sep))
if status == 0:
- return {'langs': None,
- 'error': gettext_noop("No need to edit LINGUAS file or variable for this module")}
+ return {"langs": None, "error": gettext_noop("No need to edit LINGUAS file or variable for this
module")}
configureac = branch.co_path / "configure.ac"
configurein = branch.co_path / "configure.in"
for configure in [configureac, configurein]:
- found = MakefileWrapper(branch, configure).read_variable('ALL_LINGUAS')
+ found = MakefileWrapper(branch, configure).read_variable("ALL_LINGUAS")
if found is not None:
- return {'langs': found.split(),
- 'error': gettext_noop("Entry for this language is not present in ALL_LINGUAS in
configure file.")}
- return {'langs': None,
- 'error': gettext_noop("Don’t know where to look for the LINGUAS variable, ask the module
maintainer.")}
+ return {
+ "langs": found.split(),
+ "error": gettext_noop("Entry for this language is not present in ALL_LINGUAS in configure
file."),
+ }
+ return {
+ "langs": None,
+ "error": gettext_noop("Don’t know where to look for the LINGUAS variable, ask the module
maintainer."),
+ }
def get_doc_linguas(branch, subdir):
- """Get language list in one Makefile.am (either path) """
+ """Get language list in one Makefile.am (either path)"""
linguas = None
po_path = branch.co_path / subdir
@@ -642,11 +620,14 @@ def get_doc_linguas(branch, subdir):
if linguas_file:
linguas = linguas_file.read_variable("DOC_LINGUAS", "HELP_LINGUAS", "gettext.languages")
if linguas is None:
- return {'langs': None,
- 'error': gettext_noop(
- "Don’t know where to look for the DOC_LINGUAS variable, ask the module maintainer.")}
- return {'langs': linguas.split() if isinstance(linguas, str) else linguas,
- 'error': gettext_noop("DOC_LINGUAS list doesn’t include this language.")}
+ return {
+ "langs": None,
+ "error": gettext_noop("Don’t know where to look for the DOC_LINGUAS variable, ask the module
maintainer."),
+ }
+ return {
+ "langs": linguas.split() if isinstance(linguas, str) else linguas,
+ "error": gettext_noop("DOC_LINGUAS list doesn’t include this language."),
+ }
def collect_its_data():
@@ -658,7 +639,7 @@ def collect_its_data():
if not ITS_DATA_DIR.exists():
ITS_DATA_DIR.mkdir(parents=True)
- data_to_collect = getattr(settings, 'GETTEXT_ITS_DATA', {})
+ data_to_collect = getattr(settings, "GETTEXT_ITS_DATA", {})
for module_name, files in data_to_collect.items():
mod = Module.objects.get(name=module_name)
branch = mod.get_head_branch()
@@ -671,33 +652,35 @@ def collect_its_data():
def get_fig_stats(pofile, doc_format):
- """ Extract image strings from pofile and return a list of figures dict:
- [{'path':, 'hash':, 'fuzzy':, 'translated':}, ...] """
+ """Extract image strings from pofile and return a list of figures dict:
+ [{'path':, 'hash':, 'fuzzy':, 'translated':}, ...]"""
if not isinstance(doc_format, DocFormat):
return []
# Extract image strings: beforeline/msgid/msgstr/grep auto output a fourth line
before_lines = doc_format.bef_line
command = "msgcat --no-wrap %(pofile)s| grep -A 1 -B %(before)s '%(grep)s'" % {
- 'pofile': pofile, 'grep': doc_format.img_grep, 'before': before_lines,
+ "pofile": pofile,
+ "grep": doc_format.img_grep,
+ "before": before_lines,
}
(status, output, errs) = run_shell_command(command)
- if status != STATUS_OK or output == '':
+ if status != STATUS_OK or output == "":
# FIXME: something should be logged here
return []
- lines = output.split('\n')
+ lines = output.split("\n")
while lines[0][0] != "#":
lines = lines[1:] # skip warning messages at the top of the output
figures = []
for i, line in islice(enumerate(lines), 0, None, 3 + before_lines):
# TODO: add image size
- fig = {"path": '', "hash": ''}
+ fig = {"path": "", "hash": ""}
m = doc_format.img_regex.match(lines[i + before_lines])
if m:
- fig["path"] = m.group('path')
- fig["hash"] = m.group('hash')
- fig["fuzzy"] = (line == '#, fuzzy' or line[:8] == '#| msgid')
- fig["translated"] = len(lines[i + before_lines + 1]) > 9 and not fig['fuzzy']
+ fig["path"] = m.group("path")
+ fig["hash"] = m.group("hash")
+ fig["fuzzy"] = line == "#, fuzzy" or line[:8] == "#| msgid"
+ fig["translated"] = len(lines[i + before_lines + 1]) > 9 and not fig["fuzzy"]
figures.append(fig)
return figures
@@ -705,10 +688,10 @@ def get_fig_stats(pofile, doc_format):
def check_identical_figures(fig_stats, base_path, lang):
errors = []
for fig in fig_stats:
- trans_path = base_path / lang / fig['path']
+ trans_path = base_path / lang / fig["path"]
if trans_path.exists():
trans_hash = compute_md5(trans_path)
- if fig['hash'] == trans_hash:
+ if fig["hash"] == trans_hash:
errors.append(
("warn-ext", "Figures should not be copied when identical to original (%s)." %
trans_path)
)
@@ -716,7 +699,7 @@ def check_identical_figures(fig_stats, base_path, lang):
def add_custom_header(po_path, header, value):
- """ Add a custom po file header """
+ """Add a custom po file header"""
grep_cmd = """grep "%s" %s""" % (header, po_path)
status = 1
last_headers = ["Content-Transfer-Encoding", "Plural-Forms"]
@@ -724,23 +707,23 @@ def add_custom_header(po_path, header, value):
(status, output, errs) = run_shell_command(grep_cmd)
if status != 0:
# Try to add header
- cmd = '''sed -i '/^\"%s/ a\\"%s: %s\\\\n"' %s''' % (last_headers.pop(), header, value, po_path)
+ cmd = """sed -i '/^\"%s/ a\\"%s: %s\\\\n"' %s""" % (last_headers.pop(), header, value, po_path)
(stat, out, err) = run_shell_command(cmd)
if status == 0 and not "%s: %s" % (header, value) in output:
# Set header value
- cmd = '''sed -i '/^\"%s/ c\\"%s: %s\\\\n"' %s''' % (header, header, value, po_path)
+ cmd = """sed -i '/^\"%s/ c\\"%s: %s\\\\n"' %s""" % (header, header, value, po_path)
(stat, out, err) = run_shell_command(cmd)
def exclude_untrans_messages(potfile):
"""Exclude translatable strings matching some translator comments."""
- exclude_message = 'translators: do not translate or transliterate this text'
+ exclude_message = "translators: do not translate or transliterate this text"
# Grep first the file to see if the message is in the file.
- status, _, _ = run_shell_command(['grep', '-i', exclude_message, potfile])
+ status, _, _ = run_shell_command(["grep", "-i", exclude_message, potfile])
if status != STATUS_OK:
return
- with open(str(potfile), encoding='utf-8', mode='r+') as fh:
+ with open(str(potfile), encoding="utf-8", mode="r+") as fh:
lines = fh.readlines()
fh.seek(0)
skip_unit = False
@@ -749,19 +732,19 @@ def exclude_untrans_messages(potfile):
fh.write(line)
else:
# A blank line is resetting skip_unit
- skip_unit = line != '\n'
+ skip_unit = line != "\n"
fh.truncate()
def is_po_reduced(file_path):
- status, output, errs = run_shell_command(['grep', 'X-DamnedLies-Scope: partial', file_path])
- return (status == 0)
+ status, output, errs = run_shell_command(["grep", "X-DamnedLies-Scope: partial", file_path])
+ return status == 0
def compute_md5(full_path):
m = hashlib.md5()
- block_size = 2 ** 13
- with full_path.open('rb') as fh:
+ block_size = 2**13
+ with full_path.open("rb") as fh:
while True:
data = fh.read(block_size)
if not data:
@@ -772,22 +755,26 @@ def compute_md5(full_path):
def notify_list(branch, diff):
"""Send notification about string changes described in diff."""
- template = get_template('freeze-notification.txt')
- text = template.render({
- 'module': '%s.%s' % (branch.module.name, branch.name),
- 'ourweb': settings.SITE_DOMAIN,
- 'potdiff': "\n ".join(diff),
- 'commit_log': branch.get_vcs_web_log_url(),
- })
- send_mail("String additions to '%s.%s'" % (branch.module.name, branch.name),
- text,
- from_email="GNOME Status Pages <%s>" % settings.DEFAULT_FROM_EMAIL,
- to=settings.NOTIFICATIONS_TO,
- headers={settings.EMAIL_HEADER_NAME: "stringchange"})
+ template = get_template("freeze-notification.txt")
+ text = template.render(
+ {
+ "module": "%s.%s" % (branch.module.name, branch.name),
+ "ourweb": settings.SITE_DOMAIN,
+ "potdiff": "\n ".join(diff),
+ "commit_log": branch.get_vcs_web_log_url(),
+ }
+ )
+ send_mail(
+ "String additions to '%s.%s'" % (branch.module.name, branch.name),
+ text,
+ from_email="GNOME Status Pages <%s>" % settings.DEFAULT_FROM_EMAIL,
+ to=settings.NOTIFICATIONS_TO,
+ headers={settings.EMAIL_HEADER_NAME: "stringchange"},
+ )
def url_join(base, *args):
- """ Create an url in joining base with arguments. A lot nicer than urlparse.urljoin! """
+ """Create an url in joining base with arguments. A lot nicer than urlparse.urljoin!"""
url = base
for arg in args:
if url[-1] != "/":
diff --git a/stats/views.py b/stats/views.py
index 97a0f3dd..0e77ba17 100644
--- a/stats/views.py
+++ b/stats/views.py
@@ -1,35 +1,47 @@
-from datetime import date
import os
+from datetime import date
from django.conf import settings
-from django.contrib.auth.decorators import login_required
from django.contrib import messages
+from django.contrib.auth.decorators import login_required
from django.core import serializers
-from django.http import HttpResponse, HttpResponseForbidden, HttpResponseRedirect, Http404
-from django.shortcuts import render, get_object_or_404
+from django.http import (
+ Http404,
+ HttpResponse,
+ HttpResponseForbidden,
+ HttpResponseRedirect,
+)
+from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.utils.translation import gettext as _
from common.utils import MIME_TYPES, get_user_locale, run_shell_command
+from languages.models import Language
+from people.models import Person
+from stats.forms import ModuleBranchForm
from stats.models import (
- Branch, Category, Domain, FakeLangStatistics, Module, ModuleLock, Release, Statistics
+ Branch,
+ Category,
+ Domain,
+ FakeLangStatistics,
+ Module,
+ ModuleLock,
+ Release,
+ Statistics,
)
-from stats.forms import ModuleBranchForm
from stats.utils import sort_object_list
-from languages.models import Language
-from people.models import Person
-def modules(request, format='html'):
+def modules(request, format="html"):
all_modules = Module.objects.all()
- if format in ('json', 'xml'):
+ if format in ("json", "xml"):
data = serializers.serialize(format, all_modules)
return HttpResponse(data, content_type=MIME_TYPES[format])
context = {
- 'pageSection': "module",
- 'modules': sort_object_list(all_modules, 'get_description'),
+ "pageSection": "module",
+ "modules": sort_object_list(all_modules, "get_description"),
}
- return render(request, 'module_list.html', context)
+ return render(request, "module_list.html", context)
def module(request, module_name):
@@ -44,19 +56,19 @@ def module(request, module_name):
branch.get_doc_stats(mandatory_langs=langs)
context = {
- 'pageSection': "module",
- 'module': mod,
- 'branches': branches,
- 'non_standard_repo_msg': _(settings.VCS_HOME_WARNING),
- 'can_edit_branches': mod.can_edit_branches(request.user),
- 'can_refresh': can_refresh_branch(request.user),
- 'user_language': get_user_locale(request)
+ "pageSection": "module",
+ "module": mod,
+ "branches": branches,
+ "non_standard_repo_msg": _(settings.VCS_HOME_WARNING),
+ "can_edit_branches": mod.can_edit_branches(request.user),
+ "can_refresh": can_refresh_branch(request.user),
+ "user_language": get_user_locale(request),
}
- return render(request, 'module_detail.html', context)
+ return render(request, "module_detail.html", context)
def module_branch(request, module_name, branch_name):
- """ This view is used to dynamically load a specific branch stats (jquery.load) """
+ """This view is used to dynamically load a specific branch stats (jquery.load)"""
mod = get_object_or_404(Module, name=module_name)
branch = get_object_or_404(mod.branch_set, name=branch_name)
if request.user.is_authenticated:
@@ -67,10 +79,10 @@ def module_branch(request, module_name, branch_name):
branch.get_doc_stats(mandatory_langs=langs)
context = {
- 'module': mod,
- 'branch': branch,
+ "module": mod,
+ "branch": branch,
}
- return render(request, 'branch_detail.html', context)
+ return render(request, "branch_detail.html", context)
@login_required
@@ -80,13 +92,13 @@ def module_edit_branches(request, module_name):
messages.error(request, "Sorry, you're not allowed to edit this module's branches")
return HttpResponseRedirect(mod.get_absolute_url())
- if request.method == 'POST':
+ if request.method == "POST":
form = ModuleBranchForm(mod, request.POST)
if form.is_valid():
updated = False
# Modified or deleted release or category for branch
for key, field in form.fields.items():
- if not getattr(field, 'is_branch', False):
+ if not getattr(field, "is_branch", False):
continue
if form.fields[key].initial and not form.cleaned_data[key]:
# Delete category
@@ -94,26 +106,26 @@ def module_edit_branches(request, module_name):
updated = True
continue
release_has_changed = key in form.changed_data
- category_has_changed = (key + '_cat') in form.changed_data
+ category_has_changed = (key + "_cat") in form.changed_data
if form.cleaned_data[key] and (release_has_changed or category_has_changed):
if field.initial:
cat = Category.objects.get(pk=key)
if release_has_changed:
cat.release = form.cleaned_data[key]
- cat.name = form.cleaned_data[key + '_cat']
+ cat.name = form.cleaned_data[key + "_cat"]
else:
rel = Release.objects.get(pk=form.cleaned_data[key].pk)
branch = Branch.objects.get(module=mod, name=key)
- cat = Category(release=rel, branch=branch, name=form.cleaned_data[key + '_cat'])
+ cat = Category(release=rel, branch=branch, name=form.cleaned_data[key + "_cat"])
cat.save()
updated = True
# New branch (or new category)
- if form.cleaned_data['new_branch']:
- if 'new_category' in form.cleaned_data:
- form.cleaned_data['new_category'].save()
+ if form.cleaned_data["new_branch"]:
+ if "new_category" in form.cleaned_data:
+ form.cleaned_data["new_category"].save()
updated = True
else:
- branch_name = form.cleaned_data['new_branch']
+ branch_name = form.cleaned_data["new_branch"]
try:
branch = Branch.objects.get(module=mod, name=branch_name)
except Branch.DoesNotExist:
@@ -136,10 +148,10 @@ def module_edit_branches(request, module_name):
else:
form = ModuleBranchForm(mod)
context = {
- 'module': mod,
- 'form': form,
+ "module": mod,
+ "form": form,
}
- return render(request, 'module_edit_branches.html', context)
+ return render(request, "module_edit_branches.html", context)
def branch_refresh(request, branch_id):
@@ -148,61 +160,61 @@ def branch_refresh(request, branch_id):
branch = get_object_or_404(Branch, pk=branch_id)
if ModuleLock(branch.module).is_locked:
- messages.info(request, "A statistics update is already running for branch %s of module %s" % (
- branch.name, branch.module.name))
+ messages.info(
+ request,
+ "A statistics update is already running for branch %s of module %s" % (branch.name,
branch.module.name),
+ )
else:
try:
branch.update_stats(force=True)
except Exception as exc:
- messages.error(request, "An error occurred while updating statistics for branch %s of module
%s:<br>%s" % (
- branch.name, branch.module.name, exc))
+ messages.error(
+ request,
+ "An error occurred while updating statistics for branch %s of module %s:<br>%s"
+ % (branch.name, branch.module.name, exc),
+ )
else:
- messages.success(request, "Statistics successfully updated for branch %s of module %s" % (
- branch.name, branch.module.name))
- return HttpResponseRedirect(reverse('module', args=[branch.module.name]))
+ messages.success(
+ request,
+ "Statistics successfully updated for branch %s of module %s" % (branch.name,
branch.module.name),
+ )
+ return HttpResponseRedirect(reverse("module", args=[branch.module.name]))
def docimages(request, module_name, potbase, branch_name, langcode):
mod = get_object_or_404(Module, name=module_name)
try:
stat = Statistics.objects.get(
- branch__module=mod.id,
- branch__name=branch_name,
- domain__name=potbase,
- language__locale=langcode
+ branch__module=mod.id, branch__name=branch_name, domain__name=potbase, language__locale=langcode
)
except Statistics.DoesNotExist:
pot_stat = get_object_or_404(
- Statistics,
- branch__module=mod.id,
- branch__name=branch_name,
- domain__name=potbase,
- language__isnull=True
+ Statistics, branch__module=mod.id, branch__name=branch_name, domain__name=potbase,
language__isnull=True
)
lang = get_object_or_404(Language, locale=langcode)
stat = FakeLangStatistics(pot_stat, lang)
context = {
- 'pageSection': "module",
- 'module': mod,
- 'stat': stat,
- 'locale': stat.language.locale,
- 'figstats': stat.fig_stats(),
+ "pageSection": "module",
+ "module": mod,
+ "stat": stat,
+ "locale": stat.language.locale,
+ "figstats": stat.fig_stats(),
}
- return render(request, 'module_images.html', context)
+ return render(request, "module_images.html", context)
def dynamic_po(request, module_name, branch_name, domain_name, filename):
- """ Generates a dynamic po file from the POT file of a branch """
+ """Generates a dynamic po file from the POT file of a branch"""
try:
locale, ext = filename.split(".")
- if locale.endswith('-reduced'):
+ if locale.endswith("-reduced"):
locale, reduced = locale[:-8], True
else:
reduced = False
except Exception:
raise Http404
- language = get_object_or_404(Language.objects.select_related('team'), locale=locale)
+ language = get_object_or_404(Language.objects.select_related("team"), locale=locale)
branch = get_object_or_404(Branch, module__name=module_name, name=branch_name)
try:
domain = branch.get_domains()[domain_name]
@@ -217,16 +229,16 @@ def dynamic_po(request, module_name, branch_name, domain_name, filename):
dyn_content = """# %(lang)s translation for %(pack)s.
# Copyright (C) %(year)s %(pack)s's COPYRIGHT HOLDER
# This file is distributed under the same license as the %(pack)s package.\n""" % {
- 'lang': language.name,
- 'pack': module_name,
- 'year': date.today().year
+ "lang": language.name,
+ "pack": module_name,
+ "year": date.today().year,
}
if request.user.is_authenticated:
person = Person.get_by_user(request.user)
dyn_content += "# %(name)s <%(email)s>, %(year)s.\n#\n" % {
- 'name': person.name,
- 'email': person.email,
- 'year': date.today().year,
+ "name": person.name,
+ "email": person.email,
+ "year": date.today().year,
}
else:
dyn_content += "# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\n#\n"
@@ -235,66 +247,60 @@ def dynamic_po(request, module_name, branch_name, domain_name, filename):
try:
status, output, err = run_shell_command(command, raise_on_error=True)
except OSError:
- return HttpResponse(
- f"Unable to produce a new .po file for language {locale}, msginit command failed."
- )
+ return HttpResponse(f"Unable to produce a new .po file for language {locale}, msginit command
failed.")
lines = output.split("\n")
skip_next_line = False
for i, line in enumerate(lines):
- if skip_next_line or (line and line[0] == '#'):
+ if skip_next_line or (line and line[0] == "#"):
# Skip first lines of the file
skip_next_line = False
continue
# Transformations
new_line = {
- '"Project-Id-': "\"Project-Id-Version: %s %s\\n\"" % (module_name, branch_name),
- '"Last-Transl': "\"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n\"",
- '"Language-Te': "\"Language-Team: %s <%s>\\n\"" % (
- language.name, language.team and language.team.mailing_list or "%s li org" % locale),
- '"Content-Typ': "\"Content-Type: text/plain; charset=UTF-8\\n\"",
- '"Plural-Form': "\"Plural-Forms: %s;\\n\"" % (language.plurals or "nplurals=INTEGER;
plural=EXPRESSION"),
+ '"Project-Id-': '"Project-Id-Version: %s %s\\n"' % (module_name, branch_name),
+ '"Last-Transl': '"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n"',
+ '"Language-Te': '"Language-Team: %s <%s>\\n"'
+ % (language.name, language.team and language.team.mailing_list or "%s li org" % locale),
+ '"Content-Typ': '"Content-Type: text/plain; charset=UTF-8\\n"',
+ '"Plural-Form': '"Plural-Forms: %s;\\n"' % (language.plurals or "nplurals=INTEGER;
plural=EXPRESSION"),
}.get(line[:12], line)
- if line != new_line and line[-3:] != "\\n\"":
+ if line != new_line and line[-3:] != '\\n"':
# Skip the wrapped part of the replaced line
skip_next_line = True
dyn_content += new_line + "\n"
if line == "":
# Output the remaining part of the file untouched
- dyn_content += "\n".join(lines[i + 1:])
+ dyn_content += "\n".join(lines[i + 1 :])
break
- response = HttpResponse(dyn_content, 'text/plain')
- response['Content-Disposition'] = 'inline; filename=%s' % (
- ".".join([domain.potbase(), branch.name_escaped, filename]))
+ response = HttpResponse(dyn_content, "text/plain")
+ response["Content-Disposition"] = "inline; filename=%s" % (
+ ".".join([domain.potbase(), branch.name_escaped, filename])
+ )
return response
-def releases(request, format='html'):
- active_releases = Release.objects.filter(weight__gte=0).order_by('status', '-weight', '-name')
- old_releases = Release.objects.filter(weight__lt=0).order_by('status', '-weight', '-name')
- if format in ('json', 'xml'):
+def releases(request, format="html"):
+ active_releases = Release.objects.filter(weight__gte=0).order_by("status", "-weight", "-name")
+ old_releases = Release.objects.filter(weight__lt=0).order_by("status", "-weight", "-name")
+ if format in ("json", "xml"):
from itertools import chain
+
data = serializers.serialize(format, chain(active_releases, old_releases))
return HttpResponse(data, content_type=MIME_TYPES[format])
context = {
- 'pageSection': "releases",
- 'active_releases': active_releases,
- 'old_releases': old_releases,
+ "pageSection": "releases",
+ "active_releases": active_releases,
+ "old_releases": old_releases,
}
- return render(request, 'release_list.html', context)
+ return render(request, "release_list.html", context)
-def release(request, release_name, format='html'):
+def release(request, release_name, format="html"):
release = get_object_or_404(Release, name=release_name)
- if format == 'xml':
- return render(
- request, 'release_detail.xml', {'release': release}, content_type=MIME_TYPES[format]
- )
- context = {
- 'pageSection': "releases",
- 'user_language': get_user_locale(request),
- 'release': release
- }
- return render(request, 'release_detail.html', context)
+ if format == "xml":
+ return render(request, "release_detail.xml", {"release": release}, content_type=MIME_TYPES[format])
+ context = {"pageSection": "releases", "user_language": get_user_locale(request), "release": release}
+ return render(request, "release_detail.html", context)
def compare_by_releases(request, dtype, rels_to_compare):
@@ -302,28 +308,27 @@ def compare_by_releases(request, dtype, rels_to_compare):
raise Http404("Wrong domain type")
releases = list(
Release.objects.in_bulk(
- [f'gnome-{rel_name}' for rel_name in rels_to_compare.split("/")],
- field_name='name'
+ [f"gnome-{rel_name}" for rel_name in rels_to_compare.split("/")], field_name="name"
).values()
)
if not releases:
raise Http404("No matching releases")
stats = Release.total_by_releases(dtype, releases)
context = {
- 'releases': releases,
- 'stats': stats,
+ "releases": releases,
+ "stats": stats,
}
- return render(request, 'release_compare.html', context)
+ return render(request, "release_compare.html", context)
# ********** Utility function **********
def can_refresh_branch(user):
- """ Return True if user is authorized to force statistics refresh """
+ """Return True if user is authorized to force statistics refresh"""
if not user.is_authenticated:
return False
- if user.has_perm('stats.change_module'):
+ if user.has_perm("stats.change_module"):
return True
person = Person.get_by_user(user)
- if person.is_coordinator(team='any'):
+ if person.is_coordinator(team="any"):
return True
return False
diff --git a/teams/admin.py b/teams/admin.py
index ba422850..a804d9c1 100644
--- a/teams/admin.py
+++ b/teams/admin.py
@@ -1,6 +1,7 @@
from django.contrib import admin
-from teams.models import Team, Role
+
from languages.models import Language
+from teams.models import Role, Team
class LanguageInline(admin.TabularInline):
@@ -10,23 +11,26 @@ class LanguageInline(admin.TabularInline):
class TeamAdmin(admin.ModelAdmin):
- search_fields = ('name', 'description')
- list_display = ('description', 'use_workflow')
+ search_fields = ("name", "description")
+ list_display = ("description", "use_workflow")
inlines = [LanguageInline]
def formfield_for_dbfield(self, db_field, **kwargs):
# Reduced text area for aliases
field = super().formfield_for_dbfield(db_field, **kwargs)
- if db_field.name == 'description':
- field.widget.attrs['rows'] = '4'
+ if db_field.name == "description":
+ field.widget.attrs["rows"] = "4"
return field
class RoleAdmin(admin.ModelAdmin):
search_fields = (
- 'person__first_name', 'person__last_name', 'person__username',
- 'team__description', 'role',
+ "person__first_name",
+ "person__last_name",
+ "person__username",
+ "team__description",
+ "role",
)
diff --git a/teams/forms.py b/teams/forms.py
index a64bb4d6..d8d451fd 100644
--- a/teams/forms.py
+++ b/teams/forms.py
@@ -3,18 +3,18 @@ from django.conf import settings
from django.utils.translation import gettext as _
from common.utils import is_site_admin, send_mail
-from teams.models import Team, Role, ROLE_CHOICES
+from teams.models import ROLE_CHOICES, Role, Team
class EditTeamDetailsForm(forms.ModelForm):
class Meta:
model = Team
- fields = ('webpage_url', 'mailing_list', 'mailing_list_subscribe', 'use_workflow', 'presentation')
+ fields = ("webpage_url", "mailing_list", "mailing_list_subscribe", "use_workflow", "presentation")
widgets = {
- 'webpage_url': forms.TextInput(attrs={'size': 60, 'class': 'form-control'}),
- 'mailing_list': forms.TextInput(attrs={'size': 60, 'class': 'form-control'}),
- 'mailing_list_subscribe': forms.TextInput(attrs={'size': 60, 'class': 'form-control'}),
- 'presentation': forms.Textarea(attrs={'cols': 60, 'class': 'form-control'}),
+ "webpage_url": forms.TextInput(attrs={"size": 60, "class": "form-control"}),
+ "mailing_list": forms.TextInput(attrs={"size": 60, "class": "form-control"}),
+ "mailing_list_subscribe": forms.TextInput(attrs={"size": 60, "class": "form-control"}),
+ "presentation": forms.Textarea(attrs={"cols": 60, "class": "form-control"}),
}
def __init__(self, user, *args, **kwargs):
@@ -23,14 +23,14 @@ class EditTeamDetailsForm(forms.ModelForm):
if is_site_admin(user):
# Add coordinatorship dropdown
all_members = [
- (r.id, r.person.name) for r in
Role.objects.select_related('person').filter(team=self.instance)
+ (r.id, r.person.name) for r in
Role.objects.select_related("person").filter(team=self.instance)
]
- all_members.insert(0, ('', '-------'))
+ all_members.insert(0, ("", "-------"))
try:
- current_coord_pk = Role.objects.filter(team=self.instance, role='coordinator')[0].pk
+ current_coord_pk = Role.objects.filter(team=self.instance, role="coordinator")[0].pk
except IndexError:
current_coord_pk = None
- self.fields['coordinatorship'] = forms.ChoiceField(
+ self.fields["coordinatorship"] = forms.ChoiceField(
label=_("Coordinator"),
choices=all_members,
required=False,
@@ -39,42 +39,38 @@ class EditTeamDetailsForm(forms.ModelForm):
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
- if 'coordinatorship' in self.changed_data and is_site_admin(self.user):
+ if "coordinatorship" in self.changed_data and is_site_admin(self.user):
# Change coordinator
try:
# Pass current coordinator as committer
- current_coord = Role.objects.filter(team=self.instance, role='coordinator')[0]
- current_coord.role = 'committer'
+ current_coord = Role.objects.filter(team=self.instance, role="coordinator")[0]
+ current_coord.role = "committer"
current_coord.save()
except IndexError:
pass
- if self.cleaned_data['coordinatorship']:
- new_coord = Role.objects.get(pk=self.cleaned_data['coordinatorship'])
- new_coord.role = 'coordinator'
+ if self.cleaned_data["coordinatorship"]:
+ new_coord = Role.objects.get(pk=self.cleaned_data["coordinatorship"])
+ new_coord.role = "coordinator"
new_coord.save()
class EditMemberRoleForm(forms.Form):
-
def __init__(self, roles, *args, **kwargs):
super().__init__(*args, **kwargs)
- choices = [x for x in ROLE_CHOICES if x[0] != 'coordinator']
- choices.extend([
- ('inactivate', _("Mark as Inactive")),
- ('remove', _("Remove From Team"))
- ])
+ choices = [x for x in ROLE_CHOICES if x[0] != "coordinator"]
+ choices.extend([("inactivate", _("Mark as Inactive")), ("remove", _("Remove From Team"))])
for role in roles:
self.fields[str(role.pk)] = forms.ChoiceField(
choices=choices,
label='<a href="%s">%s</a>' % (role.person.get_absolute_url(), role.person.name),
- initial=role.role
+ initial=role.role,
)
if roles:
- self.fields['form_type'] = forms.CharField(widget=forms.HiddenInput, initial=roles[0].role)
+ self.fields["form_type"] = forms.CharField(widget=forms.HiddenInput, initial=roles[0].role)
def get_fields(self):
for key in self.fields:
- if key not in ('form_type',):
+ if key not in ("form_type",):
yield self[key]
def save(self, request):
@@ -86,13 +82,13 @@ class EditMemberRoleForm(forms.Form):
team = role.team.description
role.delete()
message = _("You have been removed from the %(team)s team on %(site)s") % {
- 'team': team,
- 'site': settings.SITE_DOMAIN,
+ "team": team,
+ "site": settings.SITE_DOMAIN,
}
message += "\n\n" + _("This is an automatic message sent from %(site)s. Please do not
answer.") % {
- 'site': settings.SITE_DOMAIN
+ "site": settings.SITE_DOMAIN
}
- send_mail(_('Removed from team'), message, to=[role.person.email])
+ send_mail(_("Removed from team"), message, to=[role.person.email])
elif form_value == "inactivate":
role.is_active = False
role.save()
@@ -100,11 +96,11 @@ class EditMemberRoleForm(forms.Form):
role.role = form_value
role.save()
message = _("Your role in the %(team)s team on %(site)s has been set to “%(role)s”") % {
- 'team': role.team.description,
- 'site': settings.SITE_DOMAIN,
- 'role': role.get_role_display(),
+ "team": role.team.description,
+ "site": settings.SITE_DOMAIN,
+ "role": role.get_role_display(),
}
message += "\n\n" + _("This is an automatic message sent from %(site)s. Please do not
answer.") % {
- 'site': settings.SITE_DOMAIN
+ "site": settings.SITE_DOMAIN
}
- send_mail(_('Role changed'), message, to=[role.person.email])
+ send_mail(_("Role changed"), message, to=[role.person.email])
diff --git a/teams/migrations/0001_initial.py b/teams/migrations/0001_initial.py
index b7422934..bc7979ee 100644
--- a/teams/migrations/0001_initial.py
+++ b/teams/migrations/0001_initial.py
@@ -1,58 +1,65 @@
-from django.db import models, migrations
+from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ('people', '0001_initial'),
+ ("people", "0001_initial"),
]
operations = [
migrations.CreateModel(
- name='Role',
+ name="Role",
fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True,
primary_key=True)),
- ('role', models.CharField(
- default='translator', max_length=15,
- choices=[
- ('translator', 'Translator'), ('reviewer', 'Reviewer'),
- ('committer', 'Committer'), ('coordinator', 'Coordinator')
- ]
- )),
- ('is_active', models.BooleanField(default=True)),
- ('person', models.ForeignKey(to='people.Person', on_delete=models.CASCADE)),
+ ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True,
primary_key=True)),
+ (
+ "role",
+ models.CharField(
+ default="translator",
+ max_length=15,
+ choices=[
+ ("translator", "Translator"),
+ ("reviewer", "Reviewer"),
+ ("committer", "Committer"),
+ ("coordinator", "Coordinator"),
+ ],
+ ),
+ ),
+ ("is_active", models.BooleanField(default=True)),
+ ("person", models.ForeignKey(to="people.Person", on_delete=models.CASCADE)),
],
options={
- 'db_table': 'role',
+ "db_table": "role",
},
),
migrations.CreateModel(
- name='Team',
+ name="Team",
fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True,
primary_key=True)),
- ('name', models.CharField(max_length=80)),
- ('description', models.TextField()),
- ('use_workflow', models.BooleanField(default=True)),
- ('presentation', models.TextField(verbose_name='Presentation', blank=True)),
- ('webpage_url', models.URLField(null=True, verbose_name='Web page', blank=True)),
- ('mailing_list', models.EmailField(
- max_length=254, null=True, verbose_name='Mailing list', blank=True
- )),
- ('mailing_list_subscribe', models.URLField(null=True, verbose_name='URL to subscribe',
blank=True)),
- ('members', models.ManyToManyField(related_name='teams', through='teams.Role',
to='people.Person')),
+ ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True,
primary_key=True)),
+ ("name", models.CharField(max_length=80)),
+ ("description", models.TextField()),
+ ("use_workflow", models.BooleanField(default=True)),
+ ("presentation", models.TextField(verbose_name="Presentation", blank=True)),
+ ("webpage_url", models.URLField(null=True, verbose_name="Web page", blank=True)),
+ (
+ "mailing_list",
+ models.EmailField(max_length=254, null=True, verbose_name="Mailing list", blank=True),
+ ),
+ ("mailing_list_subscribe", models.URLField(null=True, verbose_name="URL to subscribe",
blank=True)),
+ ("members", models.ManyToManyField(related_name="teams", through="teams.Role",
to="people.Person")),
],
options={
- 'ordering': ('description',),
- 'db_table': 'team',
+ "ordering": ("description",),
+ "db_table": "team",
},
),
migrations.AddField(
- model_name='role',
- name='team',
- field=models.ForeignKey(to='teams.Team', on_delete=models.CASCADE),
+ model_name="role",
+ name="team",
+ field=models.ForeignKey(to="teams.Team", on_delete=models.CASCADE),
),
migrations.AlterUniqueTogether(
- name='role',
- unique_together=set([('team', 'person')]),
+ name="role",
+ unique_together=set([("team", "person")]),
),
]
diff --git a/teams/models.py b/teams/models.py
index 0faff942..3816b5e2 100644
--- a/teams/models.py
+++ b/teams/models.py
@@ -4,14 +4,14 @@ from django.conf import settings
from django.db import models
from django.urls import reverse
from django.utils import timezone, translation
-from django.utils.translation import gettext_lazy, gettext as _
+from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy
from common.utils import send_mail
from people.models import Person
class TeamManager(models.Manager):
-
def all_with_coordinator(self):
"""
Returns all teams with the coordinator already prefilled. Use that
@@ -20,7 +20,7 @@ class TeamManager(models.Manager):
roles.
"""
teams = self.all()
- roles = Role.objects.select_related("person").filter(role='coordinator')
+ roles = Role.objects.select_related("person").filter(role="coordinator")
role_dict = {}
for role in roles:
@@ -28,7 +28,7 @@ class TeamManager(models.Manager):
for team in teams:
try:
- team.roles = {'coordinator': role_dict[team.id]}
+ team.roles = {"coordinator": role_dict[team.id]}
except KeyError:
# Abnormal because a team must have a coordinator but of no
# consequence
@@ -68,15 +68,15 @@ class Team(models.Model):
description = models.TextField()
use_workflow = models.BooleanField(default=True)
presentation = models.TextField(blank=True, verbose_name=_("Presentation"))
- members = models.ManyToManyField(Person, through='Role', related_name='teams')
+ members = models.ManyToManyField(Person, through="Role", related_name="teams")
webpage_url = models.URLField(null=True, blank=True, verbose_name=_("Web page"))
mailing_list = models.EmailField(null=True, blank=True, verbose_name=_("Mailing list"))
mailing_list_subscribe = models.URLField(null=True, blank=True, verbose_name=_("URL to subscribe"))
objects = TeamManager()
class Meta:
- db_table = 'team'
- ordering = ('description',)
+ db_table = "team"
+ ordering = ("description",)
def __init__(self, *args, **kwargs):
models.Model.__init__(self, *args, **kwargs)
@@ -86,21 +86,18 @@ class Team(models.Model):
return self.description
def get_absolute_url(self):
- return reverse('team_slug', args=[self.name])
+ return reverse("team_slug", args=[self.name])
def can_edit(self, user):
- """ Return True if user is allowed to edit this team
- user is a User (from request.user), not a Person
+ """Return True if user is allowed to edit this team
+ user is a User (from request.user), not a Person
"""
return user.is_authenticated and user.username in [p.username for p in self.get_coordinators()]
def fill_role(self, role, person):
- """ Used by TeamManager to prefill roles in team """
+ """Used by TeamManager to prefill roles in team"""
if not self.roles:
- self.roles = {'coordinator': [],
- 'committer': [],
- 'reviewer': [],
- 'translator': []}
+ self.roles = {"coordinator": [], "committer": [], "reviewer": [], "translator": []}
self.roles[role].append(person)
def get_description(self):
@@ -110,29 +107,27 @@ class Team(models.Model):
return self.language_set.all()
def get_members_by_role_exact(self, role, only_active=True):
- """ Return a list of active members """
+ """Return a list of active members"""
try:
return self.roles[role]
except KeyError:
if only_active:
- members = Person.objects.filter(
- role__team__id=self.id, role__role=role, role__is_active=True
- )
+ members = Person.objects.filter(role__team__id=self.id, role__role=role,
role__is_active=True)
else:
members = Person.objects.filter(role__team__id=self.id, role__role=role)
return list(members)
def get_coordinators(self):
- return self.get_members_by_role_exact('coordinator', only_active=False)
+ return self.get_members_by_role_exact("coordinator", only_active=False)
def get_committers_exact(self):
- return self.get_members_by_role_exact('committer')
+ return self.get_members_by_role_exact("committer")
def get_reviewers_exact(self):
- return self.get_members_by_role_exact('reviewer')
+ return self.get_members_by_role_exact("reviewer")
def get_translators_exact(self):
- return self.get_members_by_role_exact('translator')
+ return self.get_members_by_role_exact("translator")
def get_members_by_roles(self, roles, only_active=True):
"""Requires a list of roles in argument"""
@@ -142,24 +137,22 @@ class Team(models.Model):
members += self.roles[role]
except KeyError:
if only_active:
- members = Person.objects.filter(
- role__team__id=self.id, role__role__in=roles, role__is_active=True
- )
+ members = Person.objects.filter(role__team__id=self.id, role__role__in=roles,
role__is_active=True)
else:
members = Person.objects.filter(role__team__id=self.id, role__role__in=roles)
return list(members)
def get_committers(self):
- return self.get_members_by_roles(['coordinator', 'committer'])
+ return self.get_members_by_roles(["coordinator", "committer"])
def get_reviewers(self):
- return self.get_members_by_roles(['coordinator', 'committer', 'reviewer'])
+ return self.get_members_by_roles(["coordinator", "committer", "reviewer"])
def get_translators(self):
"""Don't use get_members_by_roles to provide an optimization"""
try:
members = []
- for role in ['coordinator', 'committer', 'reviewer', 'translator']:
+ for role in ["coordinator", "committer", "reviewer", "translator"]:
members += self.roles[role]
except KeyError:
# Not necessary to filter as for other roles
@@ -167,24 +160,20 @@ class Team(models.Model):
return members
def get_inactive_members(self):
- """ Return the inactive members """
- members = list(Person.objects.filter(role__team__id=self.id,
- role__is_active=False))
+ """Return the inactive members"""
+ members = list(Person.objects.filter(role__team__id=self.id, role__is_active=False))
return members
def send_mail_to_coordinator(self, subject, message, messagekw=None):
- """ Send a message to the coordinator, in her language if available
- and if subject and message are lazy strings """
+ """Send a message to the coordinator, in her language if available
+ and if subject and message are lazy strings"""
recipients = [pers.email for pers in self.get_coordinators() if pers.email]
if not recipients:
return
with translation.override(self.language_set.first().locale):
message = "%s\n--\n" % (message % (messagekw or {}),)
message += _("This is an automated message sent from %s.") % settings.SITE_DOMAIN
- send_mail(
- str(subject), message, to=recipients,
- headers={settings.EMAIL_HEADER_NAME: "coordinator-mail"}
- )
+ send_mail(str(subject), message, to=recipients, headers={settings.EMAIL_HEADER_NAME:
"coordinator-mail"})
class FakeTeam:
@@ -192,6 +181,7 @@ class FakeTeam:
This is a class replacing a Team object when a language
has no team attached.
"""
+
fake = 1
def __init__(self, language):
@@ -199,7 +189,7 @@ class FakeTeam:
self.description = _("No team for locale %s") % self.language.locale
def get_absolute_url(self):
- return reverse('team_slug', args=[self.language.locale])
+ return reverse("team_slug", args=[self.language.locale])
def can_edit(self, user):
return False
@@ -215,10 +205,10 @@ class FakeTeam:
ROLE_CHOICES = (
- ('translator', gettext_lazy('Translator')),
- ('reviewer', gettext_lazy('Reviewer')),
- ('committer', gettext_lazy('Committer')),
- ('coordinator', gettext_lazy('Coordinator')),
+ ("translator", gettext_lazy("Translator")),
+ ("reviewer", gettext_lazy("Reviewer")),
+ ("committer", gettext_lazy("Committer")),
+ ("coordinator", gettext_lazy("Coordinator")),
)
@@ -227,24 +217,21 @@ class Role(models.Model):
This is the intermediary class between Person and Team to attribute roles to
Team members.
"""
+
team = models.ForeignKey(Team, on_delete=models.CASCADE)
person = models.ForeignKey(Person, on_delete=models.CASCADE)
- role = models.CharField(max_length=15, choices=ROLE_CHOICES,
- default='translator')
+ role = models.CharField(max_length=15, choices=ROLE_CHOICES, default="translator")
is_active = models.BooleanField(default=True)
class Meta:
- db_table = 'role'
- unique_together = ('team', 'person')
+ db_table = "role"
+ unique_together = ("team", "person")
def __str__(self):
- return "%s is %s in %s team" % (self.person.name, self.role,
- self.team.description)
+ return "%s is %s in %s team" % (self.person.name, self.role, self.team.description)
@classmethod
def inactivate_unused_roles(cls):
- """ Inactivate the roles when login older than 180 days """
+ """Inactivate the roles when login older than 180 days"""
last_login = timezone.now() - timedelta(days=30 * 6)
- cls.objects.filter(
- person__last_login__lt=last_login, is_active=True
- ).update(is_active=False)
+ cls.objects.filter(person__last_login__lt=last_login, is_active=True).update(is_active=False)
diff --git a/teams/tests.py b/teams/tests.py
index f244bda1..d5d9acbe 100644
--- a/teams/tests.py
+++ b/teams/tests.py
@@ -7,68 +7,57 @@ from django.urls import reverse
from django.utils import timezone
from django.utils.translation import gettext_lazy
-from people.models import Person
-from teams.models import Team, Role
from languages.models import Language
+from people.models import Person
+from teams.models import Role, Team
class TeamsAndRolesMixin:
@classmethod
def setUpTestData(cls):
super().setUpTestData()
- cls.pn = Person(
- first_name='John', last_name='Nothing',
- email='jn devnull com', username='jn'
- )
- cls.pn.set_password('password')
+ cls.pn = Person(first_name="John", last_name="Nothing", email="jn devnull com", username="jn")
+ cls.pn.set_password("password")
cls.pn.save()
- cls.pt = Person.objects.create(
- first_name='John', last_name='Translator',
- email='jt tf1 com', username='jt'
- )
+ cls.pt = Person.objects.create(first_name="John", last_name="Translator", email="jt tf1 com",
username="jt")
- cls.pr = Person.objects.create(
- first_name='John', last_name='Reviewer',
- email='jr csa com', username='jr'
- )
+ cls.pr = Person.objects.create(first_name="John", last_name="Reviewer", email="jr csa com",
username="jr")
# active person, but in limit date
cls.pc = Person.objects.create(
- first_name='John', last_name='Committer',
- email='jc alinsudesonpleingre fr', username='jc',
- last_login=timezone.now() - timedelta(days=30 * 6 - 1)
+ first_name="John",
+ last_name="Committer",
+ email="jc alinsudesonpleingre fr",
+ username="jc",
+ last_login=timezone.now() - timedelta(days=30 * 6 - 1),
)
- cls.pcoo = Person(
- first_name='John', last_name='Coordinator',
- email='jcoo imthebigboss fr', username='jcoo'
- )
- cls.pcoo.set_password('password')
+ cls.pcoo = Person(first_name="John", last_name="Coordinator", email="jcoo imthebigboss fr",
username="jcoo")
+ cls.pcoo.set_password("password")
cls.pcoo.save()
- cls.t = Team.objects.create(
- name='fr', description='French',
- mailing_list='french_ml example org'
- )
+ cls.t = Team.objects.create(name="fr", description="French", mailing_list="french_ml example org")
- cls.t2 = Team.objects.create(name='pt', description='Portuguese')
+ cls.t2 = Team.objects.create(name="pt", description="Portuguese")
- cls.language = Language.objects.create(name='French', locale='fr', team=cls.t)
+ cls.language = Language.objects.create(name="French", locale="fr", team=cls.t)
- Role.objects.bulk_create([
- Role(team=cls.t, person=cls.pt),
- Role(team=cls.t2, person=cls.pt, role='reviewer'),
- Role(team=cls.t, person=cls.pr, role='reviewer'),
- Role(team=cls.t, person=cls.pc, role='committer'),
- Role(team=cls.t, person=cls.pcoo, role='coordinator'),
- ])
+ Role.objects.bulk_create(
+ [
+ Role(team=cls.t, person=cls.pt),
+ Role(team=cls.t2, person=cls.pt, role="reviewer"),
+ Role(team=cls.t, person=cls.pr, role="reviewer"),
+ Role(team=cls.t, person=cls.pc, role="committer"),
+ Role(team=cls.t, person=cls.pcoo, role="coordinator"),
+ ]
+ )
class TeamTests(TeamsAndRolesMixin, TestCase):
def test_get_members_by_role_exact(self):
- members = self.t.get_members_by_role_exact('committer')
- t = Team.objects.get(name='fr')
+ members = self.t.get_members_by_role_exact("committer")
+ t = Team.objects.get(name="fr")
self.assertEqual(len(members), 1)
self.assertEqual(members[0], self.pc)
@@ -76,14 +65,14 @@ class TeamTests(TeamsAndRolesMixin, TestCase):
role.is_active = False
role.save()
- members = self.t.get_members_by_role_exact('committer')
+ members = self.t.get_members_by_role_exact("committer")
self.assertEqual(len(members), 0)
def test_get_inactive_members(self):
members = self.t.get_inactive_members()
self.assertEqual(len(members), 0)
- t = Team.objects.get(name='fr')
+ t = Team.objects.get(name="fr")
role = Role.objects.get(person=self.pc, team=t)
role.is_active = False
role.save()
@@ -146,16 +135,13 @@ class TeamTests(TeamsAndRolesMixin, TestCase):
self.run_roles_test(Team.objects.all_with_roles()[0])
def test_join_team(self):
- response = self.client.post(
- '/login/',
- data={'username': self.pn.username, 'password': 'password'}
- )
+ response = self.client.post("/login/", data={"username": self.pn.username, "password": "password"})
# Display team join page
- team_join_url = reverse('person_team_join', current_app='people')
+ team_join_url = reverse("person_team_join", current_app="people")
response = self.client.get(team_join_url)
self.assertContains(response, "<select ")
# Post for joining
- response = self.client.post(team_join_url, {'teams': [str(self.t.pk)]})
+ response = self.client.post(team_join_url, {"teams": [str(self.t.pk)]})
# Test user is member of team
self.assertTrue(self.pn.is_translator(self.t))
# Test coordinator receives email
@@ -164,96 +150,98 @@ class TeamTests(TeamsAndRolesMixin, TestCase):
# Mail should be sent in the target team's language (i.e. French here)
self.assertIn("rejoindre", mail.outbox[0].body)
# Double submission should not crash
- self.client.post(team_join_url, {'teams': [str(self.t.pk)]})
+ self.client.post(team_join_url, {"teams": [str(self.t.pk)]})
def test_leave_team(self):
- Role.objects.create(team=self.t, person=self.pn, role='translator')
- self.client.login(username='jn', password='password')
- response = self.client.post(reverse('person_team_leave', args=[self.t.name]))
- self.assertRedirects(response, reverse('person_detail_username', args=[self.pn.username]))
+ Role.objects.create(team=self.t, person=self.pn, role="translator")
+ self.client.login(username="jn", password="password")
+ response = self.client.post(reverse("person_team_leave", args=[self.t.name]))
+ self.assertRedirects(response, reverse("person_detail_username", args=[self.pn.username]))
self.pn.refresh_from_db()
self.assertEqual(self.pn.role_set.count(), 0)
def test_edit_team(self):
- """ Test team edit form """
- edit_url = reverse('team_edit', args=['fr'], current_app='teams')
- with self.assertLogs('django.request', level='WARNING'):
+ """Test team edit form"""
+ edit_url = reverse("team_edit", args=["fr"], current_app="teams")
+ with self.assertLogs("django.request", level="WARNING"):
response = self.client.get(edit_url)
self.assertEqual(response.status_code, 403)
# Login as team coordinator
+ response = self.client.post("/login/", data={"username": self.pcoo.username, "password": "password"})
+ # Try team modification
response = self.client.post(
- '/login/',
- data={'username': self.pcoo.username, 'password': 'password'}
+ edit_url,
+ {
+ "webpage_url": "http://www.gnomefr.org/",
+ "mailing_list": "gnomefr traduc org",
+ "mailing_list_subscribe": "",
+ },
)
- # Try team modification
- response = self.client.post(edit_url, {
- 'webpage_url': "http://www.gnomefr.org/",
- 'mailing_list': "gnomefr traduc org",
- 'mailing_list_subscribe': ""
- })
- team = Team.objects.get(name='fr')
+ team = Team.objects.get(name="fr")
self.assertEqual(team.webpage_url, "http://www.gnomefr.org/")
def test_edit_team_roles(self):
- team_url = reverse('team_slug', args=['fr'])
+ team_url = reverse("team_slug", args=["fr"])
# Login as team coordinator
self.client.force_login(self.pcoo)
# Team member role modification
- self.client.post(team_url, {
- 'form_type': 'reviewer',
- '%d' % Role.objects.get(team=self.t, person=self.pr).pk: 'committer',
- })
+ self.client.post(
+ team_url,
+ {
+ "form_type": "reviewer",
+ "%d" % Role.objects.get(team=self.t, person=self.pr).pk: "committer",
+ },
+ )
self.assertEqual(len(mail.outbox), 1)
- self.assertIn('Role changed', mail.outbox[0].subject)
+ self.assertIn("Role changed", mail.outbox[0].subject)
self.assertIn(
"Your role in the French team on %s has been set to “Committer”" % settings.SITE_DOMAIN,
- mail.outbox[0].body
+ mail.outbox[0].body,
)
mail.outbox = []
# Team member removal
- self.client.post(team_url, {
- 'form_type': 'translator',
- '%d' % Role.objects.get(team=self.t, person=self.pt).pk: 'remove',
- })
- self.assertEqual(len(mail.outbox), 1)
- self.assertIn('Removed from team', mail.outbox[0].subject)
- self.assertIn(
- "You have been removed from the French team on %s" % settings.SITE_DOMAIN,
- mail.outbox[0].body
+ self.client.post(
+ team_url,
+ {
+ "form_type": "translator",
+ "%d" % Role.objects.get(team=self.t, person=self.pt).pk: "remove",
+ },
)
+ self.assertEqual(len(mail.outbox), 1)
+ self.assertIn("Removed from team", mail.outbox[0].subject)
+ self.assertIn("You have been removed from the French team on %s" % settings.SITE_DOMAIN,
mail.outbox[0].body)
def test_send_mail_to_coordinator(self):
self.t.send_mail_to_coordinator(subject="foo", message="bar")
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].subject, "%sfoo" % settings.EMAIL_SUBJECT_PREFIX)
- self.assertEqual(mail.outbox[0].extra_headers, {settings.EMAIL_HEADER_NAME: 'coordinator-mail'})
+ self.assertEqual(mail.outbox[0].extra_headers, {settings.EMAIL_HEADER_NAME: "coordinator-mail"})
# the message is sent in the language of the team
self.t.send_mail_to_coordinator(subject=gettext_lazy("About Damned Lies"), message="...")
self.assertEqual(len(mail.outbox), 2)
- self.assertEqual(
- mail.outbox[1].subject,
- "%sÀ propos de Damned Lies" % settings.EMAIL_SUBJECT_PREFIX
- )
+ self.assertEqual(mail.outbox[1].subject, "%sÀ propos de Damned Lies" % settings.EMAIL_SUBJECT_PREFIX)
class JSONTeamsTest(TeamsAndRolesMixin, TestCase):
def setUp(self):
super().setUp()
- t3 = Team.objects.create(name='gl', description='Galician')
+ t3 = Team.objects.create(name="gl", description="Galician")
coor1 = Person.objects.create(
- first_name='Marcos', last_name='Coordinator',
- email='marc imthebigboss fr', username='marcos', svn_account='thesvnaccount'
+ first_name="Marcos",
+ last_name="Coordinator",
+ email="marc imthebigboss fr",
+ username="marcos",
+ svn_account="thesvnaccount",
)
coor2 = Person.objects.create(
- first_name='Pepe', last_name='Coordinator',
- email='pepe imthebigboss es', username='pepe'
+ first_name="Pepe", last_name="Coordinator", email="pepe imthebigboss es", username="pepe"
)
- Role.objects.create(team=t3, person=coor1, role='coordinator')
- Role.objects.create(team=t3, person=coor2, role='coordinator')
+ Role.objects.create(team=t3, person=coor1, role="coordinator")
+ Role.objects.create(team=t3, person=coor2, role="coordinator")
def test_json_teams(self):
"""Test JSON teams interface"""
- response = self.client.get(reverse('teams', args=['json']))
+ response = self.client.get(reverse("teams", args=["json"]))
self.assertEqual(response.status_code, 200)
expected_json = """[
{
@@ -283,11 +271,10 @@ class JSONTeamsTest(TeamsAndRolesMixin, TestCase):
"coordinators": []
}
]"""
- self.assertJSONEqual(response.content.decode('utf-8'), expected_json)
+ self.assertJSONEqual(response.content.decode("utf-8"), expected_json)
class RoleTest(TeamsAndRolesMixin, TestCase):
-
def setUp(self):
super().setUp()
diff --git a/teams/urls.py b/teams/urls.py
index 65223a3b..aa6177fa 100644
--- a/teams/urls.py
+++ b/teams/urls.py
@@ -2,15 +2,8 @@ from django.urls import path, re_path
from teams import views
-
urlpatterns = [
- re_path(r'^(?P<format>(xml|json))?/?$',
- views.teams,
- name='teams'),
- path('<locale:team_slug>/',
- views.team,
- name='team_slug'),
- path('<locale:team_slug>/edit/',
- views.team_edit,
- name='team_edit'),
+ re_path(r"^(?P<format>(xml|json))?/?$", views.teams, name="teams"),
+ path("<locale:team_slug>/", views.team, name="team_slug"),
+ path("<locale:team_slug>/edit/", views.team_edit, name="team_edit"),
]
diff --git a/teams/views.py b/teams/views.py
index 3d11d6f0..41061fed 100644
--- a/teams/views.py
+++ b/teams/views.py
@@ -1,27 +1,24 @@
-from django.http import HttpResponseRedirect, HttpResponseForbidden
-from django.shortcuts import render, get_object_or_404
+from django.http import HttpResponseForbidden, HttpResponseRedirect
+from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from common import utils
-from teams.models import Team, FakeTeam, Role
-from teams.forms import EditMemberRoleForm, EditTeamDetailsForm
from languages.models import Language
+from teams.forms import EditMemberRoleForm, EditTeamDetailsForm
+from teams.models import FakeTeam, Role, Team
-def teams(request, format='html'):
+def teams(request, format="html"):
teams = Team.objects.all_with_coordinator()
- format = request.GET.get('format') or format
- if format in ('xml', 'json'):
- return render(
- request, 'teams/team_list.%s' % format, {'teams': teams},
- content_type=utils.MIME_TYPES[format]
- )
+ format = request.GET.get("format") or format
+ if format in ("xml", "json"):
+ return render(request, "teams/team_list.%s" % format, {"teams": teams},
content_type=utils.MIME_TYPES[format])
context = {
- 'pageSection': 'teams',
- 'teams': utils.trans_sort_object_list(teams, 'description'),
+ "pageSection": "teams",
+ "teams": utils.trans_sort_object_list(teams, "description"),
}
- return render(request, 'teams/team_list.html', context)
+ return render(request, "teams/team_list.html", context)
def team(request, team_slug):
@@ -29,33 +26,33 @@ def team(request, team_slug):
team = Team.objects.get(name=team_slug)
mem_groups = (
{
- 'id': 'committers',
- 'title': _("Committers"),
- 'members': team.get_committers_exact(),
- 'form': None,
- 'no_member': _("No committers")
+ "id": "committers",
+ "title": _("Committers"),
+ "members": team.get_committers_exact(),
+ "form": None,
+ "no_member": _("No committers"),
},
{
- 'id': 'reviewers',
- 'title': _("Reviewers"),
- 'members': team.get_reviewers_exact(),
- 'form': None,
- 'no_member': _("No reviewers")
+ "id": "reviewers",
+ "title": _("Reviewers"),
+ "members": team.get_reviewers_exact(),
+ "form": None,
+ "no_member": _("No reviewers"),
},
{
- 'id': 'translators',
- 'title': _("Translators"),
- 'members': team.get_translators_exact(),
- 'form': None,
- 'no_member': _("No translators")
+ "id": "translators",
+ "title": _("Translators"),
+ "members": team.get_translators_exact(),
+ "form": None,
+ "no_member": _("No translators"),
},
{
- 'id': 'inactive',
- 'title': _("Inactive members"),
- 'members': team.get_inactive_members(),
- 'form': None,
- 'no_member': _("No inactive members")
- }
+ "id": "inactive",
+ "title": _("Inactive members"),
+ "members": team.get_inactive_members(),
+ "form": None,
+ "no_member": _("No inactive members"),
+ },
)
except Team.DoesNotExist:
lang = get_object_or_404(Language, locale=team_slug)
@@ -63,32 +60,32 @@ def team(request, team_slug):
mem_groups = ()
context = {
- 'pageSection': 'teams',
- 'team': team,
- 'can_edit_team': False,
+ "pageSection": "teams",
+ "team": team,
+ "can_edit_team": False,
}
if team.can_edit(request.user):
- if request.method == 'POST':
- form_type = request.POST['form_type']
+ if request.method == "POST":
+ form_type = request.POST["form_type"]
roles = Role.objects.filter(team=team, role=form_type, is_active=True)
form = EditMemberRoleForm(roles, request.POST)
if form.is_valid():
form.save(request)
# Create forms for template
- commit_roles = Role.objects.filter(team=team, role='committer', is_active=True)
+ commit_roles = Role.objects.filter(team=team, role="committer", is_active=True)
if commit_roles:
- mem_groups[0]['form'] = EditMemberRoleForm(commit_roles)
- review_roles = Role.objects.filter(team=team, role='reviewer', is_active=True)
+ mem_groups[0]["form"] = EditMemberRoleForm(commit_roles)
+ review_roles = Role.objects.filter(team=team, role="reviewer", is_active=True)
if review_roles:
- mem_groups[1]['form'] = EditMemberRoleForm(review_roles)
- translate_roles = Role.objects.filter(team=team, role='translator', is_active=True)
+ mem_groups[1]["form"] = EditMemberRoleForm(review_roles)
+ translate_roles = Role.objects.filter(team=team, role="translator", is_active=True)
if translate_roles:
- mem_groups[2]['form'] = EditMemberRoleForm(translate_roles)
- context['can_edit_team'] = True
+ mem_groups[2]["form"] = EditMemberRoleForm(translate_roles)
+ context["can_edit_team"] = True
- context['mem_groups'] = mem_groups
- context['can_edit_details'] = context['can_edit_team'] or utils.is_site_admin(request.user)
- return render(request, 'teams/team_detail.html', context)
+ context["mem_groups"] = mem_groups
+ context["can_edit_details"] = context["can_edit_team"] or utils.is_site_admin(request.user)
+ return render(request, "teams/team_detail.html", context)
def team_edit(request, team_slug):
@@ -96,12 +93,9 @@ def team_edit(request, team_slug):
if not (team.can_edit(request.user) or utils.is_site_admin(request.user)):
return HttpResponseForbidden("You are not allowed to edit this team.")
form = EditTeamDetailsForm(request.user, request.POST or None, instance=team)
- if request.method == 'POST':
+ if request.method == "POST":
if form.is_valid():
form.save()
- return HttpResponseRedirect(reverse('team_slug', args=[team_slug]))
- context = {
- 'team': team,
- 'form': form
- }
- return render(request, 'teams/team_edit.html', context)
+ return HttpResponseRedirect(reverse("team_slug", args=[team_slug]))
+ context = {"team": team, "form": form}
+ return render(request, "teams/team_edit.html", context)
diff --git a/vertimus/admin.py b/vertimus/admin.py
index 4fec3f3b..1a0bc612 100644
--- a/vertimus/admin.py
+++ b/vertimus/admin.py
@@ -1,18 +1,22 @@
from django.contrib import admin
-from vertimus.models import State, Action
+from vertimus.models import Action, State
class StateAdmin(admin.ModelAdmin):
- list_filter = ('name', 'language')
- raw_id_fields = ('branch', 'domain', 'person',)
- search_fields = ('branch__module__name',)
+ list_filter = ("name", "language")
+ raw_id_fields = (
+ "branch",
+ "domain",
+ "person",
+ )
+ search_fields = ("branch__module__name",)
class ActionAdmin(admin.ModelAdmin):
- list_display = ('__str__', 'state_db', 'merged_file')
- raw_id_fields = ('state_db', 'person', 'merged_file')
- search_fields = ('comment',)
+ list_display = ("__str__", "state_db", "merged_file")
+ raw_id_fields = ("state_db", "person", "merged_file")
+ search_fields = ("comment",)
admin.site.register(State, StateAdmin)
diff --git a/vertimus/feeds.py b/vertimus/feeds.py
index d1a5587b..1a0f6e07 100644
--- a/vertimus/feeds.py
+++ b/vertimus/feeds.py
@@ -9,15 +9,16 @@ from vertimus.models import Action, ActionArchived
class LatestActionsByLanguage(Feed):
- title_template = 'feeds/actions_title.html'
- description_template = 'feeds/actions_description.html'
+ title_template = "feeds/actions_title.html"
+ description_template = "feeds/actions_description.html"
def get_object(self, request, locale):
return Language.objects.get(locale=locale)
def title(self, obj):
return _("%(site)s — Workflow actions for the %(lang)s language") % {
- 'site': settings.SITE_DOMAIN, 'lang': obj.name
+ "site": settings.SITE_DOMAIN,
+ "lang": obj.name,
}
def link(self, obj):
@@ -29,20 +30,25 @@ class LatestActionsByLanguage(Feed):
return _("Latest actions of the GNOME Translation Project for the %s language") % obj.name
def items(self, obj):
- actions = Action.objects.filter(state_db__language=obj.id).select_related(
- 'state_db'
- ).union(
-
ActionArchived.objects.filter(state_db__language=obj.id).defer('sequence').select_related('state_db')
+ actions = (
+ Action.objects.filter(state_db__language=obj.id)
+ .select_related("state_db")
+ .union(
+
ActionArchived.objects.filter(state_db__language=obj.id).defer("sequence").select_related("state_db")
+ )
)
- return actions.order_by('-created')[:20]
+ return actions.order_by("-created")[:20]
def item_link(self, item):
- link = reverse('vertimus_by_names', args=(
- item.state_db.branch.module.name,
- item.state_db.branch.name,
- item.state_db.domain.name,
- item.state_db.language.locale,
- ))
+ link = reverse(
+ "vertimus_by_names",
+ args=(
+ item.state_db.branch.module.name,
+ item.state_db.branch.name,
+ item.state_db.domain.name,
+ item.state_db.language.locale,
+ ),
+ )
return "%s#%d" % (link, item.id)
def item_pubdate(self, item):
@@ -53,16 +59,14 @@ class LatestActionsByLanguage(Feed):
class LatestActionsByTeam(Feed):
- title_template = 'feeds/actions_title.html'
- description_template = 'feeds/actions_description.html'
+ title_template = "feeds/actions_title.html"
+ description_template = "feeds/actions_description.html"
def get_object(self, request, team_name):
return Team.objects.get(name=team_name)
def title(self, obj):
- return _("%(site)s — Workflow actions of the %(lang)s team") % {
- 'site': settings.SITE_DOMAIN, 'lang': obj
- }
+ return _("%(site)s — Workflow actions of the %(lang)s team") % {"site": settings.SITE_DOMAIN,
"lang": obj}
def link(self, obj):
if not obj:
@@ -73,20 +77,27 @@ class LatestActionsByTeam(Feed):
return _("Latest actions made by the %s team of the GNOME Translation Project") % obj
def items(self, obj):
- actions = Action.objects.filter(state_db__language__team=obj.id).select_related(
- 'state_db'
- ).union(
-
ActionArchived.objects.filter(state_db__language__team=obj.id).defer('sequence').select_related('state_db')
+ actions = (
+ Action.objects.filter(state_db__language__team=obj.id)
+ .select_related("state_db")
+ .union(
+ ActionArchived.objects.filter(state_db__language__team=obj.id)
+ .defer("sequence")
+ .select_related("state_db")
+ )
)
- return actions.order_by('-created')[:20]
+ return actions.order_by("-created")[:20]
def item_link(self, item):
- link = reverse('vertimus_by_names', args=(
- item.state_db.branch.module.name,
- item.state_db.branch.name,
- item.state_db.domain.name,
- item.state_db.language.locale,
- ))
+ link = reverse(
+ "vertimus_by_names",
+ args=(
+ item.state_db.branch.module.name,
+ item.state_db.branch.name,
+ item.state_db.domain.name,
+ item.state_db.language.locale,
+ ),
+ )
return "%s#%d" % (link, item.id)
def item_pubdate(self, item):
diff --git a/vertimus/forms.py b/vertimus/forms.py
index 97c7438b..278df385 100644
--- a/vertimus/forms.py
+++ b/vertimus/forms.py
@@ -3,11 +3,12 @@ import os
from django import forms
from django.core.exceptions import ValidationError
from django.urls import reverse
-from django.utils.translation import gettext, gettext_lazy as _
+from django.utils.translation import gettext
+from django.utils.translation import gettext_lazy as _
-from vertimus.models import Action, ActionCI, ActionSeparator
from stats.models import Person
from stats.utils import check_po_conformity
+from vertimus.models import Action, ActionCI, ActionSeparator
class DisabledLabel(str):
@@ -16,13 +17,14 @@ class DisabledLabel(str):
class DisablableSelect(forms.Select):
"""Custom widget to allow a Select option to be disabled."""
+
def create_option(self, *args, **kwargs):
context = super().create_option(*args, **kwargs)
- if isinstance(context['label'], DisabledLabel):
- context['attrs']['disabled'] = True
- if context['selected']:
- context['selected'] = False
- del context['attrs']['selected']
+ if isinstance(context["label"], DisabledLabel):
+ context["attrs"]["disabled"] = True
+ if context["selected"]:
+ context["selected"] = False
+ del context["attrs"]["selected"]
return context
@@ -31,63 +33,58 @@ class AuthorChoiceField(forms.ModelChoiceField):
def label_from_instance(self, obj):
if str(obj) == obj.username:
- return DisabledLabel(gettext("%(name)s (full name missing)") % {'name': obj.username})
+ return DisabledLabel(gettext("%(name)s (full name missing)") % {"name": obj.username})
if not obj.email:
- return DisabledLabel(gettext("%(name)s (email missing)") % {'name': str(obj)})
+ return DisabledLabel(gettext("%(name)s (email missing)") % {"name": str(obj)})
return str(obj)
class ActionForm(forms.Form):
- action = forms.ChoiceField(
- label=_("Action"),
- choices=(),
- widget=DisablableSelect
- )
+ action = forms.ChoiceField(label=_("Action"), choices=(), widget=DisablableSelect)
comment = forms.CharField(
label=_("Comment"),
max_length=5000,
required=False,
- widget=forms.Textarea(attrs={'rows': 8, 'cols': 70, 'class': 'form-control'})
+ widget=forms.Textarea(attrs={"rows": 8, "cols": 70, "class": "form-control"}),
)
author = AuthorChoiceField(label=_("Commit author"), queryset=Person.objects.none(), required=False)
sync_master = forms.BooleanField(required=False)
- file = forms.FileField(label=_("File"), required=False,
- help_text=_("Upload a .po, .gz, .bz2, .xz or .png file"))
+ file = forms.FileField(label=_("File"), required=False, help_text=_("Upload a .po, .gz, .bz2, .xz or
.png file"))
send_to_ml = forms.BooleanField(label=_("Send message to the team mailing list"), required=False)
def __init__(self, current_user, state, actions, has_mailing_list, *args, **kwargs):
super().__init__(*args, **kwargs)
self.actions = actions
self.current_user = current_user
- self.fields['action'].choices = [(
- act.name,
- DisabledLabel(act.description) if isinstance(act, ActionSeparator) else act.description
- ) for act in actions]
- self.fields['action'].help_link = reverse('help', args=['vertimus_workflow', 1])
+ self.fields["action"].choices = [
+ (act.name, DisabledLabel(act.description) if isinstance(act, ActionSeparator) else
act.description)
+ for act in actions
+ ]
+ self.fields["action"].help_link = reverse("help", args=["vertimus_workflow", 1])
if state and ActionCI in map(type, self.actions):
- self.fields['author'].queryset = state.involved_persons(
- extra_user=current_user
- ).order_by('last_name', 'username')
- self.fields['author'].initial = state.get_latest_po_file_action().person
+ self.fields["author"].queryset = state.involved_persons(extra_user=current_user).order_by(
+ "last_name", "username"
+ )
+ self.fields["author"].initial = state.get_latest_po_file_action().person
if not has_mailing_list:
- del self.fields['send_to_ml']
+ del self.fields["send_to_ml"]
if state and state.branch.is_head():
- del self.fields['sync_master']
+ del self.fields["sync_master"]
elif state:
main_branch = state.branch.module.get_head_branch()
- self.fields['sync_master'].label = _("Sync with %(name)s") % {'name': main_branch.name}
- self.fields['sync_master'].help_text = (
- _("Try to cherry-pick the commit to the %(name)s branch") % {'name': main_branch.name}
- )
+ self.fields["sync_master"].label = _("Sync with %(name)s") % {"name": main_branch.name}
+ self.fields["sync_master"].help_text = _("Try to cherry-pick the commit to the %(name)s branch")
% {
+ "name": main_branch.name
+ }
def clean_file(self):
- data = self.cleaned_data['file']
+ data = self.cleaned_data["file"]
if data:
ext = os.path.splitext(data.name)[1]
- if ext not in ('.po', '.gz', '.bz2', '.xz', '.png'):
+ if ext not in (".po", ".gz", ".bz2", ".xz", ".png"):
raise ValidationError(_("Only files with extension .po, .gz, .bz2, .xz or .png are
admitted."))
# If this is a .po file, check validity (msgfmt)
- if ext == '.po':
+ if ext == ".po":
if check_po_conformity(data):
raise ValidationError(
_(".po file does not pass “msgfmt -vc”. Please correct the file and try again.")
@@ -96,16 +93,16 @@ class ActionForm(forms.Form):
def clean(self):
cleaned_data = self.cleaned_data
- action_code = cleaned_data.get('action')
+ action_code = cleaned_data.get("action")
if action_code is None:
raise ValidationError(_("Invalid action. Someone probably posted another action just before
you."))
- if action_code == 'CI':
- if not cleaned_data['author']:
+ if action_code == "CI":
+ if not cleaned_data["author"]:
raise ValidationError(_("Committing a file requires a commit author."))
- if 'Token' in getattr(self.current_user, 'backend', ''):
+ if "Token" in getattr(self.current_user, "backend", ""):
raise ValidationError(_("Committing a file with token-based authentication is prohibited."))
- comment = cleaned_data.get('comment')
- file = cleaned_data.get('file')
+ comment = cleaned_data.get("comment")
+ file = cleaned_data.get("file")
action = Action.new_by_name(action_code, comment=comment, file=file)
if action.comment_is_required and not comment:
diff --git a/vertimus/migrations/0001_initial.py b/vertimus/migrations/0001_initial.py
index b33a9a77..50f31c21 100644
--- a/vertimus/migrations/0001_initial.py
+++ b/vertimus/migrations/0001_initial.py
@@ -1,271 +1,251 @@
-from django.db import models, migrations
+from django.db import migrations, models
+
import vertimus.models
class Migration(migrations.Migration):
dependencies = [
- ('stats', '__first__'),
- ('people', '__first__'),
- ('languages', '__first__'),
+ ("stats", "__first__"),
+ ("people", "__first__"),
+ ("languages", "__first__"),
]
operations = [
migrations.CreateModel(
- name='Action',
+ name="Action",
fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True,
primary_key=True)),
- ('name', models.SlugField(max_length=8)),
- ('created', models.DateTimeField(editable=False)),
- ('comment', models.TextField(null=True, blank=True)),
- ('file', models.FileField(null=True, upload_to=vertimus.models.generate_upload_filename,
blank=True)),
+ ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True,
primary_key=True)),
+ ("name", models.SlugField(max_length=8)),
+ ("created", models.DateTimeField(editable=False)),
+ ("comment", models.TextField(null=True, blank=True)),
+ ("file", models.FileField(null=True, upload_to=vertimus.models.generate_upload_filename,
blank=True)),
(
- 'merged_file',
- models.OneToOneField(null=True, blank=True, to='stats.PoFile', on_delete=models.SET_NULL)
+ "merged_file",
+ models.OneToOneField(null=True, blank=True, to="stats.PoFile",
on_delete=models.SET_NULL),
),
- ('person', models.ForeignKey(to='people.Person', on_delete=models.CASCADE)),
+ ("person", models.ForeignKey(to="people.Person", on_delete=models.CASCADE)),
],
options={
- 'db_table': 'action',
- 'verbose_name': 'action',
+ "db_table": "action",
+ "verbose_name": "action",
},
),
migrations.CreateModel(
- name='ActionArchived',
+ name="ActionArchived",
fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True,
primary_key=True)),
- ('name', models.SlugField(max_length=8)),
- ('created', models.DateTimeField(editable=False)),
- ('comment', models.TextField(null=True, blank=True)),
- ('file', models.FileField(null=True, upload_to=vertimus.models.generate_upload_filename,
blank=True)),
- ('sequence', models.IntegerField(null=True)),
+ ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True,
primary_key=True)),
+ ("name", models.SlugField(max_length=8)),
+ ("created", models.DateTimeField(editable=False)),
+ ("comment", models.TextField(null=True, blank=True)),
+ ("file", models.FileField(null=True, upload_to=vertimus.models.generate_upload_filename,
blank=True)),
+ ("sequence", models.IntegerField(null=True)),
(
- 'merged_file',
- models.OneToOneField(null=True, blank=True, to='stats.PoFile', on_delete=models.SET_NULL)
+ "merged_file",
+ models.OneToOneField(null=True, blank=True, to="stats.PoFile",
on_delete=models.SET_NULL),
),
- ('person', models.ForeignKey(to='people.Person', on_delete=models.CASCADE)),
+ ("person", models.ForeignKey(to="people.Person", on_delete=models.CASCADE)),
],
options={
- 'db_table': 'action_archived',
+ "db_table": "action_archived",
},
),
migrations.CreateModel(
- name='State',
+ name="State",
fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True,
primary_key=True)),
- ('name', models.SlugField(default='None', max_length=20)),
- ('updated', models.DateTimeField(auto_now=True)),
- ('branch', models.ForeignKey(to='stats.Branch', on_delete=models.CASCADE)),
- ('domain', models.ForeignKey(to='stats.Domain', on_delete=models.CASCADE)),
- ('language', models.ForeignKey(to='languages.Language', on_delete=models.CASCADE)),
- ('person', models.ForeignKey(default=None, to='people.Person', null=True,
on_delete=models.SET_NULL)),
+ ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True,
primary_key=True)),
+ ("name", models.SlugField(default="None", max_length=20)),
+ ("updated", models.DateTimeField(auto_now=True)),
+ ("branch", models.ForeignKey(to="stats.Branch", on_delete=models.CASCADE)),
+ ("domain", models.ForeignKey(to="stats.Domain", on_delete=models.CASCADE)),
+ ("language", models.ForeignKey(to="languages.Language", on_delete=models.CASCADE)),
+ ("person", models.ForeignKey(default=None, to="people.Person", null=True,
on_delete=models.SET_NULL)),
],
options={
- 'db_table': 'state',
- 'verbose_name': 'state',
+ "db_table": "state",
+ "verbose_name": "state",
},
),
migrations.AddField(
- model_name='actionarchived',
- name='state_db',
- field=models.ForeignKey(to='vertimus.State', on_delete=models.CASCADE),
+ model_name="actionarchived",
+ name="state_db",
+ field=models.ForeignKey(to="vertimus.State", on_delete=models.CASCADE),
),
migrations.AddField(
- model_name='action',
- name='state_db',
- field=models.ForeignKey(to='vertimus.State', on_delete=models.CASCADE),
+ model_name="action",
+ name="state_db",
+ field=models.ForeignKey(to="vertimus.State", on_delete=models.CASCADE),
),
migrations.CreateModel(
- name='ActionAA',
- fields=[
- ],
+ name="ActionAA",
+ fields=[],
options={
- 'proxy': True,
+ "proxy": True,
},
- bases=('vertimus.action',),
+ bases=("vertimus.action",),
),
migrations.CreateModel(
- name='ActionCI',
- fields=[
- ],
+ name="ActionCI",
+ fields=[],
options={
- 'proxy': True,
+ "proxy": True,
},
- bases=('vertimus.action',),
+ bases=("vertimus.action",),
),
migrations.CreateModel(
- name='ActionIC',
- fields=[
- ],
+ name="ActionIC",
+ fields=[],
options={
- 'proxy': True,
+ "proxy": True,
},
- bases=('vertimus.action',),
+ bases=("vertimus.action",),
),
migrations.CreateModel(
- name='ActionRC',
- fields=[
- ],
+ name="ActionRC",
+ fields=[],
options={
- 'proxy': True,
+ "proxy": True,
},
- bases=('vertimus.action',),
+ bases=("vertimus.action",),
),
migrations.CreateModel(
- name='ActionRP',
- fields=[
- ],
+ name="ActionRP",
+ fields=[],
options={
- 'proxy': True,
+ "proxy": True,
},
- bases=('vertimus.action',),
+ bases=("vertimus.action",),
),
migrations.CreateModel(
- name='ActionRT',
- fields=[
- ],
+ name="ActionRT",
+ fields=[],
options={
- 'proxy': True,
+ "proxy": True,
},
- bases=('vertimus.action',),
+ bases=("vertimus.action",),
),
migrations.CreateModel(
- name='ActionTC',
- fields=[
- ],
+ name="ActionTC",
+ fields=[],
options={
- 'proxy': True,
+ "proxy": True,
},
- bases=('vertimus.action',),
+ bases=("vertimus.action",),
),
migrations.CreateModel(
- name='ActionTR',
- fields=[
- ],
+ name="ActionTR",
+ fields=[],
options={
- 'proxy': True,
+ "proxy": True,
},
- bases=('vertimus.action',),
+ bases=("vertimus.action",),
),
migrations.CreateModel(
- name='ActionUNDO',
- fields=[
- ],
+ name="ActionUNDO",
+ fields=[],
options={
- 'proxy': True,
+ "proxy": True,
},
- bases=('vertimus.action',),
+ bases=("vertimus.action",),
),
migrations.CreateModel(
- name='ActionUP',
- fields=[
- ],
+ name="ActionUP",
+ fields=[],
options={
- 'proxy': True,
+ "proxy": True,
},
- bases=('vertimus.action',),
+ bases=("vertimus.action",),
),
migrations.CreateModel(
- name='ActionUT',
- fields=[
- ],
+ name="ActionUT",
+ fields=[],
options={
- 'proxy': True,
+ "proxy": True,
},
- bases=('vertimus.action',),
+ bases=("vertimus.action",),
),
migrations.CreateModel(
- name='ActionWC',
- fields=[
- ],
+ name="ActionWC",
+ fields=[],
options={
- 'proxy': True,
+ "proxy": True,
},
- bases=('vertimus.action',),
+ bases=("vertimus.action",),
),
migrations.CreateModel(
- name='StateCommitted',
- fields=[
- ],
+ name="StateCommitted",
+ fields=[],
options={
- 'proxy': True,
+ "proxy": True,
},
- bases=('vertimus.state',),
+ bases=("vertimus.state",),
),
migrations.CreateModel(
- name='StateCommitting',
- fields=[
- ],
+ name="StateCommitting",
+ fields=[],
options={
- 'proxy': True,
+ "proxy": True,
},
- bases=('vertimus.state',),
+ bases=("vertimus.state",),
),
migrations.CreateModel(
- name='StateNone',
- fields=[
- ],
+ name="StateNone",
+ fields=[],
options={
- 'proxy': True,
+ "proxy": True,
},
- bases=('vertimus.state',),
+ bases=("vertimus.state",),
),
migrations.CreateModel(
- name='StateProofread',
- fields=[
- ],
+ name="StateProofread",
+ fields=[],
options={
- 'proxy': True,
+ "proxy": True,
},
- bases=('vertimus.state',),
+ bases=("vertimus.state",),
),
migrations.CreateModel(
- name='StateProofreading',
- fields=[
- ],
+ name="StateProofreading",
+ fields=[],
options={
- 'proxy': True,
+ "proxy": True,
},
- bases=('vertimus.state',),
+ bases=("vertimus.state",),
),
migrations.CreateModel(
- name='StateToCommit',
- fields=[
- ],
+ name="StateToCommit",
+ fields=[],
options={
- 'proxy': True,
+ "proxy": True,
},
- bases=('vertimus.state',),
+ bases=("vertimus.state",),
),
migrations.CreateModel(
- name='StateToReview',
- fields=[
- ],
+ name="StateToReview",
+ fields=[],
options={
- 'proxy': True,
+ "proxy": True,
},
- bases=('vertimus.state',),
+ bases=("vertimus.state",),
),
migrations.CreateModel(
- name='StateTranslated',
- fields=[
- ],
+ name="StateTranslated",
+ fields=[],
options={
- 'proxy': True,
+ "proxy": True,
},
- bases=('vertimus.state',),
+ bases=("vertimus.state",),
),
migrations.CreateModel(
- name='StateTranslating',
- fields=[
- ],
+ name="StateTranslating",
+ fields=[],
options={
- 'proxy': True,
+ "proxy": True,
},
- bases=('vertimus.state',),
+ bases=("vertimus.state",),
),
migrations.AlterUniqueTogether(
- name='state',
- unique_together=set([('branch', 'domain', 'language')]),
+ name="state",
+ unique_together=set([("branch", "domain", "language")]),
),
]
diff --git a/vertimus/migrations/0002_state_person_blank.py b/vertimus/migrations/0002_state_person_blank.py
index deb3b327..a3cae18e 100644
--- a/vertimus/migrations/0002_state_person_blank.py
+++ b/vertimus/migrations/0002_state_person_blank.py
@@ -4,13 +4,13 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ('vertimus', '0001_initial'),
+ ("vertimus", "0001_initial"),
]
operations = [
migrations.AlterField(
- model_name='state',
- name='person',
- field=models.ForeignKey(blank=True, to='people.Person', null=True, on_delete=models.SET_NULL),
+ model_name="state",
+ name="person",
+ field=models.ForeignKey(blank=True, to="people.Person", null=True, on_delete=models.SET_NULL),
),
]
diff --git a/vertimus/migrations/0003_add_action_sent_to_ml.py
b/vertimus/migrations/0003_add_action_sent_to_ml.py
index e1bccf92..9fabed6a 100644
--- a/vertimus/migrations/0003_add_action_sent_to_ml.py
+++ b/vertimus/migrations/0003_add_action_sent_to_ml.py
@@ -4,18 +4,18 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ('vertimus', '0002_state_person_blank'),
+ ("vertimus", "0002_state_person_blank"),
]
operations = [
migrations.AddField(
- model_name='action',
- name='sent_to_ml',
+ model_name="action",
+ name="sent_to_ml",
field=models.BooleanField(default=False),
),
migrations.AddField(
- model_name='actionarchived',
- name='sent_to_ml',
+ model_name="actionarchived",
+ name="sent_to_ml",
field=models.BooleanField(default=False),
),
]
diff --git a/vertimus/migrations/0004_tables_to_utf8mb4.py b/vertimus/migrations/0004_tables_to_utf8mb4.py
index 68882e4c..5300e436 100644
--- a/vertimus/migrations/0004_tables_to_utf8mb4.py
+++ b/vertimus/migrations/0004_tables_to_utf8mb4.py
@@ -2,7 +2,7 @@ from django.db import migrations
def to_utf8mb4(apps, schema_editor):
- if not schema_editor.connection.vendor == 'mysql':
+ if not schema_editor.connection.vendor == "mysql":
return
tables = [
apps.get_model("vertimus", "Action")._meta.db_table,
@@ -11,15 +11,14 @@ def to_utf8mb4(apps, schema_editor):
]
for table_name in tables:
schema_editor.execute(
- 'ALTER TABLE %s CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci' % table_name,
- params=None
+ "ALTER TABLE %s CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci" % table_name,
params=None
)
class Migration(migrations.Migration):
dependencies = [
- ('vertimus', '0003_add_action_sent_to_ml'),
+ ("vertimus", "0003_add_action_sent_to_ml"),
]
operations = [migrations.RunPython(to_utf8mb4, migrations.RunPython.noop, atomic=False)]
diff --git a/vertimus/migrations/0005_action_proxy_pofile.py b/vertimus/migrations/0005_action_proxy_pofile.py
index 6b7f93b6..42401033 100644
--- a/vertimus/migrations/0005_action_proxy_pofile.py
+++ b/vertimus/migrations/0005_action_proxy_pofile.py
@@ -1,38 +1,37 @@
-from django.db import migrations, models
import django.db.models.deletion
+from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ('stats', '0001_initial'),
- ('vertimus', '0004_tables_to_utf8mb4'),
+ ("stats", "0001_initial"),
+ ("vertimus", "0004_tables_to_utf8mb4"),
]
operations = [
migrations.CreateModel(
- name='MergedPoFile',
- fields=[
- ],
+ name="MergedPoFile",
+ fields=[],
options={
- 'proxy': True,
- 'indexes': [],
- 'constraints': [],
+ "proxy": True,
+ "indexes": [],
+ "constraints": [],
},
- bases=('stats.pofile',),
+ bases=("stats.pofile",),
),
migrations.AlterField(
- model_name='action',
- name='merged_file',
+ model_name="action",
+ name="merged_file",
field=models.OneToOneField(
- blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL,
to='vertimus.MergedPoFile'
+ blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL,
to="vertimus.MergedPoFile"
),
),
migrations.AlterField(
- model_name='actionarchived',
- name='merged_file',
+ model_name="actionarchived",
+ name="merged_file",
field=models.OneToOneField(
- blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL,
to='vertimus.MergedPoFile'
+ blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL,
to="vertimus.MergedPoFile"
),
),
]
diff --git a/vertimus/migrations/0006_pofile_path_relative.py
b/vertimus/migrations/0006_pofile_path_relative.py
index 0818cb8c..59039949 100644
--- a/vertimus/migrations/0006_pofile_path_relative.py
+++ b/vertimus/migrations/0006_pofile_path_relative.py
@@ -1,4 +1,5 @@
import os
+
from django.conf import settings
from django.db import migrations
@@ -10,7 +11,7 @@ def strip_path_prefix(apps, schema_editor):
def strip_path(action):
old_path = action.merged_file.path
- action.merged_file.path = old_path.split('/' + media_dir + '/')[1]
+ action.merged_file.path = old_path.split("/" + media_dir + "/")[1]
action.merged_file.save()
for act in action.objects.filter(merged_file__isnull=False):
@@ -22,7 +23,7 @@ def strip_path_prefix(apps, schema_editor):
class Migration(migrations.Migration):
dependencies = [
- ('vertimus', '0005_action_proxy_pofile'),
+ ("vertimus", "0005_action_proxy_pofile"),
]
operations = [migrations.RunPython(strip_path_prefix, migrations.RunPython.noop)]
diff --git a/vertimus/migrations/0008_actions_on_delete_setnull.py
b/vertimus/migrations/0008_actions_on_delete_setnull.py
index 63815923..facd1171 100644
--- a/vertimus/migrations/0008_actions_on_delete_setnull.py
+++ b/vertimus/migrations/0008_actions_on_delete_setnull.py
@@ -4,19 +4,19 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ('people', '__first__'),
- ('vertimus', '0006_pofile_path_relative'),
+ ("people", "__first__"),
+ ("vertimus", "0006_pofile_path_relative"),
]
operations = [
migrations.AlterField(
- model_name='action',
- name='person',
- field=models.ForeignKey(null=True, on_delete=models.deletion.SET_NULL, to='people.person'),
+ model_name="action",
+ name="person",
+ field=models.ForeignKey(null=True, on_delete=models.deletion.SET_NULL, to="people.person"),
),
migrations.AlterField(
- model_name='actionarchived',
- name='person',
- field=models.ForeignKey(null=True, on_delete=models.deletion.SET_NULL, to='people.person'),
+ model_name="actionarchived",
+ name="person",
+ field=models.ForeignKey(null=True, on_delete=models.deletion.SET_NULL, to="people.person"),
),
]
diff --git a/vertimus/models.py b/vertimus/models.py
index a10ae7c4..7b93547e 100644
--- a/vertimus/models.py
+++ b/vertimus/models.py
@@ -10,14 +10,14 @@ from django.db.models.signals import post_save, pre_delete
from django.dispatch import receiver
from django.urls import reverse
from django.utils import timezone
-from django.utils.translation import (
- override, gettext, gettext_noop, gettext_lazy as _, to_language
-)
+from django.utils.translation import gettext
+from django.utils.translation import gettext_lazy as _
+from django.utils.translation import gettext_noop, override, to_language
from common.utils import run_shell_command, send_mail
from languages.models import Language
from people.models import Person
-from stats.models import Branch, Domain, Statistics, PoFile, UnableToCommit
+from stats.models import Branch, Domain, PoFile, Statistics, UnableToCommit
from stats.signals import pot_has_changed
from stats.utils import is_po_reduced, po_grep
from teams.models import Role
@@ -32,43 +32,47 @@ class SendMailFailed(Exception):
#
class State(models.Model):
"""State of a module translation"""
+
branch = models.ForeignKey(Branch, on_delete=models.CASCADE)
domain = models.ForeignKey(Domain, on_delete=models.CASCADE)
language = models.ForeignKey(Language, on_delete=models.CASCADE)
person = models.ForeignKey(Person, blank=True, null=True, on_delete=models.SET_NULL)
- name = models.SlugField(max_length=20, default='None')
+ name = models.SlugField(max_length=20, default="None")
updated = models.DateTimeField(auto_now=True, editable=False)
class Meta:
- db_table = 'state'
- verbose_name = 'state'
- unique_together = ('branch', 'domain', 'language')
+ db_table = "state"
+ verbose_name = "state"
+ unique_together = ("branch", "domain", "language")
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
- if self.name == 'None' and getattr(self.__class__, '_name', 'None') != 'None':
+ if self.name == "None" and getattr(self.__class__, "_name", "None") != "None":
self.name = self.__class__._name
self.__class__ = {
- 'None': StateNone,
- 'Translating': StateTranslating,
- 'Translated': StateTranslated,
- 'Proofreading': StateProofreading,
- 'Proofread': StateProofread,
- 'ToReview': StateToReview,
- 'ToCommit': StateToCommit,
- 'Committing': StateCommitting,
- 'Committed': StateCommitted,
+ "None": StateNone,
+ "Translating": StateTranslating,
+ "Translated": StateTranslated,
+ "Proofreading": StateProofreading,
+ "Proofread": StateProofread,
+ "ToReview": StateToReview,
+ "ToCommit": StateToCommit,
+ "Committing": StateCommitting,
+ "Committed": StateCommitted,
}.get(self.name, State)
def __str__(self):
return "%s: %s %s (%s - %s)" % (
- self.name, self.branch.module.name,
- self.branch.name, self.language.name, self.domain.name
+ self.name,
+ self.branch.module.name,
+ self.branch.name,
+ self.language.name,
+ self.domain.name,
)
def get_absolute_url(self):
- return reverse('vertimus_by_ids', args=[self.branch_id, self.domain_id, self.language_id])
+ return reverse("vertimus_by_ids", args=[self.branch_id, self.domain_id, self.language_id])
@property
def stats(self):
@@ -97,26 +101,28 @@ class State(models.Model):
return [ActionAA()]
return []
- action_names.append('WC')
+ action_names.append("WC")
# Allow the coordinator to cancel current reserved state
- if person.is_coordinator(self.language.team) and \
- self.name.endswith('ing') and 'UNDO' not in action_names:
- action_names.append('UNDO')
- if person.is_committer(self.language.team) and 'IC' not in action_names:
- action_names.extend(('Separator', 'IC', 'AA'))
- return [eval('Action' + action_name)() for action_name in action_names] # FIXME: eval is unsafe
+ if person.is_coordinator(self.language.team) and self.name.endswith("ing") and "UNDO" not in
action_names:
+ action_names.append("UNDO")
+ if person.is_committer(self.language.team) and "IC" not in action_names:
+ action_names.extend(("Separator", "IC", "AA"))
+ return [eval("Action" + action_name)() for action_name in action_names] # FIXME: eval is unsafe
def get_action_sequence_from_level(self, level):
"""Get the sequence corresponding to the requested level.
- The first level is 1."""
+ The first level is 1."""
assert level > 0, "Level must be greater than 0"
- query = ActionArchived.objects.filter(
- state_db=self
- ).values('sequence').distinct().order_by('-sequence')[level - 1:level]
+ query = (
+ ActionArchived.objects.filter(state_db=self)
+ .values("sequence")
+ .distinct()
+ .order_by("-sequence")[level - 1 : level]
+ )
sequence = None
if len(query) > 0:
- sequence = query[0]['sequence']
+ sequence = query[0]["sequence"]
return sequence
def involved_persons(self, extra_user=None):
@@ -129,14 +135,14 @@ class State(models.Model):
def get_latest_po_file_action(self):
try:
- return Action.objects.filter(file__endswith=".po", state_db=self).latest('id')
+ return Action.objects.filter(file__endswith=".po", state_db=self).latest("id")
except Action.DoesNotExist:
return None
class StateNone(State):
- _name = 'None'
- description = _('Inactive')
+ _name = "None"
+ description = _("Inactive")
class Meta:
proxy = True
@@ -144,18 +150,17 @@ class StateNone(State):
def get_available_actions(self, person):
action_names = []
- if (
- (self.language.team and person.is_translator(self.language.team))
- or person.is_maintainer_of(self.branch.module)
+ if (self.language.team and person.is_translator(self.language.team)) or person.is_maintainer_of(
+ self.branch.module
):
- action_names = ['RT', 'UT']
+ action_names = ["RT", "UT"]
return self._get_available_actions(person, action_names)
class StateTranslating(State):
- _name = 'Translating'
- description = _('Translating')
+ _name = "Translating"
+ description = _("Translating")
class Meta:
proxy = True
@@ -163,15 +168,15 @@ class StateTranslating(State):
def get_available_actions(self, person):
action_names = []
- if (self.person == person):
- action_names = ['UT', 'UNDO']
+ if self.person == person:
+ action_names = ["UT", "UNDO"]
return self._get_available_actions(person, action_names)
class StateTranslated(State):
- _name = 'Translated'
- description = _('Translated')
+ _name = "Translated"
+ description = _("Translated")
class Meta:
proxy = True
@@ -180,24 +185,24 @@ class StateTranslated(State):
action_names = []
if person.is_reviewer(self.language.team):
- action_names.append('RP')
- action_names.append('UP')
- action_names.append('TR')
+ action_names.append("RP")
+ action_names.append("UP")
+ action_names.append("TR")
if person.is_translator(self.language.team):
- action_names.append('RT')
+ action_names.append("RT")
if person.is_committer(self.language.team):
- action_names.append('TC')
+ action_names.append("TC")
if self.able_to_commit():
- action_names.append('CI')
+ action_names.append("CI")
return self._get_available_actions(person, action_names)
class StateProofreading(State):
- _name = 'Proofreading'
- description = _('Proofreading')
+ _name = "Proofreading"
+ description = _("Proofreading")
class Meta:
proxy = True
@@ -206,36 +211,36 @@ class StateProofreading(State):
action_names = []
if person.is_reviewer(self.language.team):
- if (self.person == person):
- action_names = ['UP', 'TR', 'TC', 'UNDO']
+ if self.person == person:
+ action_names = ["UP", "TR", "TC", "UNDO"]
return self._get_available_actions(person, action_names)
class StateProofread(State):
- _name = 'Proofread'
+ _name = "Proofread"
# Translators: This is a status, not a verb
- description = _('Proofread')
+ description = _("Proofread")
class Meta:
proxy = True
def get_available_actions(self, person):
if person.is_reviewer(self.language.team):
- action_names = ['TC', 'RP', 'TR']
+ action_names = ["TC", "RP", "TR"]
else:
action_names = []
if person.is_committer(self.language.team):
- action_names.append('RC')
+ action_names.append("RC")
if self.able_to_commit():
- action_names.insert(1, 'CI')
+ action_names.insert(1, "CI")
return self._get_available_actions(person, action_names)
class StateToReview(State):
- _name = 'ToReview'
- description = _('To Review')
+ _name = "ToReview"
+ description = _("To Review")
class Meta:
proxy = True
@@ -243,23 +248,23 @@ class StateToReview(State):
def get_available_actions(self, person):
action_names = []
if person.is_translator(self.language.team):
- action_names.append('RT')
+ action_names.append("RT")
return self._get_available_actions(person, action_names)
class StateToCommit(State):
- _name = 'ToCommit'
- description = _('To Commit')
+ _name = "ToCommit"
+ description = _("To Commit")
class Meta:
proxy = True
def get_available_actions(self, person):
if person.is_committer(self.language.team):
- action_names = ['RC', 'TR', 'UNDO']
+ action_names = ["RC", "TR", "UNDO"]
if self.able_to_commit():
- action_names.insert(1, 'CI')
+ action_names.insert(1, "CI")
else:
action_names = []
@@ -267,8 +272,8 @@ class StateToCommit(State):
class StateCommitting(State):
- _name = 'Committing'
- description = _('Committing')
+ _name = "Committing"
+ description = _("Committing")
class Meta:
proxy = True
@@ -277,24 +282,24 @@ class StateCommitting(State):
action_names = []
if person.is_committer(self.language.team):
- if (self.person == person):
- action_names = ['IC', 'TR', 'UNDO']
+ if self.person == person:
+ action_names = ["IC", "TR", "UNDO"]
if self.able_to_commit():
- action_names.insert(0, 'CI')
+ action_names.insert(0, "CI")
return self._get_available_actions(person, action_names)
class StateCommitted(State):
- _name = 'Committed'
- description = _('Committed')
+ _name = "Committed"
+ description = _("Committed")
class Meta:
proxy = True
def get_available_actions(self, person):
if person.is_committer(self.language.team):
- action_names = ['AA']
+ action_names = ["AA"]
else:
action_names = []
@@ -306,22 +311,22 @@ class StateCommitted(State):
#
ACTION_NAMES = (
- ('WC', _('Write a comment')),
- ('RT', _('Reserve for translation')),
- ('UT', _('Upload the new translation')),
- ('RP', _('Reserve for proofreading')),
- ('UP', _('Upload the proofread translation')),
+ ("WC", _("Write a comment")),
+ ("RT", _("Reserve for translation")),
+ ("UT", _("Upload the new translation")),
+ ("RP", _("Reserve for proofreading")),
+ ("UP", _("Upload the proofread translation")),
# Translators: this means the file is ready to be committed in repository
- ('TC', _('Ready for submission')),
- ('CI', _('Submit to repository')),
+ ("TC", _("Ready for submission")),
+ ("CI", _("Submit to repository")),
# Translators: this indicates a committer is going to commit the file in the repository
- ('RC', _('Reserve to submit')),
+ ("RC", _("Reserve to submit")),
# Translators: this is used to indicate the file has been committed in the repository
- ('IC', _('Inform of submission')),
+ ("IC", _("Inform of submission")),
# Translators: regardless of the translation completion, this file need to be reviewed
- ('TR', _('Rework needed')),
- ('AA', _('Archive the actions')),
- ('UNDO', _('Undo the last state change')),
+ ("TR", _("Rework needed")),
+ ("AA", _("Archive the actions")),
+ ("UNDO", _("Undo the last state change")),
)
@@ -339,7 +344,8 @@ def generate_upload_filename(instance, filename):
instance.state_db.domain.name,
instance.state_db.language.locale,
instance.state_db.id,
- ext)
+ ext,
+ )
return "%s/%s" % (settings.UPLOAD_DIR, new_filename)
@@ -353,7 +359,8 @@ class MergedPoFile(PoFile):
class ActionAbstract(models.Model):
- """ Common model for Action and ActionArchived """
+ """Common model for Action and ActionArchived"""
+
state_db = models.ForeignKey(State, on_delete=models.CASCADE)
person = models.ForeignKey(Person, null=True, on_delete=models.SET_NULL)
@@ -385,7 +392,7 @@ class ActionAbstract(models.Model):
@property
def person_name(self):
- return self.person.name if self.person else gettext('deleted account')
+ return self.person.name if self.person else gettext("deleted account")
@property
def most_uptodate_file(self):
@@ -397,15 +404,12 @@ class ActionAbstract(models.Model):
@property
def can_build(self):
- return (
- not isinstance(self, ActionArchived)
- and self.state_db.domain.can_build_docs(self.state_db.branch)
- )
+ return not isinstance(self, ActionArchived) and
self.state_db.domain.can_build_docs(self.state_db.branch)
@property
def build_url(self):
- path = settings.SCRATCHDIR / 'HTML' / str(self.pk) / 'index.html'
- return '/' + str(path.relative_to(settings.SCRATCHDIR)) if path.exists() else None
+ path = settings.SCRATCHDIR / "HTML" / str(self.pk) / "index.html"
+ return "/" + str(path.relative_to(settings.SCRATCHDIR)) if path.exists() else None
def get_filename(self):
if self.file:
@@ -425,21 +429,23 @@ class ActionAbstract(models.Model):
"""
history = []
if state or sequence:
- file_history = [{'action_id': 0, 'title': gettext("File in repository")}]
+ file_history = [{"action_id": 0, "title": gettext("File in repository")}]
if not sequence:
query = cls.objects.filter(state_db__id=state.id)
else:
# Not necessary to filter on state with a sequence (unique)
query = cls.objects.filter(sequence=sequence)
- for action in query.order_by('id'):
+ for action in query.order_by("id"):
history.append((action, list(file_history)))
- if action.file and action.file.path.endswith('.po'):
- file_history.insert(0, {
- 'action_id': action.id,
- 'title': gettext("Uploaded file by %(name)s on %(date)s") % {
- 'name': action.person_name,
- 'date': action.created},
- })
+ if action.file and action.file.path.endswith(".po"):
+ file_history.insert(
+ 0,
+ {
+ "action_id": action.id,
+ "title": gettext("Uploaded file by %(name)s on %(date)s")
+ % {"name": action.person_name, "date": action.created},
+ },
+ )
return history
@@ -449,20 +455,20 @@ class Action(ActionAbstract):
)
class Meta:
- db_table = 'action'
- verbose_name = 'action'
+ db_table = "action"
+ verbose_name = "action"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.name:
- self.__class__ = eval('Action' + self.name)
+ self.__class__ = eval("Action" + self.name)
else:
- if getattr(self.__class__, 'name'):
+ if getattr(self.__class__, "name"):
self.name = self.__class__.name
@classmethod
def new_by_name(cls, action_name, **kwargs):
- return eval('Action' + action_name)(**kwargs) # FIXME: eval is unsafe
+ return eval("Action" + action_name)(**kwargs) # FIXME: eval is unsafe
def save(self, *args, **kwargs):
if not self.id and not self.created:
@@ -479,29 +485,29 @@ class Action(ActionAbstract):
if self.target_state == StateCommitted:
# Committed is the last state of the workflow, archive actions
- arch_action = self.new_by_name('AA', person=self.person)
+ arch_action = self.new_by_name("AA", person=self.person)
arch_action.apply_on(self.state_db, {})
def apply_on(self, state, form_data):
if self.name not in [a.name for a in state.get_available_actions(self.person)]:
- raise Exception('Action not allowed')
+ raise Exception("Action not allowed")
if state.pk is None:
state.save()
self.state_db = state
if self.file:
self.file.save(os.path.basename(self.file.name), self.file, save=False)
- if form_data.get('comment'):
- self.comment = form_data['comment']
- self.sent_to_ml = form_data.get('send_to_ml', False)
+ if form_data.get("comment"):
+ self.comment = form_data["comment"]
+ self.sent_to_ml = form_data.get("send_to_ml", False)
self.save()
self.update_state() # Do not save action after that, they have been archived
- if form_data.get('send_to_ml'):
+ if form_data.get("send_to_ml"):
self.send_mail_new_state(state, (state.language.team.mailing_list,))
elif self.send_mail_to_ml or self.comment:
# If the action is normally sent to ML (but unchecked) or if the form
# contains a comment, send message to state participants
- recipients = set(state.involved_persons().exclude(pk=self.person.pk).values_list('email',
flat=True))
+ recipients = set(state.involved_persons().exclude(pk=self.person.pk).values_list("email",
flat=True))
if self.comment and not recipients:
# In any case, the coordinators should be aware of comments
recipients = [pers.email for pers in state.language.team.get_coordinators() if pers.email]
@@ -516,7 +522,7 @@ class Action(ActionAbstract):
if self.id:
prev_actions_with_po = prev_actions_with_po.filter(id__lt=self.id)
try:
- return prev_actions_with_po.latest('id')
+ return prev_actions_with_po.latest("id")
except Action.DoesNotExist:
return None
@@ -526,13 +532,11 @@ class Action(ActionAbstract):
return
if not self.merged_file:
merged_path = "%s.merged.po" % self.file.path[:-3]
- self.merged_file = MergedPoFile.objects.create(
- path=os.path.relpath(merged_path, settings.MEDIA_ROOT)
- )
+ self.merged_file = MergedPoFile.objects.create(path=os.path.relpath(merged_path,
settings.MEDIA_ROOT))
self.save()
return # post_save will call merge_file_with_pot again
merged_path = self.merged_file.full_path
- command = ['msgmerge', '--previous', '-o', merged_path, self.file.path, str(pot_file)]
+ command = ["msgmerge", "--previous", "-o", merged_path, self.file.path, str(pot_file)]
run_shell_command(command)
# If uploaded file is reduced, run po_grep *after* merge
if is_po_reduced(self.file.path):
@@ -553,36 +557,29 @@ class Action(ActionAbstract):
return
url = "https://%s%s" % (
- settings.SITE_DOMAIN, reverse(
- 'vertimus_by_names',
- args=(
- state.branch.module.name,
- state.branch.name,
- state.domain.name,
- state.language.locale
- )
- )
+ settings.SITE_DOMAIN,
+ reverse(
+ "vertimus_by_names",
+ args=(state.branch.module.name, state.branch.name, state.domain.name, state.language.locale),
+ ),
)
- subject = state.branch.module.name + ' - ' + state.branch.name
+ subject = state.branch.module.name + " - " + state.branch.name
# to_language may be unnecessary after https://code.djangoproject.com/ticket/32581 is solved
with override(to_language(Language.django_locale(state.language.locale))):
message = gettext("Hello,") + "\n\n" + gettext(self.default_message) + "\n%(url)s\n\n"
message = message % {
- 'module': state.branch.module.name,
- 'branch': state.branch.name,
- 'domain': state.domain.name,
- 'language': state.language.get_name(),
- 'new_state': state.description,
- 'url': url
+ "module": state.branch.module.name,
+ "branch": state.branch.name,
+ "domain": state.domain.name,
+ "language": state.language.get_name(),
+ "new_state": state.description,
+ "url": url,
}
message += self.comment or gettext("Without comment")
message += "\n\n" + self.person.name
message += "\n--\n" + gettext("This is an automated message sent from %s.") %
settings.SITE_DOMAIN
try:
- send_mail(
- subject, message, to=recipient_list,
- headers={settings.EMAIL_HEADER_NAME: state.description}
- )
+ send_mail(subject, message, to=recipient_list, headers={settings.EMAIL_HEADER_NAME:
state.description})
except Exception as exc:
raise SendMailFailed("Sending message failed: %r" % exc)
@@ -597,26 +594,24 @@ class ActionArchived(ActionAbstract):
sequence = models.IntegerField(null=True)
class Meta:
- db_table = 'action_archived'
+ db_table = "action_archived"
@classmethod
def clean_old_actions(cls, days):
- """ Delete old archived actions after some (now-days) time """
+ """Delete old archived actions after some (now-days) time"""
# In each sequence, test date of the latest action, to delete whole sequences instead of individual
actions
- for action in ActionArchived.objects.values(
- 'sequence'
- ).annotate(
- max_created=Max('created')
- ).filter(
- max_created__lt=timezone.now() - timedelta(days=days)
+ for action in (
+ ActionArchived.objects.values("sequence")
+ .annotate(max_created=Max("created"))
+ .filter(max_created__lt=timezone.now() - timedelta(days=days))
):
# Call each action delete() so as file is also deleted
- for act in ActionArchived.objects.filter(sequence=action['sequence']):
+ for act in ActionArchived.objects.filter(sequence=action["sequence"]):
act.delete()
class ActionWC(Action):
- name = 'WC'
+ name = "WC"
comment_is_required = True
default_message = gettext_noop(
"A new comment has been posted on %(module)s — %(branch)s — %(domain)s (%(language)s)."
@@ -627,7 +622,7 @@ class ActionWC(Action):
class ActionRT(Action):
- name = 'RT'
+ name = "RT"
target_state = StateTranslating
file_is_prohibited = True
@@ -636,7 +631,7 @@ class ActionRT(Action):
class ActionUT(Action):
- name = 'UT'
+ name = "UT"
target_state = StateTranslated
file_is_required = True
send_mail_to_ml = True
@@ -646,7 +641,7 @@ class ActionUT(Action):
class ActionRP(Action):
- name = 'RP'
+ name = "RP"
target_state = StateProofreading
file_is_prohibited = True
@@ -655,7 +650,7 @@ class ActionRP(Action):
class ActionUP(Action):
- name = 'UP'
+ name = "UP"
target_state = StateProofread
file_is_required = True
send_mail_to_ml = True
@@ -665,7 +660,7 @@ class ActionUP(Action):
class ActionTC(Action):
- name = 'TC'
+ name = "TC"
target_state = StateToCommit
class Meta:
@@ -679,7 +674,7 @@ class ActionTC(Action):
class ActionCI(Action):
- name = 'CI'
+ name = "CI"
target_state = StateCommitted
file_is_prohibited = True
send_mail_to_ml = True
@@ -690,33 +685,32 @@ class ActionCI(Action):
def apply_on(self, state, form_data):
self.state_db = state
action_with_po = self.get_previous_action_with_po()
- author = form_data['author'].as_author() if form_data['author'] else None
+ author = form_data["author"].as_author() if form_data["author"] else None
try:
- commit_hash = state.branch.commit_po(
- action_with_po.file.path, state.domain, state.language, author)
+ commit_hash = state.branch.commit_po(action_with_po.file.path, state.domain, state.language,
author)
except Exception:
# Commit failed, state unchanged
raise Exception(_("The commit failed. The error was: “%s”") % sys.exc_info()[1])
msg = gettext("The file has been successfully committed to the repository.")
- if form_data.get('sync_master', False) and not state.branch.is_head():
+ if form_data.get("sync_master", False) and not state.branch.is_head():
# Cherry-pick the commit on the master branch
main_branch = state.branch.module.get_head_branch()
success = main_branch.cherrypick_commit(commit_hash, state.domain)
if success:
msg += gettext(" Additionally, the synchronization with the %(name)s branch succeeded.") % {
- 'name': main_branch.name
+ "name": main_branch.name
}
else:
msg += gettext(" However, the synchronization with the %(name)s branch failed.") % {
- 'name': main_branch.name
+ "name": main_branch.name
}
super().apply_on(state, form_data) # Mail sent in super
return msg
class ActionRC(Action):
- name = 'RC'
+ name = "RC"
target_state = StateCommitting
file_is_prohibited = True
@@ -725,7 +719,7 @@ class ActionRC(Action):
class ActionIC(Action):
- name = 'IC'
+ name = "IC"
target_state = StateCommitted
send_mail_to_ml = True
@@ -734,7 +728,7 @@ class ActionIC(Action):
class ActionTR(Action):
- name = 'TR'
+ name = "TR"
target_state = StateToReview
arg_is_required = True
send_mail_to_ml = True
@@ -744,7 +738,7 @@ class ActionTR(Action):
class ActionAA(Action):
- name = 'AA'
+ name = "AA"
target_state = StateNone
class Meta:
@@ -752,7 +746,7 @@ class ActionAA(Action):
def apply_on(self, state, form_data):
super().apply_on(state, form_data)
- all_actions = Action.objects.filter(state_db=state).order_by('id').all()
+ all_actions = Action.objects.filter(state_db=state).order_by("id").all()
sequence = None
for action in all_actions:
@@ -768,7 +762,8 @@ class ActionAA(Action):
name=action.name,
created=action.created,
comment=action.comment,
- file=file_to_archive)
+ file=file_to_archive,
+ )
if file_to_archive:
action_archived.file.save(os.path.basename(action.file.name), file_to_archive, save=False)
@@ -784,20 +779,20 @@ class ActionAA(Action):
class ActionUNDO(Action):
- name = 'UNDO'
+ name = "UNDO"
class Meta:
proxy = True
def update_state(self):
# Look for the action to revert, excluding WC because this action is a noop on State
- actions = Action.objects.filter(state_db__id=self.state_db.id).exclude(name='WC').order_by('-id')
+ actions = Action.objects.filter(state_db__id=self.state_db.id).exclude(name="WC").order_by("-id")
skip_next = False
for action in actions:
if skip_next:
skip_next = False
continue
- if action.name == 'UNDO':
+ if action.name == "UNDO":
# Skip Undo and the associated action
skip_next = True
continue
@@ -810,7 +805,8 @@ class ActionUNDO(Action):
class ActionSeparator:
- """ Fake action to add a separator in action menu """
+ """Fake action to add a separator in action menu"""
+
name = None
description = "--------"
@@ -821,11 +817,11 @@ class ActionSeparator:
@receiver(pot_has_changed)
def update_uploaded_files(sender, **kwargs):
"""Callback to handle pot_file_changed signal"""
- actions = Action.objects.filter(state_db__branch=kwargs['branch'],
- state_db__domain=kwargs['domain'],
- file__endswith=".po")
+ actions = Action.objects.filter(
+ state_db__branch=kwargs["branch"], state_db__domain=kwargs["domain"], file__endswith=".po"
+ )
for action in actions:
- action.merge_file_with_pot(kwargs['potfile'])
+ action.merge_file_with_pot(kwargs["potfile"])
@receiver(post_save)
@@ -836,7 +832,7 @@ def merge_uploaded_file(sender, instance, **kwargs):
"""
if not isinstance(instance, Action):
return
- if instance.file and instance.file.path.endswith('.po'):
+ if instance.file and instance.file.path.endswith(".po"):
try:
stat = Statistics.objects.get(
branch=instance.state_db.branch, domain=instance.state_db.domain, language=None
@@ -855,15 +851,15 @@ def delete_action_files(sender, instance, **kwargs):
- the merged file
- the html dir where docs are built
"""
- if not isinstance(instance, ActionAbstract) or not getattr(instance, 'file'):
+ if not isinstance(instance, ActionAbstract) or not getattr(instance, "file"):
return
- if instance.file.path.endswith('.po'):
+ if instance.file.path.endswith(".po"):
if instance.merged_file:
if os.access(instance.merged_file.full_path, os.W_OK):
os.remove(instance.merged_file.full_path)
if os.access(instance.file.path, os.W_OK):
os.remove(instance.file.path)
- html_dir = settings.SCRATCHDIR / 'HTML' / str(instance.pk)
+ html_dir = settings.SCRATCHDIR / "HTML" / str(instance.pk)
if html_dir.exists():
shutil.rmtree(str(html_dir))
diff --git a/vertimus/templatetags/vertimus.py b/vertimus/templatetags/vertimus.py
index f08619ec..d22b6ca7 100644
--- a/vertimus/templatetags/vertimus.py
+++ b/vertimus/templatetags/vertimus.py
@@ -7,27 +7,34 @@ register = template.Library()
@register.filter
def as_tr(field):
- help_html = ''
+ help_html = ""
if field.help_text:
help_html = format_html('<br><span class="help">{}</span>', field.help_text)
- help_link = ''
+ help_link = ""
# This is a custom attribute possibly set in forms.py
- if hasattr(field.field, 'help_link'):
+ if hasattr(field.field, "help_link"):
help_link = format_html(
'<span class="help_link">'
'<a href="{}" data-target="#modal-container" role="button" data-toggle="modal">'
'<img src="{}" alt="help icon">'
- '</a></span>', field.field.help_link, static('img/help.png')
+ "</a></span>",
+ field.field.help_link,
+ static("img/help.png"),
)
- errors_html = ''
+ errors_html = ""
if field.errors:
- errors_html = ''.join(["%s" % err for err in field.errors])
+ errors_html = "".join(["%s" % err for err in field.errors])
return format_html(
- '<tr class="tr_{}"><th>{}</th><td>' +
- ('<div class="errornote error">{}</div>' if errors_html else '') +
- '{}{}{}</td></tr>',
- field.name, field.label_tag(), errors_html, field.as_widget(), help_link, help_html
+ '<tr class="tr_{}"><th>{}</th><td>'
+ + ('<div class="errornote error">{}</div>' if errors_html else "")
+ + "{}{}{}</td></tr>",
+ field.name,
+ field.label_tag(),
+ errors_html,
+ field.as_widget(),
+ help_link,
+ help_html,
)
@@ -38,5 +45,8 @@ def as_tr_cb(field):
label = format_html('{} <span class="help">({})</span>', label, field.help_text)
return format_html(
'<tr class="tr_{}"><td></td><td>{} <label for="id_{}">{}</label></td></tr>',
- field.name, field.as_widget(), field.name, label
+ field.name,
+ field.as_widget(),
+ field.name,
+ label,
)
diff --git a/vertimus/tests/tests.py b/vertimus/tests/tests.py
index b08fc7ad..512921bf 100644
--- a/vertimus/tests/tests.py
+++ b/vertimus/tests/tests.py
@@ -6,10 +6,10 @@ from unittest.mock import patch
from xml.dom.minidom import parseString
from django.conf import settings
-from django.core.files.base import File, ContentFile
+from django.core import mail
+from django.core.files.base import ContentFile, File
from django.core.files.uploadedfile import SimpleUploadedFile
from django.core.management import call_command
-from django.core import mail
from django.http import QueryDict
from django.test import TestCase
from django.test.utils import override_settings
@@ -18,53 +18,70 @@ from django.utils.datastructures import MultiValueDict
from languages.models import Language
from people.models import Person
-from teams.models import Role, Team
-from teams.tests import TeamsAndRolesMixin
-from stats.models import Module, Branch, Release, Category, CategoryName, Domain, Statistics
+from stats.models import (
+ Branch,
+ Category,
+ CategoryName,
+ Domain,
+ Module,
+ Release,
+ Statistics,
+)
from stats.tests.tests import TestModuleBase
from stats.tests.utils import PatchShellCommand, test_scratchdir
+from teams.models import Role, Team
+from teams.tests import TeamsAndRolesMixin
+from vertimus.forms import ActionForm
from vertimus.models import (
- Action, ActionArchived, ActionCI, ActionUNDO, ActionWC, State, StateCommitted,
- StateCommitting, StateNone, StateProofread, StateProofreading, StateToCommit,
- StateToReview, StateTranslated, StateTranslating,
+ Action,
+ ActionArchived,
+ ActionCI,
+ ActionUNDO,
+ ActionWC,
+ State,
+ StateCommitted,
+ StateCommitting,
+ StateNone,
+ StateProofread,
+ StateProofreading,
+ StateToCommit,
+ StateToReview,
+ StateTranslated,
+ StateTranslating,
)
-from vertimus.forms import ActionForm
MEDIA_ROOT = Path(tempfile.mkdtemp())
@override_settings(MEDIA_ROOT=MEDIA_ROOT)
class VertimusTest(TeamsAndRolesMixin, TestCase):
-
def setUp(self):
super().setUp()
self.m = Module.objects.create(
- name='gnome-hello', description='GNOME Hello',
+ name="gnome-hello",
+ description="GNOME Hello",
bugs_base="https://gitlab.gnome.org/GNOME/gnome-hello/issues",
- vcs_type='git', vcs_root="https://gitlab.gnome.org/GNOME/gnome-hello.git",
- vcs_web="https://gitlab.gnome.org/GNOME/gnome-hello/"
+ vcs_type="git",
+ vcs_root="https://gitlab.gnome.org/GNOME/gnome-hello.git",
+ vcs_web="https://gitlab.gnome.org/GNOME/gnome-hello/",
)
Branch.checkout_on_creation = False
- self.b = Branch(name='gnome-2-24', module=self.m)
+ self.b = Branch(name="gnome-2-24", module=self.m)
# Block the update of Statistics by the thread
self.b.save(update_statistics=False)
self.r = Release.objects.create(
- name='gnome-2-24', status='official',
- description='GNOME 2.24 (stable)',
- string_frozen=True
+ name="gnome-2-24", status="official", description="GNOME 2.24 (stable)", string_frozen=True
)
self.c = Category.objects.create(
- release=self.r, branch=self.b, name=CategoryName.objects.create(name='Desktop')
+ release=self.r, branch=self.b, name=CategoryName.objects.create(name="Desktop")
)
self.d = Domain.objects.create(
- module=self.m, name='po',
- description='UI translations',
- dtype='ui', layout='po/{lang}.po'
+ module=self.m, name="po", description="UI translations", dtype="ui", layout="po/{lang}.po"
)
Statistics.objects.create(language=None, branch=self.b, domain=self.d)
self.files_to_clean = []
@@ -74,12 +91,12 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
if os.path.exists(path):
os.remove(path)
- def upload_file(self, to_state, action_code='UP', pers=None):
- """ Test utility to add an uploaded file to a state """
- test_file = ContentFile('test content')
- test_file.name = 'mytestfile.po'
+ def upload_file(self, to_state, action_code="UP", pers=None):
+ """Test utility to add an uploaded file to a state"""
+ test_file = ContentFile("test content")
+ test_file.name = "mytestfile.po"
action = Action.new_by_name(action_code, person=pers or self.pr, file=test_file)
- action.apply_on(to_state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Done."})
+ action.apply_on(to_state, {"send_to_ml": action.send_mail_to_ml, "comment": "Done."})
self.files_to_clean.append(action.file.path)
return action.file
@@ -87,157 +104,157 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
state = StateNone(branch=self.b, domain=self.d, language=self.language)
action_names = [a.name for a in state.get_available_actions(self.pn)]
- self.assertEqual(action_names, ['WC'])
+ self.assertEqual(action_names, ["WC"])
for p in (self.pt, self.pr):
action_names = [a.name for a in state.get_available_actions(p)]
- self.assertEqual(action_names, ['RT', 'UT', 'WC'])
+ self.assertEqual(action_names, ["RT", "UT", "WC"])
for p in (self.pc, self.pcoo):
action_names = [a.name for a in state.get_available_actions(p)]
- self.assertEqual(action_names, ['RT', 'UT', 'WC', None, 'IC', 'AA'])
+ self.assertEqual(action_names, ["RT", "UT", "WC", None, "IC", "AA"])
def test_state_translating(self):
state = StateTranslating(branch=self.b, domain=self.d, language=self.language, person=self.pt)
for p in (self.pn, self.pr):
action_names = [a.name for a in state.get_available_actions(p)]
- self.assertEqual(action_names, ['WC'])
+ self.assertEqual(action_names, ["WC"])
action_names = [a.name for a in state.get_available_actions(self.pc)]
- self.assertEqual(action_names, ['WC', None, 'IC', 'AA'])
+ self.assertEqual(action_names, ["WC", None, "IC", "AA"])
action_names = [a.name for a in state.get_available_actions(self.pcoo)]
- self.assertEqual(action_names, ['WC', 'UNDO', None, 'IC', 'AA'])
+ self.assertEqual(action_names, ["WC", "UNDO", None, "IC", "AA"])
# Same person
action_names = [a.name for a in state.get_available_actions(self.pt)]
- self.assertEqual(action_names, ['UT', 'UNDO', 'WC'])
+ self.assertEqual(action_names, ["UT", "UNDO", "WC"])
def test_state_translated(self):
state = StateTranslated(branch=self.b, domain=self.d, language=self.language, person=self.pt)
action_names = [a.name for a in state.get_available_actions(self.pn)]
- self.assertEqual(action_names, ['WC'])
+ self.assertEqual(action_names, ["WC"])
action_names = [a.name for a in state.get_available_actions(self.pt)]
- self.assertEqual(action_names, ['RT', 'WC'])
+ self.assertEqual(action_names, ["RT", "WC"])
action_names = [a.name for a in state.get_available_actions(self.pr)]
- self.assertEqual(action_names, ['RP', 'UP', 'TR', 'RT', 'WC'])
+ self.assertEqual(action_names, ["RP", "UP", "TR", "RT", "WC"])
for p in (self.pc, self.pcoo):
action_names = [a.name for a in state.get_available_actions(p)]
- self.assertEqual(action_names, ['RP', 'UP', 'TR', 'RT', 'TC', 'WC', None, 'IC', 'AA'])
+ self.assertEqual(action_names, ["RP", "UP", "TR", "RT", "TC", "WC", None, "IC", "AA"])
def test_state_proofreading(self):
state = StateProofreading(branch=self.b, domain=self.d, language=self.language, person=self.pr)
for p in (self.pn, self.pt):
action_names = [a.name for a in state.get_available_actions(p)]
- self.assertEqual(action_names, ['WC'])
+ self.assertEqual(action_names, ["WC"])
action_names = [a.name for a in state.get_available_actions(self.pc)]
- self.assertEqual(action_names, ['WC', None, 'IC', 'AA'])
+ self.assertEqual(action_names, ["WC", None, "IC", "AA"])
action_names = [a.name for a in state.get_available_actions(self.pcoo)]
- self.assertEqual(action_names, ['WC', 'UNDO', None, 'IC', 'AA'])
+ self.assertEqual(action_names, ["WC", "UNDO", None, "IC", "AA"])
# Same person and reviewer
action_names = [a.name for a in state.get_available_actions(self.pr)]
- self.assertEqual(action_names, ['UP', 'TR', 'TC', 'UNDO', 'WC'])
+ self.assertEqual(action_names, ["UP", "TR", "TC", "UNDO", "WC"])
def test_state_proofread(self):
state = StateProofread(branch=self.b, domain=self.d, language=self.language, person=self.pr)
for p in (self.pn, self.pt):
action_names = [a.name for a in state.get_available_actions(p)]
- self.assertEqual(action_names, ['WC'])
+ self.assertEqual(action_names, ["WC"])
action_names = [a.name for a in state.get_available_actions(self.pr)]
- self.assertEqual(action_names, ['TC', 'RP', 'TR', 'WC'])
+ self.assertEqual(action_names, ["TC", "RP", "TR", "WC"])
for p in (self.pc, self.pcoo):
action_names = [a.name for a in state.get_available_actions(p)]
- self.assertEqual(action_names, ['TC', 'RP', 'TR', 'RC', 'WC', None, 'IC', 'AA'])
+ self.assertEqual(action_names, ["TC", "RP", "TR", "RC", "WC", None, "IC", "AA"])
def test_state_to_review(self):
state = StateToReview(branch=self.b, domain=self.d, language=self.language, person=self.pt)
action_names = [a.name for a in state.get_available_actions(self.pn)]
- self.assertEqual(action_names, ['WC'])
+ self.assertEqual(action_names, ["WC"])
for p in (self.pt, self.pr):
action_names = [a.name for a in state.get_available_actions(p)]
- self.assertEqual(action_names, ['RT', 'WC'])
+ self.assertEqual(action_names, ["RT", "WC"])
for p in (self.pc, self.pcoo):
action_names = [a.name for a in state.get_available_actions(p)]
- self.assertEqual(action_names, ['RT', 'WC', None, 'IC', 'AA'])
+ self.assertEqual(action_names, ["RT", "WC", None, "IC", "AA"])
def test_state_to_commit(self):
state = StateToCommit(branch=self.b, domain=self.d, language=self.language, person=self.pr)
for p in (self.pn, self.pt, self.pr):
action_names = [a.name for a in state.get_available_actions(p)]
- self.assertEqual(action_names, ['WC'])
+ self.assertEqual(action_names, ["WC"])
for p in (self.pc, self.pcoo):
action_names = [a.name for a in state.get_available_actions(p)]
- self.assertEqual(action_names, ['RC', 'TR', 'UNDO', 'WC', None, 'IC', 'AA'])
+ self.assertEqual(action_names, ["RC", "TR", "UNDO", "WC", None, "IC", "AA"])
@test_scratchdir
def test_state_committing(self):
state = StateProofreading(branch=self.b, domain=self.d, language=self.language, person=self.pr)
state.save()
- self.upload_file(state, 'UP')
- action = Action.new_by_name('RC', person=self.pc)
- action.apply_on(state, {'comment': "Reserve the commit"})
- self.assertEqual(state.name, 'Committing')
+ self.upload_file(state, "UP")
+ action = Action.new_by_name("RC", person=self.pc)
+ action.apply_on(state, {"comment": "Reserve the commit"})
+ self.assertEqual(state.name, "Committing")
for p in (self.pn, self.pt, self.pr):
action_names = [a.name for a in state.get_available_actions(p)]
- self.assertEqual(action_names, ['WC'])
+ self.assertEqual(action_names, ["WC"])
action_names = [a.name for a in state.get_available_actions(self.pcoo)]
- self.assertEqual(action_names, ['WC', 'UNDO', None, 'IC', 'AA'])
+ self.assertEqual(action_names, ["WC", "UNDO", None, "IC", "AA"])
action_names = [a.name for a in state.get_available_actions(self.pc)]
- self.assertEqual(action_names, ['IC', 'TR', 'UNDO', 'WC'])
+ self.assertEqual(action_names, ["IC", "TR", "UNDO", "WC"])
# Setup as a writable repo
- self.b.module.vcs_root = 'git gitlab gnome org:GNOME/%s.git' % self.b.module.name
+ self.b.module.vcs_root = "git gitlab gnome org:GNOME/%s.git" % self.b.module.name
self.b.module.save()
self.b.refresh_from_db()
self.assertTrue(state.able_to_commit())
action_names = [a.name for a in state.get_available_actions(self.pcoo)]
- self.assertEqual(action_names, ['CI', 'WC', 'UNDO', None, 'IC', 'AA'])
+ self.assertEqual(action_names, ["CI", "WC", "UNDO", None, "IC", "AA"])
def test_state_committed(self):
state = StateCommitted(branch=self.b, domain=self.d, language=self.language, person=self.pc)
for p in (self.pn, self.pt, self.pr):
action_names = [a.name for a in state.get_available_actions(p)]
- self.assertEqual(action_names, ['WC'])
+ self.assertEqual(action_names, ["WC"])
for p in (self.pc, self.pcoo):
action_names = [a.name for a in state.get_available_actions(p)]
- self.assertEqual(action_names, ['AA', 'WC', None, 'IC', 'AA'])
+ self.assertEqual(action_names, ["AA", "WC", None, "IC", "AA"])
def test_action_menu(self):
state = StateNone(branch=self.b, domain=self.d, language=self.language)
form = ActionForm(self.pt, state, state.get_available_actions(self.pt), False)
self.assertHTMLEqual(
- str(form['action']),
+ str(form["action"]),
'<select id="id_action" name="action">'
'<option value="RT">Reserve for translation</option>'
'<option value="UT">Upload the new translation</option>'
'<option value="WC">Write a comment</option>'
- '</select>'
+ "</select>",
)
form = ActionForm(self.pcoo, state, state.get_available_actions(self.pcoo), False)
self.assertHTMLEqual(
- str(form['action']),
+ str(form["action"]),
'<select id="id_action" name="action">'
'<option value="RT">Reserve for translation</option>'
'<option value="UT">Upload the new translation</option>'
@@ -245,7 +262,7 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
'<option value="" disabled>--------</option>'
'<option value="IC">Inform of submission</option>'
'<option value="AA">Archive the actions</option>'
- '</select>'
+ "</select>",
)
def test_action_wc(self):
@@ -253,47 +270,47 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
# State may be initially unsaved
prev_updated = state.updated
- action = Action.new_by_name('WC', person=self.pt)
- action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Hi 😉"})
+ action = Action.new_by_name("WC", person=self.pt)
+ action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Hi 😉"})
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].recipients(), [self.language.team.get_coordinators()[0].email])
- self.assertEqual(mail.outbox[0].extra_headers, {settings.EMAIL_HEADER_NAME: 'Inactive'})
+ self.assertEqual(mail.outbox[0].extra_headers, {settings.EMAIL_HEADER_NAME: "Inactive"})
# Second comment by someone else, mail sent to the other person
- action = Action.new_by_name('WC', person=self.pr)
- action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Great!"})
+ action = Action.new_by_name("WC", person=self.pr)
+ action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Great!"})
self.assertEqual(len(mail.outbox), 2)
self.assertEqual(mail.outbox[1].recipients(), [self.pt.email])
# Test that submitting a comment without text generates a validation error
- form = ActionForm(self.pt, state, [ActionWC()], True, QueryDict('action=WC&comment='))
+ form = ActionForm(self.pt, state, [ActionWC()], True, QueryDict("action=WC&comment="))
self.assertTrue("A comment is needed" in str(form.errors))
self.assertNotEqual(state.updated, prev_updated)
# Test send comment to mailing list
mail.outbox = []
- action = Action.new_by_name('WC', person=self.pt)
- action.apply_on(state, {'send_to_ml': True, 'comment': "Hi again!"})
+ action = Action.new_by_name("WC", person=self.pt)
+ action.apply_on(state, {"send_to_ml": True, "comment": "Hi again!"})
self.assertTrue(action.sent_to_ml)
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].recipients(), [self.language.team.mailing_list])
self.assertIn("Hi again!", mail.outbox[0].body)
- self.assertEqual(mail.outbox[0].extra_headers, {settings.EMAIL_HEADER_NAME: 'Inactive'})
+ self.assertEqual(mail.outbox[0].extra_headers, {settings.EMAIL_HEADER_NAME: "Inactive"})
def test_send_mail_translated(self):
- team = Team.objects.create(name='zh', description='Chinese', mailing_list='zh example org')
- zh_cn = Language.objects.create(name='Chinese', locale='zh_CN', team=team)
- call_command('compile-trans', locale='zh_CN')
+ team = Team.objects.create(name="zh", description="Chinese", mailing_list="zh example org")
+ zh_cn = Language.objects.create(name="Chinese", locale="zh_CN", team=team)
+ call_command("compile-trans", locale="zh_CN")
state = StateNone(branch=self.b, domain=self.d, language=zh_cn)
- action = Action.new_by_name('WC', person=self.pn)
- action.apply_on(state, {'send_to_ml': True, 'comment': "Comment"})
+ action = Action.new_by_name("WC", person=self.pn)
+ action.apply_on(state, {"send_to_ml": True, "comment": "Comment"})
self.assertEqual(len(mail.outbox), 1)
self.assertIn("您好,", mail.outbox[0].body)
def test_action_rt(self):
state = StateNone(branch=self.b, domain=self.d, language=self.language)
- action = Action.new_by_name('RT', person=self.pt)
- action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Reserved!"})
+ action = Action.new_by_name("RT", person=self.pt)
+ action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Reserved!"})
self.assertIsInstance(state, StateTranslating)
@test_scratchdir
@@ -308,10 +325,10 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
file_path = Path(__file__).parent / "valid_po.po"
with file_path.open() as test_file:
- action = Action.new_by_name('UT', person=self.pt, file=File(test_file))
- action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Done by translator."})
+ action = Action.new_by_name("UT", person=self.pt, file=File(test_file))
+ action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Done by translator."})
- self.assertEqual(action.file.url, '/media/upload/gnome-hello-gnome-2-24-po-fr-%d.po' % state.id)
+ self.assertEqual(action.file.url, "/media/upload/gnome-hello-gnome-2-24-po-fr-%d.po" % state.id)
self.files_to_clean.append(action.file.path)
# Merged file will not be really produced as no pot file exists on the file system
self.assertIsNone(action.merged_file)
@@ -321,18 +338,18 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].recipients(), [self.language.team.mailing_list])
self.assertEqual(mail.outbox[0].subject, "%sgnome-hello - gnome-2-24" %
settings.EMAIL_SUBJECT_PREFIX)
- self.assertEqual(mail.outbox[0].extra_headers, {settings.EMAIL_HEADER_NAME: 'Translated'})
+ self.assertEqual(mail.outbox[0].extra_headers, {settings.EMAIL_HEADER_NAME: "Translated"})
# Testing if the role was activated
role = Role.objects.get(person=self.pt, team=self.language.team)
self.assertTrue(role.is_active)
# Setup as a writable repo and test Submit to repository is already available
- self.b.module.vcs_root = 'git gitlab gnome org:GNOME/%s.git' % self.b.module.name
+ self.b.module.vcs_root = "git gitlab gnome org:GNOME/%s.git" % self.b.module.name
self.b.module.save()
self.assertEqual(
[act.name for act in state.get_available_actions(self.pcoo)],
- ['RP', 'UP', 'TR', 'RT', 'TC', 'CI', 'WC', None, 'IC', 'AA']
+ ["RP", "UP", "TR", "RT", "TC", "CI", "WC", None, "IC", "AA"],
)
@test_scratchdir
@@ -345,35 +362,36 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
file_path = Path(__file__).parent / "valid_po.po"
with file_path.open() as test_file:
- action = Action.new_by_name('UT', person=self.pr, file=File(test_file))
+ action = Action.new_by_name("UT", person=self.pr, file=File(test_file))
with self.assertRaisesRegex(Exception, "Action not allowed"):
- action.apply_on(
- state,
- {'send_to_ml': False, 'comment': "Done by reviewer."}
- )
+ action.apply_on(state, {"send_to_ml": False, "comment": "Done by reviewer."})
def test_action_rp(self):
state = StateTranslated(branch=self.b, domain=self.d, language=self.language)
state.save()
- action = Action.new_by_name('RP', person=self.pr)
- action.apply_on(state, {
- 'send_to_ml': action.send_mail_to_ml, 'comment': "",
- })
+ action = Action.new_by_name("RP", person=self.pr)
+ action.apply_on(
+ state,
+ {
+ "send_to_ml": action.send_mail_to_ml,
+ "comment": "",
+ },
+ )
self.assertIsInstance(state, StateProofreading)
self.assertEqual(len(mail.outbox), 0)
def test_action_rp_with_comment(self):
state = StateTranslated.objects.create(branch=self.b, domain=self.d, language=self.language)
- action = Action.new_by_name('WC', person=self.pt)
- action.apply_on(state, {'send_to_ml': False, 'comment': "Hi!"})
+ action = Action.new_by_name("WC", person=self.pt)
+ action.apply_on(state, {"send_to_ml": False, "comment": "Hi!"})
self.assertFalse(action.sent_to_ml)
# At least the coordinator receives the message
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].recipients(), [self.language.team.get_coordinators()[0].email])
- action = Action.new_by_name('RP', person=self.pr)
- action.apply_on(state, {'send_to_ml': False, 'comment': "I'm reviewing this!"})
+ action = Action.new_by_name("RP", person=self.pr)
+ action.apply_on(state, {"send_to_ml": False, "comment": "I'm reviewing this!"})
# If a comment is set, a message should be sent
self.assertEqual(len(mail.outbox), 2)
self.assertEqual(mail.outbox[1].recipients(), [self.pt.email])
@@ -382,11 +400,11 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
state = StateProofreading(branch=self.b, domain=self.d, language=self.language, person=self.pr)
state.save()
- test_file = ContentFile('test content')
- test_file.name = 'mytestfile.po'
+ test_file = ContentFile("test content")
+ test_file.name = "mytestfile.po"
- action = Action.new_by_name('UP', person=self.pr, file=test_file)
- action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Done."})
+ action = Action.new_by_name("UP", person=self.pr, file=test_file)
+ action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Done."})
self.files_to_clean.append(action.file.path)
self.assertIsInstance(state, StateProofread)
# Mail sent to mailing list
@@ -395,13 +413,13 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
# Comment made by someone else, file reviewed again, checkbox "Send to mailing list" unckecked
# => mail sent to the comment author
- action = Action.new_by_name('WC', person=self.pt)
- action.apply_on(state, {'send_to_ml': False, 'comment': "Hi!"})
- action = Action.new_by_name('RP', person=self.pr)
- action.apply_on(state, {'send_to_ml': False, 'comment': "Reserved by a reviewer!"})
+ action = Action.new_by_name("WC", person=self.pt)
+ action.apply_on(state, {"send_to_ml": False, "comment": "Hi!"})
+ action = Action.new_by_name("RP", person=self.pr)
+ action.apply_on(state, {"send_to_ml": False, "comment": "Reserved by a reviewer!"})
mail.outbox = []
- action = Action.new_by_name('UP', person=self.pr, file=test_file)
- action.apply_on(state, {'send_to_ml': False, 'comment': "Done second time."})
+ action = Action.new_by_name("UP", person=self.pr, file=test_file)
+ action.apply_on(state, {"send_to_ml": False, "comment": "Done second time."})
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].recipients(), [self.pt.email])
@@ -409,50 +427,50 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
state = StateProofread(branch=self.b, domain=self.d, language=self.language)
state.save()
- action = Action.new_by_name('TC', person=self.pr)
- action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Ready!"})
+ action = Action.new_by_name("TC", person=self.pr)
+ action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Ready!"})
self.assertIsInstance(state, StateToCommit)
def test_action_rc(self):
state = StateToCommit(branch=self.b, domain=self.d, language=self.language)
state.save()
- action = Action.new_by_name('RC', person=self.pc)
- action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "This work is mine!"})
+ action = Action.new_by_name("RC", person=self.pc)
+ action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "This work is mine!"})
self.assertIsInstance(state, StateCommitting)
@test_scratchdir
def test_action_ci(self):
# Setup as a writable repo
- self.b.module.vcs_root = 'git gitlab gnome org:GNOME/%s.git' % self.b.module.name
+ self.b.module.vcs_root = "git gitlab gnome org:GNOME/%s.git" % self.b.module.name
self.b.module.save()
Branch.checkout_on_creation = False
- master = Branch(name='master', module=self.m)
+ master = Branch(name="master", module=self.m)
master.save(update_statistics=False)
state = StateProofreading(branch=self.b, domain=self.d, language=self.language, person=self.pr)
state.save()
- action = Action.new_by_name('WC', person=self.pt)
- action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Hi!"})
+ action = Action.new_by_name("WC", person=self.pt)
+ action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Hi!"})
# Adding users with incomplete profiles
- pers_no_full_name = Person.objects.create(username='ûsername')
- action = Action.new_by_name('WC', person=pers_no_full_name)
- action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Hello!"})
- pers_no_email = Person.objects.create(username='noemail', first_name="Sven", last_name="Brkc")
- action = Action.new_by_name('WC', person=pers_no_email)
- action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Hello!"})
+ pers_no_full_name = Person.objects.create(username="ûsername")
+ action = Action.new_by_name("WC", person=pers_no_full_name)
+ action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Hello!"})
+ pers_no_email = Person.objects.create(username="noemail", first_name="Sven", last_name="Brkc")
+ action = Action.new_by_name("WC", person=pers_no_email)
+ action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Hello!"})
- self.upload_file(state, 'UP')
+ self.upload_file(state, "UP")
self.assertIn(ActionCI, map(type, state.get_available_actions(self.pcoo)))
form = ActionForm(self.pcoo, state, state.get_available_actions(self.pcoo), True)
- self.assertEqual(len(form.fields['author'].choices), 6)
- self.assertEqual(form.fields['author'].initial, self.pr)
- self.assertIn('sync_master', form.fields)
- self.assertEqual(form.fields['sync_master'].label, "Sync with master")
+ self.assertEqual(len(form.fields["author"].choices), 6)
+ self.assertEqual(form.fields["author"].initial, self.pr)
+ self.assertIn("sync_master", form.fields)
+ self.assertEqual(form.fields["sync_master"].label, "Sync with master")
self.assertHTMLEqual(
- str(form['author']),
+ str(form["author"]),
'<select id="id_author" name="author">'
'<option value="">---------</option>'
'<option disabled value="%d">ûsername (full name missing)</option>'
@@ -460,9 +478,14 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
'<option value="%d">John Coordinator</option>'
'<option selected value="%d">John Reviewer</option>'
'<option value="%d">John Translator</option>'
- '</select>' % (
- pers_no_full_name.pk, pers_no_email.pk, self.pcoo.pk, self.pr.pk, self.pt.pk,
- )
+ "</select>"
+ % (
+ pers_no_full_name.pk,
+ pers_no_email.pk,
+ self.pcoo.pk,
+ self.pr.pk,
+ self.pt.pk,
+ ),
)
@test_scratchdir
@@ -470,42 +493,34 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
"""
Test that a commit works even when nobody has full name, but without --author flag.
"""
- pers = Person.objects.create(email='user example org', username='username_only')
- Role.objects.create(team=self.t, person=pers, role='reviewer')
+ pers = Person.objects.create(email="user example org", username="username_only")
+ Role.objects.create(team=self.t, person=pers, role="reviewer")
self.m.vcs_root = "git gitlab gnome org:GNOME/gnome-hello.git"
self.m.save()
state = StateProofreading(branch=self.b, domain=self.d, language=self.language, person=pers)
state.save()
# Adding two comments from the commit author, as this might trigger a form error
- action = Action.new_by_name('WC', person=self.pcoo)
- action.apply_on(state, {'send_to_ml': False, 'comment': "Looks good"})
- action = Action.new_by_name('WC', person=self.pcoo)
- action.apply_on(state, {'send_to_ml': False, 'comment': "Looks good too"})
-
- self.upload_file(state, 'UP', pers=pers)
- post_data = {
- 'action': 'CI',
- 'author': '',
- 'comment': '',
- 'send_to_ml': True
- }
+ action = Action.new_by_name("WC", person=self.pcoo)
+ action.apply_on(state, {"send_to_ml": False, "comment": "Looks good"})
+ action = Action.new_by_name("WC", person=self.pcoo)
+ action.apply_on(state, {"send_to_ml": False, "comment": "Looks good too"})
+
+ self.upload_file(state, "UP", pers=pers)
+ post_data = {"action": "CI", "author": "", "comment": "", "send_to_ml": True}
form = ActionForm(self.pcoo, state, state.get_available_actions(self.pcoo), True, post_data)
# Missing author
self.assertFalse(form.is_valid())
- post_data['author'] = str(self.pcoo.pk)
+ post_data["author"] = str(self.pcoo.pk)
form = ActionForm(self.pcoo, state, state.get_available_actions(self.pcoo), True, post_data)
self.assertTrue(form.is_valid())
# path needed when copying file to commit
- (self.b.co_path / 'po').mkdir(parents=True, exist_ok=True)
- with PatchShellCommand(only=['git ']) as cmds:
- action = Action.new_by_name('CI', person=self.pcoo)
+ (self.b.co_path / "po").mkdir(parents=True, exist_ok=True)
+ with PatchShellCommand(only=["git "]) as cmds:
+ action = Action.new_by_name("CI", person=self.pcoo)
msg = action.apply_on(state, form.cleaned_data)
- self.assertIn(
- 'git commit -m Update French translation --author John Coordinator <jcoo imthebigboss fr>',
- cmds
- )
- self.assertEqual(msg, 'The file has been successfully committed to the repository.')
+ self.assertIn("git commit -m Update French translation --author John Coordinator <jcoo imthebigboss
fr>", cmds)
+ self.assertEqual(msg, "The file has been successfully committed to the repository.")
state.refresh_from_db()
# All actions should have been archived
self.assertEqual(state.action_set.count(), 0)
@@ -515,28 +530,28 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
state.save()
# Upload a new file
- action_file = self.upload_file(state, 'UP')
+ action_file = self.upload_file(state, "UP")
self.assertEqual(len(mail.outbox), 1) # Mail sent to mailing list
mail.outbox = []
file_path = settings.MEDIA_ROOT / action_file.name
self.assertTrue(file_path.exists())
- action = Action.new_by_name('TC', person=self.pc)
- action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': ''})
+ action = Action.new_by_name("TC", person=self.pc)
+ action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": ""})
self.assertEqual(len(mail.outbox), 1) # Mail sent to committers
mail.outbox = []
- action = Action.new_by_name('RC', person=self.pc)
- action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': ''})
+ action = Action.new_by_name("RC", person=self.pc)
+ action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": ""})
- action = Action.new_by_name('IC', person=self.pc)
- action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Committed."})
+ action = Action.new_by_name("IC", person=self.pc)
+ action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Committed."})
# Mail sent to mailing list
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].recipients(), [self.language.team.mailing_list])
# Team is French (but translations may not be compiled/up-to-date)
- self.assertTrue('Commité' in mail.outbox[0].body or "Committed" in mail.outbox[0].body)
+ self.assertTrue("Commité" in mail.outbox[0].body or "Committed" in mail.outbox[0].body)
self.assertIsInstance(state, StateNone)
self.assertFalse(file_path.exists(), "%s not deleted" % file_path)
@@ -551,18 +566,22 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
state = StateTranslated(branch=self.b, domain=self.d, language=self.language)
state.save()
- action = Action.new_by_name('TR', person=self.pc)
- action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Bad work :-/"})
+ action = Action.new_by_name("TR", person=self.pc)
+ action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Bad work :-/"})
self.assertIsInstance(state, StateToReview)
def test_action_aa(self):
state = StateCommitted(branch=self.b, domain=self.d, language=self.language, person=self.pr)
state.save()
- action = Action.new_by_name('AA', person=self.pc)
- action.apply_on(state, {
- 'send_to_ml': action.send_mail_to_ml, 'comment': "I don't want to disappear :)",
- })
+ action = Action.new_by_name("AA", person=self.pc)
+ action.apply_on(
+ state,
+ {
+ "send_to_ml": action.send_mail_to_ml,
+ "comment": "I don't want to disappear :)",
+ },
+ )
state = State.objects.get(branch=self.b, domain=self.d, language=self.language)
self.assertIsInstance(state, StateNone)
@@ -571,69 +590,72 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
def test_action_undo(self):
state = StateNone(branch=self.b, domain=self.d, language=self.language)
- action = Action.new_by_name('RT', person=self.pt)
- action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Reserved!"})
+ action = Action.new_by_name("RT", person=self.pt)
+ action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Reserved!"})
- action = Action.new_by_name('UNDO', person=self.pt)
- action.apply_on(state, {
- 'send_to_ml': action.send_mail_to_ml, 'comment': "Ooops! I don't want to do that. Sorry.",
- })
+ action = Action.new_by_name("UNDO", person=self.pt)
+ action.apply_on(
+ state,
+ {
+ "send_to_ml": action.send_mail_to_ml,
+ "comment": "Ooops! I don't want to do that. Sorry.",
+ },
+ )
- self.assertEqual(state.name, 'None')
+ self.assertEqual(state.name, "None")
- action = Action.new_by_name('RT', person=self.pt)
- action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Translating"})
+ action = Action.new_by_name("RT", person=self.pt)
+ action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Translating"})
- action = Action.new_by_name('UT', person=self.pt)
- action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Translated"})
+ action = Action.new_by_name("UT", person=self.pt)
+ action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Translated"})
- action = Action.new_by_name('RT', person=self.pt)
- action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Reserved!"})
+ action = Action.new_by_name("RT", person=self.pt)
+ action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Reserved!"})
- action = Action.new_by_name('UNDO', person=self.pt)
- action.apply_on(state, {
- 'send_to_ml': action.send_mail_to_ml,
- 'comment': "Ooops! I don't want to do that. Sorry."
- })
+ action = Action.new_by_name("UNDO", person=self.pt)
+ action.apply_on(
+ state, {"send_to_ml": action.send_mail_to_ml, "comment": "Ooops! I don't want to do that.
Sorry."}
+ )
self.assertEqual(action.comment, "Ooops! I don't want to do that. Sorry.")
- self.assertEqual(state.name, 'Translated')
+ self.assertEqual(state.name, "Translated")
- action = Action.new_by_name('RT', person=self.pt)
- action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Translating 1"})
+ action = Action.new_by_name("RT", person=self.pt)
+ action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Translating 1"})
- action = Action.new_by_name('UNDO', person=self.pt)
- action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Undo 1"})
+ action = Action.new_by_name("UNDO", person=self.pt)
+ action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Undo 1"})
- action = Action.new_by_name('RT', person=self.pt)
- action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Translating 2"})
+ action = Action.new_by_name("RT", person=self.pt)
+ action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Translating 2"})
- action = Action.new_by_name('UNDO', person=self.pt)
- action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Undo 2"})
+ action = Action.new_by_name("UNDO", person=self.pt)
+ action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Undo 2"})
- self.assertEqual(state.name, 'Translated')
+ self.assertEqual(state.name, "Translated")
def test_action_on_archived_module(self):
state = StateNone.objects.create(branch=self.b, domain=self.d, language=self.language)
- action = Action.new_by_name('WC', person=self.pt)
- action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Hi!"})
+ action = Action.new_by_name("WC", person=self.pt)
+ action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Hi!"})
self.m.archived = True
self.m.save()
# For an archived module, the only available action is to archive the state by a coord.
self.assertEqual(state.get_available_actions(self.pt), [])
- self.assertEqual([a.name for a in state.get_available_actions(self.pcoo)], ['AA'])
+ self.assertEqual([a.name for a in state.get_available_actions(self.pcoo)], ["AA"])
- action = Action.new_by_name('AA', person=self.pcoo)
- action.apply_on(state, {'send_to_ml': False})
- self.assertEqual(state.name, 'None')
+ action = Action.new_by_name("AA", person=self.pcoo)
+ action.apply_on(state, {"send_to_ml": False})
+ self.assertEqual(state.name, "None")
def test_delete(self):
- """ Test that a whole module tree can be properly deleted """
+ """Test that a whole module tree can be properly deleted"""
state = StateNone(branch=self.b, domain=self.d, language=self.language)
- action = Action.new_by_name('WC', person=self.pt)
- action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Hi!"})
+ action = Action.new_by_name("WC", person=self.pt)
+ action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Hi!"})
self.m.delete()
self.assertEqual(Action.objects.all().count(), 0)
@@ -644,50 +666,53 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
self.assertEqual(State.objects.all().count(), 0)
def test_delete_statistics(self):
- """ Test clean_dangling_states receiver """
+ """Test clean_dangling_states receiver"""
po_stat = Statistics.objects.create(branch=self.b, domain=self.d, language=self.language)
StateTranslating.objects.create(branch=self.b, domain=self.d, language=self.language, person=self.pt)
po_stat.delete()
self.assertEqual(State.objects.all().count(), 0)
def test_vertimus_view(self):
- url = reverse('vertimus_by_ids', args=[self.b.id, self.d.id, self.language.id])
+ url = reverse("vertimus_by_ids", args=[self.b.id, self.d.id, self.language.id])
response = self.client.get(url)
self.assertNotContains(response, '<option value="WC">')
self.assertContains(
response,
- '<span class="num1"> 0</span><span class="num2"> 0</span><span class="num3">
0</span>'
+ '<span class="num1"> 0</span><span class="num2"> 0</span><span class="num3">
0</span>',
)
- self.client.login(username=self.pn.username, password='password')
+ self.client.login(username=self.pn.username, password="password")
response = self.client.get(url)
self.assertContains(response, '<option value="WC">')
- response = self.client.post(url, data={
- 'action': 'WC',
- 'comment': 'Graçias', 'send_to_ml': '',
- }, follow=True)
- self.assertContains(response, 'Graçias')
+ response = self.client.post(
+ url,
+ data={
+ "action": "WC",
+ "comment": "Graçias",
+ "send_to_ml": "",
+ },
+ follow=True,
+ )
+ self.assertContains(response, "Graçias")
def test_vertimus_view_on_going_activities(self):
- master = Branch(name='master', module=self.m)
+ master = Branch(name="master", module=self.m)
# Block the update of Statistics by the thread
master.save(update_statistics=False)
Statistics.objects.create(branch=master, domain=self.d, language=None)
dom_alt = Domain.objects.create(
- module=self.m, name='po',
- description='UI translations',
- dtype='ui', layout='po/{lang}.po'
+ module=self.m, name="po", description="UI translations", dtype="ui", layout="po/{lang}.po"
)
StateNone.objects.create(branch=master, domain=self.d, language=self.language)
StateNone.objects.create(branch=self.b, domain=dom_alt, language=self.language)
- response = self.client.get(reverse('vertimus_by_ids', args=[master.pk, self.d.pk, self.language.pk]))
+ response = self.client.get(reverse("vertimus_by_ids", args=[master.pk, self.d.pk, self.language.pk]))
self.assertNotContains(response, "On-going activities in same module:")
state = State.objects.get(branch=self.b, domain=dom_alt, language=self.language)
state.change_state(StateTranslated)
- response = self.client.get(reverse('vertimus_by_ids', args=[master.pk, self.d.pk, self.language.pk]))
+ response = self.client.get(reverse("vertimus_by_ids", args=[master.pk, self.d.pk, self.language.pk]))
self.assertContains(response, "On-going activities in same module:")
@test_scratchdir
@@ -696,39 +721,36 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
state.save()
# Ensure pot file exists (used for merged file and by the diff view)
potfile, errs = self.d.generate_pot_file(self.b)
- pot_location = self.b.output_dir(self.d.dtype) / ('%s.%s.pot' % (self.d.potbase(), self.b.name))
+ pot_location = self.b.output_dir(self.d.dtype) / ("%s.%s.pot" % (self.d.potbase(), self.b.name))
shutil.copyfile(str(potfile), str(pot_location))
- action = Action.new_by_name('RT', person=self.pt)
- action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': ""})
- with (Path(__file__).parent / "valid_po.po").open('rb') as fh:
- action = Action.new_by_name('UT', person=self.pt, file=File(fh))
- action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Done."})
+ action = Action.new_by_name("RT", person=self.pt)
+ action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": ""})
+ with (Path(__file__).parent / "valid_po.po").open("rb") as fh:
+ action = Action.new_by_name("UT", person=self.pt, file=File(fh))
+ action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Done."})
action_history = Action.get_action_history(state=state)
- diff_url = reverse('vertimus_diff', args=[action_history[-1][0].id, '0', 0])
+ diff_url = reverse("vertimus_diff", args=[action_history[-1][0].id, "0", 0])
response = self.client.get(diff_url)
- self.assertContains(
- response,
- '<a href="%s">Latest POT file</a>' % str(pot_location).split('scratch')[1]
- )
+ self.assertContains(response, '<a href="%s">Latest POT file</a>' %
str(pot_location).split("scratch")[1])
# Should also work if action persons were deleted
self.pt.delete()
response = self.client.get(diff_url)
- self.assertContains(response, 'Latest POT file')
+ self.assertContains(response, "Latest POT file")
def test_uploaded_file_validation(self):
# Test a non valid po file
- post_content = QueryDict('action=WC&comment=Test1')
- post_file = MultiValueDict({'file': [SimpleUploadedFile('filename.po', b'Not valid po file
content')]})
+ post_content = QueryDict("action=WC&comment=Test1")
+ post_file = MultiValueDict({"file": [SimpleUploadedFile("filename.po", b"Not valid po file
content")]})
form = ActionForm(self.pt, None, [ActionWC()], True, post_content, post_file)
- self.assertTrue('file' in form.errors)
- post_file = MultiValueDict({'file': [SimpleUploadedFile('filename.po', 'Niña'.encode('latin-1'))]})
+ self.assertTrue("file" in form.errors)
+ post_file = MultiValueDict({"file": [SimpleUploadedFile("filename.po", "Niña".encode("latin-1"))]})
form = ActionForm(self.pt, None, [ActionWC()], True, post_content, post_file)
- self.assertTrue('file' in form.errors)
+ self.assertTrue("file" in form.errors)
# Test a valid po file
- with (Path(__file__).parent / "valid_po.po").open('rb') as fh:
- post_file = MultiValueDict({'file': [File(fh)]})
+ with (Path(__file__).parent / "valid_po.po").open("rb") as fh:
+ post_file = MultiValueDict({"file": [File(fh)]})
form = ActionForm(self.pt, None, [ActionWC()], True, post_content, post_file)
self.assertTrue(form.is_valid())
@@ -739,35 +761,30 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
def test_feeds(self):
state = StateNone(branch=self.b, domain=self.d, language=self.language)
- action = Action.new_by_name('RT', person=self.pt)
- action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Translating"})
+ action = Action.new_by_name("RT", person=self.pt)
+ action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Translating"})
- response = self.client.get(reverse('lang_feed', args=[self.language.locale]))
+ response = self.client.get(reverse("lang_feed", args=[self.language.locale]))
doc = parseString(response.content)
- self.assertEqual(doc.childNodes[0].tagName, 'rss')
+ self.assertEqual(doc.childNodes[0].tagName, "rss")
+ self.assertEqual(doc.childNodes[0].getAttribute("xmlns:atom"), "http://www.w3.org/2005/Atom")
self.assertEqual(
- doc.childNodes[0].getAttribute('xmlns:atom'), "http://www.w3.org/2005/Atom"
+ doc.getElementsByTagName("title")[1].toxml(),
+ """<title>po (gnome-hello/User Interface) - gnome-hello (gnome-2-24) - Reserve for
translation\n</title>""",
)
self.assertEqual(
- doc.getElementsByTagName('title')[1].toxml(),
- """<title>po (gnome-hello/User Interface) - gnome-hello (gnome-2-24) - Reserve for
translation\n</title>"""
- )
- self.assertEqual(
- doc.getElementsByTagName('guid')[0].toxml(),
- """<guid>http://testserver/vertimus/gnome-hello/gnome-2-24/po/fr/#%d</guid>""" % action.pk
+ doc.getElementsByTagName("guid")[0].toxml(),
+ """<guid>http://testserver/vertimus/gnome-hello/gnome-2-24/po/fr/#%d</guid>""" % action.pk,
)
- response = self.client.get(reverse('team_feed', args=[self.language.team.name]))
+ response = self.client.get(reverse("team_feed", args=[self.language.team.name]))
self.assertContains(
response,
- """<title>po (gnome-hello/User Interface) - gnome-hello (gnome-2-24) - Reserve for
translation\n</title>"""
+ """<title>po (gnome-hello/User Interface) - gnome-hello (gnome-2-24) - Reserve for
translation\n</title>""",
)
def test_activity_summary(self):
- StateTranslating.objects.create(
- branch=self.b, domain=self.d,
- language=self.language,
- person=self.pt)
+ StateTranslating.objects.create(branch=self.b, domain=self.d, language=self.language, person=self.pt)
response = self.client.get(reverse("activity_by_language", args=[self.language.locale]))
self.assertContains(response, self.m.description)
@@ -778,19 +795,19 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
state = StateProofreading(branch=self.b, domain=self.d, language=self.language, person=self.pr)
state.save()
with valid_path.open() as test_file:
- action = Action.new_by_name('UT', person=self.pt, file=File(test_file))
+ action = Action.new_by_name("UT", person=self.pt, file=File(test_file))
action.state_db = state
action.file.save(os.path.basename(action.file.name), action.file, save=False)
action.merged_file = None
action.save()
- response = self.client.get(reverse('action-quality-check', args=[action.pk]))
+ response = self.client.get(reverse("action-quality-check", args=[action.pk]))
self.assertContains(response, "The po file looks good!")
po_stat = Statistics.objects.create(branch=self.b, domain=self.d, language=self.language)
os.mkdir(os.path.dirname(po_stat.po_path()))
- with valid_path.open() as valid_file, open(po_stat.po_path(), 'w') as fh:
+ with valid_path.open() as valid_file, open(po_stat.po_path(), "w") as fh:
fh.write(valid_file.read())
- response = self.client.get(reverse('stats-quality-check', args=[po_stat.pk]))
+ response = self.client.get(reverse("stats-quality-check", args=[po_stat.pk]))
self.assertContains(response, "The po file looks good!")
def test_mysql(self):
@@ -798,29 +815,37 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
state = StateNone(branch=self.b, domain=self.d, language=self.language)
state.save()
- action = Action.new_by_name('RT', person=self.pr, comment="Reserved!")
- action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Reserved!"})
+ action = Action.new_by_name("RT", person=self.pr, comment="Reserved!")
+ action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Reserved!"})
- action = Action.new_by_name('UNDO', person=self.pr)
- action.apply_on(state, {
- 'send_to_ml': action.send_mail_to_ml, 'comment': "Ooops! I don't want to do that. Sorry.",
- })
+ action = Action.new_by_name("UNDO", person=self.pr)
+ action.apply_on(
+ state,
+ {
+ "send_to_ml": action.send_mail_to_ml,
+ "comment": "Ooops! I don't want to do that. Sorry.",
+ },
+ )
- action = Action.new_by_name('RT', person=self.pr)
- action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Translating"})
+ action = Action.new_by_name("RT", person=self.pr)
+ action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Translating"})
- action = Action.new_by_name('UT', person=self.pr)
- action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Translated"})
+ action = Action.new_by_name("UT", person=self.pr)
+ action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Translated"})
- action = Action.new_by_name('RP', person=self.pr)
- action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Proofreading"})
+ action = Action.new_by_name("RP", person=self.pr)
+ action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Proofreading"})
- action = Action.new_by_name('UNDO', person=self.pr)
- action.apply_on(state, {
- 'send_to_ml': action.send_mail_to_ml, 'comment': "Ooops! I don't want to do that. Sorry.",
- })
+ action = Action.new_by_name("UNDO", person=self.pr)
+ action.apply_on(
+ state,
+ {
+ "send_to_ml": action.send_mail_to_ml,
+ "comment": "Ooops! I don't want to do that. Sorry.",
+ },
+ )
- actions_db = Action.objects.filter(state_db__id=state.id).exclude(name='WC').order_by('-id')
+ actions_db = Action.objects.filter(state_db__id=state.id).exclude(name="WC").order_by("-id")
# So the last action is UNDO
self.assertIsInstance(actions_db[0], ActionUNDO)
@@ -833,58 +858,52 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
class DocsBuildingTests(TeamsAndRolesMixin, TestModuleBase):
def setUp(self):
super().setUp()
- html_dir = settings.SCRATCHDIR / 'HTML'
+ html_dir = settings.SCRATCHDIR / "HTML"
if html_dir.exists():
shutil.rmtree(str(html_dir))
def test_doc_building(self):
dom = Domain.objects.create(
- module=self.mod, name='help', description='User Guide', dtype='doc',
- layout='help_mallard/{lang}/{lang}.po'
+ module=self.mod, name="help", description="User Guide", dtype="doc",
layout="help_mallard/{lang}/{lang}.po"
)
state = StateTranslating(branch=self.branch, domain=dom, language=self.language, person=self.pt)
state.save()
file_path = Path(__file__).parent / "gnome-hello.help.fr.po"
with file_path.open() as test_file:
- action = Action.new_by_name('UT', person=self.pt, file=File(test_file))
- action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Done by translator."})
+ action = Action.new_by_name("UT", person=self.pt, file=File(test_file))
+ action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Done by translator."})
self.assertTrue(action.can_build)
self.assertIsNone(action.build_url)
- with patch('stats.models.Branch.checkout'):
- response = self.client.post(reverse('action-build-help', args=[action.pk]))
+ with patch("stats.models.Branch.checkout"):
+ response = self.client.post(reverse("action-build-help", args=[action.pk]))
self.assertEqual(response.status_code, 403)
self.client.force_login(self.pt)
- response = self.client.post(reverse('action-build-help', args=[action.pk]))
- self.assertRedirects(
- response, '/HTML/%d/index.html' % action.pk, fetch_redirect_response=False
- )
- self.assertEqual(action.build_url, '/HTML/%d/index.html' % action.pk)
- index_file = settings.SCRATCHDIR / 'HTML' / str(action.pk) / 'index.html'
- with index_file.open('r') as ifile:
+ response = self.client.post(reverse("action-build-help", args=[action.pk]))
+ self.assertRedirects(response, "/HTML/%d/index.html" % action.pk, fetch_redirect_response=False)
+ self.assertEqual(action.build_url, "/HTML/%d/index.html" % action.pk)
+ index_file = settings.SCRATCHDIR / "HTML" / str(action.pk) / "index.html"
+ with index_file.open("r") as ifile:
self.assertIn('<h2><span class="title">À propos</span></h2>', ifile.read())
def test_docbook_building(self):
dom = Domain.objects.create(
- module=self.mod, name='help', description='User Guide', dtype='doc',
- layout='help_docbook/{lang}/{lang}.po'
+ module=self.mod, name="help", description="User Guide", dtype="doc",
layout="help_docbook/{lang}/{lang}.po"
)
state = StateTranslating(branch=self.branch, domain=dom, language=self.language, person=self.pt)
state.save()
file_path = Path(__file__).parent / "gnome-hello.help.fr.po"
with file_path.open() as test_file:
- action = Action.new_by_name('UT', person=self.pt, file=File(test_file))
- action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Done by translator."})
+ action = Action.new_by_name("UT", person=self.pt, file=File(test_file))
+ action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Done by translator."})
self.assertTrue(action.can_build)
self.assertIsNone(action.build_url)
self.client.force_login(self.pt)
- with patch('stats.models.Branch.checkout'):
- response = self.client.post(reverse('action-build-help', args=[action.pk]))
- self.assertRedirects(
- response, '/HTML/%d/index.html' % action.pk, fetch_redirect_response=False
- )
- self.assertEqual(action.build_url, '/HTML/%d/index.html' % action.pk)
- index_file = settings.SCRATCHDIR / 'HTML' / str(action.pk) / 'index.html'
- with index_file.open('r') as ifile:
+ with patch("stats.models.Branch.checkout"):
+ response = self.client.post(reverse("action-build-help", args=[action.pk]))
+ self.assertRedirects(response, "/HTML/%d/index.html" % action.pk, fetch_redirect_response=False)
+ self.assertEqual(action.build_url, "/HTML/%d/index.html" % action.pk)
+ index_file = settings.SCRATCHDIR / "HTML" / str(action.pk) / "index.html"
+ with index_file.open("r") as ifile:
self.assertIn('<h2><span class="title">À propos</span></h2>', ifile.read())
diff --git a/vertimus/urls.py b/vertimus/urls.py
index f35c3224..c024fbc7 100644
--- a/vertimus/urls.py
+++ b/vertimus/urls.py
@@ -3,39 +3,24 @@ from django.urls import path
from damnedlies.urls import module_branch_domain
from vertimus import views
-
urlpatterns = [
- path('<int:stats_id>/<int:lang_id>/',
- views.vertimus_by_stats_id,
- name='vertimus_by_stats_id'),
- path('<int:branch_id>/<int:domain_id>/<int:language_id>/',
- views.vertimus_by_ids,
- name='vertimus_by_ids'),
- path('diff/<int:action_id_1>/<int:action_id_2>/<int:level>/',
- views.vertimus_diff,
- name='vertimus_diff'),
- path('uploads/%s/<locale:locale_name>)/latest/' % module_branch_domain,
- views.latest_uploaded_po,
- name='latest_uploaded_po'),
- path('%s/<locale:locale_name>/level<int:level>/' % module_branch_domain,
- views.vertimus_by_names,
- name='vertimus_archives_by_names'),
- path('%s/<locale:locale_name>/' % module_branch_domain,
- views.vertimus_by_names,
- name='vertimus_by_names'),
- path('<locale:locale>/activity_summary/',
- views.activity_by_language,
- name='activity_by_language'),
- path('action/<int:action_pk>/qcheck/',
- views.QualityCheckView.as_view(),
- name='action-quality-check'),
- path('stats/<int:stats_pk>/qcheck/',
- views.QualityCheckView.as_view(),
- name='stats-quality-check'),
- path('stats/<int:stats_pk>/msgiddiff/',
- views.MsgiddiffView.as_view(),
- name='stats-msgiddiff'),
- path('action/<int:action_pk>/build_help/',
- views.BuildTranslatedDocsView.as_view(),
- name='action-build-help'),
+ path("<int:stats_id>/<int:lang_id>/", views.vertimus_by_stats_id, name="vertimus_by_stats_id"),
+ path("<int:branch_id>/<int:domain_id>/<int:language_id>/", views.vertimus_by_ids,
name="vertimus_by_ids"),
+ path("diff/<int:action_id_1>/<int:action_id_2>/<int:level>/", views.vertimus_diff, name="vertimus_diff"),
+ path(
+ "uploads/%s/<locale:locale_name>)/latest/" % module_branch_domain,
+ views.latest_uploaded_po,
+ name="latest_uploaded_po",
+ ),
+ path(
+ "%s/<locale:locale_name>/level<int:level>/" % module_branch_domain,
+ views.vertimus_by_names,
+ name="vertimus_archives_by_names",
+ ),
+ path("%s/<locale:locale_name>/" % module_branch_domain, views.vertimus_by_names,
name="vertimus_by_names"),
+ path("<locale:locale>/activity_summary/", views.activity_by_language, name="activity_by_language"),
+ path("action/<int:action_pk>/qcheck/", views.QualityCheckView.as_view(), name="action-quality-check"),
+ path("stats/<int:stats_pk>/qcheck/", views.QualityCheckView.as_view(), name="stats-quality-check"),
+ path("stats/<int:stats_pk>/msgiddiff/", views.MsgiddiffView.as_view(), name="stats-msgiddiff"),
+ path("action/<int:action_pk>/build_help/", views.BuildTranslatedDocsView.as_view(),
name="action-build-help"),
]
diff --git a/vertimus/views.py b/vertimus/views.py
index b39dfaa8..6d3af856 100644
--- a/vertimus/views.py
+++ b/vertimus/views.py
@@ -10,9 +10,12 @@ from xml.dom.minidom import parse
from django.conf import settings
from django.contrib import messages
from django.http import (
- Http404, HttpResponseRedirect, HttpResponseForbidden, StreamingHttpResponse,
+ Http404,
+ HttpResponseForbidden,
+ HttpResponseRedirect,
+ StreamingHttpResponse,
)
-from django.shortcuts import render, get_object_or_404
+from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.utils.html import escape
from django.utils.safestring import mark_safe
@@ -20,11 +23,22 @@ from django.utils.translation import gettext as _
from django.views.generic import View
from stats.models import (
- Statistics, FakeLangStatistics, Module, ModuleLock, Branch, Domain, Language,
+ Branch,
+ Domain,
+ FakeLangStatistics,
+ Language,
+ Module,
+ ModuleLock,
+ Statistics,
+)
+from stats.utils import (
+ DocFormat,
+ UndetectableDocFormat,
+ check_po_quality,
+ is_po_reduced,
)
-from stats.utils import DocFormat, UndetectableDocFormat, check_po_quality, is_po_reduced
-from vertimus.models import State, Action, ActionArchived, SendMailFailed
from vertimus.forms import ActionForm
+from vertimus.models import Action, ActionArchived, SendMailFailed, State
def vertimus_by_stats_id(request, stats_id, lang_id):
@@ -42,13 +56,10 @@ def vertimus_by_ids(request, branch_id, domain_id, language_id):
return vertimus(request, branch, domain, language)
-def vertimus_by_names(request, module_name, branch_name,
- domain_name, locale_name, level="0"):
+def vertimus_by_names(request, module_name, branch_name, domain_name, locale_name, level="0"):
"""Access to Vertimus view by Branch, Domain and Language names"""
module = get_object_or_404(Module, name=module_name)
- branch = get_object_or_404(
- Branch.objects.select_related('module'), name=branch_name, module__id=module.id
- )
+ branch = get_object_or_404(Branch.objects.select_related("module"), name=branch_name,
module__id=module.id)
try:
domain = branch.get_domains()[domain_name]
except KeyError:
@@ -59,17 +70,19 @@ def vertimus_by_names(request, module_name, branch_name,
def vertimus(request, branch, domain, language, stats=None, level="0"):
"""The Vertimus view and form management. Level argument is used to
- access to the previous action history, first level (1) is the
- grandparent, second (2) is the parent of the grandparent, etc."""
+ access to the previous action history, first level (1) is the
+ grandparent, second (2) is the parent of the grandparent, etc."""
level = int(level)
pot_stats, stats, state = get_vertimus_state(branch, domain, language, stats=stats)
# Filtering on domain.name instead of domain because we can have several domains
# working on the same set of strings (e.g. when an extraction method changed,
# each extraction is mapped to a different domain with branch_from/branch_to delimitations)
- other_branch_states = State.objects.filter(
- branch__module=branch.module, domain__name=domain.name, language=language
- ).exclude(branch=branch.pk).exclude(name='None')
+ other_branch_states = (
+ State.objects.filter(branch__module=branch.module, domain__name=domain.name, language=language)
+ .exclude(branch=branch.pk)
+ .exclude(name="None")
+ )
if level == 0:
# Current actions
@@ -90,66 +103,53 @@ def vertimus(request, branch, domain, language, stats=None, level="0"):
# possible to edit an archived workflow
available_actions = state.get_available_actions(person)
has_ml = bool(language.team.mailing_list) if language.team else False
- if request.method == 'POST':
- action_form = ActionForm(
- request.user, state, available_actions, has_ml, request.POST, request.FILES
- )
+ if request.method == "POST":
+ action_form = ActionForm(request.user, state, available_actions, has_ml, request.POST,
request.FILES)
if action_form.is_valid():
# Process the data in form.cleaned_data
- action = action_form.cleaned_data['action']
+ action = action_form.cleaned_data["action"]
- action = Action.new_by_name(
- action, person=person, file=request.FILES.get('file', None)
- )
+ action = Action.new_by_name(action, person=person, file=request.FILES.get("file", None))
try:
msg = action.apply_on(state, action_form.cleaned_data)
except SendMailFailed:
- messages.error(
- request,
- _("A problem occurred while sending mail, no mail have been sent")
- )
+ messages.error(request, _("A problem occurred while sending mail, no mail have been
sent"))
except Exception as e:
- messages.error(
- request,
- _("An error occurred during applying your action: %s") % e
- )
+ messages.error(request, _("An error occurred during applying your action: %s") % e)
else:
if msg:
messages.success(request, msg)
return HttpResponseRedirect(
- reverse(
- 'vertimus_by_names',
- args=(branch.module.name, branch.name, domain.name, language.locale)
- )
+ reverse("vertimus_by_names", args=(branch.module.name, branch.name, domain.name,
language.locale))
)
elif available_actions:
action_form = ActionForm(request.user, state, available_actions, has_ml)
context = {
- 'pageSection': 'module',
- 'stats': stats,
- 'pot_stats': pot_stats,
- 'po_url': stats.po_url(),
- 'po_url_reduced': stats.has_reducedstat() and stats.po_url(reduced=True) or '',
- 'branch': branch,
- 'other_states': other_branch_states,
- 'domain': domain,
- 'language': language,
- 'module': branch.module,
- 'non_standard_repo_msg': _(settings.VCS_HOME_WARNING),
- 'state': state,
- 'is_team_member': person and language.team and person.is_translator(language.team),
- 'action_history': action_history,
- 'action_form': action_form,
- 'level': level,
- 'grandparent_level': grandparent_level,
+ "pageSection": "module",
+ "stats": stats,
+ "pot_stats": pot_stats,
+ "po_url": stats.po_url(),
+ "po_url_reduced": stats.has_reducedstat() and stats.po_url(reduced=True) or "",
+ "branch": branch,
+ "other_states": other_branch_states,
+ "domain": domain,
+ "language": language,
+ "module": branch.module,
+ "non_standard_repo_msg": _(settings.VCS_HOME_WARNING),
+ "state": state,
+ "is_team_member": person and language.team and person.is_translator(language.team),
+ "action_history": action_history,
+ "action_form": action_form,
+ "level": level,
+ "grandparent_level": grandparent_level,
}
if stats.has_figures():
- context['fig_stats'] = stats.fig_stats()
- del context['fig_stats']['prc']
- return render(request, 'vertimus/vertimus_detail.html', context)
+ context["fig_stats"] = stats.fig_stats()
+ del context["fig_stats"]["prc"]
+ return render(request, "vertimus/vertimus_detail.html", context)
def get_vertimus_state(branch, domain, language, stats=None):
@@ -184,9 +184,9 @@ def vertimus_diff(request, action_id_1, action_id_2, level):
raise Http404("File not found")
descr_1 = _('<a href="%(url)s">Uploaded file</a> by %(name)s on %(date)s') % {
- 'url': action_1.most_uptodate_file.url,
- 'name': action_1.person_name,
- 'date': action_1.created
+ "url": action_1.most_uptodate_file.url,
+ "name": action_1.person_name,
+ "date": action_1.created,
}
if action_id_2 not in (None, 0):
@@ -194,9 +194,9 @@ def vertimus_diff(request, action_id_1, action_id_2, level):
action_2 = get_object_or_404(action_real, pk=action_id_2)
file_path_2 = action_2.most_uptodate_filepath
descr_2 = _('<a href="%(url)s">Uploaded file</a> by %(name)s on %(date)s') % {
- 'url': action_2.most_uptodate_file.url,
- 'name': action_2.person_name,
- 'date': action_2.created,
+ "url": action_2.most_uptodate_file.url,
+ "name": action_2.person_name,
+ "date": action_2.created,
}
else:
action_2 = None
@@ -207,52 +207,49 @@ def vertimus_diff(request, action_id_1, action_id_2, level):
if action_2:
file_path_2 = action_2.most_uptodate_filepath
descr_2 = _('<a href="%(url)s">Uploaded file</a> by %(name)s on %(date)s') % {
- 'url': action_2.most_uptodate_file.url,
- 'name': action_2.person_name,
- 'date': action_2.created,
+ "url": action_2.most_uptodate_file.url,
+ "name": action_2.person_name,
+ "date": action_2.created,
}
else:
# 3) Lastly, the file should be the more recently committed file (merged)
try:
stats = Statistics.objects.get(branch=state.branch, domain=state.domain,
language=state.language)
descr_2 = _('<a href="%(url)s">Latest committed file</a> for %(lang)s') % {
- 'url': stats.po_url(),
- 'lang': state.language.get_name(),
+ "url": stats.po_url(),
+ "lang": state.language.get_name(),
}
except Statistics.DoesNotExist:
stats = get_object_or_404(Statistics, branch=state.branch, domain=state.domain,
language=None)
descr_2 = '<a href="%(url)s">%(text)s</a>' % {
- 'url': stats.pot_url(),
- 'text': _("Latest POT file"),
+ "url": stats.pot_url(),
+ "text": _("Latest POT file"),
}
file_path_2 = stats.po_path(reduced=reduced)
if not os.path.exists(file_path_2):
raise Http404("File not found")
d = difflib.HtmlDiff(wrapcolumn=80)
- with open(file_path_1, encoding='utf-8', errors='replace') as fh1, \
- open(file_path_2, encoding='utf-8', errors='replace') as fh2:
- diff_content = d.make_table(
- fh2.readlines(), fh1.readlines(),
- descr_2, descr_1, context=True
- )
+ with open(file_path_1, encoding="utf-8", errors="replace") as fh1, open(
+ file_path_2, encoding="utf-8", errors="replace"
+ ) as fh2:
+ diff_content = d.make_table(fh2.readlines(), fh1.readlines(), descr_2, descr_1, context=True)
context = {
- 'diff_content': diff_content,
- 'state': state,
+ "diff_content": diff_content,
+ "state": state,
}
- return render(request, 'vertimus/vertimus_diff.html', context)
+ return render(request, "vertimus/vertimus_diff.html", context)
def latest_uploaded_po(request, module_name, branch_name, domain_name, locale_name):
- """ Redirect to the latest uploaded po for a module/branch/language """
+ """Redirect to the latest uploaded po for a module/branch/language"""
branch = get_object_or_404(Branch, module__name=module_name, name=branch_name)
domain = get_object_or_404(Domain, module__name=module_name, name=domain_name)
lang = get_object_or_404(Language, locale=locale_name)
- latest_upload = Action.objects.filter(state_db__branch=branch,
- state_db__domain=domain,
- state_db__language=lang,
- file__endswith=".po").order_by('-created')[:1]
+ latest_upload = Action.objects.filter(
+ state_db__branch=branch, state_db__domain=domain, state_db__language=lang, file__endswith=".po"
+ ).order_by("-created")[:1]
if not latest_upload:
raise Http404
return HttpResponseRedirect(latest_upload[0].merged_file.url)
@@ -260,13 +257,13 @@ def latest_uploaded_po(request, module_name, branch_name, domain_name, locale_na
def activity_by_language(request, locale):
language = get_object_or_404(Language, locale=locale)
- states = State.objects.filter(language=language).exclude(name='None')
+ states = State.objects.filter(language=language).exclude(name="None")
context = {
- 'pageSection': "languages",
- 'language': language,
- 'activities': states,
+ "pageSection": "languages",
+ "language": language,
+ "activities": states,
}
- return render(request, 'vertimus/activity_summary.html', context)
+ return render(request, "vertimus/activity_summary.html", context)
class PoFileActionBase(View):
@@ -277,12 +274,12 @@ class PoFileActionBase(View):
def get_po_file(self):
pofile = None
- if self.kwargs.get('action_pk'):
- self.action = get_object_or_404(Action, pk=self.kwargs['action_pk'])
+ if self.kwargs.get("action_pk"):
+ self.action = get_object_or_404(Action, pk=self.kwargs["action_pk"])
if self.action.has_po_file():
pofile = self.action.most_uptodate_filepath
- elif self.kwargs.get('stats_pk'):
- stats = get_object_or_404(Statistics, pk=self.kwargs['stats_pk'])
+ elif self.kwargs.get("stats_pk"):
+ stats = get_object_or_404(Statistics, pk=self.kwargs["stats_pk"])
pofile = stats.po_path()
else:
raise Http404("action_pk and stats_pk are both None")
@@ -290,30 +287,32 @@ class PoFileActionBase(View):
class QualityCheckView(PoFileActionBase):
- template_name = 'vertimus/quality-check.html'
+ template_name = "vertimus/quality-check.html"
def get_context_data(self, **kwargs):
- context = {'base': 'base_modal.html'}
+ context = {"base": "base_modal.html"}
if self.pofile is None:
- context['results'] = _('No po file to check')
+ context["results"] = _("No po file to check")
else:
- context['checks'] = ['xmltags']
+ context["checks"] = ["xmltags"]
try:
- results = check_po_quality(self.pofile, context['checks'])
+ results = check_po_quality(self.pofile, context["checks"])
except OSError as err:
- context['results'] = 'Sorry, the server was not able to run the quality checks on this file
(%s)' % err
+ context["results"] = "Sorry, the server was not able to run the quality checks on this file
(%s)" % err
else:
if results:
- context['results'] = mark_safe(re.sub(
- r'^(# \(pofilter\) .*)', r'<span class="highlight">\1</span>', escape(results),
flags=re.M
- ))
+ context["results"] = mark_safe(
+ re.sub(
+ r"^(# \(pofilter\) .*)", r'<span class="highlight">\1</span>', escape(results),
flags=re.M
+ )
+ )
else:
- context['results'] = _('The po file looks good!')
+ context["results"] = _("The po file looks good!")
return context
class MsgiddiffView(PoFileActionBase):
- HEADER = '''
+ HEADER = """
<!DOCTYPE html>
<html>
<head>
@@ -333,47 +332,47 @@ class MsgiddiffView(PoFileActionBase):
</style>
</head>
<body>
-''' + '<div class="warning">{}</div>'.format(
+""" + '<div class="warning">{}</div>'.format(
_(
"WARNING: This file is <b>NOT</b> suitable as a base for completing this translation. "
"It contains HTML markup to highlight differential parts of changed strings."
)
)
- FOOTER = '</body</html>'
+ FOOTER = "</body</html>"
def streamed_file(self, po_file):
def strip(line):
- if line.startswith('#| '):
+ if line.startswith("#| "):
line = line[3:]
- if line.startswith('msgid '):
+ if line.startswith("msgid "):
line = line[6:]
- return line.rstrip('\n').strip('"')
+ return line.rstrip("\n").strip('"')
def line_fmt(no, line):
- return '<span class="noline">%d</span> ' % no + html.escape(line) + '<br>'
+ return '<span class="noline">%d</span> ' % no + html.escape(line) + "<br>"
yield self.HEADER
prev_id = curr_id = None
no_wrap = False
stored_comments = []
in_fuzzy = False
- with open(po_file, 'r') as fh:
+ with open(po_file, "r") as fh:
for noline, line in enumerate(fh.readlines(), start=1):
- if line.startswith(('#.', '#:')):
+ if line.startswith(("#.", "#:")):
stored_comments.append((noline, line))
continue
- if line == '\n':
+ if line == "\n":
in_fuzzy = False
stored_comments = []
continue
- if line.startswith('#, fuzzy'):
+ if line.startswith("#, fuzzy"):
in_fuzzy = True
diff_printed = False
prev_id = []
curr_id = []
- no_wrap = 'no-wrap' in line
+ no_wrap = "no-wrap" in line
yield '<div class="separator">(…)</div>\n'
for no, com in stored_comments:
yield line_fmt(no, com)
@@ -381,17 +380,19 @@ class MsgiddiffView(PoFileActionBase):
continue
if in_fuzzy:
- if line.startswith('#|'):
+ if line.startswith("#|"):
prev_id.append(line)
continue
if line.startswith('msgstr "'):
# Compute and display inline diff
- sep = '\n' if no_wrap else ''
+ sep = "\n" if no_wrap else ""
yield (
- '<div class="diff%s">' % (' nowrap' if no_wrap else '') + diff_strings(
+ '<div class="diff%s">' % (" nowrap" if no_wrap else "")
+ + diff_strings(
html.escape(sep.join(strip(_line) for _line in prev_id)),
- html.escape(sep.join(strip(_line) for _, _line in curr_id))
- ) + '</div>'
+ html.escape(sep.join(strip(_line) for _, _line in curr_id)),
+ )
+ + "</div>"
)
for no, _line in curr_id:
yield line_fmt(no, _line)
@@ -404,20 +405,20 @@ class MsgiddiffView(PoFileActionBase):
yield self.FOOTER
def get(self, request, *args, **kwargs):
- stats = get_object_or_404(Statistics, pk=self.kwargs['stats_pk'])
+ stats = get_object_or_404(Statistics, pk=self.kwargs["stats_pk"])
pofile = stats.po_path()
return StreamingHttpResponse(self.streamed_file(pofile))
class BuildTranslatedDocsView(PoFileActionBase):
- http_method_names = ['post']
+ http_method_names = ["post"]
def post(self, request, *args, **kwargs):
pofile = self.get_po_file()
if pofile is None:
- raise Http404('No target po file for this action')
+ raise Http404("No target po file for this action")
- html_dir = settings.SCRATCHDIR / 'HTML' / str(self.kwargs['action_pk'])
+ html_dir = settings.SCRATCHDIR / "HTML" / str(self.kwargs["action_pk"])
if html_dir.exists():
# If the build already ran, redirect to the static results
return HttpResponseRedirect(self.action.build_url)
@@ -425,7 +426,7 @@ class BuildTranslatedDocsView(PoFileActionBase):
state = self.action.state_db
team = state.language.team
if not request.user.is_authenticated or not team or not request.user.person.is_translator(team):
- return HttpResponseForbidden('Only team members can build docs.')
+ return HttpResponseForbidden("Only team members can build docs.")
with ModuleLock(state.branch.module):
state.branch.checkout()
@@ -446,63 +447,66 @@ class BuildTranslatedDocsView(PoFileActionBase):
except UndetectableDocFormat as err:
return str(err)
- build_error = _('Build failed (%(program)s): %(err)s')
- with tempfile.NamedTemporaryFile(suffix='.gmo') as gmo, \
- tempfile.TemporaryDirectory() as build_dir:
- result = subprocess.run([
- 'msgfmt', po_file, '-o', os.path.join(gmo.name)
- ], stderr=subprocess.PIPE)
+ build_error = _("Build failed (%(program)s): %(err)s")
+ with tempfile.NamedTemporaryFile(suffix=".gmo") as gmo, tempfile.TemporaryDirectory() as build_dir:
+ result = subprocess.run(["msgfmt", po_file, "-o", os.path.join(gmo.name)],
stderr=subprocess.PIPE)
if result.returncode != 0:
return build_error % {
- 'program': 'msgfmt',
- 'err': result.stderr.decode(errors='replace'),
+ "program": "msgfmt",
+ "err": result.stderr.decode(errors="replace"),
}
sources = doc_format.source_files()
- result = subprocess.run([
- 'itstool', '-m', gmo.name, '-l', state.language.locale,
- '-o', str(build_dir), '--strict',
- *[str(s) for s in sources],
- ], cwd=str(doc_format.vcs_path), stderr=subprocess.PIPE)
+ result = subprocess.run(
+ [
+ "itstool",
+ "-m",
+ gmo.name,
+ "-l",
+ state.language.locale,
+ "-o",
+ str(build_dir),
+ "--strict",
+ *[str(s) for s in sources],
+ ],
+ cwd=str(doc_format.vcs_path),
+ stderr=subprocess.PIPE,
+ )
if result.returncode != 0:
return build_error % {
- 'program': 'itstool',
- 'err': result.stderr.decode(errors='replace'),
+ "program": "itstool",
+ "err": result.stderr.decode(errors="replace"),
}
# Now build the html version
if not html_dir.exists():
html_dir.mkdir(parents=True)
- if doc_format.format == 'mallard':
+ if doc_format.format == "mallard":
# With mallard, specifying the directory is enough.
build_ref = [str(build_dir)]
else:
build_ref = [os.path.join(build_dir, s.name) for s in sources]
- cmd = [
- 'yelp-build', 'html', '-o', str(html_dir),
- '-p', str(doc_format.vcs_path / 'C'),
- *build_ref
- ]
+ cmd = ["yelp-build", "html", "-o", str(html_dir), "-p", str(doc_format.vcs_path / "C"),
*build_ref]
result = subprocess.run(cmd, cwd=str(build_dir), stderr=subprocess.PIPE)
- index_html = html_dir / 'index.html'
+ index_html = html_dir / "index.html"
if result.returncode != 0 or (not index_html.exists() and len(result.stderr) > 0):
shutil.rmtree(str(html_dir))
return build_error % {
- 'program': 'yelp-build',
- 'err': result.stderr.decode(errors='replace'),
+ "program": "yelp-build",
+ "err": result.stderr.decode(errors="replace"),
}
if not index_html.exists() and os.path.isfile(build_ref[0]):
# Create an index.html symlink to the base html doc if needed
try:
doc = parse(build_ref[0])
- base_name = doc.getElementsByTagName("article")[0].attributes.get('id').value
+ base_name = doc.getElementsByTagName("article")[0].attributes.get("id").value
except (AttributeError, IndexError):
pass
else:
- html_name = '%s.html' % base_name
- (html_dir / 'index.html').symlink_to(html_dir / html_name)
- return ''
+ html_name = "%s.html" % base_name
+ (html_dir / "index.html").symlink_to(html_dir / html_name)
+ return ""
def diff_strings(previous, current):
@@ -513,22 +517,22 @@ def diff_strings(previous, current):
'lorem<ins> foo</ins> ipsum dolor <del>sit </del>amet'
"""
if not previous or not current:
- return ''
+ return ""
seqm = difflib.SequenceMatcher(
- a=previous.replace('\r\n', '\n'),
- b=current.replace('\r\n', '\n'),
+ a=previous.replace("\r\n", "\n"),
+ b=current.replace("\r\n", "\n"),
)
output = []
for opcode, a0, a1, b0, b1 in seqm.get_opcodes():
- if opcode == 'equal':
+ if opcode == "equal":
output.append(seqm.a[a0:a1])
- elif opcode == 'insert':
+ elif opcode == "insert":
output.append('<ins title="New text">' + seqm.b[b0:b1] + "</ins>")
- elif opcode == 'delete':
+ elif opcode == "delete":
output.append('<del title="Deleted text">' + seqm.a[a0:a1] + "</del>")
- elif opcode == 'replace':
+ elif opcode == "replace":
output.append('<del title="Deleted text">' + seqm.a[a0:a1] + "</del>")
output.append('<ins title="New text">' + seqm.b[b0:b1] + "</ins>")
else:
raise RuntimeError("unexpected opcode")
- return mark_safe(''.join(output))
+ return mark_safe("".join(output))
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]