[damned-lies] Added a separate api module
- From: Claude Paroz <claudep src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [damned-lies] Added a separate api module
- Date: Fri, 11 Jan 2019 20:18:06 +0000 (UTC)
commit 2af3e0e88102fd68e55d1d9739e5834260fa4b5b
Author: Claude Paroz <claude 2xlibre net>
Date: Fri Jan 11 20:16:42 2019 +0100
Added a separate api module
api/__init__.py | 0
api/tests.py | 95 ++++++++++++++++++++++
api/urls.py | 23 ++++++
api/views.py | 210 +++++++++++++++++++++++++++++++++++++++++++++++++
damnedlies/settings.py | 1 +
damnedlies/urls.py | 2 +
people/models.py | 2 +-
vertimus/views.py | 31 +++++---
8 files changed, 350 insertions(+), 14 deletions(-)
---
diff --git a/api/__init__.py b/api/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/api/tests.py b/api/tests.py
new file mode 100644
index 00000000..fb7d67de
--- /dev/null
+++ b/api/tests.py
@@ -0,0 +1,95 @@
+from django.test import TestCase
+from django.urls import reverse
+
+
+class APITests(TestCase):
+ 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/',
+ })
+
+ def test_modules(self):
+ response = self.client.get(reverse('api-modules'))
+ result = response.json()
+ 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']))
+ result = response.json()
+ 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'}
+ )
+
+ def test_teams(self):
+ response = self.client.get(reverse('api-teams'))
+ result = response.json()
+ 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']))
+ result = response.json()
+ self.assertEqual(result['coordinators'], [{'name': 'John Coordinator'}])
+
+ def test_languages(self):
+ 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
+ })
+
+ def test_releases(self):
+ 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'
+ })
+
+ def test_release(self):
+ 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')
+
+ def test_release_stats(self):
+ 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]['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']))
+ result = response.json()
+ 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'])
+ )
+ 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')
diff --git a/api/urls.py b/api/urls.py
new file mode 100644
index 00000000..fba4dfb8
--- /dev/null
+++ b/api/urls.py
@@ -0,0 +1,23 @@
+from django.urls import path
+
+from . import views
+
+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('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(
+ 'modules/<name:module_name>/branches/<name:branch_name>/domains/<name:domain_name>'
+ '/languages/<locale:lang>',
+ views.ModuleLangStatsView.as_view(),
+ name='api-module-lang-stats'
+ ),
+]
diff --git a/api/views.py b/api/views.py
new file mode 100644
index 00000000..ac21591e
--- /dev/null
+++ b/api/views.py
@@ -0,0 +1,210 @@
+from django.http import Http404, JsonResponse
+from django.shortcuts import get_object_or_404
+from django.urls import reverse
+from django.views.generic import View
+
+from languages.models import Language
+from stats.models import Module, Release
+from teams.models import Team
+from vertimus.models import State
+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
+ )
+
+ def serialize_obj(self, obj, fields=None):
+ data = {field: obj[field] for field in fields}
+ data['href'] = self.get_href(obj)
+ return data
+
+ def serialize_qs(self, qs):
+ return [self.serialize_obj(obj, self.fields) for obj in qs]
+
+
+class SerializeObjectView(SerializeListView):
+ def get(self, *args, **kwargs):
+ return JsonResponse(self.serialize_obj(self.get_object()))
+
+ def serialize_obj(self, obj, fields=None):
+ return serialize_instance(obj, fields or self.fields)
+
+
+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'),
+ })
+
+
+class ModulesView(SerializeListView):
+ fields = ['name']
+
+ def get_queryset(self):
+ return Module.objects.filter(archived=False)
+
+ def get_href(self, obj):
+ 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 not in ['id', 'archived']
+ ]
+
+ def get_object(self):
+ 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')
+ ]
+ return data
+
+
+class TeamsView(SerializeListView):
+ fields = ['name', 'description']
+
+ def get_queryset(self):
+ return Team.objects.all().order_by('name')
+
+ def get_href(self, obj):
+ 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']
+ ]
+
+ def get_object(self):
+ 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()
+ ]
+ return data
+
+
+class LanguagesView(SerializeListView):
+ fields = ['name', 'locale', 'team__description', 'plurals']
+
+ def get_queryset(self):
+ return Language.objects.all().order_by('name')
+
+ def get_href(self, obj):
+ return reverse('api-team', args=[obj['locale']])
+
+
+class ReleasesView(SerializeListView):
+ fields = ['name', 'description']
+
+ def get_queryset(self):
+ return Release.objects.all().order_by('name')
+
+ def get_href(self, obj):
+ return reverse('api-release', args=[obj['name']])
+
+
+class ReleaseView(SerializeObjectView):
+ fields = ['name', 'description', 'string_frozen', 'status', 'branches']
+
+ def get_object(self):
+ 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']])
+ return data
+
+
+class ReleaseStatsView(View):
+ def get(self, *args, **kwargs):
+ 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,
+ })
+
+
+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'])
+ item_list = []
+ 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,
+ })
+
+
+class ModuleLangStatsView(View):
+ def get(self, *args, **kwargs):
+ 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'])
+ pot_stats, stats, state = get_vertimus_state(branch, domain, language)
+ return JsonResponse({
+ 'module': module.name,
+ 'branch': branch.name,
+ 'domain': domain.name,
+ 'language': 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(),
+ })
+
+
+def serialize_instance(instance, field_names):
+ data = {}
+ for field in field_names:
+ value = getattr(instance, field)
+ if hasattr(value, 'all'):
+ value = [str(o) for o in value.all()]
+ data[field] = value
+ return data
diff --git a/damnedlies/settings.py b/damnedlies/settings.py
index 962e5bec..ccef97a6 100644
--- a/damnedlies/settings.py
+++ b/damnedlies/settings.py
@@ -146,6 +146,7 @@ INSTALLED_APPS = [
'teams',
'vertimus',
'feeds',
+ 'api',
]
AUTHENTICATION_BACKENDS = [
diff --git a/damnedlies/urls.py b/damnedlies/urls.py
index 9b71efa5..5fd6b246 100644
--- a/damnedlies/urls.py
+++ b/damnedlies/urls.py
@@ -18,6 +18,7 @@ class LocaleConverter(StringConverter):
class NameConverter(StringConverter):
+ """Converter for module, branch, or domain names."""
regex = '[-~\w\+\.]+'
@@ -75,6 +76,7 @@ urlpatterns = [
path('i18n/', include('django.conf.urls.i18n')),
path('admin/', admin.site.urls),
path('rss/', include('feeds.urls')),
+ path('api/v1/', include('api.urls')),
path('', include('social_django.urls', namespace='social')),
]
diff --git a/people/models.py b/people/models.py
index 41bd86c4..67ecdb8a 100644
--- a/people/models.py
+++ b/people/models.py
@@ -108,7 +108,7 @@ class Person(User):
@property
def name(self):
if self.first_name or self.last_name:
- return self.first_name + " " + self.last_name
+ return " ".join([name for name in [self.first_name, self.last_name] if name])
else:
return self.username
diff --git a/vertimus/views.py b/vertimus/views.py
index 3694b59f..7685c2af 100644
--- a/vertimus/views.py
+++ b/vertimus/views.py
@@ -61,19 +61,7 @@ def vertimus(request, branch, domain, language, stats=None, level="0"):
grandparent, second (2) is the parent of the grandparent, etc."""
level = int(level)
- pot_stats = get_object_or_404(Statistics, branch=branch, domain=domain, language=None)
- if not stats:
- try:
- stats = Statistics.objects.get(branch=branch, domain=domain, language=language)
- except Statistics.DoesNotExist:
- stats = FakeLangStatistics(pot_stats, language)
-
- # Get the state of the translation
- try:
- state = State.objects.get(branch=branch, domain=domain, language=language)
- except State.DoesNotExist:
- # No need to save the state at this stage
- state = State(branch=branch, domain=domain, language=language)
+ 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)
@@ -155,6 +143,23 @@ def vertimus(request, branch, domain, language, stats=None, level="0"):
return render(request, 'vertimus/vertimus_detail.html', context)
+def get_vertimus_state(branch, domain, language, stats=None):
+ pot_stats = get_object_or_404(Statistics, branch=branch, domain=domain, language=None)
+ if not stats:
+ try:
+ stats = Statistics.objects.get(branch=branch, domain=domain, language=language)
+ except Statistics.DoesNotExist:
+ stats = FakeLangStatistics(pot_stats, language)
+
+ # Get the state of the translation
+ try:
+ state = State.objects.get(branch=branch, domain=domain, language=language)
+ except State.DoesNotExist:
+ # No need to save the state at this stage
+ state = State(branch=branch, domain=domain, language=language)
+ return pot_stats, stats, state
+
+
def vertimus_diff(request, action_id_1, action_id_2, level):
"""Show a diff between current action po file and previous file"""
if int(level) != 0:
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]