Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/Pootle-2.0.0/local_apps
diff options
context:
space:
mode:
Diffstat (limited to 'Pootle-2.0.0/local_apps')
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/__init__.py21
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/admin.py98
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/convert.py30
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/forms.py42
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/lib/__init__.py0
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/lib/util.py65
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/lib/view_handler.py82
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/management/__init__.py144
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/management/commands/__init__.py0
-rwxr-xr-xPootle-2.0.0/local_apps/pootle_app/management/commands/initdb.py199
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/management/commands/makepropermessages.py236
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/management/commands/refresh_stats.py74
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/models/__init__.py34
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/models/custom_sql_util.py29
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/models/directory.py151
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/models/language.py83
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/models/permissions.py167
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/models/profile.py164
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/models/project.py141
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/models/search.py188
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/models/signals.py27
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/models/store_iteration.py98
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/models/submission.py82
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/models/suggestion.py71
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/models/translation_project.py552
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/project_tree.py271
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/templates/admin/admin_general.html16
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/templates/admin/admin_general_languages.html22
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/templates/admin/admin_general_permissions.html31
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/templates/admin/admin_general_projects.html22
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/templates/admin/admin_general_settings.html56
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/templates/admin/admin_general_users.html22
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/templates/index/about.html25
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/templates/index/index.html114
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/templates/index/login.html42
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/templates/language/item_block.html26
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/templates/language/item_stats.html18
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/templates/language/item_summary.html38
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/templates/language/item_title.html10
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/templates/language/language_admin.html36
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/templates/language/language_index.html51
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/templates/language/search.html29
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/templates/language/tp_admin_files.html20
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/templates/language/tp_admin_permissions.html28
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/templates/language/tp_overview.html72
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/templates/language/tp_review.html38
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/templates/language/tp_translate.html54
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/templates/language/translatepage.html340
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/templates/project/project.html69
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/templates/project/project_admin.html26
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/templates/project/projects.html60
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/templates/top_contributers_table.html25
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/templates/translation_summary_legend.html11
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/tests.py534
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/unit_update.py113
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/url_manip.py87
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/url_state.py270
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/urls.py42
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/__init__.py0
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/admin/__init__.py0
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/admin/adminlanguages.py37
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/admin/adminpages.py155
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/admin/adminprojects.py37
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/admin/adminroot.py37
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/admin/adminusers.py84
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/admin/urls.py30
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/admin/util.py215
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/base.py26
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/index/__init__.py0
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/index/about.py57
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/index/index.py114
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/index/login.py84
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/index/logout.py26
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/index/robots.py30
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/index/urls.py27
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/language/__init__.py0
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/language/admin_files.py71
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/language/admin_permissions.py255
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/language/dispatch.py130
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/language/item_dict.py301
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/language/language_admin.py56
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/language/language_index.py99
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/language/navbar_dict.py97
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/language/project_index.py90
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/language/search_forms.py105
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/language/tp_common.py288
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/language/tp_review.py63
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/language/tp_translate.py63
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/language/translate_page.py933
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/language/urls.py53
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/language/view.py334
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/pagelayout.py70
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/profile/__init__.py0
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/profile/view.py67
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/project/__init__.py0
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/project/project_admin.py80
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/project/project_language_index.py95
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/project/projects_index.py46
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/project/urls.py28
-rw-r--r--Pootle-2.0.0/local_apps/pootle_app/views/top_stats.py79
-rw-r--r--Pootle-2.0.0/local_apps/pootle_autonotices/__init__.py40
-rw-r--r--Pootle-2.0.0/local_apps/pootle_autonotices/signals.py131
-rw-r--r--Pootle-2.0.0/local_apps/pootle_misc/__init__.py0
-rw-r--r--Pootle-2.0.0/local_apps/pootle_misc/baseurl.py48
-rw-r--r--Pootle-2.0.0/local_apps/pootle_misc/context_processors.py26
-rw-r--r--Pootle-2.0.0/local_apps/pootle_misc/dbinit.py161
-rw-r--r--Pootle-2.0.0/local_apps/pootle_misc/middleware/__init__.py0
-rw-r--r--Pootle-2.0.0/local_apps/pootle_misc/middleware/baseurl.py48
-rw-r--r--Pootle-2.0.0/local_apps/pootle_misc/middleware/errorpages.py73
-rw-r--r--Pootle-2.0.0/local_apps/pootle_misc/middleware/siteconfig.py72
-rw-r--r--Pootle-2.0.0/local_apps/pootle_misc/siteconfig.py51
-rw-r--r--Pootle-2.0.0/local_apps/pootle_misc/templatetags/__init__.py0
-rw-r--r--Pootle-2.0.0/local_apps/pootle_misc/templatetags/baseurl.py29
-rw-r--r--Pootle-2.0.0/local_apps/pootle_misc/templatetags/cleanhtml.py38
-rw-r--r--Pootle-2.0.0/local_apps/pootle_misc/templatetags/render_pager.py56
-rw-r--r--Pootle-2.0.0/local_apps/pootle_misc/util.py52
-rw-r--r--Pootle-2.0.0/local_apps/pootle_notifications/__init__.py0
-rw-r--r--Pootle-2.0.0/local_apps/pootle_notifications/feeds.py70
-rw-r--r--Pootle-2.0.0/local_apps/pootle_notifications/models.py40
-rw-r--r--Pootle-2.0.0/local_apps/pootle_notifications/templates/latest_news_snippet.html11
-rw-r--r--Pootle-2.0.0/local_apps/pootle_notifications/templates/notice_body.html2
-rw-r--r--Pootle-2.0.0/local_apps/pootle_notifications/templates/notice_title.html1
-rw-r--r--Pootle-2.0.0/local_apps/pootle_notifications/templates/notices.html91
-rw-r--r--Pootle-2.0.0/local_apps/pootle_notifications/templates/viewnotice.html12
-rw-r--r--Pootle-2.0.0/local_apps/pootle_notifications/templatetags/__init__.py0
-rw-r--r--Pootle-2.0.0/local_apps/pootle_notifications/templatetags/notification_tags.py37
-rw-r--r--Pootle-2.0.0/local_apps/pootle_notifications/urls.py28
-rw-r--r--Pootle-2.0.0/local_apps/pootle_notifications/views.py129
-rw-r--r--Pootle-2.0.0/local_apps/pootle_store/__init__.py0
-rw-r--r--Pootle-2.0.0/local_apps/pootle_store/fields.py337
-rw-r--r--Pootle-2.0.0/local_apps/pootle_store/models.py369
-rw-r--r--Pootle-2.0.0/local_apps/pootle_store/signals.py25
-rw-r--r--Pootle-2.0.0/local_apps/pootle_store/util.py87
133 files changed, 11822 insertions, 0 deletions
diff --git a/Pootle-2.0.0/local_apps/pootle_app/__init__.py b/Pootle-2.0.0/local_apps/pootle_app/__init__.py
new file mode 100644
index 0000000..0073171
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/__init__.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 Zuza Software Foundation
+#
+# This file is part of translate.
+#
+# translate is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# translate is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with translate; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
diff --git a/Pootle-2.0.0/local_apps/pootle_app/admin.py b/Pootle-2.0.0/local_apps/pootle_app/admin.py
new file mode 100644
index 0000000..2c5a1b2
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/admin.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+import re
+
+from translate.lang.data import langcode_re
+
+from django.utils.translation import ugettext_lazy as _
+from django.contrib.auth.admin import UserAdmin
+from django.contrib.auth.models import User
+from django import forms
+from django.contrib import admin
+
+from pootle_app.models import Language, Project
+from pootle_app.models.profile import PootleProfile
+
+
+### Language
+
+class MyLanguageAdminForm(forms.ModelForm):
+ def clean_code(self):
+ if not self.cleaned_data['code'] == 'templates' and not langcode_re.match(self.cleaned_data['code']):
+ raise forms.ValidationError(_('Language code does not follow the ISO convention'))
+ return self.cleaned_data["code"]
+
+
+class LanguageAdmin(admin.ModelAdmin):
+ list_display = ('code', 'fullname')
+ list_display_links = ('code', 'fullname')
+ fieldsets = (
+ (None, {
+ 'fields': ('code', 'fullname', 'specialchars')
+ }),
+ ('Plural information', {
+ 'fields': ('nplurals', 'pluralequation')
+ }),
+ )
+ form = MyLanguageAdminForm
+
+admin.site.register(Language, LanguageAdmin)
+
+
+### Project
+
+class MyProjectAdminForm(forms.ModelForm):
+
+ def clean_code(self):
+ if re.search("[^a-zA-Z0-9_]", self.cleaned_data['code']):
+ raise forms.ValidationError(_('Project code may only contain letters, numbers and _'))
+ return self.cleaned_data["code"]
+
+class ProjectAdmin(admin.ModelAdmin):
+ list_display = ('code', 'fullname', 'description', 'localfiletype')
+ list_display_links = ('code', 'fullname')
+ prepopulated_fields = {"fullname": ("code",)}
+ radio_fields = {"treestyle": admin.VERTICAL}
+ fieldsets = (
+ (None, {
+ 'fields': ('code', 'fullname', 'description', 'localfiletype')
+ }),
+ (_('Advanced Options'), {
+ 'classes': ('collapse',),
+ 'fields': ('treestyle', 'ignoredfiles')
+ }),
+ )
+ form = MyProjectAdminForm
+
+admin.site.register(Project, ProjectAdmin)
+
+
+### User / PootleProfile
+
+admin.site.unregister(User)
+
+class PootleProfileInline(admin.StackedInline):
+ model = PootleProfile
+
+class MyUserAdmin(UserAdmin):
+ inlines = [PootleProfileInline]
+
+admin.site.register(User, MyUserAdmin)
diff --git a/Pootle-2.0.0/local_apps/pootle_app/convert.py b/Pootle-2.0.0/local_apps/pootle_app/convert.py
new file mode 100644
index 0000000..42571bc
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/convert.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of translate.
+#
+# translate is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# translate is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with translate; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from translate.convert import xliff2po, po2xliff, po2ts, xliff2oo, po2oo
+
+convert_table = {
+ ('po', 'oo'): po2oo.convertoo,
+ ('po', 'ts'): po2ts.convertpo,
+ ('po', 'xlf'): po2xliff.convertpo,
+ ('xlf', 'oo'): xliff2oo.convertoo,
+ ('xlf', 'po'): xliff2po.convertxliff,
+}
diff --git a/Pootle-2.0.0/local_apps/pootle_app/forms.py b/Pootle-2.0.0/local_apps/pootle_app/forms.py
new file mode 100644
index 0000000..749bcf2
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/forms.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+from django import forms
+from djblets.siteconfig.forms import SiteSettingsForm
+from django.utils.translation import ugettext_lazy as _
+
+
+class GeneralSettingsForm(SiteSettingsForm):
+ TITLE = forms.CharField(
+ label=_("Title"),
+ help_text = _("The name for this Pootle server"),
+ max_length=50,
+ required=True,
+ )
+ DESCRIPTION = forms.CharField(
+ label=_("Description"),
+ help_text = _("The description and instructions shown on the front page and about page. Be sure to use valid HTML."),
+ max_length=1024,
+ required=True,
+ widget=forms.Textarea,
+ )
+
+ class Meta:
+ title = "General Settings"
diff --git a/Pootle-2.0.0/local_apps/pootle_app/lib/__init__.py b/Pootle-2.0.0/local_apps/pootle_app/lib/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/lib/__init__.py
diff --git a/Pootle-2.0.0/local_apps/pootle_app/lib/util.py b/Pootle-2.0.0/local_apps/pootle_app/lib/util.py
new file mode 100644
index 0000000..c87b201
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/lib/util.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+"""Some code helpers."""
+
+
+def lazy(result_name):
+ """This is used to create an attribute whose value is
+ lazily computed. The parameter names an object variable that
+ will be used to hold the lazily computed value. At the start,
+ this variable should hold the value undefined.
+
+ TODO: Replace this with a nice Python descriptor.
+
+ class Person(object):
+ def __init__(self):
+ self.name = 'John'
+ self.surname = 'Doe'
+
+ @lazy('_fullname')
+ def _get_fullname(self):
+ return self.name + ' ' + self.surname
+ """
+
+ def lazify(f):
+ def evaluator(self):
+ try:
+ return getattr(self, result_name)
+ except AttributeError:
+ result = f(self)
+ setattr(self, result_name, result)
+ return result
+ return evaluator
+ return lazify
+
+def lazy_property(name, getter):
+ def deleter(self):
+ delattr(self, name)
+
+ return property(lazy(name)(getter), None, deleter)
+
+
+from django.db import models
+class RelatedManager(models.Manager):
+ """Model manager that always does full joins on relations, saves
+ us lots of database queries later"""
+ def get_query_set(self):
+ return super(RelatedManager, self).get_query_set().select_related(depth=1)
diff --git a/Pootle-2.0.0/local_apps/pootle_app/lib/view_handler.py b/Pootle-2.0.0/local_apps/pootle_app/lib/view_handler.py
new file mode 100644
index 0000000..3ccb00d
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/lib/view_handler.py
@@ -0,0 +1,82 @@
+
+from django.forms.util import ValidationError
+from django.utils.safestring import mark_safe
+
+
+class FormError(ValidationError):
+ pass
+class SubmitError(FormError):
+ pass
+class HandlerError(FormError):
+ pass
+
+
+class View(object):
+ def find_post_handler_action(self, request):
+ action_names = [action_name for action_name in self.handlers
+ if action_name in set(request.POST)]
+ if len(action_names) == 1:
+ return action_names[0]
+ else:
+ raise SubmitError('Only one submit action may be handled per POST')
+
+ def find_handlers(self, forms):
+ handlers = {}
+ for form in forms.itervalues():
+ for action_name, _action_label in form.actions:
+ if action_name not in handlers:
+ handlers[action_name] = form
+ else:
+ raise HandlerError('More than one form defines the handler %s' % action_name)
+ return handlers
+
+ def __init__(self, forms):
+ self.handlers = self.find_handlers(forms)
+ self.forms = forms
+
+ def __call__(self, request, *args, **kwargs):
+ template_vars = {}
+ for form_name, form_class in self.forms.iteritems():
+ if form_class.must_display(request, *args, **kwargs):
+ template_vars[form_name] = form_class(request)
+ else:
+ template_vars[form_name] = None
+ if request.method == 'POST':
+ action = self.find_post_handler_action(request)
+ form = self.handlers[action](request, data=request.POST, files=request.FILES)
+ template_vars.update(form.dispatch(action, request, *args, **kwargs))
+ return self.GET(template_vars, request, *args, **kwargs)
+
+ def GET(self, template_vars, request, *args, **kwargs):
+ raise NotImplementedError()
+
+
+class Handler(object):
+ Form = None # This should be implemented as an inner class
+
+ actions = [] # This should be all
+
+ @classmethod
+ def must_display(cls, request, *args, **kwargs):
+ return True
+
+ def __init__(self, request, data=None, files=None):
+ self.form = self.Form(data=data, files=files)
+
+ def dispatch(self, action, request, *args, **kwargs):
+ handler = getattr(self, action)
+ return handler(request, *args, **kwargs)
+
+ def render_submit(self, action):
+ return '<input type="submit" name="%(action_name)s" value="%(action_value)s" />' % {
+ 'action_name': action[0],
+ 'action_value': unicode(action[1]) }
+
+ def as_p(self):
+ return mark_safe("""
+ %(inner_form)s
+ <p>%(submits)s</p>""" % {
+ 'inner_form': self.form.as_p(),
+ 'submits': ''.join(self.render_submit(action) for action in self.actions) }
+ )
+
diff --git a/Pootle-2.0.0/local_apps/pootle_app/management/__init__.py b/Pootle-2.0.0/local_apps/pootle_app/management/__init__.py
new file mode 100644
index 0000000..1437950
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/management/__init__.py
@@ -0,0 +1,144 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 Zuza Software Foundation
+#
+# This file is part of translate.
+#
+# translate is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# translate is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with translate; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from django.db.models.signals import post_syncdb
+from django.utils.translation import ugettext_noop as _
+from django.contrib.auth.models import User, Permission
+from django.contrib.contenttypes.models import ContentType
+
+import pootle_app.models
+from pootle_app.models import PootleProfile, Directory, Language, Project
+from pootle_app.models.permissions import PermissionSet, get_pootle_permission
+
+
+def create_essential_users():
+ """Create default and nobody User instances required for pootle permission system"""
+ # The nobody user is used to represent an anonymous user in cases where
+ # we need to associate model information with such a user. An example is
+ # in the permission system: we need a way to store rights for anonymous
+ # users; thus we use the nobody user.
+ nobody, created = User.objects.get_or_create(username=u"nobody",
+ first_name=u"any anonymous user",
+ is_active=True)
+ if created:
+ nobody.set_unusable_password()
+ nobody.save()
+
+ # The default user represents any valid, non-anonymous user and is used to
+ # associate information any such user. An example is in the permission
+ # system: we need a way to store default rights for users. We use the
+ # default user for this.
+ #
+ # In a future version of Pootle we should think about using Django's
+ # groups to do better permissions handling.
+ default, created = User.objects.get_or_create(username=u"default",
+ first_name=u"any authenticated user",
+ is_active=True)
+ if created:
+ default.set_unusable_password()
+ default.save()
+
+def create_pootle_permissions():
+ """define Pootle's directory level permissions"""
+ pootle_content_type, created = ContentType.objects.get_or_create(name="pootle", app_label="pootle_app", model="")
+ view, created = Permission.objects.get_or_create(name=_("Can view a translation project"),
+ content_type=pootle_content_type, codename="view")
+ suggest, created = Permission.objects.get_or_create(name=_("Can make a suggestion for a translation"),
+ content_type=pootle_content_type, codename="suggest")
+ translate, created = Permission.objects.get_or_create(name=_("Can submit a translation"),
+ content_type=pootle_content_type, codename="translate")
+ overwrite, created = Permission.objects.get_or_create(name=_("Can overwrite translations on uploading files"),
+ content_type=pootle_content_type, codename="overwrite")
+ review, created = Permission.objects.get_or_create(name=_("Can review translations"),
+ content_type=pootle_content_type, codename="review")
+ archive, created = Permission.objects.get_or_create(name=_("Can download archives of translation projects"),
+ content_type=pootle_content_type, codename="archive")
+ administrate, created = Permission.objects.get_or_create(name=_("Can administrate a translation project"),
+ content_type=pootle_content_type, codename="administrate")
+ commit, created = Permission.objects.get_or_create(name=_("Can commit to version control"),
+ content_type=pootle_content_type, codename="commit")
+
+def create_pootle_permission_sets():
+ """Create the default permission set for the anonymous (non-logged in) user
+ ('nobody') and for the logged in user ('default')."""
+ nobody = PootleProfile.objects.get(user__username='nobody')
+ default = PootleProfile.objects.get(user__username='default')
+
+ view = get_pootle_permission('view')
+ suggest = get_pootle_permission('suggest')
+ translate = get_pootle_permission('translate')
+ archive = get_pootle_permission('archive')
+
+ # Default permissions for tree root
+ root = Directory.objects.root
+ permission_set, created = PermissionSet.objects.get_or_create(profile=nobody, directory=root)
+ if created:
+ permission_set.positive_permissions = [view, suggest]
+ permission_set.save()
+
+ permission_set, created = PermissionSet.objects.get_or_create(profile=default, directory=root)
+ if created:
+ permission_set.positive_permissions = [view, suggest, translate, archive]
+ permission_set.save()
+
+ # Default permissions for templates language
+ templates = Directory.objects.get(pootle_path="/templates/")
+
+ #override with no permissions for templates language
+ permission_set, created = PermissionSet.objects.get_or_create(profile=nobody, directory=templates)
+ if created:
+ #FIXME should we set everything as negative?
+ permission_set.negative_permissions = [suggest]
+ permission_set.save()
+
+ permission_set, created = PermissionSet.objects.get_or_create(profile=default, directory=templates)
+ if created:
+ permission_set.negative_permissions = [suggest, translate]
+ permission_set.save()
+
+def create_root_directory():
+ """Create root Directory item."""
+ directory, created = Directory.objects.get_or_create(name='')
+
+
+def create_template_language():
+ """template language is used to give users access to the untranslated template files"""
+ templates, created = Language.objects.get_or_create(code="templates", fullname = u'Templates')
+
+def create_terminology_project():
+ """terminology project is used to display terminology suggestions while translating"""
+ terminology, created = Project.objects.get_or_create(code="terminology", fullname=u"Terminology")
+
+def post_syncdb_handler(sender, created_models, **kwargs):
+ if PootleProfile in created_models:
+ create_essential_users()
+ if Directory in created_models:
+ create_root_directory()
+ if Language in created_models:
+ create_template_language()
+ if Project in created_models:
+ create_terminology_project()
+ if PermissionSet in created_models:
+ create_pootle_permissions()
+ create_pootle_permission_sets()
+
+
+post_syncdb.connect(post_syncdb_handler, sender=pootle_app.models)
diff --git a/Pootle-2.0.0/local_apps/pootle_app/management/commands/__init__.py b/Pootle-2.0.0/local_apps/pootle_app/management/commands/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/management/commands/__init__.py
diff --git a/Pootle-2.0.0/local_apps/pootle_app/management/commands/initdb.py b/Pootle-2.0.0/local_apps/pootle_app/management/commands/initdb.py
new file mode 100755
index 0000000..7e6ae52
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/management/commands/initdb.py
@@ -0,0 +1,199 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+import os
+os.environ['DJANGO_SETTINGS_MODULE'] = 'pootle.settings'
+
+from django.db import transaction
+
+from django.core.management.base import NoArgsCommand
+from django.contrib.auth.models import User
+
+class Command(NoArgsCommand):
+ def handle_noargs(self, **options):
+ create_default_db()
+
+def create_default_db():
+ """This creates the default database to get a working Pootle installation.
+
+ You can tweak the methods called or their implementation elsewhere in the
+ file. This provides some sane default to get things working."""
+ try:
+ try:
+ transaction.enter_transaction_management()
+ transaction.managed(True)
+
+ create_default_projects()
+ create_default_languages()
+ create_default_admin()
+ except:
+ if transaction.is_dirty():
+ transaction.rollback()
+ transaction.leave_transaction_management()
+ raise
+ finally:
+ if transaction.is_managed():
+ if transaction.is_dirty():
+ transaction.commit()
+ transaction.leave_transaction_management()
+
+def create_default_projects():
+ """Create the default projects that we host. You might want to add your
+ projects here, although you can also add things through the web interface
+ later."""
+ from pootle_app.models import Project
+
+ pootle = Project(code=u"pootle")
+ pootle.fullname = u"Pootle"
+ pootle.description = "<div dir='ltr' lang='en'>Interface translations for Pootle. <br /> See the <a href='http://pootle.locamotion.org'>official Pootle server</a> for the translations of Pootle.</div>"
+ pootle.checkstyle = "standard"
+ pootle.localfiletype = "po"
+ pootle.treestyle = "auto"
+ pootle.save()
+
+ tutorial = Project(code=u"tutorial")
+ tutorial.fullname = u"Tutorial"
+ tutorial.description = "<div dir='ltr' lang='en'>Tutorial project where users can play with Pootle and learn more about translation and localisation.<br />For more help on localisation, visit the <a href='http://translate.sourceforge.net/wiki/guide/start'>localisation guide</a>.</div>"
+ tutorial.checkstyle = "standard"
+ tutorial.localfiletype = "po"
+ tutorial.treestyle = "auto"
+ tutorial.save()
+
+def create_default_languages():
+ """Create the default languages. We afford this priviledge to languages
+ with reasonably complete interface translations for Pootle."""
+ from pootle_app.models import Language
+
+ af = Language(code="af")
+ af.fullname = u"Afrikaans"
+ af.specialchars = u"ëïêôûáéíóúý"
+ af.nplurals = '2'
+ af.pluralequation = "(n != 1)"
+ af.save()
+
+ # Akan
+ ak = Language(code='ak')
+ ak.fullname = u'Akan'
+ ak.pluralequation = u'(n > 1)'
+ ak.specialchars = "ɛɔƐƆ"
+ ak.nplurals = u'2'
+ ak.save()
+
+# Español
+# Spanish (Argentina)
+# es_AR = Language(code="es_AR")
+# es_AR.fullname = u'Spanish; Castilian (Argentina)'
+# es_AR.nplurals = '2'
+# es_AR.pluralequation = '(n != 1)'
+# es_AR.save()
+
+ # Haitian Creole
+ ht = Language(code="ht")
+ ht.fullname = u'Haitian; Haitian Creole'
+ ht.nplurals = '2'
+ ht.pluralequation = '(n !=1)'
+ ht.save()
+
+ # Sesotho sa Leboa
+ # Northern Sotho
+ nso = Language(code="nso")
+ nso.fullname = u'Northern Sotho'
+ nso.nplurals = '2'
+ nso.pluralequation = '(n > 1)'
+ nso.specialchars = "šŠ"
+ nso.save()
+
+ # Tshivenḓa
+ # Venda
+ ve = Language(code="ve")
+ ve.fullname = u'Venda'
+ ve.nplurals = '2'
+ ve.pluralequation = '(n != 1)'
+ ve.specialchars = "ḓṋḽṱ ḒṊḼṰ ṅṄ"
+ ve.save()
+
+ # Wolof
+ wo = Language(code="wo")
+ wo.fullname = u'Wolof'
+ wo.nplurals = '2'
+ wo.pluralequation = '(n != 1)'
+ wo.save()
+
+ # 简体中文
+ # Simplified Chinese (China mainland used below, but also used in Singapore and Malaysia)
+ zh_CN = Language(code="zh_CN")
+ zh_CN.fullname = u'Chinese (China)'
+ zh_CN.nplurals = '1'
+ zh_CN.pluralequation = '0'
+ zh_CN.specialchars = u"←→↔×÷©…—‘’“”【】《》"
+ zh_CN.save()
+
+ # 繁體中文
+ # Traditional Chinese (Hong Kong used below, but also used in Taiwan and Macau)
+ zh_HK = Language(code="zh_HK")
+ zh_HK.fullname = u'Chinese (Hong Kong)'
+ zh_HK.nplurals = '1'
+ zh_HK.pluralequation = '0'
+ zh_HK.specialchars = u"←→↔×÷©…—‘’“”「」『』【】《》"
+ zh_HK.save()
+
+ # 繁體中文
+ # Traditional Chinese (Taiwan used below, but also used in Hong Kong and Macau)
+ zh_TW = Language(code="zh_TW")
+ zh_TW.fullname = u'Chinese (Taiwan)'
+ zh_TW.nplurals = '1'
+ zh_TW.pluralequation = '0'
+ zh_TW.specialchars = u"←→↔×÷©…—‘’“”「」『』【】《》"
+ zh_TW.save()
+
+ ca_valencia = Language(code='ca@valencia')
+ ca_valencia.fullname = u'Catalan (Valencia)'
+ ca_valencia.nplurals = '2'
+ ca_valencia.pluralequation = '(n != 1)'
+ ca_valencia.save()
+
+ son = Language(code='son')
+ son.fullname = u'Songhai languages'
+ son.nplurals = '1'
+ son.pluralequation = '0'
+ son.specialchars = u'ɲŋšžãõẽĩƝŊŠŽÃÕẼĨ'
+ son.save()
+
+ # import languages from toolkit
+ from translate.lang import data
+ for code, props in data.languages.items():
+ try:
+ lang, created = Language.objects.get_or_create(code=code, fullname=props[0],
+ nplurals=props[1], pluralequation=props[2])
+ except:
+ pass
+
+
+def create_default_admin():
+ """Create the default user(s) for Pootle. You definitely want to change
+ the admin account so that your default install is not accessible with the
+ default credentials. The users 'noboby' and 'default' should be left as is."""
+ admin = User(username=u"admin",
+ first_name=u"Administrator",
+ is_active=True,
+ is_superuser=True,
+ is_staff=True)
+ admin.set_password("admin")
+ admin.save()
diff --git a/Pootle-2.0.0/local_apps/pootle_app/management/commands/makepropermessages.py b/Pootle-2.0.0/local_apps/pootle_app/management/commands/makepropermessages.py
new file mode 100644
index 0000000..10f76f2
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/management/commands/makepropermessages.py
@@ -0,0 +1,236 @@
+# Copied from Django to fix some bugs
+
+import re
+import os
+import sys
+import glob
+import warnings
+from itertools import dropwhile
+from optparse import make_option
+
+from django.core.management.base import CommandError, BaseCommand
+
+try:
+ set
+except NameError:
+ from sets import Set as set # For Python 2.3
+
+# Intentionally silence DeprecationWarnings about os.popen3 in Python 2.6. It's
+# still sensible for us to use it, since subprocess didn't exist in 2.3.
+warnings.filterwarnings('ignore', category=DeprecationWarning, message=r'os\.popen3')
+
+pythonize_re = re.compile(r'\n\s*//')
+
+def handle_extensions(extensions=('html',)):
+ """
+ organizes multiple extensions that are separated with commas or passed by
+ using --extension/-e multiple times.
+
+ for example: running 'django-admin makemessages -e js,txt -e xhtml -a'
+ would result in a extension list: ['.js', '.txt', '.xhtml']
+
+ >>> handle_extensions(['.html', 'html,js,py,py,py,.py', 'py,.py'])
+ ['.html', '.js']
+ >>> handle_extensions(['.html, txt,.tpl'])
+ ['.html', '.tpl', '.txt']
+ """
+ ext_list = []
+ for ext in extensions:
+ ext_list.extend(ext.replace(' ','').split(','))
+ for i, ext in enumerate(ext_list):
+ if not ext.startswith('.'):
+ ext_list[i] = '.%s' % ext_list[i]
+
+ # we don't want *.py files here because of the way non-*.py files
+ # are handled in make_messages() (they are copied to file.ext.py files to
+ # trick xgettext to parse them as Python files)
+ return set([x for x in ext_list if x != '.py'])
+
+def make_messages(locale=None, domain='django', verbosity='1', all=False, extensions=None):
+ """
+ Uses the locale directory from the Django SVN tree or an application/
+ project to process all
+ """
+ # Need to ensure that the i18n framework is enabled
+ from django.conf import settings
+ if settings.configured:
+ settings.USE_I18N = True
+ else:
+ settings.configure(USE_I18N = True)
+
+ from django.utils.translation import templatize
+
+ if os.path.isdir(os.path.join('conf', 'locale')):
+ localedir = os.path.abspath(os.path.join('conf', 'locale'))
+ elif os.path.isdir('locale'):
+ localedir = os.path.abspath('locale')
+ else:
+ raise CommandError("This script should be run from the Django SVN tree or your project or app tree. If you did indeed run it from the SVN checkout or your project or application, maybe you are just missing the conf/locale (in the django tree) or locale (for project and application) directory? It is not created automatically, you have to create it by hand if you want to enable i18n for your project or application.")
+
+ if domain not in ('django', 'djangojs'):
+ raise CommandError("currently makemessages only supports domains 'django' and 'djangojs'")
+
+ if (locale is None and not all) or domain is None:
+ # backwards compatible error message
+ if not sys.argv[0].endswith("make-messages.py"):
+ message = "Type '%s help %s' for usage.\n" % (os.path.basename(sys.argv[0]), sys.argv[1])
+ else:
+ message = "usage: make-messages.py -l <language>\n or: make-messages.py -a\n"
+ raise CommandError(message)
+
+ # xgettext versions prior to 0.15 assumed Python source files were encoded
+ # in iso-8859-1, and produce utf-8 output. In the case where xgettext is
+ # given utf-8 input (required for Django files with non-ASCII characters),
+ # this results in a utf-8 re-encoding of the original utf-8 that needs to be
+ # undone to restore the original utf-8. So we check the xgettext version
+ # here once and set a flag to remember if a utf-8 decoding needs to be done
+ # on xgettext's output for Python files. We default to assuming this isn't
+ # necessary if we run into any trouble determining the version.
+ xgettext_reencodes_utf8 = False
+ (stdin, stdout, stderr) = os.popen3('xgettext --version', 't')
+ match = re.search(r'(?P<major>\d+)\.(?P<minor>\d+)', stdout.read())
+ if match:
+ xversion = (int(match.group('major')), int(match.group('minor')))
+ if xversion < (0, 15):
+ xgettext_reencodes_utf8 = True
+
+ languages = []
+ if locale is not None:
+ languages.append(locale)
+ elif all:
+ locale_dirs = filter(os.path.isdir, glob.glob('%s/*' % localedir))
+ languages = [os.path.basename(l) for l in locale_dirs]
+
+ for locale in languages:
+ if verbosity > 0:
+ print "processing language", locale
+ basedir = os.path.join(localedir, locale, 'LC_MESSAGES')
+ if not os.path.isdir(basedir):
+ os.makedirs(basedir)
+
+ pofile = os.path.join(basedir, '%s.po' % domain)
+ potfile = os.path.join(basedir, '%s.pot' % domain)
+
+ if os.path.exists(potfile):
+ os.unlink(potfile)
+
+ all_files = []
+ for (dirpath, dirnames, filenames) in os.walk("."):
+ all_files.extend([(dirpath, f) for f in filenames])
+ all_files.sort()
+ for dirpath, file in all_files:
+ file_base, file_ext = os.path.splitext(file)
+ if domain == 'djangojs' and file_ext == '.js':
+ if verbosity > 1:
+ sys.stdout.write('processing file %s in %s\n' % (file, dirpath))
+ src = open(os.path.join(dirpath, file), "rU").read()
+ src = pythonize_re.sub('\n#', src)
+ thefile = '%s.py' % file
+ open(os.path.join(dirpath, thefile), "w").write(src)
+ cmd = 'xgettext -d %s -L Perl --keyword=gettext_noop --keyword=gettext_lazy --keyword=ngettext_lazy:1,2 --from-code UTF-8 --add-comments=l10n --copyright-holder="Zuza Software Foundation (Translate.org.za)" -o - "%s"' % (domain, os.path.join(dirpath, thefile))
+ (stdin, stdout, stderr) = os.popen3(cmd, 't')
+ msgs = stdout.read()
+ errors = stderr.read()
+ if errors:
+ raise CommandError("errors happened while running xgettext on %s\n%s" % (file, errors))
+ old = '#: '+os.path.join(dirpath, thefile)[2:]
+ new = '#: '+os.path.join(dirpath, file)[2:]
+ msgs = msgs.replace(old, new)
+ if os.path.exists(potfile):
+ # Strip the header
+ msgs = '\n'.join(dropwhile(len, msgs.split('\n')))
+ else:
+ msgs = msgs.replace('charset=CHARSET', 'charset=UTF-8')
+ if msgs:
+ open(potfile, 'ab').write(msgs)
+ os.unlink(os.path.join(dirpath, thefile))
+ elif domain == 'django' and (file_ext == '.py' or file_ext in extensions):
+ thefile = file
+ if file_ext in extensions:
+ src = open(os.path.join(dirpath, file), "rU").read()
+ thefile = '%s.py' % file
+ open(os.path.join(dirpath, thefile), "w").write(templatize(src))
+ if verbosity > 1:
+ sys.stdout.write('processing file %s in %s\n' % (file, dirpath))
+ cmd = 'xgettext -d %s -L Python --keyword=gettext_noop --keyword=gettext_lazy --keyword=ngettext_lazy:1,2 --keyword=ugettext_noop --keyword=ugettext_lazy --keyword=ungettext_lazy:1,2 --from-code UTF-8 --add-comments=l10n --copyright-holder="Zuza Software Foundation (Translate.org.za)" -o - "%s"' % (
+ domain, os.path.join(dirpath, thefile))
+ (stdin, stdout, stderr) = os.popen3(cmd, 't')
+ msgs = stdout.read()
+ errors = stderr.read()
+ if errors:
+ raise CommandError("errors happened while running xgettext on %s\n%s" % (file, errors))
+
+ if xgettext_reencodes_utf8:
+ msgs = msgs.decode('utf-8').encode('iso-8859-1')
+
+ if thefile != file:
+ old = '#: '+os.path.join(dirpath, thefile)[2:]
+ new = '#: '+os.path.join(dirpath, file)[2:]
+ msgs = msgs.replace(old, new)
+ if os.path.exists(potfile):
+ # Strip the header
+ msgs = '\n'.join(dropwhile(len, msgs.split('\n')))
+ else:
+ msgs = msgs.replace('charset=CHARSET', 'charset=UTF-8')
+ if msgs:
+ open(potfile, 'ab').write(msgs)
+ if thefile != file:
+ os.unlink(os.path.join(dirpath, thefile))
+
+ if os.path.exists(potfile):
+ (stdin, stdout, stderr) = os.popen3('msguniq --to-code=utf-8 "%s"' % potfile, 't')
+ msgs = stdout.read()
+ errors = stderr.read()
+ if errors:
+ raise CommandError("errors happened while running msguniq\n%s" % errors)
+ open(potfile, 'w').write(msgs)
+ if os.path.exists(pofile):
+ (stdin, stdout, stderr) = os.popen3('msgmerge -q "%s" "%s"' % (pofile, potfile), 't')
+ msgs = stdout.read()
+ errors = stderr.read()
+ if errors:
+ raise CommandError("errors happened while running msgmerge\n%s" % errors)
+ open(pofile, 'wb').write(msgs)
+ os.unlink(potfile)
+
+
+class Command(BaseCommand):
+ option_list = BaseCommand.option_list + (
+ make_option('--locale', '-l', default=None, dest='locale',
+ help='Creates or updates the message files only for the given locale (e.g. pt_BR).'),
+ make_option('--domain', '-d', default='django', dest='domain',
+ help='The domain of the message files (default: "django").'),
+ make_option('--verbose', action='store', dest='verbosity',
+ default='1', type='choice', choices=['0', '1', '2'],
+ help='Verbosity level; 0=minimal output, 1=normal output, 2=all output'),
+ make_option('--all', '-a', action='store_true', dest='all',
+ default=False, help='Reexamines all source code and templates for new translation strings and updates all message files for all available languages.'),
+ make_option('--extension', '-e', dest='extensions',
+ help='The file extension(s) to examine (default: ".html", separate multiple extensions with commas, or use -e multiple times)',
+ action='append'),
+ )
+ help = "Runs over the entire source tree of the current directory and pulls out all strings marked for translation. It creates (or updates) a message file in the conf/locale (in the django tree) or locale (for project and application) directory."
+
+ requires_model_validation = False
+ can_import_settings = False
+
+ def handle(self, *args, **options):
+ if len(args) != 0:
+ raise CommandError("Command doesn't accept any arguments")
+
+ locale = options.get('locale')
+ domain = options.get('domain')
+ verbosity = int(options.get('verbosity'))
+ process_all = options.get('all')
+ extensions = options.get('extensions') or ['html']
+
+ if domain == 'djangojs':
+ extensions = []
+ else:
+ extensions = handle_extensions(extensions)
+
+ if '.js' in extensions:
+ raise CommandError("JavaScript files should be examined by using the special 'djangojs' domain only.")
+
+ make_messages(locale, domain, verbosity, process_all, extensions)
+
diff --git a/Pootle-2.0.0/local_apps/pootle_app/management/commands/refresh_stats.py b/Pootle-2.0.0/local_apps/pootle_app/management/commands/refresh_stats.py
new file mode 100644
index 0000000..a5fb536
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/management/commands/refresh_stats.py
@@ -0,0 +1,74 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+import os
+os.environ['DJANGO_SETTINGS_MODULE'] = 'pootle.settings'
+
+import logging
+from optparse import make_option
+
+from django.core.management.base import NoArgsCommand
+
+from pootle_app.models import TranslationProject
+from pootle_app import project_tree
+
+class Command(NoArgsCommand):
+ option_list = NoArgsCommand.option_list + (
+ make_option('--directory', action='store', dest='directory', default='',
+ help='directory to refresh relative to po directory'),
+ make_option('--recompute', action='store_true', dest='recompute', default=False,
+ help='Update the mtime of file, thereby forcing stats and index recomputation.'),
+ )
+ help = "Allow stats and text indices to be refreshed manually."
+
+ def handle_noargs(self, **options):
+ refresh_path = options.get('directory', '')
+ recompute = options.get('recompute', False)
+
+ # reduce size of parse pool early on
+ from pootle_store.fields import TranslationStoreFieldFile
+ TranslationStoreFieldFile._store_cache.maxsize = 2
+ TranslationStoreFieldFile._store_cache.cullsize = 2
+
+
+ for translation_project in TranslationProject.objects.filter(real_path__startswith=refresh_path):
+ if not os.path.isdir(translation_project.abs_real_path):
+ # translation project no longer exists
+ translation_project.delete()
+ continue
+
+ # rescan translation_projects
+ project_tree.scan_translation_project_files(translation_project)
+ if recompute:
+ for store in translation_project.stores.all():
+ # We force stats and indexing information to be recomputed by
+ # updating the mtimes of the files whose information we want
+ # to update.
+ logging.info("Resetting mtime for %s to now", store.real_path)
+ os.utime(store.abs_real_path, None)
+
+ # This will force the indexer of a TranslationProject to be
+ # initialized. The indexer will update the text index of the
+ # TranslationProject if it is out of date.
+ translation_project.indexer
+
+ logging.info("Updating stats for %s", translation_project.fullname)
+ translation_project.getcompletestats()
+
diff --git a/Pootle-2.0.0/local_apps/pootle_app/models/__init__.py b/Pootle-2.0.0/local_apps/pootle_app/models/__init__.py
new file mode 100644
index 0000000..f2adbca
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/models/__init__.py
@@ -0,0 +1,34 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 Zuza Software Foundation
+#
+# This file is part of translate.
+#
+# translate is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# translate is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with translate; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from pootle_app.models.language import Language
+from pootle_app.models.project import Project
+from pootle_app.models.profile import PootleProfile
+from pootle_app.models.submission import Submission
+from pootle_app.models.suggestion import Suggestion
+from pootle_app.models.directory import Directory
+from pootle_app.models.permissions import PermissionSet, PermissionSetCache
+from pootle_app.models.translation_project import TranslationProject
+
+__all__ = ["PootleProfile", "Language", "Project", "Submission", "Suggestion",
+ "Directory", "PermissionSet", "PermissionSetCache", "TranslationProject"]
+
+
diff --git a/Pootle-2.0.0/local_apps/pootle_app/models/custom_sql_util.py b/Pootle-2.0.0/local_apps/pootle_app/models/custom_sql_util.py
new file mode 100644
index 0000000..8350146
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/models/custom_sql_util.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of translate.
+#
+# translate is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# translate is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with translate; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+def table_name(table):
+ return table._meta.db_table
+
+def field_name(table, fieldname):
+ return '%s.%s' % (table_name(table), table._meta.get_field(fieldname).column)
+
+def primary_key_name(table):
+ return field_name(table, table._meta.pk.name)
diff --git a/Pootle-2.0.0/local_apps/pootle_app/models/directory.py b/Pootle-2.0.0/local_apps/pootle_app/models/directory.py
new file mode 100644
index 0000000..25f8fd1
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/models/directory.py
@@ -0,0 +1,151 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of translate.
+#
+# translate is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# translate is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with translate; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from django.db import models
+from django.db.models.signals import pre_save
+from pootle_store.util import dictsum, statssum, completestatssum
+from pootle_misc.util import getfromcache
+from pootle_misc.baseurl import l
+
+class DirectoryManager(models.Manager):
+ def get_query_set(self):
+ # ForeignKey fields with null=True are not selected by
+ # select_related unless explicitly specified
+ return super(DirectoryManager, self).get_query_set().select_related('parent')
+
+ def _get_root(self):
+ return self.get(parent=None)
+
+ root = property(_get_root)
+
+def filter_next_store(query, store_name):
+ if store_name is None:
+ return query
+ else:
+ return query.filter(name__gte=store_name)
+
+class Directory(models.Model):
+ class Meta:
+ ordering = ['name']
+ app_label = "pootle_app"
+
+
+ is_dir = True
+
+ name = models.CharField(max_length=255, null=False)
+ parent = models.ForeignKey('Directory', related_name='child_dirs', null=True, db_index=True)
+ pootle_path = models.CharField(max_length=255, null=False, db_index=True)
+
+ objects = DirectoryManager()
+
+ def get_relative(self, path):
+ """Given a path of the form a/b/c, where the path is relative
+ to this directory, recurse the path and return the object
+ (either a Directory or a Store) named 'c'.
+
+ This does not currently deal with .. path components."""
+
+ from pootle_store.models import Store
+
+ if path not in (None, ''):
+ pootle_path = '%s%s' % (self.pootle_path, path)
+ try:
+ return Directory.objects.get(pootle_path=pootle_path)
+ except Directory.DoesNotExist, e:
+ try:
+ return Store.objects.get(pootle_path=pootle_path)
+ except Store.DoesNotExist:
+ raise e
+ else:
+ return self
+
+ def filter_stores(self, search=None, starting_store=None):
+ if search is None:
+ return filter_next_store(self.child_stores, starting_store)
+ elif search.contains_only_file_specific_criteria():
+ return filter_next_store(self.child_stores, starting_store)
+ else:
+ raise Exception("Can't filter on unit-specific information")
+
+ def get_or_make_subdir(self, child_name):
+ try:
+ return self.child_dirs.get(name=child_name)
+ except Directory.DoesNotExist:
+ child_dir = Directory(name=child_name, parent=self)
+ child_dir.save()
+ return child_dir
+
+ def __unicode__(self):
+ return self.pootle_path
+
+ def get_absolute_url(self):
+ return l(self.pootle_path)
+
+ @getfromcache
+ def getquickstats(self):
+ """calculate aggregate stats for all directory based on stats
+ of all descenging stores and dirs"""
+
+ #FIXME: can we replace this with a quicker path query?
+ file_result = statssum(self.child_stores.all())
+ dir_result = statssum(self.child_dirs.all())
+ stats = dictsum(file_result, dir_result)
+ return stats
+
+ @getfromcache
+ def getcompletestats(self, checker):
+ file_result = completestatssum(self.child_stores.all(), checker)
+ dir_result = completestatssum(self.child_dirs.all(), checker)
+ stats = dictsum(file_result, dir_result)
+ return stats
+
+ def is_language(self):
+ """does this directory point at a language"""
+ return self.pootle_path.count('/') == 2
+
+ def is_translationproject(self):
+ """does this directory point at a translation project"""
+ return self.pootle_path.count('/') == 3
+
+ def get_translationproject(self):
+ """returns the translation project belonging to this directory."""
+ if self.is_language():
+ return None
+ else:
+ if self.is_translationproject():
+ return self.translationproject
+ else:
+ aux_dir = self
+ while not aux_dir.is_translationproject() and\
+ aux_dir.parent is not None:
+ aux_dir = aux_dir.parent
+ return aux_dir.translationproject
+
+
+def set_directory_pootle_path(sender, instance, **kwargs):
+ if instance.parent is not None:
+ instance.pootle_path = '%s%s/' % (instance.parent.pootle_path, instance.name)
+ else:
+ instance.pootle_path = '/'
+
+pre_save.connect(set_directory_pootle_path, sender=Directory)
+
+
diff --git a/Pootle-2.0.0/local_apps/pootle_app/models/language.py b/Pootle-2.0.0/local_apps/pootle_app/models/language.py
new file mode 100644
index 0000000..b5aa72a
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/models/language.py
@@ -0,0 +1,83 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+from django.utils.translation import ugettext_lazy as _
+from django.db import models
+from django.db.models.signals import pre_save, post_delete
+
+from pootle.i18n.gettext import tr_lang
+
+from pootle_misc.util import getfromcache
+from pootle_misc.baseurl import l
+
+from pootle_app.models.directory import Directory
+from pootle_app.lib.util import RelatedManager
+
+class Language(models.Model):
+ objects = RelatedManager()
+ class Meta:
+ app_label = "pootle_app"
+ ordering = ['code']
+
+ code_help_text = _('ISO 639 language code for the language, possibly followed by an underscore (_) and an ISO 3166 country code. <a href="http://www.w3.org/International/articles/language-tags/">More information</a>')
+ code = models.CharField(max_length=50, null=False, unique=True, db_index=True, verbose_name=_("Code"), help_text=code_help_text)
+ fullname = models.CharField(max_length=255, null=False, verbose_name=_("Full Name"))
+
+ specialchars_help_text = _('Enter any special characters that users might find difficult to type')
+ specialchars = models.CharField(max_length=255, blank=True, verbose_name=_("Special Characters"), help_text=specialchars_help_text)
+
+ plurals_help_text = _('For more information, visit <a href="http://translate.sourceforge.net/wiki/l10n/pluralforms">our wiki page</a> on plural forms.')
+ nplural_choices = ((0, _('Unknown')), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6))
+ nplurals = models.SmallIntegerField(default=0, choices=nplural_choices, verbose_name=_("Number of Plurals"), help_text=plurals_help_text)
+
+ pluralequation = models.CharField(max_length=255, blank=True, verbose_name=_("Plural Equation"), help_text=plurals_help_text)
+ directory = models.OneToOneField(Directory, db_index=True, editable=False)
+
+ pootle_path = property(lambda self: '/%s/' % self.code)
+
+ def __repr__(self):
+ return self.fullname
+
+ def __unicode__(self):
+ return self.localname()
+
+ @getfromcache
+ def getquickstats(self):
+ return self.directory.getquickstats()
+
+ def get_absolute_url(self):
+ return l(self.pootle_path)
+
+ def localname(self):
+ """localized fullname"""
+ return u"%s - %s" % (tr_lang(self.fullname), self.code)
+
+ def translated_percentage(self):
+ return int(100.0 * self.getquickstats()['translatedsourcewords'] / max(self.getquickstats()['totalsourcewords'], 1))
+
+def set_data(sender, instance, **kwargs):
+ # create corresponding directory object
+ instance.directory = Directory.objects.root.get_or_make_subdir(instance.code)
+
+pre_save.connect(set_data, sender=Language)
+
+def delete_directory(sender, instance, **kwargs):
+ instance.directory.delete()
+post_delete.connect(delete_directory, sender=Language)
diff --git a/Pootle-2.0.0/local_apps/pootle_app/models/permissions.py b/Pootle-2.0.0/local_apps/pootle_app/models/permissions.py
new file mode 100644
index 0000000..bcb3c9c
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/models/permissions.py
@@ -0,0 +1,167 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 Zuza Software Foundation
+#
+# This file is part of translate.
+#
+# translate is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# translate is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with translate; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from django.db import models
+from django.contrib.auth.models import Permission
+from django.contrib.contenttypes.models import ContentType
+from django.db.models.signals import pre_delete, post_save
+
+from pootle_app.lib.util import RelatedManager
+from pootle_app.models.directory import Directory
+
+def get_pootle_permission(codename):
+ # The content type of our permission
+ content_type = ContentType.objects.get(name='pootle', app_label='pootle_app')
+ # Get the pootle view permission
+ return Permission.objects.get(content_type=content_type, codename=codename)
+
+def get_pootle_permissions(codenames=None):
+ """gets the available rights and their localized names"""
+ # l10n: Verb
+ content_type = ContentType.objects.get(name='pootle', app_label='pootle_app')
+ if codenames is not None:
+ permissions = Permission.objects.filter(content_type=content_type, codename__in=codenames)
+ else:
+ permissions = Permission.objects.filter(content_type=content_type)
+ return dict((permission.codename, permission) for permission in permissions)
+
+def get_permission_set_by_username(username, directory):
+ try:
+ return PermissionSet.objects.get(profile__user__username=username, directory=directory)
+ except PermissionSet.DoesNotExist:
+ pass
+ try:
+ return PermissionSet.objects.get(profile__user__username='default', directory=directory)
+ except PermissionSet.DoesNotExist:
+ return None
+
+def get_matching_permission_set(profile, directory):
+ if profile.user.is_authenticated():
+ try:
+ return PermissionSet.objects.get(profile=profile, directory=directory)
+ except PermissionSet.DoesNotExist:
+ return get_permission_set_by_username('default', directory)
+ else:
+ return get_permission_set_by_username('nobody', directory)
+
+def get_matching_permissions_recurse(profile, directory):
+ """Build a (permission codename -> permission) dictionary which
+ reflects the permissions that the PootleProfile 'profile' has in
+ the directory 'directory'. This is done by taking the permissions
+ associated with 'profile' in all parent directories into account.
+
+ Recurse from 'directory' all the way up to the root directory.
+ Once we hit the root, find a PermissionSet which matches the
+ supplied PootleProfile 'profile' and Directory 'directory'. Add
+ the positive permissions from this PermissionSet to the dictionary
+ which we are building and use the negative permissions associated
+ with the root directory to remove permissions from the permissions
+ dictionary.
+
+ Once this has been done for the root directory, we recurse one
+ level up and do the same to the child directory and so on until we
+ reach the directory from which we started this process. By that
+ point we'll have a permissions dictionary reflecting the
+ permissions that 'profile' has in 'directory'."""
+ if directory.parent is not None:
+ permissions = get_matching_permissions(profile, directory.parent)
+ else:
+ permissions = {}
+
+ permission_set = get_matching_permission_set(profile, directory)
+ if permission_set is not None:
+ permissions.update((permission.codename, permission)
+ for permission in permission_set.positive_permissions.all())
+ for permission in permission_set.negative_permissions.all():
+ if permission.codename in permissions:
+ del permissions[permission.codename]
+ return permissions
+
+def get_matching_permissions(profile, directory):
+ try:
+ cached_permission_set = PermissionSetCache.objects.get(profile=profile, directory=directory)
+ return dict((permission.codename, permission) for permission in cached_permission_set.permissions.all())
+ except PermissionSetCache.DoesNotExist:
+ permissions = get_matching_permissions_recurse(profile, directory)
+ # Ensure that administrative superusers always get admin rights
+ if profile.user.is_superuser and 'administrate' not in permissions:
+ permissions['administrate'] = get_pootle_permission('administrate')
+ cached_permission_set = PermissionSetCache(profile=profile, directory=directory)
+ cached_permission_set.save()
+ cached_permission_set.permissions = permissions.values()
+ cached_permission_set.save()
+ return permissions
+
+
+def check_profile_permission(profile, permission_codename, directory):
+ """it checks if current user has the permission the perform C{permission_codename}"""
+ if profile.user.is_superuser:
+ return True
+ permissions = get_matching_permissions(profile, directory)
+ return permission_codename in permissions
+
+def check_permission(permission_codename, request):
+ """it checks if current user has the permission the perform C{permission_codename}"""
+ if request.user.is_superuser:
+ return True
+ return permission_codename in request.permissions
+
+class PermissionSet(models.Model):
+ objects = RelatedManager()
+ class Meta:
+ unique_together = ('profile', 'directory')
+ app_label = "pootle_app"
+
+ profile = models.ForeignKey('pootle_app.PootleProfile', db_index=True)
+ directory = models.ForeignKey(Directory, db_index=True, related_name='permission_sets')
+ positive_permissions = models.ManyToManyField(Permission, db_index=True, related_name='permission_sets_positive')
+ negative_permissions = models.ManyToManyField(Permission, db_index=True, related_name='permission_sets_negative')
+
+class PermissionSetCache(models.Model):
+ objects = RelatedManager()
+ class Meta:
+ unique_together = ('profile', 'directory')
+ app_label = "pootle_app"
+
+
+ profile = models.ForeignKey('pootle_app.PootleProfile', db_index=True)
+ directory = models.ForeignKey(Directory, db_index=True, related_name='permission_set_caches')
+ permissions = models.ManyToManyField(Permission, related_name='cached_permissions', db_index=True)
+
+
+def nuke_permission_set_caches(profile, directory):
+ """Delete all PermissionSetCache objects matching the current
+ profile and whose directories are subdirectories of directory."""
+ for permission_set_cache in PermissionSetCache.objects.filter(profile=profile, directory__pootle_path__startswith=directory.pootle_path):
+ permission_set_cache.delete()
+
+def void_cached_permissions(sender, instance, **kwargs):
+ if instance.profile.user.username == 'default':
+ profile_to_permission_set = dict((permission_set.profile, permission_set) for permission_set
+ in PermissionSet.objects.filter(directory=instance.directory))
+ for permission_set_cache in PermissionSetCache.objects.filter(directory=instance.directory):
+ if permission_set_cache.profile not in profile_to_permission_set:
+ nuke_permission_set_caches(permission_set_cache.profile, instance.directory)
+ else:
+ nuke_permission_set_caches(instance.profile, instance.directory)
+
+post_save.connect(void_cached_permissions, sender=PermissionSet)
+pre_delete.connect(void_cached_permissions, sender=PermissionSet)
diff --git a/Pootle-2.0.0/local_apps/pootle_app/models/profile.py b/Pootle-2.0.0/local_apps/pootle_app/models/profile.py
new file mode 100644
index 0000000..283247f
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/models/profile.py
@@ -0,0 +1,164 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 Zuza Software Foundation
+#
+# This file is part of translate.
+#
+# translate is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# translate is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with translate; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import locale
+
+from django.db import models
+from django.db.models import Q
+from django.contrib.auth.models import User, UserManager, AnonymousUser
+from django.utils.translation import ugettext_lazy as _
+from django.db.models.signals import post_save
+
+from pootle.i18n.override import lang_choices
+
+from pootle_misc.baseurl import l
+
+from pootle_app.models.language import Language
+from pootle_app.models.project import Project
+
+
+class PootleUserManager(UserManager):
+ """A manager class which is meant to replace the manager class for the User model. This manager
+ hides the 'nobody' and 'default' users for normal queries, since they are special users. Code
+ that needs access to these users should use the methods get_default_user and get_nobody_user."""
+ def get_default_user(self):
+ return super(PootleUserManager, self).get_query_set().select_related(depth=1).get(username='default')
+
+ def get_nobody_user(self):
+ return super(PootleUserManager, self).get_query_set().select_related(depth=1).get(username='nobody')
+
+ def hide_defaults(self):
+ return super(PootleUserManager, self).get_query_set().exclude(username__in=('nobody', 'default'))
+
+# Since PootleUserManager has no state, we can just replace the User manager's class with PootleUserManager
+# to get the desired functionality.
+User.objects.__class__ = PootleUserManager
+
+class PootleProfileManager(models.Manager):
+ def get_query_set(self):
+ return super(PootleProfileManager, self).get_query_set().select_related(
+ 'languages', 'projects', 'alt_src_langs')
+
+class PootleProfile(models.Model):
+ objects = PootleProfileManager()
+ class Meta:
+ app_label = "pootle_app"
+
+ # This is the only required field
+ user = models.OneToOneField(User, unique=True, db_index=True)
+
+ unit_rows = models.SmallIntegerField(default=10)
+ input_height = models.SmallIntegerField(default=5)
+ languages = models.ManyToManyField(Language, blank=True, limit_choices_to=~Q(code='templates'), related_name="user_languages", db_index=True)
+ projects = models.ManyToManyField(Project, blank=True, db_index=True)
+ ui_lang = models.CharField(max_length=50, blank=True, null=True, choices=(choice for choice in lang_choices()), verbose_name=_('Interface Language'))
+ alt_src_langs = models.ManyToManyField(Language, blank=True, db_index=True, limit_choices_to=~Q(code='templates'), related_name="user_alt_src_langs")
+
+ def __unicode__(self):
+ return self.user.username
+ def get_absolute_url(self):
+ return l('/accounts/%s/' % self.user.username)
+
+ def _get_status(self):
+ #FIXME: what's this for?
+ return "Foo"
+
+ status = property(_get_status)
+ isopen = property(lambda self: True)
+
+ def _get_pootle_user(self):
+ if self.user_id is not None:
+ return self.user
+ else:
+ return AnonymousUser()
+
+ def getuserstatistics(self):
+ """ get user statistics for user statistics links"""
+ from pootle_app.models.suggestion import _get_suggestions
+ userstatistics = []
+ userstatistics.append({'text': _('Suggestions Accepted'), 'count': _get_suggestions(self, "accepted").count()})
+ userstatistics.append({'text': _('Suggestions Pending'), 'count': _get_suggestions(self, "pending").count()})
+ userstatistics.append({'text': _('Suggestions Reviewed'), 'count': self.suggestions_reviewer_set.count()})
+ userstatistics.append({'text': _('Submissions Made'), 'count': self.submission_set.count()})
+ return userstatistics
+
+ def getquicklinks(self):
+ """gets a set of quick links to user's project-languages"""
+ from pootle_app.models.translation_project import TranslationProject
+ from pootle_app.models.permissions import check_profile_permission
+ quicklinks = []
+ # TODO: This can be done MUCH more efficiently with a bit of
+ # query forethought. Why don't we just select all the
+ # TranslationProject objects from the database which match the
+ # user's Languages and Projects? This should be efficient.
+ #
+ # But this will only work once we move TranslationProject
+ # wholly to the DB (and away from its current brain damaged
+ # half-non-db/half-db implementation).
+
+ for language in Language.objects.filter(user_languages=self):
+ langlinks = []
+ for translation_project in TranslationProject.objects.filter(
+ language=language, project__pootleprofile=self):
+ isprojectadmin = check_profile_permission(self, 'administrate',
+ translation_project.directory)
+
+ langlinks.append({
+ 'code': translation_project.project.code,
+ 'name': translation_project.project.fullname,
+ 'isprojectadmin': isprojectadmin,
+ })
+
+ islangadmin = check_profile_permission(self, 'administrate', language.directory)
+ quicklinks.append({'code': language.code,
+ 'name': language.localname(),
+ 'islangadmin': islangadmin,
+ 'projects': langlinks})
+ quicklinks.sort(cmp=locale.strcoll, key=lambda dict: dict['name'])
+ return quicklinks
+
+ pootle_user = property(_get_pootle_user)
+
+
+def create_pootle_profile(sender, instance, **kwargs):
+ """A post-save hook for the User model which ensures that it gets an
+ associated PootleProfile."""
+ try:
+ profile = instance.get_profile()
+ except PootleProfile.DoesNotExist:
+ profile = PootleProfile(user=instance)
+ profile.save()
+
+post_save.connect(create_pootle_profile, sender=User)
+
+def get_profile(user):
+ """Return the PootleProfile associated with a user.
+
+ This function is only necessary if 'user' could be an anonymous
+ user. If you know for certain that a user is logged in, then use
+ the .get_profile() method on a user instead."""
+ if user.is_authenticated():
+ # Return the PootleProfile associated with authenticated users
+ return user.get_profile()
+ else:
+ # Anonymous users get the PootleProfile associated with the 'nobody' user
+ return User.objects.get(username='nobody').get_profile()
+
diff --git a/Pootle-2.0.0/local_apps/pootle_app/models/project.py b/Pootle-2.0.0/local_apps/pootle_app/models/project.py
new file mode 100644
index 0000000..6a69f0b
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/models/project.py
@@ -0,0 +1,141 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of translate.
+#
+# translate is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# translate is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with translate; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import os
+
+from django.db.models.signals import pre_save
+from django.utils.translation import ugettext_lazy as _
+from django.db import models
+
+from translate.filters import checks
+from translate.lang.data import langcode_re
+
+from pootle_store.util import absolute_real_path, statssum
+from pootle_misc.util import getfromcache
+from pootle_misc.baseurl import l
+
+class Project(models.Model):
+ class Meta:
+ app_label = "pootle_app"
+ ordering = ['code']
+
+ code_help_text = _('A short code for the project. This should only contain ASCII characters, numbers, and the underscore (_) character.')
+ description_help_text = _('A description of this project. This is useful to give more information or instructions. This field should be valid HTML.')
+
+ checker_choices = [('standard', 'standard')]
+ checkers = list(checks.projectcheckers.keys())
+ checkers.sort()
+ checker_choices.extend([(checker, checker) for checker in checkers])
+ local_choices = (
+ ('po', _('Gettext PO')),
+ ('xlf', _('XLIFF'))
+ )
+ treestyle_choices = (
+ # TODO: check that the None is stored and handled correctly
+ ('auto', _('Automatic detection (slower)')),
+ ('gnu', _('GNU style: files named by language code')),
+ ('nongnu', _('Non-GNU: Each language in its own directory')),
+ )
+
+ code = models.CharField(max_length=255, null=False, unique=True, db_index=True, verbose_name=_('Code'), help_text=code_help_text)
+ fullname = models.CharField(max_length=255, null=False, verbose_name=_("Full Name"))
+ description = models.TextField(blank=True, help_text=description_help_text)
+ checkstyle = models.CharField(max_length=50, default='standard', null=False, choices=checker_choices, verbose_name=_('Quality Checks'))
+ localfiletype = models.CharField(max_length=50, default="po", choices=local_choices, verbose_name=_('File Type'))
+ treestyle = models.CharField(max_length=20, default='auto', choices=treestyle_choices, verbose_name=_('Project Tree Style'))
+ ignoredfiles = models.CharField(max_length=255, blank=True, null=False, default="", verbose_name=_('Ignore Files'))
+
+ def __unicode__(self):
+ return self.fullname
+
+ @getfromcache
+ def getquickstats(self):
+ return statssum(self.translationproject_set.all())
+
+ def translated_percentage(self):
+ return int(100.0 * self.getquickstats()['translatedsourcewords'] / max(self.getquickstats()['totalsourcewords'], 1))
+
+ def _get_pootle_path(self):
+ return "/projects/" + self.code + "/"
+ pootle_path = property(_get_pootle_path)
+
+ def get_real_path(self):
+ return absolute_real_path(self.code)
+
+ def get_absolute_url(self):
+ return l(self.pootle_path)
+
+ def get_template_filtetype(self):
+ if self.localfiletype == 'po':
+ return 'pot'
+ else:
+ return self.localfiletype
+
+ def file_belongs_to_project(self, filename, match_templates=True):
+ """tests if filename matches project filetype (ie. extension),
+ if match_templates is true will also check if file matches
+ template filetype"""
+
+ return filename.endswith(os.path.extsep + self.localfiletype) or \
+ match_templates and filename.endswith(os.path.extsep + self.get_template_filtetype())
+
+ def get_treestyle(self):
+ """returns the real treestyle, if treestyle is set to auto it
+ checks the project directory and tries to guess if it is gnu
+ style or nongnu style.
+
+ we are biased towards nongnu because it makes managing project
+ from the web easier"""
+
+ if self.treestyle != "auto":
+ return self.treestyle
+ else:
+ dirlisting = os.walk(self.get_real_path())
+ dirpath, dirnames, filenames = dirlisting.next()
+
+ if not dirnames:
+ # no subdirectories
+ if filter(self.file_belongs_to_project, filenames):
+ # translation files found, assume gnu
+ return "gnu"
+ else:
+ # no subdirs and no translation files, assume nongnu
+ return "nongnu"
+ else:
+ # there are subdirectories
+ if filter(lambda dirname: dirname == 'templates' or langcode_re.match(dirname), dirnames):
+ # found language dirs assume nongnu
+ return "nongnu"
+ else:
+ # no language subdirs found, look for any translation file
+ for dirpath, dirnames, filenames in os.walk(self.get_real_path()):
+ if filter(self.file_belongs_to_project, filenames):
+ return "gnu"
+ # when unsure assume nongnu
+ return "nongnu"
+
+
+def create_project_directory(sender, instance, **kwargs):
+ project_path = absolute_real_path(instance.code)
+ if not os.path.exists(project_path):
+ os.makedirs(project_path)
+
+pre_save.connect(create_project_directory, sender=Project)
diff --git a/Pootle-2.0.0/local_apps/pootle_app/models/search.py b/Pootle-2.0.0/local_apps/pootle_app/models/search.py
new file mode 100644
index 0000000..7cb7a28
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/models/search.py
@@ -0,0 +1,188 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+"""An object to represent a "search" or "navigation" over a search query, the
+results of a quality check, or similar.
+
+This is not a Django model.
+"""
+
+import bisect
+
+from translate.tools import pogrep
+
+from pootle_app.lib.util import lazy_property
+
+def member(sorted_set, element):
+ """Check whether element appears in sorted_set."""
+ pos = bisect.bisect_left(sorted_set, element)
+ if pos < len(sorted_set):
+ return sorted_set[pos] == element
+ else:
+ return False
+
+def as_seq_with_len(seq):
+ if not hasattr(seq.__class__, '__len__'):
+ return list(seq)
+ else:
+ return seq
+
+def intersect(set_a, set_b):
+ """Find the intersection of the sorted sets set_a and set_b."""
+ # If both set_a and set_b have elements
+ set_a = as_seq_with_len(set_a)
+ set_b = as_seq_with_len(set_b)
+ if len(set_b) != 0 and len(set_a) != 0:
+ # Find the position of the element in set_a that is at least
+ # as large as the minimum element in set_b.
+ start_a = bisect.bisect_left(set_a, set_b[0])
+ # For each element in set_a...
+ for element in set_a[start_a:]:
+ # ...which is also in set_b...
+ if member(set_b, element):
+ yield element
+
+
+def narrow_to_search_text(total, store, translatables, search):
+ if search.search_text not in (None, '') and search.search_results is None:
+ # We'll get here if the user is searching for a piece of text and if no indexer
+ # (such as Xapian or Lucene) is usable. First build a grepper...
+ grepfilter = pogrep.GrepFilter(search.search_text, search.search_fields, ignorecase=True)
+ # ...then filter the items using the grepper.
+ return (item for item in translatables
+ if grepfilter.filterunit(store.file.store.units[item]))
+
+ elif search.search_results is not None:
+ mapped_indices = [total[item] for item in search.search_results[store.pootle_path]]
+ return intersect(mapped_indices, translatables)
+ else:
+ return translatables
+
+
+def narrow_to_matches(stats, translatables, search):
+ if len(search.match_names) > 0:
+ matches = reduce(set.__or__,
+ (set(stats[match_name])
+ for match_name in search.match_names
+ if match_name in stats),
+ set())
+ return intersect(sorted(matches), translatables)
+ else:
+ return translatables
+
+
+def search_results_to_dict(hits):
+ result_dict = {}
+ for doc in hits:
+ filename, item = doc["pofilename"][0], int(doc["itemno"][0])
+ if filename not in result_dict:
+ result_dict[filename] = []
+ result_dict[filename].append(item)
+ for lst in result_dict.itervalues():
+ lst.sort()
+ return result_dict
+
+
+def do_search_query(indexer, search):
+ searchparts = []
+ # Split the search expression into single words. Otherwise xapian and
+ # lucene would interprete the whole string as an "OR" combination of
+ # words instead of the desired "AND".
+ for word in search.search_text.split():
+ # Generate a list for the query based on the selected fields
+ querylist = [(f, word) for f in search.search_fields]
+ textquery = indexer.make_query(querylist, False)
+ searchparts.append(textquery)
+ # TODO: add other search items
+ limitedquery = indexer.make_query(searchparts, True)
+ return indexer.search(limitedquery, ['pofilename', 'itemno'])
+
+
+class Search(object):
+ def __init__(self, match_names=[], search_text=None, search_fields=None, translation_project=None):
+ self.match_names = match_names
+ self.search_text = search_text
+ if search_fields is None:
+ search_fields = ['source', 'target']
+ self.search_fields = search_fields
+ self.translation_project = translation_project
+
+ def _get_search_results(self):
+ if self.search_text not in (None, '') and \
+ self.translation_project is not None and \
+ self.translation_project.has_index:
+ return search_results_to_dict(do_search_query(self.translation_project.indexer, self))
+ else:
+ return None
+
+ search_results = lazy_property('_search_results', _get_search_results)
+
+ def contains_only_file_specific_criteria(self):
+ return self.search_text in (None, '') and self.match_names == []
+
+ def _all_matches(self, store, last_index, range, transform):
+ if self.contains_only_file_specific_criteria():
+ # This is a special shortcut that we use when we don't
+ # need to narrow our search based on unit-specific
+ # properties. In this case, we know that last_item is the
+ # sought after item, unless of course item >= number of
+ # units
+ stats = store.getcompletestats(self.translation_project.checker)
+ if last_index < stats['total']:
+ return iter([last_index])
+ else:
+ return iter([])
+ else:
+ if self.search_results is not None and \
+ store.pootle_path not in self.search_results:
+ return iter([])
+
+ stats = store.file.getcompletestats(self.translation_project.checker)
+ total = stats['total']
+ result = total[range[0]:range[1]]
+ result = narrow_to_matches(stats, result, self)
+ result = narrow_to_search_text(total, store, result, self)
+ return (bisect.bisect_left(total, item) for item in transform(result))
+
+ def next_matches(self, store, last_index):
+ # stats['total'] is an array of indices into the units array
+ # of a store. But we want indices of the units that we see in
+ # Pootle. bisect.bisect_left of a member in stats['total']
+ # gives us the index of the unit as we see it in Pootle.
+ if last_index < 0:
+ last_index = 0
+ return self._all_matches(store, last_index, (last_index, None), lambda x: x)
+
+ def prev_matches(self, store, last_index):
+ if last_index < 0:
+ # last_index = -1 means that last_index is
+ # unspecified. Normally this means that we want to start
+ # searching at the start of stores. But in reverse
+ # iteration mode, we view len(stats['total']) as
+ # equivalent to the position of -1. This is because we
+ # consider all elements in stats['total'] in the range [0,
+ # last_index]. Thus, if we don't yet have a valid index
+ # into the file, we want to include the very last element
+ # of stats['total'] as well when searching. Thus
+ # [0:len(stats['total'])] gives us what we need.
+ stats = store.getcompletestats(self.translation_project.checker)
+ last_index = stats['total'] - 1
+ return self._all_matches(store, last_index, (0, last_index + 1), lambda x: reversed(list(x)))
+
diff --git a/Pootle-2.0.0/local_apps/pootle_app/models/signals.py b/Pootle-2.0.0/local_apps/pootle_app/models/signals.py
new file mode 100644
index 0000000..23539d5
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/models/signals.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of translate.
+#
+# translate is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# translate is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with translate; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from django.dispatch import Signal
+
+post_vc_update = Signal(providing_args=["oldstats", "remotestats", "newstats"])
+post_vc_commit = Signal(providing_args=["store", "stats", "user", "success"])
+post_template_update = Signal(providing_args=["oldstats", "newstats"])
+post_file_upload = Signal(providing_args=["user", "oldstats", "newstats", "archive"])
diff --git a/Pootle-2.0.0/local_apps/pootle_app/models/store_iteration.py b/Pootle-2.0.0/local_apps/pootle_app/models/store_iteration.py
new file mode 100644
index 0000000..de91a45
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/models/store_iteration.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2004-2006 Zuza Software Foundation
+#
+# This file is part of translate.
+#
+# translate is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# translate is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with translate; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import bisect
+
+from pootle_store.models import Store
+from pootle_app.models.search import Search
+
+def member(sorted_set, element):
+ """Check whether element appears in sorted_set."""
+ pos = bisect.bisect_left(sorted_set, element)
+ if pos < len(sorted_set):
+ return sorted_set[pos] == element
+ else:
+ return False
+
+def intersect(set_a, set_b):
+ """Find the intersection of the sorted sets set_a and set_b."""
+ # If both set_a and set_b have elements
+ if len(set_b) != 0 and len(set_a) != 0:
+ # Find the position of the element in set_a that is at least
+ # as large as the minimum element in set_b.
+ start_a = bisect.bisect_left(set_a, set_b[0])
+ # For each element in set_a...
+ for element in set_a[start_a:]:
+ # ...which is also in set_b...
+ if member(set_b, element):
+ yield element
+
+
+BLOCK_SIZE = 100
+
+
+def do_query(query, next_matches, last_index):
+ i = 0
+ # Read BLOCK_SIZE Stores from the database (query[x:y] creates a
+ # ranged database query)...
+ result = query[i:i+BLOCK_SIZE]
+ while len(result) > 0:
+ # For each of the just-read BLOCK_SIZE Stores...
+ for store in result:
+ try:
+ # See whether we have a match
+ return store, next_matches(store, last_index).next()
+ except StopIteration:
+ pass
+ # If we get here, then there were no more matches in the
+ # previous store. Thus, for the next store we need to
+ # start at the first index.
+ last_index = -1
+
+ # Now we want to move on by BLOCK_SIZE Stores to the next
+ # Stores.
+ i += BLOCK_SIZE
+ # Build a range query to get our next set of Stores
+ result = query[i:i+BLOCK_SIZE]
+ raise StopIteration()
+
+def get_next_match(path_obj, starting_store=None, last_index=-1, search=Search()):
+ if path_obj.is_dir:
+ query = Store.objects.filter(pootle_path__startswith=path_obj.pootle_path).order_by('pootle_path')
+ if starting_store is not None:
+ query = query.filter(pootle_path__gte=starting_store)
+ return do_query(query, search.next_matches, last_index)
+ else:
+ return path_obj, search.next_matches(path_obj, last_index).next()
+
+def get_prev_match(path_obj, starting_store=None, last_index=-1, search=Search()):
+ if path_obj.is_dir:
+ query = Store.objects.filter(pootle_path__startswith=path_obj.pootle_path).order_by('-pootle_path')
+ if starting_store is not None:
+ query = query.filter(pootle_path__lte=starting_store)
+ return do_query(query, search.prev_matches, last_index)
+ else:
+ return path_obj, search.next_matches(path_obj, last_index).next()
+
+
+def iter_stores(directory):
+ return Store.objects.filter(pootle_path__startswith=directory.pootle_path).order_by('pootle_path')
+
diff --git a/Pootle-2.0.0/local_apps/pootle_app/models/submission.py b/Pootle-2.0.0/local_apps/pootle_app/models/submission.py
new file mode 100644
index 0000000..7b6c825
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/models/submission.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of translate.
+#
+# translate is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# translate is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with translate; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from django.db import models
+
+from pootle_app.models import custom_sql_util
+from pootle_app.models.translation_project import TranslationProject
+from pootle_app.models.suggestion import Suggestion
+from pootle_app.models.profile import PootleProfile
+
+class SubmissionManager(models.Manager):
+ def get_top_submitters(self):
+ """Return a list of Submissions, where each Submission represents a
+ user profile (note that if a user has never made suggestions,
+ there will be no entry for that user) where the order is
+ descending in the number of suggestion contributions from the
+ users.
+
+ One would expect us to return a list of PootleProfiles instead
+ of a list of Submissions. This would be ideal, but if we are
+ to allow code to further filter the top suggestion results
+ using criteria from the Submission model, then we have to
+ return a list of Submissions.
+
+ The number of contributions from a profile is stored in the
+ attribute 'num_contribs' and the profile is stored in
+ 'submitter'.
+
+ Please note that the Submission objects returned here are
+ useless. That is, they are valid Submission objects, but for
+ a user with 10 suggestions, we'll be given one of these
+ objects, without knowing which. But we're not interested in
+ the Submission objects anyway.
+ """
+ fields = {
+ 'profile_id': custom_sql_util.primary_key_name(PootleProfile),
+ 'submitter': custom_sql_util.field_name(Submission, 'submitter'),
+ }
+ # select_related('submitter__user') will let Django also
+ # select the PootleProfile and its related User along with the
+ # Submission objects in the query. We do this, since we almost
+ # certainly want to get this information after calling
+ # get_top_submitters.
+ return self.extra(select = {'num_contribs': 'COUNT(%(profile_id)s)' % fields},
+ tables = [custom_sql_util.table_name(PootleProfile)],
+ where = ["%(profile_id)s = %(submitter)s GROUP BY %(profile_id)s" % fields]
+ ).order_by('-num_contribs').select_related('submitter__user')
+
+class Submission(models.Model):
+ class Meta:
+ app_label = "pootle_app"
+ get_latest_by = "creation_time"
+
+ creation_time = models.DateTimeField(db_index=True)
+ translation_project = models.ForeignKey(TranslationProject, db_index=True)
+ submitter = models.ForeignKey(PootleProfile, null=True, db_index=True)
+ from_suggestion = models.OneToOneField(Suggestion, null=True, db_index=True)
+
+ objects = SubmissionManager()
+
+ def __unicode__(self):
+ return u"%s (%s)" % (self.creation_time.strftime("%Y-%m-%d %H:%M"), unicode(self.submitter))
+
+
diff --git a/Pootle-2.0.0/local_apps/pootle_app/models/suggestion.py b/Pootle-2.0.0/local_apps/pootle_app/models/suggestion.py
new file mode 100644
index 0000000..0a7fa1b
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/models/suggestion.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of translate.
+#
+# translate is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# translate is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with translate; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from django.db import models
+from django.utils.translation import ugettext_lazy as _
+
+from pootle_app.models import custom_sql_util
+from pootle_app.models.profile import PootleProfile
+
+class SuggestionManager(models.Manager):
+ def _get_top_results(self, profile_field):
+ fields = {
+ 'profile_id': custom_sql_util.primary_key_name(PootleProfile),
+ 'profile_field': custom_sql_util.field_name(Suggestion, profile_field)
+ }
+ # select_related('suggester__user') will let Django also
+ # select the PootleProfile and its related User along with the
+ # Suggestion objects in the query. We do this, since we almost
+ # certainly want to get this information after calling
+ # get_top_suggesters.
+ return self.extra(select = {'num_contribs': 'COUNT(%(profile_id)s)' % fields},
+ tables = [custom_sql_util.table_name(PootleProfile)],
+ where = ["%(profile_id)s = %(profile_field)s GROUP BY %(profile_id)s" % fields]
+ ).order_by('-num_contribs')
+
+ def get_top_suggesters(self):
+ return self._get_top_results('suggester').select_related('suggester__user')
+
+ def get_top_reviewers(self):
+ return self._get_top_results('reviewer').select_related('reviewer__user')
+
+class Suggestion(models.Model):
+ class Meta:
+ app_label = "pootle_app"
+
+ state_choices = [('pending', _('Pending')),
+ ('accepted', _('Accepted')),
+ ('rejected', _('Rejected')),
+ ]
+
+ creation_time = models.DateTimeField(auto_now_add=True, db_index=True)
+ translation_project = models.ForeignKey('pootle_app.TranslationProject', db_index=True)
+ suggester = models.ForeignKey(PootleProfile, null=True, related_name='suggestions_suggester_set', db_index=True)
+ reviewer = models.ForeignKey(PootleProfile, null=True, related_name='suggestions_reviewer_set', db_index=True)
+ review_time = models.DateTimeField(null=True, db_index=True)
+ unit = models.IntegerField(null=False, db_index=True)
+ state = models.CharField(max_length=16, default='pending', null=False, choices=state_choices, db_index=True)
+
+ objects = SuggestionManager()
+
+def _get_suggestions(profile, status):
+ return Suggestion.objects.filter(suggester=profile, state=status)
+
diff --git a/Pootle-2.0.0/local_apps/pootle_app/models/translation_project.py b/Pootle-2.0.0/local_apps/pootle_app/models/translation_project.py
new file mode 100644
index 0000000..9e83d2a
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/models/translation_project.py
@@ -0,0 +1,552 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+import os
+import cStringIO
+import gettext
+import zipfile
+import logging
+
+from django.conf import settings
+from django.db import models
+from django.db.models.signals import post_init, pre_save, post_save, post_delete
+from django.core.exceptions import PermissionDenied
+from django.utils.translation import ugettext_lazy as _
+
+from translate.filters import checks
+from translate.search import match, indexing
+from translate.storage import base, versioncontrol
+
+from pootle.scripts import hooks
+from pootle_misc.util import getfromcache
+from pootle_misc.baseurl import l
+from pootle_store.models import Store
+from pootle_store.util import relative_real_path, absolute_real_path, dictsum
+
+from pootle_app.lib.util import RelatedManager
+from pootle_app.models.project import Project
+from pootle_app.models.language import Language
+from pootle_app.models.directory import Directory
+from pootle_app import project_tree
+from pootle_app.models.permissions import check_permission
+from translate.storage import statsdb
+from pootle_app.models.signals import post_vc_update, post_vc_commit
+
+
+class TranslationProjectNonDBState(object):
+ def __init__(self, parent):
+ self.parent = parent
+ # terminology matcher
+ self.termmatcher = None
+ self.termmatchermtime = None
+ self._indexing_enabled = True
+ self._index_initialized = False
+
+translation_project_non_db_state = {}
+
+def create_translation_project(language, project):
+ if project_tree.translation_project_should_exist(language, project):
+ try:
+ translation_project, created = TranslationProject.objects.get_or_create(language=language, project=project)
+ project_tree.scan_translation_project_files(translation_project)
+ return translation_project
+ except OSError:
+ return None
+ except IndexError:
+ return None
+
+def scan_translation_projects():
+ for language in Language.objects.all():
+ for project in Project.objects.all():
+ create_translation_project(language, project)
+
+class VersionControlError(Exception):
+ pass
+
+class TranslationProject(models.Model):
+ objects = RelatedManager()
+ index_directory = ".translation_index"
+ class Meta:
+ unique_together = ('language', 'project')
+ app_label = "pootle_app"
+
+ language = models.ForeignKey(Language, db_index=True)
+ project = models.ForeignKey(Project, db_index=True)
+ real_path = models.FilePathField(editable=False)
+ directory = models.OneToOneField(Directory, db_index=True, editable=False)
+ pootle_path = models.CharField(max_length=255, null=False, unique=True, db_index=True, editable=False)
+
+ def __unicode__(self):
+ return self.pootle_path
+
+ def get_absolute_url(self):
+ return l(self.pootle_path)
+
+ fullname = property(lambda self: "%s [%s]" % (self.project.fullname, self.language.fullname))
+ def _get_abs_real_path(self):
+ return absolute_real_path(self.real_path)
+
+ def _set_abs_real_path(self, value):
+ self.real_path = relative_real_path(value)
+
+ abs_real_path = property(_get_abs_real_path, _set_abs_real_path)
+
+ def _get_treestyle(self):
+ return self.project.get_treestyle()
+
+ file_style = property(_get_treestyle)
+
+ def _get_checker(self):
+ checkerclasses = [checks.projectcheckers.get(self.project.checkstyle,
+ checks.StandardChecker),
+ checks.StandardUnitChecker]
+ return checks.TeeChecker(checkerclasses=checkerclasses,
+ errorhandler=self.filtererrorhandler,
+ languagecode=self.language.code)
+
+ checker = property(_get_checker)
+
+ def filtererrorhandler(self, functionname, str1, str2, e):
+ logging.error("error in filter %s: %r, %r, %s", functionname, str1, str2, e)
+ return False
+
+ def _get_non_db_state(self):
+ if self.id not in translation_project_non_db_state:
+ translation_project_non_db_state[self.id] = TranslationProjectNonDBState(self)
+ return translation_project_non_db_state[self.id]
+
+ non_db_state = property(_get_non_db_state)
+
+ @getfromcache
+ def getquickstats(self):
+ if not self.is_template_project:
+ return self.directory.getquickstats()
+ else:
+ #FIXME: Hackish return empty_stats to avoid messing up
+ # with project and language stats
+ empty_stats = {'fuzzy': 0,
+ 'fuzzysourcewords': 0,
+ 'review': 0,
+ 'total': 0,
+ 'totalsourcewords': 0,
+ 'translated': 0,
+ 'translatedsourcewords': 0,
+ 'translatedtargetwords': 0,
+ 'untranslated': 0,
+ 'untranslatedsourcewords': 0}
+ return empty_stats
+
+ @getfromcache
+ def getcompletestats(self):
+ if not self.is_template_project:
+ return self.directory.getcompletestats(self.checker)
+ else:
+ #FIXME: Hackish return empty_stats to avoid messing up
+ # with project and language stats
+ empty_stats = {'fuzzy': 0,
+ 'fuzzysourcewords': 0,
+ 'review': 0,
+ 'total': 0,
+ 'totalsourcewords': 0,
+ 'translated': 0,
+ 'translatedsourcewords': 0,
+ 'translatedtargetwords': 0,
+ 'untranslated': 0,
+ 'untranslatedsourcewords': 0}
+ return empty_stats
+
+ def _get_indexer(self):
+ if self.non_db_state._indexing_enabled:
+ try:
+ indexer = self.make_indexer()
+ if not self.non_db_state._index_initialized:
+ self.init_index(indexer)
+ self.non_db_state._index_initialized = True
+ return indexer
+ except Exception, e:
+ logging.warning("Could not initialize indexer for %s in %s: %s", self.project.code, self.language.code, str(e))
+ self.non_db_state._indexing_enabled = False
+ return None
+ else:
+ return None
+
+ indexer = property(_get_indexer)
+
+ def _has_index(self):
+ return self.non_db_state._indexing_enabled and \
+ (self.non_db_state._index_initialized or self.indexer != None)
+
+ has_index = property(_has_index)
+
+ def update_file_from_version_control(self, store):
+ try:
+ hooks.hook(self.project.code, "preupdate", store.file.path)
+ except:
+ pass
+
+ # keep a copy of working files in memory before updating
+ oldstats = store.getquickstats()
+ working_copy = store.file.store
+
+ try:
+ logging.debug("updating %s from version control", store.file.path)
+ versioncontrol.updatefile(store.file.path)
+ store.file._delete_store_cache()
+ remotestats = store.getquickstats()
+ except Exception, e:
+ #something wrong, file potentially modified, bail out
+ #and replace with working copy
+ logging.error("near fatal catastrophe, exception %s while updating %s from version control", e, store.file.path)
+ working_copy.save()
+ raise VersionControlError
+
+ #FIXME: try to avoid merging if file was not updated
+ logging.debug("merging %s with version control update", store.file.path)
+ store.mergefile(working_copy, "versionmerge", allownewstrings=False, suggestions=False, notranslate=False, obsoletemissing=False)
+
+ try:
+ hooks.hook(self.project.code, "postupdate", store.file.path)
+ except:
+ pass
+
+ newstats = store.getquickstats()
+ return oldstats, remotestats, newstats
+
+ def update_project(self, request):
+ """updates project translation files from version control,
+ retaining uncommitted translations"""
+
+ if not check_permission("commit", request):
+ raise PermissionDenied(_("You do not have rights to update from version control here"))
+
+ old_stats = self.getquickstats()
+ remote_stats = {}
+
+ stores = self.stores.all()
+ for store in stores:
+ try:
+ oldstats, remotestats, newstats = self.update_file_from_version_control(store)
+ remote_stats = dictsum(remote_stats, remotestats)
+ except VersionControlError:
+ pass
+
+ project_tree.scan_translation_project_files(self)
+ new_stats = self.getquickstats()
+
+ request.user.message_set.create(message=unicode(_("Updated %s files from version control", self.fullname)))
+ request.user.message_set.create(message=stats_message("working copy", old_stats))
+ request.user.message_set.create(message=stats_message("remote copy", remote_stats))
+ request.user.message_set.create(message=stats_message("merged copy", new_stats))
+
+ post_vc_update.send(sender=self, oldstats=old_stats, remotestats=remote_stats, newstats=new_stats)
+
+ def update_file(self, request, store):
+ """updates file from version control, retaining uncommitted
+ translations"""
+ if not check_permission("commit", request):
+ raise PermissionDenied(_("You do not have rights to update from version control here"))
+
+ try:
+ oldstats, remotestats, newstats = self.update_file_from_version_control(store)
+ except VersionControlError:
+ request.user.message_set.create(message=unicode(_("Failed to update %s from version control", store.file.name)))
+
+ request.user.message_set.create(message=unicode(_("Updated file %s from version control", store.file.name)))
+ request.user.message_set.create(message=stats_message("working copy", oldstats))
+ request.user.message_set.create(message=stats_message("remote copy", remotestats))
+ request.user.message_set.create(message=stats_message("merged copy", newstats))
+
+ project_tree.scan_translation_project_files(self)
+ post_vc_update.send(sender=self, oldstats=oldstats, remotestats=remotestats, newstats=newstats)
+
+ def commitpofile(self, request, store):
+ """commits an individual PO file to version control"""
+ if not check_permission("commit", request):
+ raise PermissionDenied(_("You do not have rights to commit files here"))
+
+ stats = store.file.getquickstats()
+ author = request.user.username
+ message = stats_message("Commit from %s by user %s." % (settings.TITLE, author), stats)
+
+ try:
+ filestocommit = hooks.hook(self.project.code, "precommit", store.file.path, author=author, message=message)
+ except ImportError:
+ # Failed to import the hook - we're going to assume there just isn't a hook to
+ # import. That means we'll commit the original file.
+ filestocommit = [store.file.path]
+
+ success = True
+ try:
+ for file in filestocommit:
+ versioncontrol.commitfile(file, message=message, author=author)
+ request.user.message_set.create(message="Committed file: <em>%s</em>" % file)
+ except Exception, e:
+ logging.error("Failed to commit files: %s", e)
+ request.user.message_set.create(message="Failed to commit file: %s" % e)
+ success = False
+ try:
+ hooks.hook(self.project.code, "postcommit", store.file.path, success=success)
+ except:
+ #FIXME: We should not hide the exception - makes development impossible
+ pass
+ post_vc_commit.send(sender=self, store=store, stats=stats, user=request.user, success=success)
+ return success
+
+
+ def initialize(self):
+ try:
+ hooks.hook(self.project.code, "initialize", self.real_path, self.language.code)
+ except Exception, e:
+ logging.error("Failed to initialize (%s): %s", self.language.code, e)
+
+ ##############################################################################################
+
+ def get_archive(self, stores):
+ """returns an archive of the given filenames"""
+ tempzipfile = None
+ try:
+ # using zip command line is fast
+ from tempfile import mkstemp
+ # The temporary file below is opened and immediately closed for security reasons
+ fd, tempzipfile = mkstemp(prefix='pootle', suffix='.zip')
+ os.close(fd)
+ os.system("cd %s ; zip -r - %s > %s" % (self.abs_real_path, " ".join(store.abs_real_path[len(self.abs_real_path)+1:] for store in stores), tempzipfile))
+ filedata = open(tempzipfile, "r").read()
+ if filedata:
+ return filedata
+ finally:
+ if tempzipfile is not None and os.path.exists(tempzipfile):
+ os.remove(tempzipfile)
+
+ # but if it doesn't work, we can do it from python
+ archivecontents = cStringIO.StringIO()
+ archive = zipfile.ZipFile(archivecontents, 'w', zipfile.ZIP_DEFLATED)
+ for store in stores:
+ archive.write(store.abs_real_path.encode('utf-8'), store.abs_real_path[len(self.abs_real_path)+1:].encode('utf-8'))
+ archive.close()
+ return archivecontents.getvalue()
+
+
+ ##############################################################################################
+
+ def make_indexer(self):
+ """get an indexing object for this project
+
+ Since we do not want to keep the indexing databases open for the lifetime of
+ the TranslationProject (it is cached!), it may NOT be part of the Project object,
+ but should be used via a short living local variable.
+ """
+ indexdir = os.path.join(self.abs_real_path, self.index_directory)
+ index = indexing.get_indexer(indexdir)
+ index.set_field_analyzers({
+ "pofilename": index.ANALYZER_EXACT,
+ "itemno": index.ANALYZER_EXACT,
+ "pomtime": index.ANALYZER_EXACT})
+ return index
+
+ def init_index(self, indexer):
+ """initializes the search index"""
+ for store in Store.objects.filter(pootle_path__startswith=self.pootle_path):
+ self.update_index(indexer, store, optimize=False)
+
+
+ def update_index(self, indexer, store, items=None, optimize=True):
+ """updates the index with the contents of pofilename (limit to items if given)
+
+ There are three reasons for calling this function:
+ 1. creating a new instance of L{TranslationProject} (see L{initindex})
+ -> check if the index is up-to-date / rebuild the index if necessary
+ 2. translating a unit via the web interface
+ -> (re)index only the specified unit(s)
+
+ The argument L{items} should be None for 1.
+
+ known problems:
+ 1. This function should get called, when the po file changes externally.
+ WARNING: You have to stop the pootle server before manually changing
+ po files, if you want to keep the index database in sync.
+
+ @param items: list of unit numbers within the po file OR None (=rebuild all)
+ @type items: [int]
+ @param optimize: should the indexing database be optimized afterwards
+ @type optimize: bool
+ """
+ #FIXME: leverage file updated signal to check if index needs updating
+ if indexer == None:
+ return False
+ # check if the pomtime in the index == the latest pomtime
+ try:
+ pomtime = str(hash(statsdb.get_mod_info(store.file.path))**2)
+ pofilenamequery = indexer.make_query([("pofilename", store.pootle_path)], True)
+ pomtimequery = indexer.make_query([("pomtime", pomtime)], True)
+ gooditemsquery = indexer.make_query([pofilenamequery, pomtimequery], True)
+ gooditemsnum = indexer.get_query_result(gooditemsquery).get_matches_count()
+ # if there is at least one up-to-date indexing item, then the po file
+ # was not changed externally -> no need to update the database
+ if (gooditemsnum > 0) and (not items):
+ # nothing to be done
+ return
+ elif items:
+ # Update only specific items - usually single translation via the web
+ # interface. All other items should still be up-to-date (even with an
+ # older pomtime).
+ # delete the relevant items from the database
+ itemsquery = indexer.make_query([("itemno", str(itemno)) for itemno in items], False)
+ indexer.delete_doc([pofilenamequery, itemsquery])
+ else:
+ # (items is None)
+ # The po file is not indexed - or it was changed externally
+ # delete all items of this file
+ indexer.delete_doc({"pofilename": store.pootle_path})
+ if items is None:
+ # rebuild the whole index
+ items = range(store.file.getitemslen())
+ addlist = []
+ for itemno in items:
+ unit = store.file.getitem(itemno)
+ doc = {"pofilename": store.pootle_path, "pomtime": str(pomtime), "itemno": str(itemno)}
+ if unit.hasplural():
+ orig = "\n".join(unit.source.strings)
+ trans = "\n".join(unit.target.strings)
+ else:
+ orig = unit.source
+ trans = unit.target
+ doc["source"] = orig
+ doc["target"] = trans
+ doc["notes"] = unit.getnotes()
+ doc["locations"] = unit.getlocations()
+ addlist.append(doc)
+ if addlist:
+ indexer.begin_transaction()
+ try:
+ for add_item in addlist:
+ indexer.index_document(add_item)
+ finally:
+ indexer.commit_transaction()
+ indexer.flush(optimize=optimize)
+ except (base.ParseError, IOError, OSError):
+ indexer.delete_doc({"pofilename": store.pootle_path})
+ logging.error("Not indexing %s, since it is corrupt", store.pootle_path)
+
+ ##############################################################################################
+
+ is_terminology_project = property(lambda self: self.project.code == "terminology")
+ is_template_project = property(lambda self: self.language.code == 'templates')
+
+ stores = property(lambda self: Store.objects.filter(pootle_path__startswith=self.pootle_path))
+
+ def gettermbase(self, make_matcher):
+ """returns this project's terminology store"""
+ if self.is_terminology_project:
+ query = self.stores
+ if query.count() > 0:
+ return make_matcher(self)
+ else:
+ termfilename = "pootle-terminology." + self.project.localfiletype
+ try:
+ store = Store.objects.get(pootle_path=termfilename)
+ return make_matcher(store)
+ except Store.DoesNotExist:
+ pass
+ raise StopIteration()
+
+ def gettermmatcher(self):
+ """returns the terminology matcher"""
+ def make_matcher(termbase):
+ newmtime = termbase.pomtime
+ if newmtime != self.non_db_state.termmatchermtime:
+ if self.is_terminology_project:
+ return match.terminologymatcher([store.file.store for store in self.stores.all()]), newmtime
+ else:
+ return match.terminologymatcher(termbase), newmtime
+
+ if self.non_db_state.termmatcher is None:
+ try:
+ self.non_db_state.termmatcher, self.non_db_state.termmatchermtime = self.gettermbase(make_matcher)
+ except StopIteration:
+ if not self.is_terminology_project:
+ try:
+ termproject = TranslationProject.objects.get(language=self.language_id, project__code='terminology')
+ self.non_db_state.termmatcher = termproject.gettermmatcher()
+ self.non_db_state.termmatchermtime = termproject.non_db_state.termmatchermtime
+ except TranslationProject.DoesNotExist:
+ pass
+ return self.non_db_state.termmatcher
+
+ ##############################################################################################
+
+ #FIXME: we should cache results to ease live translation
+ def translate_message(self, singular, plural=None, n=1):
+ for store in self.stores:
+ store.file.store.require_index()
+ unit = store.file.store.findunit(singular)
+ if unit is not None and unit.istranslated():
+ if unit.hasplural() and n != 1:
+ nplural, pluralequation = store.file.store.getheaderplural()
+ if pluralequation:
+ pluralfn = gettext.c2py(pluralequation)
+ target = unit.target.strings[pluralfn(n)]
+ if target is not None:
+ return target
+ else:
+ return unit.target
+
+ # no translation found
+ if n != 1 and plural is not None:
+ return plural
+ else:
+ return singular
+
+
+def stats_message(version, stats):
+ return "%s: %d of %d messages translated (%d fuzzy)." % \
+ (version, stats.get("translated", 0), stats.get("total", 0), stats.get("fuzzy", 0))
+
+def set_data(sender, instance, **kwargs):
+ project_dir = instance.project.get_real_path()
+ ext = project_tree.get_extension(instance.language, instance.project)
+ instance.abs_real_path = project_tree.get_translation_project_dir(instance.language, project_dir, instance.file_style, make_dirs=True)
+ instance.directory = Directory.objects.root\
+ .get_or_make_subdir(instance.language.code)\
+ .get_or_make_subdir(instance.project.code)
+ instance.pootle_path = instance.directory.pootle_path
+
+pre_save.connect(set_data, sender=TranslationProject)
+
+def delete_directory(sender, instance, **kwargs):
+ instance.directory.delete()
+post_delete.connect(delete_directory, sender=TranslationProject)
+
+def add_pomtime(sender, instance, **kwargs):
+ instance.pomtime = 0
+
+post_init.connect(add_pomtime, sender=TranslationProject)
+
+def scan_languages(sender, instance, **kwargs):
+ for language in Language.objects.all():
+ create_translation_project(language, instance)
+
+post_save.connect(scan_languages, sender=Project)
+
+def scan_projects(sender, instance, **kwargs):
+ for project in Project.objects.all():
+ create_translation_project(instance, project)
+
+post_save.connect(scan_projects, sender=Language)
diff --git a/Pootle-2.0.0/local_apps/pootle_app/project_tree.py b/Pootle-2.0.0/local_apps/pootle_app/project_tree.py
new file mode 100644
index 0000000..f529766
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/project_tree.py
@@ -0,0 +1,271 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+import os
+import cStringIO
+
+from translate.lang import data as langdata
+from translate.convert import pot2po
+
+from pootle_misc.util import deletefromcache
+from pootle_store.models import Store
+from pootle_store.util import absolute_real_path, relative_real_path
+from pootle_app.models.directory import Directory
+from pootle_app.models.signals import post_template_update
+
+def language_match_filename(language_code, path_name):
+ name, ext = os.path.splitext(os.path.basename(path_name))
+ return langdata.languagematch(language_code, name)
+
+def direct_language_match_filename(language_code, path_name):
+ name, ext = os.path.splitext(os.path.basename(path_name))
+ return language_code == name
+
+def match_template_filename(project, path_name):
+ """test if path_name might point at a template file for given
+ project"""
+ name, ext = os.path.splitext(os.path.basename(path_name))
+ #FIXME: is the test for matching extension redundant?
+ if ext == os.path.extsep + project.get_template_filtetype():
+ if ext != os.path.extsep + project.localfiletype:
+ # template extension is distinct, surely file is a template
+ return True
+ elif not langdata.langcode_re.match(name):
+ # file name can't possibly match any language, assume it is a template
+ return True
+ return False
+
+def get_matching_language_dirs(project_dir, language):
+ return [lang_dir for lang_dir in os.listdir(project_dir)
+ if language.code == lang_dir]
+
+def get_non_existant_language_dir(project_dir, language, file_style, make_dirs):
+ if file_style == "gnu":
+ return project_dir
+ else:
+ if make_dirs:
+ language_dir = os.path.join(project_dir, language.code)
+ os.mkdir(language_dir)
+ return language_dir
+ else:
+ raise IndexError("directory not found for language %s, project %s" % (language.code, project_dir))
+
+def get_or_make_language_dir(project_dir, language, file_style, make_dirs):
+ matching_language_dirs = get_matching_language_dirs(project_dir, language)
+ if len(matching_language_dirs) == 0:
+ # if no matching directories can be found, check if it is a GNU-style project
+ return get_non_existant_language_dir(project_dir, language, file_style, make_dirs)
+ else:
+ return os.path.join(project_dir, matching_language_dirs[0])
+
+def get_language_dir(project_dir, language, file_style, make_dirs):
+ language_dir = os.path.join(project_dir, language.code)
+ if not os.path.exists(language_dir):
+ return get_or_make_language_dir(project_dir, language, file_style, make_dirs)
+ else:
+ return language_dir
+
+
+def get_translation_project_dir(language, project_dir, file_style, make_dirs=False):
+ """returns the base directory containing po files for the project
+
+ If make_dirs is True, then we will create project and language
+ directories as necessary.
+ """
+ if file_style == 'gnu':
+ return project_dir
+ else:
+ return get_language_dir(project_dir, language, file_style, make_dirs)
+
+def is_hidden_file(path):
+ return path[0] == '.'
+
+def split_files_and_dirs(ignored_files, ext, real_dir, file_filter):
+ files = []
+ dirs = []
+ for child_path in [child_path for child_path in os.listdir(real_dir)
+ if child_path not in ignored_files and not is_hidden_file(child_path)]:
+ full_child_path = os.path.join(real_dir, child_path)
+ if os.path.isfile(full_child_path) and full_child_path.endswith(ext) and file_filter(full_child_path):
+ files.append(child_path)
+ elif os.path.isdir(full_child_path):
+ dirs.append(child_path)
+ return files, dirs
+
+def add_items(fs_items, db_items, create_db_item):
+ items = []
+ fs_items_set = set(fs_items)
+ db_items_set = set(db_items)
+
+ items_to_delete = db_items_set - fs_items_set
+ items_to_create = fs_items_set - db_items_set
+
+ for name in items_to_delete:
+ db_items[name].delete()
+
+ for name in db_items_set - items_to_delete:
+ items.append(db_items[name])
+
+ for name in items_to_create:
+ item = create_db_item(name)
+ items.append(item)
+ item.save()
+
+ return items
+
+def add_files(ignored_files, ext, real_dir, db_dir, file_filter=lambda _x: True):
+ files, dirs = split_files_and_dirs(ignored_files, ext, real_dir, file_filter)
+ existing_stores = dict((store.name, store) for store in db_dir.child_stores.all())
+ existing_dirs = dict((dir.name, dir) for dir in db_dir.child_dirs.all())
+ add_items(files, existing_stores,
+ lambda name: Store(file = relative_real_path(os.path.join(real_dir, name)),
+ parent = db_dir,
+ name = name))
+
+ db_subdirs = add_items(dirs, existing_dirs,
+ lambda name: Directory(name=name, parent=db_dir))
+
+ for db_subdir in db_subdirs:
+ fs_subdir = os.path.join(real_dir, db_subdir.name)
+ add_files(ignored_files, ext, fs_subdir, db_subdir, file_filter)
+
+
+def translation_project_should_exist(language, project):
+ """tests if there are translation files corresponding to given
+ language and project"""
+ if project.get_treestyle() == "gnu":
+ # GNU style projects are tricky
+
+ if language.code == 'templates':
+ # language is template look for template files
+ for dirpath, dirnames, filenames in os.walk(project.get_real_path()):
+ for filename in filenames:
+ if filename.endswith(os.path.extsep + project.get_template_filtetype()):
+ if project.get_template_filtetype() != project.localfiletype:
+ # templates and translation files have a
+ # different extension, easy to detect
+ # templates
+ return True
+ elif not langdata.langcode_re.match(os.path.splitext(filename)[0]):
+ # can't tell templates by their extension,
+ # assume any translation file that can't
+ # be a language name is a template
+ return True
+ else:
+ # find files with the language name in the project dir
+ for dirpath, dirnames, filenames in os.walk(project.get_real_path()):
+ for filename in filenames:
+ if project.file_belongs_to_project(filename, match_templates=False) and \
+ os.path.splitext(filename)[0] == language.code:
+ return True
+ else:
+ # find directory with the language name in the project dir
+ dirpath, dirnames, filename = os.walk(project.get_real_path()).next()
+ if language.code in dirnames:
+ return True
+
+ return False
+
+def scan_translation_project_files(translation_project):
+ """returns a list of po files for the project and language"""
+ project = translation_project.project
+ real_path = translation_project.abs_real_path
+ directory = translation_project.directory
+ ignored_files = set(p.strip() for p in project.ignoredfiles.split(','))
+ ext = os.extsep + project.localfiletype
+
+ # scan for pots if template project
+ if translation_project.is_template_project:
+ ext = os.extsep + project.get_template_filtetype()
+
+ if translation_project.file_style == 'gnu':
+ if translation_project.is_template_project:
+ add_files(ignored_files, ext, real_path, directory,
+ lambda filename: match_template_filename(project, filename))
+ else:
+ add_files(ignored_files, ext, real_path, directory,
+ lambda filename: direct_language_match_filename(translation_project.language.code, filename))
+ else:
+ add_files(ignored_files, ext, real_path, directory)
+ for store in translation_project.stores.all():
+ store.file._delete_store_cache()
+
+
+def get_extension(language, project):
+ """file extension used for this project, returns pot if it's a po
+ project and language is templates"""
+ ext = project.localfiletype
+ if language.code == 'templates' and ext == 'po':
+ return 'pot'
+ else:
+ return ext
+
+def ensure_target_dir_exists(target_path):
+ target_dir = os.path.dirname(target_path)
+ if not os.path.exists(target_dir):
+ os.makedirs(target_dir)
+
+def read_original_target(target_path):
+ try:
+ return open(target_path, "rb")
+ except:
+ return None
+
+def convert_template(template_path, target_path):
+ ensure_target_dir_exists(target_path)
+ template_file = open(template_path, "rb")
+ target_file = cStringIO.StringIO()
+ original_file = read_original_target(target_path)
+ pot2po.convertpot(template_file, target_file, original_file)
+ try:
+ output_file = open(target_path, "wb")
+ output_file.write(target_file.getvalue())
+ finally:
+ output_file.close()
+
+def get_translated_name_gnu(translation_project, store):
+ path_parts = store.file.path.split(os.sep)
+ path_parts[-1] = "%s.%s" % (translation_project.language.code,
+ translation_project.project.localfiletype)
+ return os.sep.join(path_parts)
+
+def get_translated_name(translation_project, store):
+ name, ext = os.path.splitext(store.name)
+ path_parts = store.file.name.split(os.sep)
+
+ # replace language code
+ path_parts[1] = translation_project.language.code
+ # replace extension
+ path_parts[-1] = name + '.' + translation_project.project.localfiletype
+
+ return absolute_real_path(os.sep.join(path_parts))
+
+def convert_templates(template_translation_project, translation_project):
+ oldstats = translation_project.getquickstats()
+ for store in template_translation_project.stores.all():
+ if translation_project.file_style == 'gnu':
+ new_store_path = get_translated_name_gnu(translation_project, store)
+ else:
+ new_store_path = get_translated_name(translation_project, store)
+ convert_template(store.file.path, new_store_path)
+ scan_translation_project_files(translation_project)
+ newstats = translation_project.getquickstats()
+ post_template_update.send(sender=translation_project, oldstats=oldstats, newstats=newstats)
+
diff --git a/Pootle-2.0.0/local_apps/pootle_app/templates/admin/admin_general.html b/Pootle-2.0.0/local_apps/pootle_app/templates/admin/admin_general.html
new file mode 100644
index 0000000..e6532b8
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/templates/admin/admin_general.html
@@ -0,0 +1,16 @@
+{% load i18n render_pager %}
+<form action="" method="post" name="{{ text.formid }}" id="{{ text.formid }}">
+ {% if text.error_msg %}
+ <div id="site-message">
+ <div class="error">{% trans text.error_msg %}</div>
+ </div>
+ {% endif %}
+ {{ formset.management_form }}
+ <table cellpadding="1" cellspacing="1" class="sortable">
+ {{ formset_text }}
+ </table>
+ <p>
+ <input type="submit" name="{{ text.submitname }}" value="{{ text.savechanges }}" />
+ </p>
+</form>
+{{ objects|render_pager }}
diff --git a/Pootle-2.0.0/local_apps/pootle_app/templates/admin/admin_general_languages.html b/Pootle-2.0.0/local_apps/pootle_app/templates/admin/admin_general_languages.html
new file mode 100644
index 0000000..dbb4620
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/templates/admin/admin_general_languages.html
@@ -0,0 +1,22 @@
+{% extends "admin_profile_base.html" %}
+{% load i18n baseurl %}
+
+{% block title %}
+{{ block.super }} » {% trans "Languages" %}
+{% endblock %}
+
+{% block bodyclass %}adminlanguages{% endblock %}
+
+{% block breadcrumb_content %}
+{{ block.super }} » {% trans "Languages" %}
+{% endblock %}
+
+{% block section_description %}{% trans "Here you can add, edit, and delete all the available languages in this Pootle server." %}{% endblock %}
+
+{% block section_body %}
+{% include "admin/admin_general.html" %}
+{% endblock %}
+
+{% block scripts_extra %}
+<script type="text/javascript" src='{{ "js/sorttable.js"|m }}'></script>
+{% endblock %}
diff --git a/Pootle-2.0.0/local_apps/pootle_app/templates/admin/admin_general_permissions.html b/Pootle-2.0.0/local_apps/pootle_app/templates/admin/admin_general_permissions.html
new file mode 100644
index 0000000..ae9c901
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/templates/admin/admin_general_permissions.html
@@ -0,0 +1,31 @@
+{% extends "admin_profile_base.html" %}
+{% load i18n baseurl %}
+
+{% block title %}
+{{ block.super }} » {% trans "Permissions" %}
+{% endblock %}
+
+{% block bodyclass %}adminpermissions{% endblock %}
+
+{% block breadcrumb_content %}
+{{ block.super }} » {% trans "Permissions" %}
+{% endblock %}
+
+{% block section_description %}{% trans "Here you can add, edit, and delete the default permissions in this Pootle server." %}{% endblock %}
+
+{% block section_body %}
+<form action="" method="post" name="permissionsform" id="permissionsform">
+ {{ permission_set_formset.management_form }}
+ <table cellpadding="1" cellspacing="1">
+ <tr>
+ <th>{% trans "Username" %}</th><th>{% trans "Permissions" %}</th><th>{% trans "Delete" %}</th>
+ </tr>
+ {% for form in permission_set_formset.forms %}
+ {{ form.as_table }}
+ {% endfor %}
+ </table>
+ <p>
+ <input type="submit" name="doupdaterights" value="{% trans 'Update Rights' %}" />
+ </p>
+</form>
+{% endblock %}
diff --git a/Pootle-2.0.0/local_apps/pootle_app/templates/admin/admin_general_projects.html b/Pootle-2.0.0/local_apps/pootle_app/templates/admin/admin_general_projects.html
new file mode 100644
index 0000000..ef4d8cd
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/templates/admin/admin_general_projects.html
@@ -0,0 +1,22 @@
+{% extends "admin_profile_base.html" %}
+{% load i18n baseurl %}
+
+{% block title %}
+{{ block.super }} » {% trans "Projects" %}
+{% endblock %}
+
+{% block bodyclass %}adminprojects{% endblock %}
+
+{% block breadcrumb_content %}
+{{ block.super }} » {% trans "Projects" %}
+{% endblock %}
+
+{% block section_description %}{% trans "Here you can add, edit, and delete all the available projects in this Pootle server." %}{% endblock %}
+
+{% block section_body %}
+{% include "admin/admin_general.html" %}
+{% endblock %}
+
+{% block scripts_extra %}
+<script type="text/javascript" src='{{ "js/sorttable.js"|m }}'></script>
+{% endblock %}
diff --git a/Pootle-2.0.0/local_apps/pootle_app/templates/admin/admin_general_settings.html b/Pootle-2.0.0/local_apps/pootle_app/templates/admin/admin_general_settings.html
new file mode 100644
index 0000000..2fc4aed
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/templates/admin/admin_general_settings.html
@@ -0,0 +1,56 @@
+{% extends "admin_profile_base.html" %}
+{% load i18n baseurl %}
+
+{% block title %}
+{{ block.super }} » {% trans "General Settings" %}
+{% endblock %}
+
+{% block bodyclass %}admingeneral{% endblock %}
+
+{% block breadcrumb_content %}
+{{ block.super }} » {% trans "General Settings" %}
+{% endblock %}
+
+{% block section_description %}{% trans "Configure here the general settings for your site." %}{% endblock %}
+
+{% block section_body %}
+<div id="settings">
+<form action="" method="post">
+ {% for field in form %}
+ <p>
+ {{ field.label_tag }}
+ {{ field }}
+ {% if field.help_text %}
+ <span class="help_text">{{ field.help_text }}</span>
+ {% endif %}
+ </p>
+ {% endfor %}
+ <input type="submit" value='{% trans "Save" %}' />
+</form>
+</div>
+<div id="depchecks">
+ <h2>{% trans "Dependency Checks" %}</h2>
+ <ul id="required">
+ {% for dep in required %}
+ <li class="{{ dep.state }} {{ dep.dependency }}">{{ dep.text }}</li>
+ {% endfor %}
+ </ul>
+ {% if optional %}
+ <h3>{% trans "Optional" %}</h3>
+ <ul id="optional">
+ {% for dep in optional %}
+ <li class="warning {{ dep.dependency }}">{{ dep.text }}</li>
+ {% endfor %}
+ </ul>
+ {% endif %}
+ {% if optimal %}
+ <h3>{% trans "Optimizations" %}</h3>
+ <ul id="optimal">
+ {% for dep in optimal %}
+ <li class="warning {{ dep.dependency }}">{{ dep.text }}</li>
+ {% endfor %}
+ </ul>
+ {% endif %}
+</div>
+{% endblock %}
+
diff --git a/Pootle-2.0.0/local_apps/pootle_app/templates/admin/admin_general_users.html b/Pootle-2.0.0/local_apps/pootle_app/templates/admin/admin_general_users.html
new file mode 100644
index 0000000..ef84f9e
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/templates/admin/admin_general_users.html
@@ -0,0 +1,22 @@
+{% extends "admin_profile_base.html" %}
+{% load i18n baseurl %}
+
+{% block title %}
+{{ block.super }} » {% trans "Users" %}
+{% endblock %}
+
+{% block bodyclass %}adminusers{% endblock %}
+
+{% block breadcrumb_content %}
+{{ block.super }} » {% trans "Users" %}
+{% endblock %}
+
+{% block section_description %}{% trans "Here you can add, edit, and delete all the available users in this Pootle server." %}{% endblock %}
+
+{% block section_body %}
+{% include "admin/admin_general.html" %}
+{% endblock %}
+
+{% block scripts_extra %}
+<script type="text/javascript" src='{{ "js/sorttable.js"|m }}'></script>
+{% endblock %}
diff --git a/Pootle-2.0.0/local_apps/pootle_app/templates/index/about.html b/Pootle-2.0.0/local_apps/pootle_app/templates/index/about.html
new file mode 100644
index 0000000..931b03e
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/templates/index/about.html
@@ -0,0 +1,25 @@
+{% extends "base.html" %}
+{% load i18n %}
+
+{% get_current_language as LANGUAGE_CODE %}
+
+{% block title %}
+{{ block.super }}: {% trans "About" %}
+{% endblock %}
+
+{% block body.id %}about{% endblock body.id %}
+
+{% block precontent %}
+ {{ description|safe }}
+{% endblock precontent %}
+
+{% block content %}
+<div lang="{{ LANGUAGE_CODE }}">
+ <h2 class="title">{% trans "About Pootle" %}</h2>
+ <p class="about">{% blocktrans %}This site is running Pootle &mdash; powerful web software to empower teams to do translation and translation management. This community tool is Free Software, and any team can use it and contribute to the Pootle community. Read more on the <a href="http://translate.sourceforge.net/">project website</a>.{% endblocktrans %}</p>
+ <p class="about">{{ pootle_version }}</p>
+ <!--
+ {{ version_details }}
+ -->
+</div>
+{% endblock content%}
diff --git a/Pootle-2.0.0/local_apps/pootle_app/templates/index/index.html b/Pootle-2.0.0/local_apps/pootle_app/templates/index/index.html
new file mode 100644
index 0000000..80489e4
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/templates/index/index.html
@@ -0,0 +1,114 @@
+{% extends "base.html" %}
+
+{% load i18n baseurl notification_tags cleanhtml %}
+
+{% get_current_language as LANGUAGE_CODE %}
+{% get_current_language_bidi as LANGUAGE_BIDI %}
+
+{% block extra_head %}
+<link rel="alternate" type="application/rss+xml" href="{% url pootle_notifications.feeds.view "" %}" />
+{% endblock %}
+
+{% block body.id %}frontpage{% endblock body.id %}
+
+{% block precontent %}
+<div class="intro">{{ description|safe|linebreaks|clean }}</div>
+{% endblock precontent %}
+
+{% block content %}
+<div class="module first" lang="{{ LANGUAGE_CODE }}">
+ <div class="hd">
+ <h2>{{ languagelink }}</h2>
+ </div>
+ <div class="bd">
+ <table class="sortable">
+ <tr>
+ <th>{% trans 'Language' %}</th>
+ <th>{% trans 'Overall Completion' %}</th>
+ <th>{% trans 'Last Activity' %}</th>
+ </tr>
+ {% for item in languages %}
+ {% ifnotequal item.total 0 %}
+ <tr class="{% cycle 'even' 'odd' %}">
+ <td class="language"><a href="{% filter l %}/{{ item.code }}/{% endfilter %}">{{ item.name }}</a></td>
+ <td>
+ <div class="sortkey">{{ item.transper }}</div>
+ <div class="graph" title="{{ item.completed_title }}" dir="{% if LANGUAGE_BIDI %}rtl{% else %}ltr{% endif %}">
+ <div class="translated" style="width: {{ item.transper }}px"></div>
+ {% if item.fuzzy %}
+ <div class="fuzzy" style="{%if LANGUAGE_BIDI%}right{%else%}left{%endif%}: {{ item.transper }}px; width: {{ item.fuzzyper }}px"></div>
+ {% endif %}
+ {% if item.untrans %}
+ <div class="untranslated" style="{% if LANGUAGE_BIDI %}right{% else %}left{% endif %}: {{ item.transper|add:item.fuzzyper }}px; width: {{ item.untransper }}px"></div>
+ {% endif %}
+ </div>
+ </td>
+ <td>{{ item.lastactivity }}</td>
+ </tr>
+ {% endifnotequal %}
+ {% endfor %}
+ </table>
+ </div>
+</div>
+
+<div class="module" lang="{{ LANGUAGE_CODE }}">
+ <div class="hd">
+ <h2>{{ projectlink }}</h2>
+ </div>
+ <div class="bd">
+ <table class="sortable">
+ <tr>
+ <th>{% trans 'Project' %}</th>
+ <th>{% trans 'Overall Completion' %}</th>
+ <th>{% trans 'Last Activity' %}</th>
+ </tr>
+ {% for item in projects %}
+ <tr class="{% cycle 'even' 'odd' %}">
+ <td class="project"><a href="{% filter l %}/projects/{{ item.code }}/{% endfilter %}" title="{{ item.description }}">{{ item.name }}</a></td>
+ <td>
+ <div class="sortkey">{{ item.transper }}</div>
+ <div class="graph" title="{{ item.completed_title }}" dir="{% if LANGUAGE_BIDI %}rtl{% else %}ltr{% endif %}">
+ <div class="translated" style="width: {{ item.transper }}px"> </div>
+ {% if item.fuzzy %}
+ <div class="fuzzy" style="{% if LANGUAGE_BIDI %}right{% else %}left{% endif %}: {{ item.transper }}px; width: {{ item.fuzzyper }}px"></div>
+ {% endif %}
+ {% if item.untrans %}
+ <div class="untranslated" style="{% if LANGUAGE_BIDI %}right{% else %}left{% endif %}: {{ item.transper|add:item.fuzzyper }}px; width: {{ item.untransper }}px"></div>
+ {% endif %}
+ </div>
+ </td>
+ <td>{{ item.lastactivity }}</td>
+ </tr>
+ {% endfor %}
+ </table>
+ </div>
+</div>
+{% endblock content %}
+
+{% block postcontent %}
+<div class="module latestnews{% if moreprojects %} first{% endif %}" lang="{{ LANGUAGE_CODE }}">
+ <div class="hd"><h2>{% trans 'Latest News' %}</h2></div>
+ <div class="bd">
+ {% render_latest_news "" 5 %}
+ <p class="subscribe"><a href="{% url pootle_notifications.feeds.view "" %}">{% trans "Subscribe to the RSS feed." %}</a></p>
+ </div>
+</div>
+
+<div class="module first clear" lang="{{ LANGUAGE_CODE }}">
+ <div class="bd">
+ {% include "translation_summary_legend.html" %}
+ </div>
+</div>
+
+{% include "top_contributers_table.html" %}
+{% endblock postcontent %}
+
+{% block scripts_extra %}
+<script type="text/javascript" src='{{ "js/sorttable.js"|m }}'></script>
+<script type="text/javascript" src='{{ "js/jquery/jquery.bidi.js"|m }}'></script>
+<script type="text/javascript">
+ $(document).ready(function() {
+ $(".intro, li.newsitem, .newsitem address, .newsitem blockcode, .newsitem blockquote, .newsitem :header, .newsitem p, .newsitem pre, .newsitem li, .newsitem dt, .newsitem dd, .newsitem ul, .newsitem ol, .newsitem dl").filter(":not([dir])").bidi();
+ });
+</script>
+{% endblock %}
diff --git a/Pootle-2.0.0/local_apps/pootle_app/templates/index/login.html b/Pootle-2.0.0/local_apps/pootle_app/templates/index/login.html
new file mode 100644
index 0000000..d7d55d8
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/templates/index/login.html
@@ -0,0 +1,42 @@
+{% extends "base.html" %}
+{% load i18n baseurl %}
+
+{% block title %}
+{{ block.super }}: {% trans "Login" %}
+{% endblock %}
+
+{% block content %}
+<div id="login" dir="{% if LANGUAGE_BIDI %}rtl{% else %}ltr{% endif %}">
+ <h2>{% trans 'Login to Pootle' %}</h2>
+ <form method="post" action='{{ "/accounts/login/"|l }}'
+ name="loginform" id="loginform">
+ {% if form.errors %}
+ <div id="site-message">
+ <div class="error">
+ {% trans "Your username and password didn't match. Please try again." %}
+ </div>
+ </div>
+ {% endif %}
+ {{ form.as_p }}
+ <p class="submit">
+ <input type="submit" name="login" value="{% trans 'Login' %}" />
+ <input type="hidden" name="next" value="{{ next }}" />
+ </p>
+</form>
+<p><a href='{{ "/accounts/password/reset/"|l }}'>{% trans "Lost your Password?" %}</a></p>
+</div>
+
+{% if settings.CAN_REGISTER %}
+<h2 class="registerlink">{% trans "Don't have an account yet?" %}
+ <a href='{{ "/accounts/register"|l }}' title="{% trans 'Register.' %}">{% trans 'Register.' %}</a>
+</h2>
+{% endif %}
+{% endblock content %}
+
+{% block scripts_extra %}
+ <script>
+ $(document).ready(function () {
+ $("#id_username").focus();
+ });
+ </script>
+{% endblock scripts_extra %}
diff --git a/Pootle-2.0.0/local_apps/pootle_app/templates/language/item_block.html b/Pootle-2.0.0/local_apps/pootle_app/templates/language/item_block.html
new file mode 100644
index 0000000..3234ee9
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/templates/language/item_block.html
@@ -0,0 +1,26 @@
+{% load i18n baseurl %}
+
+<!-- itemblock -->
+<div class="contentsitem" lang="{{ LANGUAGE_CODE }}" dir="{% if LANGUAGE_BIDI %}rtl{% else %}ltr{% endif %}">
+ <img src="{% filter m %}images/{{ item.icon }}.png{% endfilter %}"
+ class="icon" alt="icon" lang="{{ LANGUAGE_CODE }}" dir="{% if LANGUAGE_BIDI %}rtl{% else %}ltr{% endif %}"/>
+
+ {% if item.title %}
+ <h3 class="title">
+ <a href="{{ item.href|l }}">{{ item.title }}</a>
+ {% if item.data.errors %}
+ <img src="{{ "images/error.png"|m }}" alt="{% trans 'Error' %}"
+ title="{{ item.errortooltip }}" class="error" />
+ {% endif %}
+ </h3>
+ {% endif %}
+
+ {% if item.actions %}
+ <div id="actionlinks" class="item-description">
+ {% for link in item.actions %}
+ <a href="{{ link.href|l }}" title="{{ link.title }}">{{ link.text }}</a>{% if not forloop.last %} | {% endif %}
+ {% endfor %}
+ </div>
+ {% endif %}
+</div>
+<!-- /itemblock -->
diff --git a/Pootle-2.0.0/local_apps/pootle_app/templates/language/item_stats.html b/Pootle-2.0.0/local_apps/pootle_app/templates/language/item_stats.html
new file mode 100644
index 0000000..47f288e
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/templates/language/item_stats.html
@@ -0,0 +1,18 @@
+{% load i18n baseurl %}
+
+{% get_current_language as LANGUAGE_CODE %}
+
+{% block itemstats %}
+<!-- itemstats -->
+<div class="item-statistics small" lang="{{ LANGUAGE_CODE }}">
+ <p>{{ item.stats.summary|safe }}</p>
+ {% if item.stats.checks %}
+ <dl>
+ {% for check in item.stats.checks %}
+ <dt><a href="{{ check.href|l }}">{{ check.text }}</a></dt><dd>{{ check.stats }}</dd>
+ {% endfor %}
+ </dl>
+ {% endif %}
+</div>
+<!-- /itemstats -->
+{% endblock itemstats %}
diff --git a/Pootle-2.0.0/local_apps/pootle_app/templates/language/item_summary.html b/Pootle-2.0.0/local_apps/pootle_app/templates/language/item_summary.html
new file mode 100644
index 0000000..1c6a469
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/templates/language/item_summary.html
@@ -0,0 +1,38 @@
+{% load i18n baseurl %}
+
+{% get_current_language_bidi as LANGUAGE_BIDI %}
+
+{% block itemstats %}
+<td class="stats-name">
+ <a href="{{ item.href|l }}">{{ item.title }}</a>
+ {% if item.data.errors %}
+ <img src="{{ "images/error.png"|m }}" alt="{% trans 'Error' %}"
+ title="{{ item.errortooltip }}" class="error" />
+ {% endif %}
+</td>
+{% if item.data %}
+<td class="stats-graph">
+ <div class="sortkey">{{ item.data.translatedpercentage }}</div>
+ <div class="graph" title="{{ item.tooltip }}">
+ <div class="translated" style="width: {{ item.data.translatedpercentage }}px;"></div>
+ {% if item.data.fuzzysourcewords %}
+ <div class="fuzzy" style="{% if LANGUAGE_BIDI %}right{%else%}left{%endif%}: {{ item.data.translatedpercentage }}px; width: {{ item.data.fuzzypercentage }}px;"></div>
+ {% endif %}
+ {% if item.data.untranslatedsourcewords %}
+ <div class="untranslated" style="{% if LANGUAGE_BIDI %}right{%else%}left{%endif%}: {{ item.data.translatedpercentage|add:item.data.fuzzypercentage }}px; width: {{ item.data.untranslatedpercentage }}px;"></div>
+ {% endif %}
+ </div>
+</td>
+<td class="stats">
+ <ul>
+ {% if item.data.untranslatedsourcewords or item.data.fuzzysourcewords %}
+ <li class="todo"><a href="{% filter l %}{% if item.isfile %}{{ item.href_todo }}{% else %}{{ item.href }}{{ "translate.html?match_names=check-isfuzzy,untranslated&view_mode=review"|force_escape }}{% endif %}{% endfilter %}" title="{{ item.todo_tooltip }}">{{ item.todo_text }}</a></li>
+ {% endif %}
+ {% ifequal item.data.translatedsourcewords item.data.totalsourcewords %}
+ <li class="complete">{% trans "Complete" %}</li>
+ {% endifequal %}
+ </ul>
+</td>
+<td class="stats-words">{{ item.data.totalsourcewords }}</td>
+{% endif %}
+{% endblock itemstats %}
diff --git a/Pootle-2.0.0/local_apps/pootle_app/templates/language/item_title.html b/Pootle-2.0.0/local_apps/pootle_app/templates/language/item_title.html
new file mode 100644
index 0000000..1622d63
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/templates/language/item_title.html
@@ -0,0 +1,10 @@
+{% load i18n baseurl %}
+
+{% for item in navitems %}
+ {% if item.path.language %}<a href="{{ item.path.language.href|l }}">{{ item.path.language.text }}</a>{% endif %}
+ {% if item.path.project %} » <a href="{{ item.path.project.href|l }}">{{ item.path.project.text }}</a>{% endif %}
+ {% for pathlink in item.path.pathlinks %}
+ {% if forloop.first %} » {% endif %}
+ <a href="{{ pathlink.href|l }}">{{ pathlink.text }}</a>{{ pathlink.sep }}
+ {% endfor %}
+{% endfor %}
diff --git a/Pootle-2.0.0/local_apps/pootle_app/templates/language/language_admin.html b/Pootle-2.0.0/local_apps/pootle_app/templates/language/language_admin.html
new file mode 100644
index 0000000..28072bc
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/templates/language/language_admin.html
@@ -0,0 +1,36 @@
+{% extends "language_base.html" %}
+
+{% load i18n baseurl %}
+
+{% block title %}
+{{ block.super }} » {% trans "Permissions" %}
+{% endblock %}
+
+{% block bodyclass %}languageadmin{% endblock %}
+
+{% block precontent %}
+{{ block.super }}
+<div class="intro">
+ {{ filestyle_text }}
+</div>
+{% endblock %}
+
+{% block content %}
+<div class="settings-container centered" lang="{{ LANGUAGE_CODE }}">
+ <p>{% trans "Below you can add, edit, and delete the permissions for this language." %}</p>
+ <form action="" method="post" name="permissionsform" id="permissionsform">
+ {{ permission_set_formset.management_form }}
+ <table cellpadding="1" cellspacing="1">
+ <tr>
+ <th>{% trans "Username" %}</th><th>{% trans "Permissions" %}</th><th>{% trans "Delete" %}</th>
+ </tr>
+ {% for form in permission_set_formset.forms %}
+ {{ form.as_table }}
+ {% endfor %}
+ </table>
+ <p>
+ <input type="submit" name="doupdaterights" value="{% trans 'Update Permissions' %}" />
+ </p>
+ </form>
+</div>
+{% endblock %}
diff --git a/Pootle-2.0.0/local_apps/pootle_app/templates/language/language_index.html b/Pootle-2.0.0/local_apps/pootle_app/templates/language/language_index.html
new file mode 100644
index 0000000..afe410e
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/templates/language/language_index.html
@@ -0,0 +1,51 @@
+{% extends "language_base.html" %}
+{% load i18n baseurl %}
+
+{% get_current_language as LANGUAGE_CODE %}
+{% get_current_language_bidi as LANGUAGE_BIDI %}
+
+{% block body.id %}languageindex{% endblock body.id %}
+
+{% block bodyclass %}languageoverview{% endblock bodyclass %}
+
+{% block content %}
+<div class="item-statistics" lang="{{ LANGUAGE_CODE }}">{{ language.stats }}</div>
+
+<div class="module-primary" lang="{{ LANGUAGE_CODE }}">
+ <div class="bd">
+ <table class="sortable stats" id="stats" cellpadding="5" cellspacing="0" width="100%">
+ <thead>
+ <tr>
+ <th class="stats">{{ statsheadings.name }}</th>
+ <th class="stats">{{ statsheadings.progress }}</th>
+ <th class="stats sorttable_numeric">{{ statsheadings.summary }}</th>
+ <th class="stats">{{ statsheadings.totalwords }}</th>
+ <th>{% trans 'Last Activity' %}</th>
+ </tr>
+ </thead>
+ <tbody class="stats">
+ {% for item in projects %}
+ <tr class="item {% cycle 'even' 'odd' %}" title="{{ item.description|striptags }}">
+ {% include "language/item_summary.html" %}
+ <td>{{ item.lastactivity }}</td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ </div>
+</div>
+{% endblock content %}
+
+{% block postcontent %}
+<div class="module first clear" lang="{{ LANGUAGE_CODE }}">
+ <div class="bd">
+ {% include "translation_summary_legend.html" %}
+ </div>
+</div>
+
+{% include "top_contributers_table.html" %}
+{% endblock postcontent %}
+
+{% block scripts_extra %}
+<script type="text/javascript" src='{{ "js/sorttable.js"|m }}'></script>
+{% endblock %}
diff --git a/Pootle-2.0.0/local_apps/pootle_app/templates/language/search.html b/Pootle-2.0.0/local_apps/pootle_app/templates/language/search.html
new file mode 100644
index 0000000..6d1c53a
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/templates/language/search.html
@@ -0,0 +1,29 @@
+{% load i18n baseurl %}
+
+{% get_current_language as LANGUAGE_CODE %}
+{% get_current_language_bidi as LANGUAGE_BIDI %}
+
+<!--! Search form -->
+<div id="search" lang="{{ LANGUAGE_CODE }}" dir="{% if LANGUAGE_BIDI %}rtl{% else %}ltr{% endif %}">
+ <form action="translate.html?view_mode=translate" method="post" id="searchform">
+ {{ search.search_form.as_p }}
+ <input type="hidden" name="new_search" value="new_search" />
+ {% if store %}
+ <input type="hidden" name="store" value="{{ store }}" />
+ {% endif %}
+ <a class="advancedlink">
+ {% if LANGUAGE_BIDI %}
+ <img src='{{ "images/sarrow_down_rtl.png"|m }}' alt="{{ search.advanced_search_title }}" title="{{ search.advanced_search_title }}" class="togglesearch" />
+ <img src='{{ "images/sarrow_up_rtl.png"|m }}' alt="{{ search.advanced_search_title }}" title="{{ search.advanced_search_title }}" class="togglesearch" style="display: none;"/>
+ {% else %}
+ <img src='{{ "images/sarrow_down.png"|m }}' alt="{{ search.advanced_search_title }}" title="{{ search.advanced_search_title }}" class="togglesearch" />
+ <img src='{{ "images/sarrow_up.png"|m }}' alt="{{ search.advanced_search_title }}" title="{{ search.advanced_search_title }}" class="togglesearch" style="display: none;"/>
+ {% endif %}
+ </a>
+ <div class="advancedsearch">
+ {% for field in search.advanced_search_form %}
+ <p>{{ field }} {{ field.label_tag }}</p>
+ {% endfor %}
+ </div>
+ </form>
+</div>
diff --git a/Pootle-2.0.0/local_apps/pootle_app/templates/language/tp_admin_files.html b/Pootle-2.0.0/local_apps/pootle_app/templates/language/tp_admin_files.html
new file mode 100644
index 0000000..6b97624
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/templates/language/tp_admin_files.html
@@ -0,0 +1,20 @@
+{% extends "tp_base_admin.html" %}
+
+{% load i18n baseurl %}
+
+{% block title %}
+{{ block.super }} » {% trans "Files" %}
+{% endblock %}
+
+{% block bodyclass %}tpadminfiles{% endblock %}
+
+{% block section_description %}{% blocktrans %}Here you can delete the available files in this translation project. You may also want to <a href="?scan_files=1">rescan the project files</a> or <a href="?template_update=1">update the files from templates</a>.{% endblocktrans %}{% endblock %}
+
+{% block section_body %}
+{% include "admin/admin_general.html" %}
+{% endblock %}
+
+{% block scripts_extra %}
+{{ block.super }}
+<script type="text/javascript" src='{{ "js/sorttable.js"|m }}'></script>
+{% endblock %}
diff --git a/Pootle-2.0.0/local_apps/pootle_app/templates/language/tp_admin_permissions.html b/Pootle-2.0.0/local_apps/pootle_app/templates/language/tp_admin_permissions.html
new file mode 100644
index 0000000..5bd6092
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/templates/language/tp_admin_permissions.html
@@ -0,0 +1,28 @@
+{% extends "tp_base_admin.html" %}
+
+{% load i18n %}
+
+{% block title %}
+{{ block.super }} » {% trans "Permissions" %}
+{% endblock %}
+
+{% block bodyclass %}tpadminpermissions{% endblock %}
+
+{% block section_description %}{{ filestyle_text }}{% endblock %}
+
+{% block section_body %}
+<form action="" method="post" name="permissionsform" id="permissionsform">
+ {{ permission_set_formset.management_form }}
+ <table cellpadding="1" cellspacing="1">
+ <tr>
+ <th>{% trans "Username" %}</th><th>{% trans "Permissions" %}</th><th>{% trans "Delete" %}</th>
+ </tr>
+ {% for form in permission_set_formset.forms %}
+ {{ form.as_table }}
+ {% endfor %}
+ </table>
+ <p>
+ <input type="submit" name="doupdaterights" value="{% trans 'Update Rights' %}" />
+ </p>
+</form>
+{% endblock %}
diff --git a/Pootle-2.0.0/local_apps/pootle_app/templates/language/tp_overview.html b/Pootle-2.0.0/local_apps/pootle_app/templates/language/tp_overview.html
new file mode 100644
index 0000000..df965aa
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/templates/language/tp_overview.html
@@ -0,0 +1,72 @@
+{% extends "tp_base_extended.html" %}
+
+{% load i18n baseurl %}
+
+{% get_current_language as LANGUAGE_CODE %}
+{% get_current_language_bidi as LANGUAGE_BIDI %}
+
+{% block body.id %}fileindex{% endblock body.id %}
+
+{% block bodyclass %}tpoverview{% endblock bodyclass %}
+
+{% block breadcrumbs_content %}
+{% include "language/item_title.html" %}
+{% endblock %}
+
+{% block content %}
+{% if children %}
+<!--! The statistics view -->
+<div class="module-primary" lang="{{ LANGUAGE_CODE }}">
+ <div class="bd">
+ <table class="sortable stats" id="stats" cellpadding="5"
+ cellspacing="0" width="100%" lang="{{ LANGUAGE_CODE }}" dir="{% if LANGUAGE_BIDI %}rtl{% else %}ltr{% endif %}">
+ <thead>
+ <tr>
+ <th class="stats">{{ stats_headings.name }}</th>
+ <th class="stats">{{ stats_headings.progress }}</th>
+ <th class="stats sorttable_numeric">{{ stats_headings.summary }}</th>
+ <th class="stats">{{ stats_headings.totalwords }}</th>
+ </tr>
+ </thead>
+ <tbody class="stats">
+ {% for item in children %}
+ <tr class="item {% cycle 'even' 'odd' %}" title="{{ item.description }}">
+ {% include "language/item_summary.html" %}
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ </div>
+</div>
+{% endif %}
+{% endblock content %}
+
+{% block postcontent %}
+{% if upload %}
+<div id="upload" class="module first clear" lang="{{ LANGUAGE_CODE }}">
+ <div class="bd">
+ <h2>{{ upload.form.title }}</h2>
+ <form action="" method="post" id="uploadform" name="uploadform" enctype="multipart/form-data">
+ {{ upload.as_p }}
+ </form>
+ </div>
+</div>
+{% endif %}
+
+{% if update %}
+<div id="update" class="module first clear" lang="{{ LANGUAGE_CODE }}">
+ <div class="bd">
+ <form action="" method="post" name="updateform" enctype="multipart/form-data">
+ {{ update.as_p }}
+ </form>
+ </div>
+</div>
+{% endif %}
+
+{% include "top_contributers_table.html" %}
+{% endblock postcontent %}
+
+{% block scripts_extra %}
+{{ block.super }}
+<script type="text/javascript" src='{{ "js/sorttable.js"|m }}'></script>
+{% endblock %}
diff --git a/Pootle-2.0.0/local_apps/pootle_app/templates/language/tp_review.html b/Pootle-2.0.0/local_apps/pootle_app/templates/language/tp_review.html
new file mode 100644
index 0000000..7edf2ce
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/templates/language/tp_review.html
@@ -0,0 +1,38 @@
+{% extends "tp_base_extended.html" %}
+
+{% load i18n %}
+
+{% block title %}
+{{ block.super }} » {% trans "Review" %}
+{% endblock %}
+
+{% get_current_language as LANGUAGE_CODE %}
+{% get_current_language_bidi as LANGUAGE_BIDI %}
+
+{% block bodyclass %}tpreview{% endblock bodyclass %}
+
+{% block breadcrumbs_content %}
+{% include "language/item_title.html" %}
+{% endblock %}
+
+{% block content %}
+{% if children %}
+<!-- The review view -->
+{% for item in children %}
+<div class="module-primary" lang="{{ LANGUAGE_CODE }}">
+ <div class="bd">
+ {% if item.isdir or item.isfile %}
+ <div class="item {% cycle 'even' 'odd' %}">
+ {% include "language/item_block.html" %}
+ {% include "language/item_stats.html" %}
+ </div>
+ {% endif %}
+ </div>
+</div>
+{% endfor %}
+{% endif %}
+{% endblock content %}
+
+{% block postcontent %}
+{% include "top_contributers_table.html" %}
+{% endblock postcontent %}
diff --git a/Pootle-2.0.0/local_apps/pootle_app/templates/language/tp_translate.html b/Pootle-2.0.0/local_apps/pootle_app/templates/language/tp_translate.html
new file mode 100644
index 0000000..e124c68
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/templates/language/tp_translate.html
@@ -0,0 +1,54 @@
+{% extends "tp_base_extended.html" %}
+
+{% load i18n %}
+
+{% block title %}
+{{ block.super }} » {% trans "Translate" %}
+{% endblock %}
+
+{% block bodyclass %}tptranslate{% endblock bodyclass %}
+
+{% block breadcrumbs_content %}
+{% include "language/item_title.html" %}
+{% endblock %}
+
+{% block content %}
+<!-- The editing view -->
+{% for item in children %}
+<div class="module-primary" lang="{{ LANGUAGE_CODE }}">
+ <div class="bd">
+ {% if item.isdir or item.isfile %}
+ <div class="item {% cycle 'even' 'odd' %}">
+ {% include "language/item_block.html" %}
+ {% include "language/item_stats.html" %}
+ </div>
+ {% endif %}
+ </div>
+</div>
+{% endfor %}
+{% endblock content %}
+
+{% block postcontent %}
+{% if upload %}
+<div id="upload" class="module first clear" lang="{{ LANGUAGE_CODE }}">
+ <div class="bd">
+ <h2>{{ upload.form.title }}</h2>
+ <form action="" method="post" id="uploadform" name="uploadform" enctype="multipart/form-data">
+ {{ upload.as_p }}
+ </form>
+ </div>
+</div>
+{% endif %}
+
+{% if update %}
+<div id="update" class="module first clear" lang="{{ LANGUAGE_CODE }}">
+ <div class="bd">
+ <form action="" method="post" name="updateform" enctype="multipart/form-data">
+ {{ update.as_p }}
+ </form>
+ </div>
+</div>
+{% endif %}
+
+{% include "top_contributers_table.html" %}
+{% endblock postcontent %}
diff --git a/Pootle-2.0.0/local_apps/pootle_app/templates/language/translatepage.html b/Pootle-2.0.0/local_apps/pootle_app/templates/language/translatepage.html
new file mode 100644
index 0000000..32da92c
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/templates/language/translatepage.html
@@ -0,0 +1,340 @@
+{% extends "tp_base_extended.html" %}
+
+{% load i18n baseurl %}
+
+{% get_current_language as LANGUAGE_CODE %}
+{% get_current_language_bidi as LANGUAGE_BIDI %}
+
+{% block title %}
+{{ block.super }} » {% trans "Translate" %} » {{ title_path }}
+{% endblock %}
+
+{% block body.id %}translatepage{% endblock body.id %}
+
+{% block bodyclass %}{% if checking_text %}tpreview{% else %}tptranslate{% endif %}{% endblock %}
+
+{% block extra_head %}
+<script src='{{ "js/jquery/jquery.textarea-expander.js"|m }}' type="text/javascript"></script>
+<script type="text/javascript">
+ $(document).ready (function() {
+ $('textarea.expanding').TextAreaExpander('20', '100');
+ });
+</script>
+{% endblock extra_head %}
+
+{% block breadcrumbs_content %}
+{% include "language/item_title.html" %}
+{% endblock %}
+
+{% block innernav %}
+<!--! Navigation bar -->
+<div id="innernav" class="navbar">
+ {% for item in navitems %}
+ {% include "language/item_block.html" %}
+ {% include "language/item_stats.html" %}
+ {% endfor %}
+</div><!--innernav-->
+{% endblock innernav %}
+
+{% block precontent %}
+{{ block.super }}
+{% if checking_text %}
+<div class="check">
+ <img src='{{ "images/help.png"|m }}' class="checkinfo" />
+ <span>{{ checking_text|safe }}</span>
+</div>
+{% endif %}
+{% endblock precontent %}
+
+{% block content %}
+<div class="module-primary">
+ <div class="nomargin">
+ <!--! start/next/previous/end links -->
+ {% if pagelinks %}
+ <ul class="translations-nav" lang="{{ LANGUAGE_CODE }}">
+ {% for pagelink in pagelinks %}
+ <li><a href="{{ pagelink.href|l }}">{{ pagelink.text }}</a></li>
+ {% endfor %}
+ </ul>
+ {% endif %}
+ <div class="translate-form">
+ {% if notice %}
+ <div id="site-message" dir="{% if LANGUAGE_BIDI %}rtl{% else %}ltr{% endif %}">{{ notice }}</div>
+ {% else %}
+ <form action="{{actionurl|l}}" method="post" name="translate" id="translate">
+ {{ search.search_form.as_hidden }}
+ {{ search.advanced_search_form.as_hidden }}
+ <table cellpadding="0" cellspacing="0" class="translate-table" dir="{% if LANGUAGE_BIDI %}rtl{% else %}ltr{% endif %}">
+ <tr lang="{{ LANGUAGE_CODE }}">
+ {% if canedit %}
+ <th colspan="1" rowspan="1" class="translate-table-title translate-edit"></th>
+ {% endif %}
+ <th colspan="1" rowspan="1" class="translate-table-title translate-original">{{ original_title }}</th>
+ <th colspan="1" rowspan="1" class="translate-table-title translate-translation">{{ translation_title }}</th>
+ </tr>
+ {% for item in items %}
+ <tr title="{{ item.translator_comments }} {{ item.developer_comments }}">
+ {% if canedit %}
+ <td>
+ {% if item.trans.editlink %}
+ <a href="{{ item.trans.editlink.href|l }}" id="{{ item.trans.editlink.linkid }}" title="{% trans 'Edit translation' %}">
+ <div class="translation-action">{{ item.itemid }}</div>
+ </a>
+ {% endif %}
+ </td>
+ {% endif %}
+
+ <!--! Cell with original -->
+ <td colspan="1" rowspan="1" class="translate-original {{ item.polarity }} {{ item.focus_class }}" dir="ltr">
+ <div id="{{ item.orig.itemid }}" class="translate-original {{ item.orig.focus_class }}">
+ {% for field in item.orig.pure %}
+ <input type="hidden" name="{{ field.pureid }}" value="{{ field.value }}" id="{{ field.pureid }}"/>
+ {% endfor %}
+ {% if not item.orig.isplural %}
+ <div class="translation-text" lang="en">{{ item.orig.text|safe }}</div>
+ {% else %}
+ <span class="translation-text-headers" lang="{{ LANGUAGE_CODE }}">{{ item.orig.singular_title }}</span><br />
+ <span class="translation-text" lang="en">{{ item.orig.singular_text|safe }}</span><br />
+ <span class="translation-text-headers" lang="{{ LANGUAGE_CODE }}">{{ item.orig.plural_title }}</span><br />
+ <span class="translation-text" lang="en">{{ item.orig.plural_text|safe }}</span><br />
+ {% endif %}
+ </div>
+
+ <!--! Alternative source language translation (if enabled and available) -->
+ {% for altsrc in item.altsrcs %}
+ {% if altsrc.available %}
+ <div class="translation-text-headers altsrclangname" lang="{{ LANGUAGE_CODE }}" dir="{% if LANGUAGE_BIDI %}rtl{% else %}ltr{% endif %}">{{ altsrc.title }}</div>
+ <div class="translate-original {{ item.orig.focus_class }}">
+ {% if not altsrc.isplural %}
+ <div id="{{ item.orig.itemid }}" class="translation-text" lang="{{ altsrc.languagecode }}" dir="{{ altsrc.dir }}">{{ altsrc.text|safe }}</div>
+ {% else %}
+ {% for form in altsrc.forms %}
+ {% if form.title %}
+ <span class="translation-text-headers" lang="{{ LANGUAGE_CODE }}">{{ form.title }}</span><br />
+ {% endif %}
+ <span class="translation-text" lang="{{ altsrc.languagecode }}" dir="{{ altsrc.dir }}">{{ form.text|safe }}</span>
+ {% if form.title %}
+ <br/>
+ {% endif %}
+ {% endfor %}
+ {% endif %}
+ </div>
+ {% endif %}
+ {% endfor %}
+
+ {% if item.editable and item.message_context %}
+ <div class="translate-context">
+ {{ item.message_context }}
+ </div>
+ {% endif %}
+ </td>
+
+ <!--! Cell with translation (view/suggest/review/edit) -->
+ <td colspan="1" rowspan="1" class="translate-translation {{ item.polarity }} {{ item.focus_class }}">
+ <div id="{{ item.trans.itemid }}" class="translate-translation {{ item.orig.focus_class }}">
+ {% if item.editable %}
+ <!--! Begin: Normal editing -->
+
+ {% if item.tm %}
+ <!--! Translation memory -->
+ <div id="tm" class="sidebar" dir="{% if LANGUAGE_BIDI %}rtl{% else %}ltr{% endif %}">
+ <div class="sidetitle" lang="{{ LANGUAGE_CODE }}">{{ related_title }}</div>
+ {% for unit in item.tm %}
+ <div class="tm-unit" title="{{ unit.getnotes }}" onclick="writespecial('{{ unit.target }}', '{{ item.trans.itemid }}');">
+ <span class="tm-original" dir="ltr" lang="en">{{ unit.source }}</span>
+ <span class="tm-translation" dir="{{ language.dir }}" lang="{{ language.code }}">{{ unit.target }}</span>
+ </div>
+ {% endfor %}
+ </div>
+ {% endif %}
+
+ {% if not item.trans.isplural %}
+ <!--! No plurals -->
+ <textarea {% if not canedit %}readonly="readonly"{% endif %} dir="{{ language.dir }}" lang="{{ language.code }}"
+ onfocus="setfocusedelement(this);" class="translation expanding {{ item.state_class }}"
+ name="{{ item.trans.itemid }}" id="area{{ item.trans.itemid }}" rows="{{ item.trans.rows }}">{{ item.trans.text|safe }}</textarea>
+ {% else %}
+ <!--! with plurals -->
+ {% for form in item.trans.forms %}
+ <div class="translation-text-headers" lang="{{ LANGUAGE_CODE }}">{{ form.title }}</div>
+ <textarea {% if not canedit %}readonly="readonly"{% endif %} dir="{{ language.dir }}" lang="{{ language.code }}" onfocus="setfocusedelement(this);" class="translation expanding {{ item.state_class }}" name="{{ form.name }}" id="area{{ form.name }}" rows="{{ item.trans.rows }}">{{ form.text|safe }}</textarea>
+ {% endfor %}
+ {% endif %}
+
+ {% if item.hassuggestion %}
+ <!--! Reviewing mode: display original strings for diffing -->
+ <div id="translate-original-container">
+ <strong class="{{ item.state_class }}">{{ item.trans.current_title }}</strong>
+ <div class="translate-original-block">
+ {% for form in item.trans.forms %}
+ <div lang="{{ language.code }}" dir="{{ language.dir }}">{{ form.diff|safe }}</div>
+ {% endfor %}
+ </div>
+ </div>
+
+ <!--! (Re)view suggestions while editing -->
+ <div id="translate-suggestion-container">
+ {% for sugg in item.trans.suggestions %}
+ <div id="suggestion{{ sugg.suggid }}" class="translate-suggestion-block">
+ <strong>{{ sugg.title }}</strong>
+ <div class="translate-suggestion">
+ {% for form in sugg.forms %}
+ {% if form.title %}
+ <div class="translation-text-headers" lang="{{ LANGUAGE_CODE }}">{{ form.title }}</div>
+ {% endif %}
+ <div lang="{{ language.code }}" dir="{{ language.dir }}">{{ form.diff|safe }}</div>
+ <input type="hidden" id="{{ form.suggid }}" name="{{ form.suggid }}" value="{{ form.value }}" />
+ {% endfor %}
+ {% if canreview %}
+ <a title="{{ accept_title }}" accesskey="a" id="accept{{ sugg.suggid }}" class="acceptsugg"><img src='{{ "images/tick.png"|m }}' dir="{% if LANGUAGE_BIDI %}rtl{% else %}ltr{% endif %}" /></a>
+ <a title="{{ reject_title }}" accesskey="r" id="reject{{ sugg.suggid }}" class="rejectsugg"><img src='{{ "images/cross.png"|m }}' dir="{% if LANGUAGE_BIDI %}rtl{% else %}ltr{% endif %}" /></a>
+ {% endif %}
+ </div>
+ </div>
+ {% endfor %}
+ </div>
+ {% endif %}
+
+ <!--! Buttons, resize links, special characters -->
+ <div lang="{{ LANGUAGE_CODE }}">
+ <div class="translate-buttons-block">
+ {% for button in item.trans.buttons.desired %}
+ {% ifequal button 'translate' %}
+ <input type="submit" name="submit{{ item.trans.buttons.item }}" accesskey="s" value="{% trans 'Submit' %}" />
+ {% endifequal %}
+ {% ifequal button 'suggest' %}
+ <input type="submit" name="submitsuggest{{ item.trans.buttons.item }}" accesskey="e" value="{% trans 'Suggest' %}" />
+ {% endifequal %}
+ {% ifequal button 'copy' %}
+ <input type="button" accesskey="c"
+ onclick="copyorigtranslation('{{ item.trans.buttons.item }}'); return false"
+ value="{% trans 'Copy' %}" />
+ {% endifequal %}
+ {% ifequal button 'skip' %}
+ <input type="submit" name="skip{{ item.trans.buttons.item }}" accesskey="k" value="{% trans 'Skip' %}" />
+ {% endifequal %}
+ {% ifequal button 'back' %}
+ <input type="submit" name="back{{ item.trans.buttons.item }}" accesskey="b" value="{% trans 'Back' %}" />
+ {% endifequal %}
+ {% endfor %}
+ <input type="hidden" name="store" value="{{ store }}" />
+ <input type="hidden" name="path" value="{{ store|l }}" />
+ </div>
+ {% if cantranslate %}
+ <div class="translate-fuzzy-block">
+ <input type="checkbox" {% if item.fuzzy %}checked="checked"{% endif %} name="fuzzy{{ item.trans.buttons.item }}" accesskey="f" id="fuzzy{{ item.trans.buttons.item }}" class="fuzzycheck" />
+ <label for="fuzzy{{ item.trans.buttons.item }}">{{ fuzzytext }}</label>
+ </div>
+ {% else %}
+ <div class="translate-fuzzy-block">
+ <input type="checkbox" disabled="disabled" {% if item.fuzzy %}checked="checked"{% endif %} name="fuzzy{{ item.trans.buttons.item }}" id="fuzzy{{ item.trans.buttons.item }}" class="fuzzycheck" />
+ <label for="fuzzy{{ item.trans.buttons.item }}">{{ fuzzytext }}</label>
+ </div>
+ {% endif %}
+ </div>
+
+ {% if item.trans.buttons.specialchars and canedit %}
+ <div class="translate-specialchars-block" lang="{{ language.code }}">
+ {% for specialchar in item.trans.buttons.specialchars %}
+ {% if not specialchar.isspace %}
+ <a onclick="writespecial('{{ specialchar }}', '{{ item.trans.itemid }}'); return false;" href="#">{{ specialchar }}</a>
+ {% else %}
+ <span class="extraspace"> </span>
+ {% endif %}
+ {% endfor %}
+ </div>
+ {% endif %}
+
+ {% if canedit %}
+ <script type="text/javascript">
+ <!--! //We should comment this for old browsers, but then kid doesn't evaluate the expression -->
+ $(document).ready(function(){
+ document.forms.translate['{{ item.trans.focusbox }}'].focus();
+ });
+ <!--! //-->
+ </script>
+ {% endif %}
+ <!--! End: Normal editing -->
+ {% else %}
+ <!--! Normal view mode -->
+ <div dir="{% if LANGUAGE_BIDI %}rtl{% else %}ltr{% endif %}" class="{{ item.state_class }}">
+ {% if not item.trans.isplural %}
+ <div dir="{{ language.dir }}" lang="{{ language.code }}" class="translation-text" id="{{ item.trans.itemid }}">{{ item.trans.text|safe }}</div>
+ {% else %}
+ <div>
+ {% for form in item.trans.forms %}
+ <span class="translation-text-headers" lang="{{ LANGUAGE_CODE }}">{{ form.title }}</span><br />
+ <span class="translation-text">{{ form.text|safe }}</span><br />
+ {% endfor %}
+ </div>
+ {% endif %}
+
+ <a href="#" class="{% if item.hassuggestion %}sugglink{% else %}sugglink hide{% endif %}">
+ <span class='scount'>{{ item.trans.suggtext }}</span>
+ </a>
+ {% if item.trans.suggestions %}
+ <div class="suggestions">
+ <ul class="sugglist">
+ {% for sugg in item.trans.suggestions %}
+ <li>
+ <span class="suggauthor" dir="{% if LANGUAGE_BIDI %}rtl{% else %}ltr{% endif %}">({{ sugg.author }})</span>
+ <ul>
+ {% for form in sugg.forms %}
+ <li>{{ form.value }}</li>
+ {% endfor %}
+ </ul>
+ </li>
+ {% endfor %}
+ </ul>
+ </div>
+ {% endif %}
+ </div>
+ {% endif %}
+ </div>
+ </td>
+ </tr>
+ {% if item.editable %}
+ <tr>
+ {% if canedit %}
+ <td></td>
+ {% endif %}
+ <td class="translate-original {{ item.polarity }} comments">
+ {% if item.developer_comments %}
+ <div title="{{ developer_comments_title }}" class="developer-comments" lang="en" dir="ltr">{{ item.developer_comments|safe }}</div>
+ {% endif %}
+ {% if item.locations %}
+ <div class="translate-locations" lang="en" dir="ltr">{{ item.locations }}</div>
+ {% endif %}
+ </td>
+ <td class="translate-translation {{ item.polarity }} comments" lang="{{ LANGUAGE_CODE }}">
+ <div>{{ translator_comments_title }}</div>
+ <textarea dir="{{ language.dir }}" lang="{{ language.code }}" name="translator_comments{{ item.itemid }}"
+ id="translator_comments{{ item.itemid }}" rows="2"
+ class="comments expanding">{{ item.translator_comments|safe }}</textarea>
+ </td>
+ </tr>
+ {% endif %}
+ {% endfor %}
+ </table>
+ </form>
+ {% endif %}
+
+ {% if pagelinks %}
+ <!--! start/next/previous/end links -->
+ <ul class="translations-nav" lang="{{ LANGUAGE_CODE }}">
+ {% for pagelink in pagelinks %}
+ <li><a href="{{ pagelink.href|l }}">{{ pagelink.text }}</a></li>
+ {% endfor %}
+ </ul>
+ {% endif %}
+ </div>
+ </div>
+</div>
+{% endblock content %}
+
+{% block scripts_extra %}
+{{ block.super }}
+<script src='{{ "js/json2.min.js"|m }}' type="text/javascript"></script>
+<script src='{{ "js/autoexpand.js"|m }}' type="text/javascript"></script>
+<script src='{{ "js/translatepage.js"|m }}' type="text/javascript"></script>
+{% endblock scripts_extra %}
diff --git a/Pootle-2.0.0/local_apps/pootle_app/templates/project/project.html b/Pootle-2.0.0/local_apps/pootle_app/templates/project/project.html
new file mode 100644
index 0000000..c5e3810
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/templates/project/project.html
@@ -0,0 +1,69 @@
+{% extends "project_base.html" %}
+
+{% load i18n baseurl cleanhtml %}
+
+{% get_current_language as LANGUAGE_CODE %}
+{% get_current_language_bidi as LANGUAGE_BIDI %}
+
+{% block title %}
+{{ block.super }} » {{ project.name }}
+{% endblock %}
+
+{% block body.id %}projectindex{% endblock body.id %}
+
+{% block bodyclass %}projectoverview{% endblock %}
+
+{% block breadcrumbs_content %}
+<a href="{%filter l%}/projects/{{ project.code }}/{%endfilter%}">{{ project.name }}</a>
+{% endblock breadcrumbs_content %}
+
+{% block content %}
+<div class="intro">
+ {{ description|safe|linebreaks|clean }}
+</div>
+
+<div class="item-statistics" lang="{{ LANGUAGE_CODE }}">{{ project.stats }}</div>
+
+<div class="module-primary" lang="{{ LANGUAGE_CODE }}">
+ <div class="bd">
+ <table class="sortable stats" id="stats" cellpadding="5" cellspacing="0" width="100%">
+ <thead>
+ <tr>
+ <th class="stats">{{ statsheadings.name }}</th>
+ <th class="stats">{{ statsheadings.progress }}</th>
+ <th class="stats sorttable_numeric">{{ statsheadings.summary }}</th>
+ <th class="stats">{{ statsheadings.totalwords }}</th>
+ <th>{% trans 'Last Activity' %}</th>
+ </tr>
+ </thead>
+ <tbody class="stats">
+ {% for item in languages %}
+ <tr class="item {% cycle 'even' 'odd' %}">
+ {% include "language/item_summary.html" %}
+ <td>{{ item.lastactivity }}</td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ </div>
+</div>
+{% endblock content %}
+
+{% block postcontent %}
+<div class="module first clear" lang="{{ LANGUAGE_CODE }}">
+ <div class="bd">
+ {% include "translation_summary_legend.html" %}
+ </div>
+</div>
+{% include "top_contributers_table.html" %}
+{% endblock postcontent %}
+
+{% block scripts_extra %}
+<script type="text/javascript" src='{{ "js/sorttable.js"|m }}'></script>
+<script type="text/javascript" src='{{ "js/jquery/jquery.bidi.js"|m }}'></script>
+<script type="text/javascript">
+ $(document).ready(function() {
+ $(".intro").filter(":not([dir])").bidi();
+ });
+</script>
+{% endblock %}
diff --git a/Pootle-2.0.0/local_apps/pootle_app/templates/project/project_admin.html b/Pootle-2.0.0/local_apps/pootle_app/templates/project/project_admin.html
new file mode 100644
index 0000000..1b06de5
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/templates/project/project_admin.html
@@ -0,0 +1,26 @@
+{% extends "project_base.html" %}
+
+{% load i18n baseurl %}
+
+{% get_current_language as LANGUAGE_CODE %}
+
+{% block title %}
+{{ block.super }} » {{ project.name }} » {% trans "Administrate" %}
+{% endblock %}
+
+{% block bodyclass %}projectadmin{% endblock %}
+
+{% block breadcrumbs_content %}
+<a href="{% filter l %}/projects/{{ project.code }}/{% endfilter %}">{{ project.name }}</a>
+{% endblock breadcrumbs_content %}
+
+{% block content %}
+<div class="settings-container centered" lang="{{ LANGUAGE_CODE }}">
+ <p>{% trans "Here you can add, edit, or delete the languages for this project." %}</p>
+ {% include "admin/admin_general.html" %}
+</div>
+{% endblock %}
+
+{% block scripts_extra %}
+<script type="text/javascript" src='{{ "js/sorttable.js"|m }}'></script>
+{% endblock %}
diff --git a/Pootle-2.0.0/local_apps/pootle_app/templates/project/projects.html b/Pootle-2.0.0/local_apps/pootle_app/templates/project/projects.html
new file mode 100644
index 0000000..ddd1a46
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/templates/project/projects.html
@@ -0,0 +1,60 @@
+{% extends "base.html" %}
+
+{% load i18n baseurl %}
+
+{% get_current_language as LANGUAGE_CODE %}
+{% get_current_language_bidi as LANGUAGE_BIDI %}
+
+{% block title %}
+{{ block.super }}: {% trans "Projects" %}
+{% endblock %}
+
+{% block body.id %}projectsindex{% endblock body.id %}
+
+{% block content %}
+<div class="module first" lang="{{ LANGUAGE_CODE }}">
+ <div class="hd">
+ <h2>{{ projectlink }}</h2>
+ </div>
+ <div class="bd">
+ <table class="sortable">
+ <tr>
+ <th>{% trans 'Project' %}</th>
+ <th>{% trans 'Overall Completion' %}</th>
+ <th>{% trans 'Last Activity' %}</th>
+ </tr>
+ {% for item in projects %}
+ <tr class="{% cycle 'even' 'odd' %}">
+ <td class="project"><a href="{% filter l %}/projects/{{ item.code }}/{% endfilter %}" title="{{ item.description }}">{{ item.name }}</a></td>
+ <td>
+ <div class="sortkey">{{ item.transper }}</div>
+ <div class="graph" title="{{ item.completed_title }}" dir="{% if LANGUAGE_BIDI %}rtl{% else %}ltr{% endif %}">
+ <div class="translated" style="width: {{ item.transper }}px"></div>
+ {% if item.fuzzy %}
+ <div class="fuzzy" style="{% if LANGUAGE_BIDI %}right{% else %}left{% endif %}: {{ item.transper }}px; width: {{ item.fuzzyper }}px"></div>
+ {% endif %}
+ {% if item.untrans %}
+ <div class="untranslated" style="{% if LANGUAGE_BIDI %}right{% else %}left{% endif %}: {{ item.transper|add:item.fuzzyper }}px; width: {{ item.untransper }}px"></div>
+ {% endif %}
+ </div>
+ </td>
+ <td>{{ item.lastactivity }}</td>
+ </tr>
+ {% endfor %}
+ </table>
+ </div>
+</div>
+{% endblock content %}
+
+{% block postcontent %}
+<div class="module first clear" lang="{{ LANGUAGE_CODE }}">
+ <div class="bd">
+ {% include "translation_summary_legend.html" %}
+ </div>
+</div>
+{% include "top_contributers_table.html" %}
+{% endblock postcontent %}
+
+{% block scripts_extra %}
+<script type="text/javascript" src='{{ "js/sorttable.js"|m }}'></script>
+{% endblock %}
diff --git a/Pootle-2.0.0/local_apps/pootle_app/templates/top_contributers_table.html b/Pootle-2.0.0/local_apps/pootle_app/templates/top_contributers_table.html
new file mode 100644
index 0000000..65108be
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/templates/top_contributers_table.html
@@ -0,0 +1,25 @@
+{% load i18n baseurl %}
+
+{% get_current_language as LANGUAGE_CODE %}
+
+{% block top_contributers_table %}
+<div class="module-primary clear topcontributors" lang="{{ LANGUAGE_CODE }}">
+ <div class="hd"><h2>{% trans 'Top Contributors' %}</h2></div>
+ <div class="bd">
+ {% for stats in topstats %}
+ <table class="topcontributors">
+ <tr>
+ <th>{{ stats.headerlabel }}</th>
+ </tr>
+ {% for user in stats.data %}
+ <tr class="{% cycle 'even' 'odd' %}">
+ <td class="stats-name">{{ user.username }}</td>
+ <td class="stats-words">{{ user.num_contribs }}</td>
+ </tr>
+ {% endfor %}
+ </table>
+ {% endfor %}
+ </div>
+ <div class="clear"></div>
+</div>
+{% endblock top_contributers_table %}
diff --git a/Pootle-2.0.0/local_apps/pootle_app/templates/translation_summary_legend.html b/Pootle-2.0.0/local_apps/pootle_app/templates/translation_summary_legend.html
new file mode 100644
index 0000000..ba90761
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/templates/translation_summary_legend.html
@@ -0,0 +1,11 @@
+{% load i18n baseurl %}
+
+{% block translation_summary_legend %}
+<!-- translation_summary_legend -->
+<div id="translationsummarylegend">
+ <div><img src='{{ "images/green-bar.png"|m }}' alt="" />{% trans 'Translated' %}</div>
+ <div><img src='{{ "images/purple-bar.png"|m }}' alt="" />{% trans 'Needs review' %}</div>
+ <div><img src='{{ "images/red-bar.png"|m }}' alt="" />{% trans 'Untranslated' %}</div>
+</div>
+<!-- /translation_summary_legend -->
+{% endblock translation_summary_legend %}
diff --git a/Pootle-2.0.0/local_apps/pootle_app/tests.py b/Pootle-2.0.0/local_apps/pootle_app/tests.py
new file mode 100644
index 0000000..f128622
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/tests.py
@@ -0,0 +1,534 @@
+import tempfile
+import shutil
+import urlparse
+import os
+import StringIO
+import zipfile
+
+from translate.misc import wStringIO
+
+from django.conf import settings
+from django.test import TestCase
+from django.http import QueryDict
+from django.core.management import call_command
+from django.contrib.auth.models import User
+from pootle_app.models.translation_project import scan_translation_projects
+from pootle_store.models import fs
+from pootle_app.models import Project, Language
+from pootle_store.models import Store
+
+def formset_dict(data):
+ """convert human readable POST dictionary into brain dead django formset dictionary"""
+ new_data = {'form-TOTAL_FORMS': len(data), 'form-INITIAL_FORMS': 0}
+ for i in range(len(data)):
+ for key, value in data[i].iteritems():
+ new_data["form-%d-%s" % (i, key)] = value
+ return new_data
+
+class PootleTestCase(TestCase):
+ """Base TestCase class, set's up a pootle environment with a
+ couple of test files and projects"""
+
+
+ def _setup_test_podir(self):
+ self.testpodir = tempfile.mkdtemp()
+ settings.PODIRECTORY = self.testpodir
+ fs.location = self.testpodir
+
+ gnu = os.path.join(self.testpodir, "terminology")
+ os.mkdir(gnu)
+ potfile = file(os.path.join(gnu, "terminology.pot"), 'w')
+ potfile.write('#: test.c\nmsgid "test"\nmsgstr ""\n')
+ potfile.close()
+ pofile = file(os.path.join(gnu, "ar.po"), 'w')
+ pofile.write('#: test.c\nmsgid "test"\nmsgstr "rest"\n')
+ pofile.close()
+
+ nongnu = os.path.join(self.testpodir, "pootle")
+ os.mkdir(nongnu)
+ nongnu_ar = os.path.join(nongnu, "ar")
+ os.mkdir(nongnu_ar)
+ nongnu_ja = os.path.join(nongnu, "ja")
+ os.mkdir(nongnu_ja)
+ nongnu_af = os.path.join(nongnu, "af")
+ os.mkdir(nongnu_af)
+ pofile = file(os.path.join(nongnu_af, "pootle.po"), 'w')
+ pofile.write('''#: fish.c
+msgid "fish"
+msgstr ""
+
+#: test.c
+msgid "test"
+msgstr "rest"
+
+''')
+ pofile.close()
+
+
+ def _setup_test_users(self):
+ nonpriv = User(username=u"nonpriv",
+ first_name="Non privileged test user",
+ is_active=True)
+ nonpriv.set_password("nonpriv")
+ nonpriv.save()
+
+ def _teardown_test_podir(self):
+ shutil.rmtree(self.testpodir)
+
+ def setUp(self):
+ self._setup_test_podir()
+
+ #FIXME: replace initdb with a fixture
+ call_command('initdb')
+
+ self._setup_test_users()
+ scan_translation_projects()
+
+ def tearDown(self):
+ self._teardown_test_podir()
+
+
+ def follow_redirect(self, response):
+ """follow a redirect chain until a non redirect response is recieved"""
+ new_response = response
+ while new_response.status_code in (301, 302, 303, 307):
+ scheme, netloc, path, query, fragment = urlparse.urlsplit(new_response['location'])
+ new_response = self.client.get(path, QueryDict(query))
+ return new_response
+
+class AnonTests(PootleTestCase):
+ def test_login(self):
+ """Checks that login works and sets cookies"""
+ response = self.client.get('/')
+ self.assertContains(response, "Log In")
+
+ response = self.client.post('/accounts/login/', {'username':'admin', 'password':'admin'})
+ self.assertRedirects(response, '/accounts/admin/')
+
+ def test_admin_not_logged(self):
+ """checks that admin pages are not accessible without login"""
+ response = self.client.get("/admin/")
+ self.assertContains(response, '', status_code=403)
+
+
+class AdminTests(PootleTestCase):
+ def setUp(self):
+ super(AdminTests, self).setUp()
+ self.client.login(username='admin', password='admin')
+
+ def test_logout(self):
+ """tests login and logout links"""
+ response = self.client.get('/')
+ self.assertContains(response, "Log Out")
+
+ response = self.client.get("/accounts/logout/")
+ self.assertRedirects(response, '/')
+
+ response = self.client.get('/')
+ self.assertContains(response, "Log In")
+
+ def test_admin_rights(self):
+ """checks that admin user can access admin pages"""
+ response = self.client.get('/')
+ self.assertContains(response, "<a href='/admin/'>Admin</a>")
+ response = self.client.get('/admin/')
+ self.assertContains(response, 'General Settings')
+
+ def test_add_project(self):
+ """Checks that we can add a project successfully."""
+
+ response = self.client.get("/admin/projects.html")
+ self.assertContains(response, "<a href='/projects/pootle/admin.html'>pootle</a>")
+ self.assertContains(response, "<a href='/projects/terminology/admin.html'>terminology</a>")
+
+ add_dict = {
+ "code": "testproject",
+ "localfiletype": "xlf",
+ "fullname": "Test Project",
+ "checkstyle": "standard",
+ "treestyle": "gnu",
+ }
+
+ response = self.client.post("/admin/projects.html", formset_dict([add_dict]))
+ self.assertContains(response, "<a href='/projects/testproject/admin.html'>testproject</a>")
+
+ # check for the actual model
+ testproject = Project.objects.get(code="testproject")
+
+ self.assertTrue(testproject)
+ self.assertEqual(testproject.fullname, add_dict['fullname'])
+ self.assertEqual(testproject.checkstyle, add_dict['checkstyle'])
+ self.assertEqual(testproject.localfiletype, add_dict['localfiletype'])
+ self.assertEqual(testproject.treestyle, add_dict['treestyle'])
+
+ def test_add_project_language(self):
+ """Tests that we can add a language to a project, then access
+ its page when there are no files."""
+ fish = Language(code="fish", fullname="fish")
+ fish.save()
+
+ response = self.client.get("/projects/pootle/admin.html")
+ self.assertContains(response, "fish")
+
+ project = Project.objects.get(code='pootle')
+ add_dict = {
+ "language": fish.id,
+ "project": project.id,
+ }
+ response = self.client.post("/projects/pootle/admin.html", formset_dict([add_dict]))
+ self.assertContains(response, '/fish/pootle/')
+
+ response = self.client.get("/fish/")
+ self.assertContains(response, '<a href="/fish/">fish</a>')
+ self.assertContains(response, '<a href="/fish/pootle/">Pootle</a>')
+ self.assertContains(response, "1 project, 0% translated")
+
+ def test_upload_new_file(self):
+ """Tests that we can upload a new file into a project."""
+ pocontent = StringIO.StringIO('#: test.c\nmsgid "test"\nmsgstr "rest"\n')
+ pocontent.name = "test_new_upload.po"
+
+ post_dict = {
+ 'file': pocontent,
+ 'overwrite': 'merge',
+ 'do_upload': 'upload',
+ }
+ response = self.client.post("/ar/pootle/", post_dict)
+
+ self.assertContains(response, 'href="/ar/pootle/test_new_upload.po')
+ store = Store.objects.get(pootle_path="/ar/pootle/test_new_upload.po")
+ self.assertTrue(os.path.isfile(store.file.path))
+ self.assertEqual(store.file.read(), pocontent.getvalue())
+
+ download = self.client.get("/ar/pootle/test_new_upload.po/export/po")
+ self.assertEqual(download.content, pocontent.getvalue())
+
+ def test_upload_suggestions(self):
+ """Tests that we can upload when we only have suggest rights."""
+ pocontent = StringIO.StringIO('#: test.c\nmsgid "test"\nmsgstr "samaka"\n')
+ pocontent.name = "pootle.po"
+
+ post_dict = {
+ 'file': pocontent,
+ 'overwrite': 'merge',
+ 'do_upload': 'upload',
+ }
+ response = self.client.post("/af/pootle/", post_dict)
+
+ # Check that the orignal file didn't take the new suggestion.
+ # We test with 'in' since the header is added
+ store = Store.objects.get(pootle_path="/af/pootle/pootle.po")
+ self.assertFalse('msgstr "samaka"' in store.file.read())
+ self.assertTrue('msgstr "samaka"' in store.pending.read())
+
+ def test_upload_overwrite(self):
+ """Tests that we can overwrite a file in a project."""
+ pocontent = StringIO.StringIO('#: test.c\nmsgid "fish"\nmsgstr ""\n#: test.c\nmsgid "test"\nmsgstr "barf"\n\n')
+ pocontent.name = "pootle.po"
+
+ post_dict = {
+ 'file': pocontent,
+ 'overwrite': 'overwrite',
+ 'do_upload': 'upload',
+ }
+ response = self.client.post("/af/pootle/", post_dict)
+
+ # Now we only test with 'in' since the header is added
+ store = Store.objects.get(pootle_path="/af/pootle/pootle.po")
+ self.assertEqual(store.file.read(), pocontent.getvalue())
+
+ def test_upload_new_archive(self):
+ """Tests that we can upload a new archive of files into a project."""
+ po_content_1 = '#: test.c\nmsgid "test"\nmsgstr "rest"\n'
+ po_content_2 = '#: frog.c\nmsgid "tadpole"\nmsgstr "fish"\n'
+
+ archivefile = wStringIO.StringIO()
+ archivefile.name = "fish.zip"
+ archive = zipfile.ZipFile(archivefile, "w", zipfile.ZIP_DEFLATED)
+ archive.writestr("test_archive_1.po", po_content_1)
+ archive.writestr("test_archive_2.po", po_content_2)
+ archive.close()
+
+ archivefile.seek(0)
+ post_dict = {
+ 'file': archivefile,
+ 'overwrite': 'merge',
+ 'do_upload': 'upload',
+ }
+ response = self.client.post("/ar/pootle/", post_dict)
+
+ self.assertContains(response, 'href="/ar/pootle/test_archive_1.po')
+ self.assertContains(response, 'href="/ar/pootle/test_archive_2.po')
+
+ store = Store.objects.get(pootle_path="/ar/pootle/test_archive_1.po")
+ self.assertTrue(os.path.isfile(store.file.path))
+ self.assertEqual(store.file.read(), po_content_1)
+
+ download = self.client.get("/ar/pootle/test_archive_2.po/export/po")
+ self.assertEqual(po_content_2, download.content)
+
+
+ def test_upload_over_file(self):
+ """Tests that we can upload a new version of a file into a project."""
+ pocontent = StringIO.StringIO('''#: fish.c
+msgid "fish"
+msgstr ""
+
+#: test.c
+msgid "test"
+msgstr "resto"
+
+''')
+ pocontent.name = "pootle.po"
+ post_dict = {
+ 'file': pocontent,
+ 'overwrite': 'overwrite',
+ 'do_upload': 'upload',
+ }
+ response = self.client.post("/af/pootle/", post_dict)
+
+ pocontent = StringIO.StringIO('#: test.c\nmsgid "test"\nmsgstr "blo3"\n\n#: fish.c\nmsgid "fish"\nmsgstr "stink"\n')
+ pocontent.name = "pootle.po"
+
+ post_dict = {
+ 'file': pocontent,
+ 'overwrite': 'merge',
+ 'do_upload': 'upload',
+ }
+ response = self.client.post("/af/pootle/", post_dict)
+
+ # NOTE: this is what we do currently: any altered strings become suggestions.
+ # It may be a good idea to change this
+ mergedcontent = '#: fish.c\nmsgid "fish"\nmsgstr "stink"\n'
+ suggestedcontent = '#: test.c\nmsgid ""\n"_: suggested by admin [1963585124]\\n"\n"test"\nmsgstr "blo3"\n'
+ store = Store.objects.get(pootle_path="/af/pootle/pootle.po")
+ self.assertTrue(store.file.read().find(mergedcontent) >= 0)
+ self.assertTrue(os.path.isfile(store.pending.path))
+ self.assertTrue(store.pending.read().find(suggestedcontent) >= 0)
+
+
+ def test_upload_new_xliff_file(self):
+ """Tests that we can upload a new XLIFF file into a project."""
+ xliffcontent = StringIO.StringIO('''<?xml version='1.0' encoding='utf-8'?>
+ <xliff xmlns="urn:oasis:names:tc:xliff:document:1.1" version="1.1">
+ <file original="" source-language="en-US" datatype="po">
+ <body>
+ <trans-unit id="1" xml:space="preserve">
+ <source>test</source>
+ <target state="needs-review-translation">rest</target>
+ <context-group name="po-reference" purpose="location">
+ <context context-type="sourcefile">test.c</context>
+ </context-group>
+ </trans-unit>
+ </body>
+ </file>
+ </xliff>
+''')
+ xliffcontent.name = 'test_new_xliff_upload.xlf'
+
+ post_dict = {
+ 'file': xliffcontent,
+ 'overwrite': 'overwrite',
+ 'do_upload': 'upload',
+ }
+
+ response = self.client.post("/ar/pootle/", post_dict)
+ self.assertContains(response,' href="/ar/pootle/test_new_xliff_upload.po')
+
+ #FIXME: test conversion?
+
+ def test_upload_xliff_over_file(self):
+ """Tests that we can upload a new version of a XLIFF file into a project."""
+ pocontent = StringIO.StringIO('#: test.c\nmsgid "test"\nmsgstr "rest"\n\n#: frog.c\nmsgid "tadpole"\nmsgstr "fish"\n')
+ pocontent.name = "test_upload_xliff.po"
+ post_dict = {
+ 'file': pocontent,
+ 'overwrite': 'overwrite',
+ 'do_upload': 'upload',
+ }
+ response = self.client.post("/ar/pootle/", post_dict)
+
+ xlfcontent = StringIO.StringIO('''<?xml version="1.0" encoding="utf-8"?>
+ <xliff version="1.1" xmlns="urn:oasis:names:tc:xliff:document:1.1">
+ <file datatype="po" original="test_existing.po" source-language="en-US">
+ <body>
+ <trans-unit id="test" xml:space="preserve" approved="yes">
+ <source>test</source>
+ <target state="translated">rested</target>
+ <context-group name="po-reference" purpose="location">
+ <context context-type="sourcefile">test.c</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="slink" xml:space="preserve" approved="yes">
+ <source>slink</source>
+ <target state="translated">stink</target>
+ <context-group name="po-reference" purpose="location">
+ <context context-type="sourcefile">toad.c</context>
+ </context-group>
+ </trans-unit>
+ </body>
+ </file>
+ </xliff>''')
+ xlfcontent.name = "test_upload_xliff.xlf"
+
+ post_dict = {
+ 'file': xlfcontent,
+ 'overwrite': 'merge',
+ 'do_upload': 'upload',
+ }
+ response = self.client.post("/ar/pootle/", post_dict)
+
+ # NOTE: this is what we do currently: any altered strings become suggestions.
+ # It may be a good idea to change this
+ mergedcontent = '#: test.c\nmsgid "test"\nmsgstr "rest"\n\n#~ msgid "tadpole"\n#~ msgstr "fish"\n\n#: toad.c\nmsgid "slink"\nmsgstr "stink"\n'
+ suggestedcontent = '#: test.c\nmsgid ""\n"_: suggested by admin [595179475]\\n"\n"test"\nmsgstr "rested"\n'
+ store = Store.objects.get(pootle_path="/ar/pootle/test_upload_xliff.po")
+
+ self.assertTrue(os.path.isfile(store.file.path))
+ self.assertTrue(store.file.read().find(mergedcontent) >= 0)
+
+ self.assertTrue(os.path.isfile(store.pending.path))
+ self.assertTrue(store.pending.read().find(suggestedcontent) >= 0)
+
+
+ def test_submit_translation(self):
+ """Tests that we can translate units."""
+
+ submit_dict = {
+ 'trans0': 'submitted translation',
+ 'submit0': 'Submit',
+ 'store': '/af/pootle/pootle.po',
+ }
+ submit_dict.update(formset_dict([]))
+ response = self.client.post("/af/pootle/pootle.po", submit_dict,
+ QUERY_STRING='view_mode=translate')
+
+ self.assertContains(response, 'submitted translation')
+
+ store = Store.objects.get(pootle_path="/af/pootle/pootle.po")
+ self.assertTrue(store.file.read().find('submitted translation') >= 0)
+
+ def test_submit_plural_translation(self):
+ """Tests that we can submit a translation with plurals."""
+ pocontent = StringIO.StringIO('msgid "singular"\nmsgid_plural "plural"\nmsgstr[0] ""\nmsgstr[1] ""\n')
+ pocontent.name = 'test_plural_submit.po'
+
+ post_dict = {
+ 'file': pocontent,
+ 'overwrite': 'overwrite',
+ 'do_upload': 'upload',
+ }
+ response = self.client.post("/ar/pootle/", post_dict)
+
+ submit_dict = {
+ 'trans0-0': 'a fish',
+ 'trans0-1': 'some fish',
+ 'trans0-2': 'lots of fish',
+ 'submit0': 'Submit',
+ 'store': '/ar/pootle/test_plural_submit.po',
+ }
+ submit_dict.update(formset_dict([]))
+ response = self.client.post("/ar/pootle/test_plural_submit.po", submit_dict,
+ QUERY_STRING='view_mode=translate')
+
+ self.assertContains(response, 'a fish')
+ self.assertContains(response, 'some fish')
+ self.assertContains(response, 'lots of fish')
+
+ def test_submit_plural_to_singular_lang(self):
+ """Tests that we can submit a translation with plurals to a language without plurals."""
+
+ pocontent = StringIO.StringIO('msgid "singular"\nmsgid_plural "plural"\nmsgstr[0] ""\nmsgstr[1] ""\n')
+ pocontent.name = 'test_plural_submit.po'
+
+ post_dict = {
+ 'file': pocontent,
+ 'overwrite': 'overwrite',
+ 'do_upload': 'upload',
+ }
+ response = self.client.post("/ja/pootle/", post_dict)
+
+ submit_dict = {
+ 'trans0': 'just fish',
+ 'submit0': 'Submit',
+ 'store': '/ja/pootle/test_plural_submit.po',
+ }
+ submit_dict.update(formset_dict([]))
+ response = self.client.post("/ja/pootle/test_plural_submit.po", submit_dict,
+ QUERY_STRING='view_mode=translate')
+
+ self.assertContains(response, 'just fish')
+
+ expectedcontent = 'msgid "singular"\nmsgid_plural "plural"\nmsgstr[0] "just fish"\n'
+ store = Store.objects.get(pootle_path="/ja/pootle/test_plural_submit.po")
+ self.assertTrue(store.file.read().find(expectedcontent) >= 0)
+
+
+ def test_submit_fuzzy(self):
+ """Tests that we can mark a unit as fuzzy."""
+
+ # Fetch the page and check that the fuzzy checkbox is NOT checked.
+
+ response = self.client.get("/af/pootle/pootle.po", {'view_mode': 'translate'})
+ self.assertContains(response, '<input type="checkbox" name="fuzzy0" accesskey="f" id="fuzzy0" class="fuzzycheck" />')
+
+ submit_dict = {
+ 'trans0': 'fuzzy translation',
+ 'fuzzy0': 'on',
+ 'submit0': 'Submit',
+ 'store': '/af/pootle/pootle.po',
+ }
+ submit_dict.update(formset_dict([]))
+ response = self.client.post("/af/pootle/pootle.po", submit_dict,
+ QUERY_STRING='view_mode=translate')
+ # Fetch the page again and check that the fuzzy checkbox IS checked.
+ response = self.client.get("/af/pootle/pootle.po", {'view_mode': 'translate'})
+ self.assertContains(response, '<input type="checkbox" checked="checked" name="fuzzy0" accesskey="f" id="fuzzy0" class="fuzzycheck" />')
+
+ store = Store.objects.get(pootle_path="/af/pootle/pootle.po")
+ self.assertTrue(store.file.getitem(0).isfuzzy())
+
+ # Submit the translation again, without the fuzzy checkbox checked
+ submit_dict = {
+ 'trans0': 'fuzzy translation',
+ 'fuzzy0': '',
+ 'submit0': 'Submit',
+ 'store': '/af/pootle/pootle.po',
+ }
+ submit_dict.update(formset_dict([]))
+ response = self.client.post("/af/pootle/pootle.po", submit_dict,
+ QUERY_STRING='view_mode=translate')
+ # Fetch the page once more and check that the fuzzy checkbox is NOT checked.
+ response = self.client.get("/af/pootle/pootle.po", {'view_mode': 'translate'})
+ self.assertContains(response, '<input type="checkbox" name="fuzzy0" accesskey="f" id="fuzzy0" class="fuzzycheck" />')
+ self.assertFalse(store.file.getitem(0).isfuzzy())
+
+ def test_submit_translator_comments(self):
+ """Tests that we can edit translator comments."""
+
+ submit_dict = {
+ 'trans0': 'fish',
+ 'translator_comments0': 'goodbye\nand thanks for all the fish',
+ 'submit0': 'Submit',
+ 'store': '/af/pootle/pootle.po',
+ }
+ submit_dict.update(formset_dict([]))
+ response = self.client.post("/af/pootle/pootle.po", submit_dict,
+ QUERY_STRING='view_mode=translate')
+
+ store = Store.objects.get(pootle_path='/af/pootle/pootle.po')
+ self.assertEqual(store.file.getitem(0).getnotes(), 'goodbye\nand thanks for all the fish')
+
+
+class NonprivTests(PootleTestCase):
+ def setUp(self):
+ super(NonprivTests, self).setUp()
+ self.client.login(username='nonpriv', password='nonpriv')
+
+ def test_non_admin_rights(self):
+ """checks that non privileged users cannot access admin pages"""
+ response = self.client.get('/admin/')
+ self.assertContains(response, '', status_code=403)
+
+
+
diff --git a/Pootle-2.0.0/local_apps/pootle_app/unit_update.py b/Pootle-2.0.0/local_apps/pootle_app/unit_update.py
new file mode 100644
index 0000000..4125d02
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/unit_update.py
@@ -0,0 +1,113 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of translate.
+#
+# translate is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# translate is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with translate; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import datetime
+
+from django.utils.translation import ugettext as _
+from django.core.exceptions import PermissionDenied
+
+from pootle_app.models import Suggestion, Submission
+from pootle_app.models.profile import get_profile
+from pootle_app.models.permissions import check_permission
+
+def _suggestion_hash(store, item, trans):
+ # since django's IntegerField is always 32 bit on mysql we cast to
+ # make sure we don't pass larger hashes
+ return int(hash((store.pootle_path, item, unicode(trans))) & 0xfffffff)
+
+def suggest_translation(store, item, trans, request):
+ if not check_permission("suggest", request):
+ raise PermissionDenied(_("You do not have rights to suggest changes here"))
+ translation_project = request.translation_project
+ s = Suggestion(
+ creation_time = datetime.datetime.utcnow(),
+ translation_project = translation_project,
+ suggester = get_profile(request.user),
+ unit = _suggestion_hash(store, item, trans),
+ state = 'pending',
+ )
+ s.save()
+ store.addsuggestion(item, trans, s.suggester.user.username,
+ translation_project.checker)
+ #FIXME: we don't handle identical suggestions
+
+
+def update_translation(store, item, newvalues, request, suggestion=None):
+ """updates a translation with a new value..."""
+
+ if not check_permission("translate", request):
+ raise PermissionDenied(_("You do not have rights to change translations here"))
+
+ translation_project = request.translation_project
+
+ s = Submission(
+ creation_time = datetime.datetime.utcnow(),
+ translation_project = translation_project,
+ submitter = get_profile(request.user),
+ from_suggestion = suggestion,
+ )
+ try:
+ s.save()
+ except:
+ # FIXME: making from_suggestion OneToOne was a mistake since
+ # we can't distinguish between identical suggestions.
+ pass
+
+ store.file.updateunit(item, newvalues, translation_project.checker,
+ user=request.user, language=translation_project.language)
+ translation_project.update_index(translation_project.indexer, store, [item])
+
+
+def update_suggestion(state, store, item, newtrans, request):
+ """Marks the suggestion specified by the parameters with the given status,
+ and returns that suggestion object"""
+ translation_project = request.translation_project
+ suggestion, created = Suggestion.objects.get_or_create(translation_project=translation_project,
+ unit=_suggestion_hash(store, item, newtrans))
+ suggestion.state = state
+ suggestion.reviewer = get_profile(request.user)
+ suggestion.review_time = datetime.datetime.utcnow()
+ suggestion.save()
+ return suggestion
+
+
+def reject_suggestion(store, item, suggitem, newtrans, request):
+ """rejects the suggestion and removes it from the pending file"""
+ if not check_permission("review", request):
+ raise PermissionDenied(_("You do not have rights to review suggestions here"))
+
+ update_suggestion('rejected', store, item, newtrans, request)
+ # Deletes the suggestion from the .pending file
+ store.deletesuggestion(item, suggitem, newtrans,
+ request.translation_project.checker)
+
+def accept_suggestion(store, item, suggitem, newtrans, request):
+ """accepts the suggestion into the main pofile"""
+ if not check_permission("review", request):
+ raise PermissionDenied(_("You do not have rights to review suggestions here"))
+
+ suggestion = update_suggestion('accepted', store, item, newtrans, request)
+
+ new_values = {"target": newtrans, "fuzzy": False}
+ update_translation(store, item, new_values, request, suggestion)
+ store.deletesuggestion(item, suggitem, newtrans,
+ request.translation_project.checker)
+
diff --git a/Pootle-2.0.0/local_apps/pootle_app/url_manip.py b/Pootle-2.0.0/local_apps/pootle_app/url_manip.py
new file mode 100644
index 0000000..a00f061
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/url_manip.py
@@ -0,0 +1,87 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+import urllib
+
+def strip_trailing_slash(path):
+ """If path ends with a /, strip it and return the stripped version."""
+ if len(path) > 0 and path[-1] == '/':
+ return path[:-1]
+ else:
+ return path
+
+def add_trailing_slash(path):
+ """If path does not end with /, add it and return."""
+ if len(path) > 0 and path[-1] == '/':
+ return path
+ else:
+ return path + '/'
+
+
+def url_split(path):
+ try:
+ slash_pos = strip_trailing_slash(path).rindex('/')
+ return path[:slash_pos+1], path[slash_pos+1:]
+ except ValueError:
+ return '', path
+
+def split_trailing_slash(p):
+ if p[-1] == u'/':
+ return p[:-1], p[-1]
+ else:
+ return p, u''
+
+def get_relative(ref_path, abs_path):
+ def get_last_agreement(ref_chain, abs_chain):
+ max_pos = min(len(ref_chain), len(abs_chain))
+ for i in xrange(max_pos):
+ if ref_chain[i] != abs_chain[i]:
+ return i
+ return max_pos
+
+ abs_path, abs_slash = split_trailing_slash(abs_path)
+
+ ref_chain = ref_path.split('/')
+ ref_chain.pop()
+
+ abs_chain = abs_path.split('/')
+
+ cut_pos = get_last_agreement(ref_chain, abs_chain)
+ go_up = (len(ref_chain) - cut_pos) * ['..']
+ go_down = abs_chain[cut_pos:]
+ result = u'/'.join(go_up + go_down)
+ if result == '' and abs_slash != '':
+ return './'
+ else:
+ return result + abs_slash
+
+def parent(url):
+ parent_part, _child_part = url_split(url)
+ return parent_part
+
+def make_url(url, args={}):
+ if len(args) > 0:
+ return u'%s?%s' % (url, urllib.urlencode(sorted(args.iteritems())))
+ else:
+ return url
+
+def basename(url):
+ _parent_part, child_part = url_split(url)
+ return child_part
diff --git a/Pootle-2.0.0/local_apps/pootle_app/url_state.py b/Pootle-2.0.0/local_apps/pootle_app/url_state.py
new file mode 100644
index 0000000..125c32c
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/url_state.py
@@ -0,0 +1,270 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# Pootle is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# Pootle is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Pootle; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+class Value(object):
+ """Python descriptor for marshalling GET/POST state into Python
+ variables and back.
+
+ The descriptor is given a name (see __init__) which it uses to
+ read its state from a dictionary (containing GET/POST variables).
+
+ The descriptor stores its state within the object from which it is
+ invoked using its own name prefix with an underscore (so if x is a
+ descripor in class Foo, and foo is an instance of Foo, then the
+ actual value of x is stored in foo._x). If its state is missing
+ from its host object (i.e. if _x is not an attribute of foo), the
+ descriptor will return its default value (so foo.x will give a
+ default value).
+
+ This is a base class which will marshal strings from and to a
+ GET/POST dictionary.
+
+ How-To Guide for descriptors: http://users.rcn.com/python/download/Descriptor.htm
+ """
+ default_value = None
+
+ def __init__(self, var_name):
+ self.var_name = var_name
+
+ def _member_name(self):
+ """Return the name that we'll use to store this descriptor's
+ data under in its host object. A decriptor foo will have its
+ data stored under _foo."""
+ return '_' + self.var_name
+
+ def __get__(self, obj, type=None):
+ return getattr(obj, self._member_name(), self.default_value)
+
+ def __set__(self, obj, value):
+ setattr(obj, self._member_name(), value)
+
+ def __delete__(self, obj):
+ delattr(obj, self._member_name())
+
+ def add_to_dict(self, obj, dct):
+ """If the descriptor's value is not equal to its default
+ value, then encode it to a string and store it in the GET/POST
+ dictionary C{dct}."""
+ value = self.__get__(obj)
+ if value != self.default_value:
+ dct[self.var_name] = self._encode(value)
+
+ def read_from_params(self, obj, params):
+ """Read the value of this descriptor from the GET/POST
+ dictionary C{params}, decode it and use it to set the
+ descriptor value."""
+ try:
+ self.__set__(obj, self._decode(params[self.var_name]))
+ except KeyError:
+ pass
+
+ def _decode(self, value):
+ return value
+
+ def _encode(self, value):
+ return value
+
+ def __repr__(self):
+ return "Value<%s>" % self.var_name
+
+class BooleanValue(Value):
+ default_value = False
+
+ def _decode(self, value):
+ if value == 'True':
+ return True
+ else:
+ return False
+
+ def _encode(self, value):
+ if value:
+ return 'True'
+ else:
+ return 'False'
+
+class IntValue(Value):
+ """Read a GET/POST variable into an integer. The descriptor's
+ default value is supplied when initializing an IntValue. This
+ value is used when the descriptor has no state stored in its host
+ object or when the GET/POST variable doesn't decode to an integer.
+
+ >>> class Foo(object):
+ ... bar = IntValue('bar', 1)
+ >>> foo = Foo()
+ >>> foo.bar
+ 1
+ >>> foo.__class__.bar._decode('10')
+ 10
+ >>> foo.__class__.bar._decode('a')
+ 1
+ >>> foo.__class__.bar._encode(20)
+ '20'
+ >>> foo.__class__.bar.read_from_params(foo, {'bar': '5'})
+ >>> foo.bar
+ 5
+ >>> get_vars = {}
+ >>> foo.__class__.bar.add_to_dict(foo, get_vars)
+ >>> get_vars
+ {'bar': '5'}
+ """
+ def __init__(self, var_name, default_value):
+ super(IntValue, self).__init__(var_name)
+ self.default_value = default_value
+
+ def _decode(self, value):
+ try:
+ return int(value)
+ except ValueError:
+ return self.default_value
+
+ def _encode(self, value):
+ assert isinstance(value, int)
+ return str(value)
+
+class ChoiceValue(Value):
+ """Read a string GET/POST variable ensuring that it matches one of
+ the choices specified when constructing this object. If not, set
+ this descriptor value to the first choice.
+
+ >>> class Foo(object):
+ ... bar = ChoiceValue('bar', ('chocolate', 'strawberry', 'caramel'))
+ >>> foo = Foo()
+ >>> foo.__class__.bar._decode('strawberry')
+ 'strawberry'
+ >>> foo.__class__.bar._decode('banana')
+ 'chocolate'
+ """
+
+ def __init__(self, var_name, choices):
+ super(ChoiceValue, self).__init__(var_name)
+ self.default_value = choices[0]
+ self._choices = choices
+
+ def _decode(self, value):
+ if value in self._choices:
+ return value
+ else:
+ return self.default_value
+
+ def _encode(self, value):
+ if value in self._choices:
+ return value
+ else:
+ return self.default_value
+
+class ListValue(Value):
+ """Read a comma separated GET/POST variable into a Python list of
+ strings.
+
+ >>> class Foo(object):
+ ... bar = ListValue('bar')
+ >>> foo = Foo()
+ >>> foo.__class__.bar._decode('a,b,c')
+ ['a', 'b', 'c']
+ >>> foo.__class__.bar._encode(['a', 'b', 'c'])
+ 'a,b,c'
+ >>> foo.__class__.bar.read_from_params(foo, {'bar': 'x,y,z'})
+ >>> foo.bar
+ ['x', 'y', 'z']
+ >>> get_vars = {}
+ >>> foo.__class__.bar.add_to_dict(foo, get_vars)
+ >>> get_vars
+ {'bar': 'x,y,z'}
+ """
+ default_value = []
+
+ def _decode(self, value):
+ if value in ('', None):
+ return []
+ else:
+ return value.split(',')
+
+ def _encode(self, value):
+ return ','.join(str(item) for item in value)
+
+def get_descriptors(cls, descriptors, visited):
+ """Enumerate a class and all its subclasses in a depth-first post
+ order traversal and collect all the Python descriptors into the
+ list 'descriptors'"""
+ for base in cls.__bases__:
+ if base not in visited:
+ visited.add(base)
+ descriptors = get_descriptors(base, descriptors, visited)
+ descriptors.extend(descriptor for descriptor in cls.__dict__.itervalues()
+ if isinstance(descriptor, Value))
+ return descriptors
+
+class State(object):
+ """Base class for classes which, using any of the *Value classes
+ as descriptors, will read GET or POST variables intelligently.
+
+ To see an example implementation, look for TranslatePageState."""
+
+ def get_descriptors(self):
+ """Return a list of all the *Value descriptors (all defined
+ above) that are defined in the class of the current object as
+ well as its superclasses.
+
+ Thus, if we have the following subclass:
+
+ >>> class FooState(State):
+ ... bar = IntValue('bar', 0)
+ ... baz = ListValue('baz')
+ >>> state = FooState()
+ >>> sorted(state.get_descriptors())
+ ['bar', 'baz']
+ """
+ return get_descriptors(self.__class__, [], set())
+
+ def iter_items(self):
+ """Iterate through all the *Value descriptors returning
+ (descriptor name, descriptor value) pairs.
+
+ >>> class FooState(State):
+ ... bar = IntValue('bar', 0)
+ ... baz = ListValue('baz')
+ >>> state = FooState(bar=1, baz=['a', 'b', 'c'])
+ >>> sorted(list(state.iter_items()))
+ [('bar', 1), ('baz', ['a', 'b', 'c'])]
+ """
+ for member in self.get_descriptors():
+ yield member.var_name, getattr(self, member.var_name)
+
+
+ def __init__(self, data={}, **initial):
+ """Initialize a state object possibly reading initial state
+ from C{data} (which is in raw text) and overriding those
+ values with the keyword parameters C{initial} (these
+ parameters are not raw, so for an IntValue, you'd pass an
+ integer).
+ """
+ for member in self.get_descriptors():
+ member.read_from_params(self, data)
+ for key, value in initial.iteritems():
+ setattr(self, key, value)
+
+ def encode(self):
+ """Encode all the state members in this object to a GET/POST
+ dictionary."""
+ result = {}
+ for member in self.get_descriptors():
+ member.add_to_dict(self, result)
+ return result
diff --git a/Pootle-2.0.0/local_apps/pootle_app/urls.py b/Pootle-2.0.0/local_apps/pootle_app/urls.py
new file mode 100644
index 0000000..202374f
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/urls.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 Zuza Software Foundation
+#
+# This file is part of translate.
+#
+# translate is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# translate is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with translate; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from django.conf.urls.defaults import *
+from django.conf import settings
+
+urlpatterns = patterns('',
+ (r'^accounts/login/$', 'pootle_app.views.index.login.view'),
+ (r'^accounts/logout/$', 'pootle_app.views.index.logout.view'),
+ (r'^accounts/personal/edit/$', 'pootle_app.views.profile.view.edit_personal_info'),
+)
+
+# Onle include registration urls if registration is enabled
+if settings.CAN_REGISTER:
+ urlpatterns += patterns('', (r'^accounts/', include('registration.urls')))
+
+urlpatterns += patterns('',
+ (r'^accounts/', include('profiles.urls')),
+ (r'^admin', include('pootle_app.views.admin.urls')),
+ (r'^projects', include('pootle_app.views.project.urls')),
+ (r'', include('pootle_notifications.urls')),
+ (r'', include('pootle_app.views.index.urls')),
+ (r'', include('pootle_app.views.language.urls')),
+)
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/__init__.py b/Pootle-2.0.0/local_apps/pootle_app/views/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/__init__.py
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/admin/__init__.py b/Pootle-2.0.0/local_apps/pootle_app/views/admin/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/admin/__init__.py
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/admin/adminlanguages.py b/Pootle-2.0.0/local_apps/pootle_app/views/admin/adminlanguages.py
new file mode 100644
index 0000000..482f94b
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/admin/adminlanguages.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 Zuza Software Foundation
+#
+# This file is part of translate.
+#
+# translate is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# translate is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with translate; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from django.utils.translation import ugettext as _
+from pootle_app.views.admin import util
+from pootle_app.models import Language
+from pootle_app.admin import MyLanguageAdminForm
+
+@util.user_is_admin
+def view(request):
+ model_args = {}
+ model_args['title'] = _("Languages")
+ model_args['submitname'] = "changelanguages"
+ model_args['formid'] = "languages"
+ link = '/%s/admin.html'
+ return util.edit(request, 'admin/admin_general_languages.html', Language, model_args, link,
+ form=MyLanguageAdminForm, can_delete=True)
+
+
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/admin/adminpages.py b/Pootle-2.0.0/local_apps/pootle_app/views/admin/adminpages.py
new file mode 100644
index 0000000..5ad8f9a
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/admin/adminpages.py
@@ -0,0 +1,155 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2006-2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This file is somewhat based on the older Pootle/translatepage.py
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, see <http://www.gnu.org/licenses/>.
+
+from django.utils.translation import ugettext as _
+
+from pootle_app.views.admin.util import user_is_admin
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+
+from pootle import depcheck
+
+from pootle_misc.siteconfig import load_site_config
+from pootle_app.forms import GeneralSettingsForm
+
+
+def required_depcheck():
+ required = []
+
+ status, version = depcheck.test_translate()
+ if status:
+ text = _('Translate Toolkit version %s installed.', version)
+ state = 'good'
+ else:
+ text = _('Translate Toolkit version %(installed)s installed. Pootle requires version %(required)s.', {'installed': version, 'required': "1.5.0"})
+ state = 'error'
+ required.append({'dependency': 'translate', 'state': state, 'text': text })
+
+ status = depcheck.test_sqlite()
+ if status:
+ text = _('SQLite is installed.')
+ state = 'good'
+ else:
+ text = _('SQLite is missing. Pootle requires SQLite for translation statistics.')
+ state = 'error'
+ required.append({'dependency': 'sqlite', 'state': state, 'text': text })
+
+ status, version = depcheck.test_django()
+ if status:
+ text = _('Django version %s is installed.', version)
+ state = 'good'
+ else:
+ text = _('Django version %s is installed. Pootle only works with the 1.x series.')
+ state = 'error'
+ required.append({'dependency': 'django', 'state': state, 'text': text})
+
+ return required
+
+def optional_depcheck():
+ optional = []
+
+ if not depcheck.test_unzip():
+ optional.append({'dependency': 'unzip',
+ 'text': _('''Can't find the unzip command. Uploading archives is faster if "unzip" is available.''')})
+
+ if not depcheck.test_iso_codes():
+ optional.append({'dependency': 'iso-codes',
+ 'text': _("Can't find the ISO codes package. Pootle uses ISO codes to translate language names.")})
+
+ if not depcheck.test_lxml():
+ optional.append({'dependency': 'lxml',
+ 'text': _("Can't find lxml. Pootle uses lxml to make sure HTML tags inserted in news items are safe and correct.")})
+
+ if not depcheck.test_levenshtein():
+ optional.append({'dependency': 'levenshtein',
+ 'text': _("Can't find python-levenshtein package. Updating from templates is faster with python-levenshtein.")})
+
+ if not depcheck.test_indexer():
+ optional.append({'dependency': 'indexer',
+ 'text': _("No text indexing engine found. Searching is faster if an indexing engine like Xapian or Lucene is installed.")})
+
+ return optional
+
+
+def optimal_depcheck():
+ optimal = []
+
+ if not depcheck.test_db():
+ if depcheck.test_mysqldb():
+ text = _("Using the default sqlite3 database engine. SQLite is only suitable for small installations with a small number of users. Pootle will perform better with the MySQL database engine.")
+ else:
+ text = _("Using the default sqlite3 database engine. SQLite is only suitable for small installations with a small number of users. Pootle will perform better with the MySQL database engine, but you need to install python-MySQLdb first.")
+ optimal.append({'dependency': 'db', 'text': text})
+
+ if depcheck.test_cache():
+ if depcheck.test_memcache():
+ if not depcheck.test_memcached():
+ # memcached configured but connection failing
+ optimal.append({'dependency': 'cache',
+ 'text': _("Pootle is configured to use memcached as a caching backend, but can't connect to the memcached server. Caching is currently disabled.")})
+ else:
+ if not depcheck.test_session():
+ if depcheck.test_cached_db_session():
+ text = _('For optimal performance, use django.contrib.sessions.backends.cached_db as the session engine.')
+ else:
+ text = _('For optimal performance, use django.contrib.sessions.backends.cache as the session engine.')
+ optimal.append({'dependency': 'session', 'text': text})
+ else:
+ optimal.append({'dependency': 'cache',
+ 'text': _('Pootle is configured to use memcached as caching backend, but Python support for memcached is not installed. Caching is currently disabled.')})
+ else:
+ optimal.append({'dependency': 'cache',
+ 'text': _('For optimal performance, use memcached as the caching backend.')})
+
+ if not depcheck.test_webserver():
+ optimal.append({'dependency': 'webserver',
+ 'text': _("For optimal performance, use Apache as the webserver.")})
+
+ if not depcheck.test_debug():
+ optimal.append({'dependency': 'debug',
+ 'text': _('Running in debug mode. Debug mode is only needed when developing Pootle. For optimal performance, disable debugging mode.')})
+
+ if not depcheck.test_livetranslation():
+ optimal.append({'dependency': 'livetranslation',
+ 'text': _("Running in live translation mode. Live translation is useful as a tool to learn about Pootle and localization, but has high impact on performance.")})
+
+ return optimal
+
+
+@user_is_admin
+def view(request, path):
+ siteconfig = load_site_config()
+ if request.POST:
+ setting_form = GeneralSettingsForm(siteconfig, data=request.POST)
+ if setting_form.is_valid():
+ setting_form.save()
+ load_site_config()
+ else:
+ setting_form = GeneralSettingsForm(siteconfig)
+
+ template = 'admin/admin_general_settings.html'
+ template_vars = {
+ 'form': setting_form,
+ 'required': required_depcheck(),
+ 'optional': optional_depcheck(),
+ 'optimal': optimal_depcheck(),
+ }
+ return render_to_response(template, template_vars, context_instance=RequestContext(request))
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/admin/adminprojects.py b/Pootle-2.0.0/local_apps/pootle_app/views/admin/adminprojects.py
new file mode 100644
index 0000000..b7de535
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/admin/adminprojects.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 Zuza Software Foundation
+#
+# This file is part of translate.
+#
+# translate is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# translate is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with translate; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from django.utils.translation import ugettext as _
+from pootle_app.views.admin import util
+from pootle_app.models import Project
+from pootle_app.admin import MyProjectAdminForm
+
+@util.user_is_admin
+def view(request):
+ model_args = {}
+ model_args['title'] = _("Projects")
+ model_args['formid'] = "projects"
+ model_args['submitname'] = "changeprojects"
+ link = '/projects/%s/admin.html'
+ return util.edit(request, 'admin/admin_general_projects.html', Project, model_args, link,
+ form=MyProjectAdminForm, exclude='description', can_delete=True)
+
+
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/admin/adminroot.py b/Pootle-2.0.0/local_apps/pootle_app/views/admin/adminroot.py
new file mode 100644
index 0000000..77bb57d
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/admin/adminroot.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# Pootle is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# Pootle is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Pootle; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+
+from pootle_app.models.directory import Directory
+from pootle_app.views.language.admin_permissions import process_update as process_permission_update
+from pootle_app.views.admin.util import user_is_admin
+
+@user_is_admin
+def view(request):
+ permission_set_formset = process_permission_update(request, Directory.objects.root)
+
+ template_vars = {
+ "permission_set_formset": permission_set_formset,
+ "hide_fileadmin_links": True,
+ }
+ return render_to_response("admin/admin_general_permissions.html", template_vars, context_instance=RequestContext(request))
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/admin/adminusers.py b/Pootle-2.0.0/local_apps/pootle_app/views/admin/adminusers.py
new file mode 100644
index 0000000..8cea647
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/admin/adminusers.py
@@ -0,0 +1,84 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 Zuza Software Foundation
+#
+# This file is part of translate.
+#
+# translate is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# translate is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with translate; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from django.utils.translation import ugettext as _
+from pootle_app.views.admin import util
+from django.contrib.auth.models import User
+
+from django import forms
+from django.forms.models import BaseModelFormSet
+
+@util.user_is_admin
+def view(request):
+ model_args = {}
+ model_args['title'] = _("Users")
+ model_args['submitname'] = "changeusers"
+ model_args['formid'] = "users"
+ return util.edit(request, 'admin/admin_general_users.html', User, model_args,
+ fields=('username', 'first_name', 'last_name', 'email', 'is_active', 'is_superuser'),
+ formset=BaseUserFormSet, queryset=User.objects.hide_defaults().order_by('username'), can_delete=True)
+
+class BaseUserFormSet(BaseModelFormSet):
+ """This formset deals with user admininistration. We have to add a
+ password field so that the passwords of users can be set.
+
+ We override the save_existing and save_new formset methods so that
+ we can 1) yank out the password field before the formset attempts
+ to save the field 'set_password' (which would fail anyway, since
+ the User model has no such field) and 2) set the password for an
+ object once it has been saved.
+ """
+
+ def add_fields(self, form, index):
+ super(BaseUserFormSet, self).add_fields(form, index)
+ form.fields["set_password"] = forms.CharField(required=False, label=_("Password"), widget=forms.PasswordInput())
+
+ def del_field(self, form):
+ password = form['set_password'].data
+ del form.fields['set_password']
+ return password
+
+ def save_extra(self, instance, password, commit=True):
+ """process fields that require behavior different from model default"""
+ changed = False
+ # don't store plain text password, use set_password method to
+ # set encrypted password
+ if password != '':
+ instance.set_password(password)
+ changed = True
+ # no point in seperating admin rights from access to
+ # django_admin, make sure the two bits are in synch
+ if instance.is_staff != instance.is_superuser:
+ instance.is_staff = instance.is_superuser
+ changed = True
+
+ if commit and changed:
+ instance.save()
+
+ return instance
+
+ def save_existing(self, form, instance, commit=True):
+ password = self.del_field(form)
+ return self.save_extra(super(BaseUserFormSet, self).save_existing(form, instance, commit), password, commit)
+
+ def save_new(self, form, commit=True):
+ password = self.del_field(form)
+ return self.save_extra(super(BaseUserFormSet, self).save_new(form, commit), password, commit)
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/admin/urls.py b/Pootle-2.0.0/local_apps/pootle_app/views/admin/urls.py
new file mode 100644
index 0000000..3b3606e
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/admin/urls.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 Zuza Software Foundation
+#
+# This file is part of translate.
+#
+# translate is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# translate is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with translate; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('pootle_app.views.admin',
+ (r'^/users.html$', 'adminusers.view'),
+ (r'^/languages.html$', 'adminlanguages.view'),
+ (r'^/projects.html$', 'adminprojects.view'),
+ (r'^/permissions.html$', 'adminroot.view'),
+ (r'^(/|/index.html)?$', 'adminpages.view'),
+)
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/admin/util.py b/Pootle-2.0.0/local_apps/pootle_app/views/admin/util.py
new file mode 100644
index 0000000..daa01a8
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/admin/util.py
@@ -0,0 +1,215 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008-2009 Zuza Software Foundation
+#
+# This file is part of translate.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+from django.core.exceptions import PermissionDenied
+from django.forms.models import modelformset_factory
+from django.utils.translation import ugettext as _
+from django.utils.safestring import mark_safe
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.forms.util import ErrorList
+from django.core.paginator import Paginator
+
+from pootle_misc.baseurl import l
+from pootle_app.models.permissions import get_matching_permissions, check_permission
+from pootle_app.models.profile import get_profile
+
+
+def user_is_admin(f):
+ def decorated_f(request, *args, **kwargs):
+ if not request.user.is_superuser:
+ raise PermissionDenied(_("You do not have rights to administer Pootle."))
+ else:
+ return f(request, *args, **kwargs)
+ return decorated_f
+
+def has_permission(permission_code):
+ def wrap_f(f):
+ def decorated_f(request, path_obj, *args, **kwargs):
+ request.permissions = get_matching_permissions(get_profile(request.user), path_obj.directory)
+ if check_permission(permission_code, request):
+ return f(request, path_obj, *args, **kwargs)
+ else:
+ raise PermissionDenied(_("You do not have rights to administer %s.", path_obj.fullname))
+ return decorated_f
+ return wrap_f
+
+
+def form_set_as_table(formset, link=None, linkfield='code'):
+ """Create an HTML table from the formset. The first form in the
+ formset is used to obtain a list of the fields that need to be
+ displayed. All these fields not appearing in 'exclude' will be
+ placed into consecutive columns.
+
+ Errors, if there are any, appear in the row above the form which
+ triggered any errors.
+
+ If the forms are based on database models, the order of the
+ columns is determined by the order of the fields in the model
+ specification."""
+ def add_header(result, fields, form):
+ result.append('<tr>\n')
+ for field in fields:
+ result.append('<th>')
+ if form.fields[field].label is not None:
+ result.append(unicode(form.fields[field].label))
+ result.append('</th>\n')
+ result.append('</tr>\n')
+
+ def add_errors(result, fields, form):
+ # If the form has errors, then we'll add a table row with the
+ # errors.
+ if len(form.errors) > 0:
+ result.append('<tr>\n')
+ for field in fields:
+ result.append('<td>')
+ result.append(form.errors.get(field, ErrorList()).as_ul())
+ result.append('</td>\n')
+ result.append('</tr>\n')
+
+ def add_widgets(result, fields, form, link, zebra):
+ result.append('<tr class="%s">\n' % zebra)
+ for i, field in enumerate(fields):
+ result.append('<td class="%s">' % field)
+ # Include a hidden element containing the form's id to the
+ # first column.
+ if i == 0:
+ result.append(form['id'].as_hidden())
+
+ """
+ 'link' indicates whether we put the first field as a link or as widget
+ """
+ if field == linkfield and linkfield in form.initial and link :
+ if callable(link):
+ result.append(link(form.instance))
+ result.append(form[field].as_hidden())
+ else:
+ link = l(link % form.initial[linkfield])
+ result.append("<a href='"+link+"'>"+form.initial[linkfield]+"</a>")
+ result.append(form[field].as_hidden())
+ else:
+ result.append(form[field].as_widget())
+ result.append('</td>\n')
+ result.append('</tr>\n')
+
+ result = []
+ try:
+ first_form = formset.forms[0]
+ # Get the fields of the form, but filter our the 'id' field,
+ # since we don't want to print a table column for it.
+ fields = [field for field in first_form.fields if field != 'id']
+ add_header(result, fields, first_form)
+ for i, form in enumerate(formset.forms):
+ if i % 2:
+ zebra = "odd"
+ else:
+ zebra = "even"
+ add_errors(result, fields, form)
+ add_widgets(result, fields, form, link, zebra)
+ except IndexError:
+ result.append('<tr>\n')
+ result.append('<td>\n')
+ result.append(_('No files in this project.'))
+ result.append('</td>\n')
+ result.append('</tr>\n')
+ return u''.join(result)
+
+
+def paginate(request, queryset):
+ paginator = Paginator(queryset, 30)
+
+ try:
+ page = int(request.GET.get('page', '1'))
+ except ValueError:
+ # wasn't an int use 1
+ page = 1
+ # page value too large
+ page = min(page, paginator.num_pages)
+
+ return paginator.page(page)
+
+
+def process_modelformset(request, model_class, queryset, **kwargs):
+ """With the Django model class 'model_class' and the Django form class 'form_class',
+ construct a Django formset which can manipulate """
+
+ # Create a formset class for the model 'model_class' (i.e. it will contain forms whose
+ # contents are based on the fields of 'model_class'); parameters for the construction
+ # of the forms used in the formset should be in kwargs. In Django 1.0, the interface
+ # to modelformset_factory is
+ # def modelformset_factory(model, form=ModelForm, formfield_callback=lambda f: f.formfield(),
+ # formset=BaseModelFormSet,
+ # extra=1, can_delete=False, can_order=False,
+ # max_num=0, fields=None, exclude=None)
+ formset_class = modelformset_factory(model_class, **kwargs)
+
+ if queryset is None:
+ queryset = model_class.objects.all()
+
+
+ # If the request is a POST, we want to possibly update our data
+ if request.method == 'POST':
+ # Create a formset from all the 'model_class' instances whose values will
+ # be updated using the contents of request.POST
+ objects = paginate(request, queryset)
+ formset = formset_class(request.POST, queryset=objects.object_list)
+ # Validate all the forms in the formset
+ if formset.is_valid():
+ # If all is well, Django can save all our data for us
+ formset.save()
+ else:
+ # Otherwise, complain to the user that something went wrong
+ return formset, _("There are errors in the form. Please review the problems below."), objects
+
+ # hack to force reevaluation of same query
+ queryset = queryset.filter()
+
+ objects = paginate(request, queryset)
+ return formset_class(queryset=objects.object_list), None, objects
+
+
+def edit(request, template, model_class,
+ model_args={'title':'','formid':'','submitname':''},
+ link=None, linkfield='code', queryset=None, **kwargs):
+
+ formset, msg, objects = process_modelformset(request, model_class, queryset=queryset, **kwargs)
+ #FIXME: title should differ depending on model_class
+ template_vars = {
+ "formset_text": mark_safe(form_set_as_table(formset, link, linkfield)),
+ "formset": formset,
+ "objects": objects,
+ "text": {
+ "home": _("Home"),
+ "title": model_args.get('title', ''),
+ "savechanges": _("Save Changes"),
+ "submitname": model_args['submitname'],
+ "formid": model_args['formid'],
+ "error_msg": msg,
+ }
+ }
+ if model_args.get("project", None):
+ template_vars["project"] = model_args['project']
+ if model_args.get("search", None):
+ template_vars["search"] = model_args['search']
+ if model_args.get("navitems", None):
+ template_vars["navitems"] = model_args['navitems']
+ if model_args.get("feed_path", None):
+ template_vars["feed_path"] = model_args['feed_path']
+ return render_to_response(template, template_vars, context_instance=RequestContext(request))
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/base.py b/Pootle-2.0.0/local_apps/pootle_app/views/base.py
new file mode 100644
index 0000000..2ca6f4f
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/base.py
@@ -0,0 +1,26 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of translate.
+#
+# translate is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# translate is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# translate; if not, write to the Free Software Foundation, Inc., 59 Temple
+# Place, Suite 330, Boston, MA 02111-1307 USA
+
+from pootle_app.lib import view_handler
+from pootle_app.views import pagelayout
+
+class BaseView(view_handler.View):
+ def GET(self, template_vars, request):
+ pagelayout.completetemplatevars(template_vars, request)
+ return template_vars
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/index/__init__.py b/Pootle-2.0.0/local_apps/pootle_app/views/index/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/index/__init__.py
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/index/about.py b/Pootle-2.0.0/local_apps/pootle_app/views/index/about.py
new file mode 100644
index 0000000..6dd89a2
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/index/about.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+import sys
+
+from django.utils.translation import ugettext as _
+
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.conf import settings
+
+# get versions
+import django
+from translate import __version__ as toolkitversion
+from pootle import __version__ as pootleversion
+
+
+def view(request):
+ data = {
+ 'description': _(settings.DESCRIPTION),
+ 'keywords' : [ 'Pootle',
+ 'locamotion',
+ 'translate',
+ 'translation',
+ 'localisation',
+ 'localization',
+ 'l10n',
+ 'traduction',
+ 'traduire',
+ ],
+ 'pootle_version': _("Pootle %(pootle_ver)s is powered by Translate Toolkit %(toolkit_ver)s",
+ {'pootle_ver': pootleversion.sver, 'toolkit_ver': toolkitversion.sver}),
+ 'version_details': "\n".join([
+ "Django %s" % django.get_version(),
+ "Python %s" % sys.version,
+ "Running on %s" % sys.platform,
+ ])
+ }
+
+ return render_to_response('index/about.html', data, context_instance=RequestContext(request))
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/index/index.py b/Pootle-2.0.0/local_apps/pootle_app/views/index/index.py
new file mode 100644
index 0000000..9823a86
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/index/index.py
@@ -0,0 +1,114 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+import locale
+
+from django.utils.translation import ugettext as _
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+
+from pootle_app.models.profile import get_profile
+from pootle_app.models import Project, Directory, Submission, Language
+from pootle_app.models.permissions import get_matching_permissions, check_permission
+from pootle_app.views import pagelayout
+from pootle_app.views.top_stats import gentopstats
+from pootle.i18n.gettext import tr_lang
+from pootle_app.views.language.item_dict import add_percentages
+
+def limit(query):
+ return query[:5]
+
+def get_items(request, model, get_last_action, name_func):
+
+ items = []
+ if not check_permission('view', request):
+ return items
+
+ for item in model.objects.all():
+ stats = item.getquickstats()
+ stats = add_percentages(stats)
+
+ lastact = get_last_action(item)
+ items.append({
+ 'code': item.code,
+ 'name': name_func(item.fullname),
+ 'lastactivity': lastact,
+ 'trans': stats["translatedsourcewords"],
+ 'fuzzy': stats["fuzzysourcewords"],
+ 'untrans': stats["untranslatedsourcewords"],
+ 'total': stats["totalsourcewords"],
+ 'transper': stats["translatedpercentage"],
+ 'fuzzyper': stats["fuzzypercentage"],
+ 'untransper': stats["untranslatedpercentage"],
+ 'completed_title': _("%(percentage)d%% complete",
+ {'percentage': stats['translatedpercentage']}),
+ })
+ items.sort(lambda x, y: locale.strcoll(x['name'], y['name']))
+ return items
+
+def getlanguages(request):
+ def get_last_action(item):
+ try:
+ return Submission.objects.filter(translation_project__language=item).latest()
+ except Submission.DoesNotExist:
+ return ''
+
+ return get_items(request, Language, get_last_action, tr_lang)
+
+def getprojects(request):
+ def get_last_action(item):
+ try:
+ return Submission.objects.filter(translation_project__project=item).latest()
+ except Submission.DoesNotExist:
+ return ''
+
+ return get_items(request, Project, get_last_action, lambda name: name)
+
+
+def view(request):
+ request.permissions = get_matching_permissions(get_profile(request.user), Directory.objects.root)
+ topstats = gentopstats(lambda query: query)
+
+ templatevars = {
+ 'description': pagelayout.get_description(),
+ 'keywords': [
+ 'Pootle',
+ 'translate',
+ 'translation',
+ 'localisation',
+ 'localization',
+ 'l10n',
+ 'traduction',
+ 'traduire',
+ ],
+ 'languagelink': _('Languages'),
+ 'languages': getlanguages(request),
+ 'projectlink': _('Projects'),
+ 'projects': getprojects(request),
+ 'topstats': topstats,
+ 'instancetitle': pagelayout.get_title(),
+ 'translationlegend': {'translated': _('Translations are complete'),
+ 'fuzzy': _('Translations need to be checked (they are marked fuzzy)'
+ ), 'untranslated': _('Untranslated')},
+ }
+ templatevars['moreprojects'] = len(templatevars['projects']) >\
+ len(templatevars['languages']),
+
+ return render_to_response('index/index.html', templatevars, RequestContext(request))
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/index/login.py b/Pootle-2.0.0/local_apps/pootle_app/views/index/login.py
new file mode 100644
index 0000000..cfc32fa
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/index/login.py
@@ -0,0 +1,84 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+from translate.lang import data
+
+from django import forms
+from django.utils.translation import ugettext as _
+from django.conf import settings
+from django.contrib.auth import REDIRECT_FIELD_NAME, login
+from django.contrib.auth.forms import AuthenticationForm
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.utils.encoding import iri_to_uri
+from django.utils.http import urlquote
+
+from pootle.i18n.override import lang_choices
+
+from pootle_misc.baseurl import redirect
+
+def redirect_after_login(request):
+ redirect_to = request.REQUEST.get(REDIRECT_FIELD_NAME, None)
+ if redirect_to is None or '://' in redirect_to or ' ' in redirect_to:
+ redirect_to = iri_to_uri('/accounts/%s/' % urlquote(request.user.username))
+ return redirect(redirect_to)
+
+
+def language_list(request):
+ """returns the list of localised language names, with 'default'"""
+ tr_default = _("Default")
+ if tr_default != "Default":
+ tr_default = u"%s | \u202dDefault" % tr_default
+
+ choices = lang_choices()
+ choices.insert(0, ('', tr_default))
+ return choices
+
+
+
+def view(request):
+ class LangAuthenticationForm(AuthenticationForm):
+ language = forms.ChoiceField(label=_('Interface Language'), choices=language_list(request),
+ initial="", required=False)
+
+ if request.user.is_authenticated():
+ return redirect_after_login(request)
+ else:
+ if request.POST:
+ form = LangAuthenticationForm(request, data=request.POST)
+ # do login here
+ if form.is_valid():
+ login(request, form.get_user())
+
+ if request.session.test_cookie_worked():
+ request.session.delete_test_cookie()
+
+ language = request.POST.get('language')
+ request.session['django_language'] = language
+ response = redirect_after_login(request)
+ return response
+ else:
+ form = LangAuthenticationForm(request)
+ request.session.set_test_cookie()
+ context = {
+ 'form': form
+ }
+ return render_to_response("index/login.html", context, context_instance=RequestContext(request))
+
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/index/logout.py b/Pootle-2.0.0/local_apps/pootle_app/views/index/logout.py
new file mode 100644
index 0000000..39373ea
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/index/logout.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+from pootle_misc.baseurl import redirect
+
+def view(request):
+ from django.contrib.auth import logout
+ logout(request)
+ return redirect('/')
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/index/robots.py b/Pootle-2.0.0/local_apps/pootle_app/views/index/robots.py
new file mode 100644
index 0000000..ac72257
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/index/robots.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+from django.http import HttpResponse
+from pootle_app.models import Language
+
+def view(request):
+ """generates the robots.txt file"""
+ langcodes = [language.code for language in Language.objects.all()]
+ content = "User-agent: *\n"
+ for langcode in langcodes + ["accounts"]:
+ content += "Disallow: /%s/\n" % langcode
+ return HttpResponse(content, mimetype="text/plain")
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/index/urls.py b/Pootle-2.0.0/local_apps/pootle_app/views/index/urls.py
new file mode 100644
index 0000000..0a5605d
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/index/urls.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('pootle_app.views.index',
+ (r'^robots.txt$', 'robots.view'),
+ (r'^about.html$', 'about.view'),
+ (r'^/?$|^index.html$', 'index.view'),
+)
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/language/__init__.py b/Pootle-2.0.0/local_apps/pootle_app/views/language/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/language/__init__.py
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/language/admin_files.py b/Pootle-2.0.0/local_apps/pootle_app/views/language/admin_files.py
new file mode 100644
index 0000000..6af2aa8
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/language/admin_files.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 Zuza Software Foundation
+#
+# This file is part of translate.
+#
+# translate is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# translate is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with translate; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from django.utils.translation import ugettext as _
+from django.forms.models import BaseModelFormSet
+
+from pootle_app.views.admin import util
+from pootle_app.views.language import search_forms
+from pootle_app.views.language import navbar_dict
+from pootle_store.models import Store
+from pootle_app.models.translation_project import TranslationProject
+from pootle_app import project_tree
+
+
+class StoreFormset(BaseModelFormSet):
+ def save_existing_objects(self, commit=True):
+ result = super(StoreFormset, self).save_existing_objects(commit)
+ for store in self.deleted_objects:
+ #hackish: we disabled deleting files when field is
+ # deleted except for when value is being overwritten, but
+ # this form is the only place in pootle where actual file
+ # system files should be deleted
+ store.file.storage.delete(store.file.name)
+ return result
+
+
+@util.has_permission('administrate')
+def view(request, translation_project):
+ queryset = translation_project.stores
+
+ try:
+ template_translation_project = TranslationProject.objects.get(project=translation_project.project,
+ language__code='templates')
+ if 'template_update' in request.GET:
+ project_tree.convert_templates(template_translation_project, translation_project)
+ except TranslationProject.DoesNotExist:
+ pass
+
+ if 'scan_files' in request.GET:
+ project_tree.scan_translation_project_files(translation_project)
+
+
+ model_args = {}
+ model_args['title'] = _("Files")
+ model_args['submitname'] = "changestores"
+ model_args['formid'] = "stores"
+ model_args['search'] = search_forms.get_search_form(request)
+ model_args['navitems'] = [navbar_dict.make_directory_navbar_dict(request, translation_project.directory)]
+ model_args['feed_path'] = translation_project.directory.pootle_path[1:]
+ link = "%s"
+ return util.edit(request, 'language/tp_admin_files.html', Store, model_args,
+ link, linkfield='pootle_path', queryset=queryset,
+ formset=StoreFormset, can_delete=True, extra=0)
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/language/admin_permissions.py b/Pootle-2.0.0/local_apps/pootle_app/views/language/admin_permissions.py
new file mode 100644
index 0000000..9e9bd0e
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/language/admin_permissions.py
@@ -0,0 +1,255 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# Pootle is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# Pootle is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Pootle; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from django import forms
+from django.forms.formsets import formset_factory, BaseFormSet
+from django.utils.translation import ugettext as _
+from django.utils.safestring import mark_safe
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+
+from pootle_app.models.profile import PootleProfile
+from pootle_app.models.permissions import get_pootle_permissions, PermissionSet, get_matching_permissions
+from pootle_app.views.language import navbar_dict
+from pootle_app.views.language import search_forms
+from pootle_app.views.admin import util
+
+class PermissionSetForm(forms.Form):
+ """A PermissionSetForm represents a PermissionSet to the user.
+
+ This form will be used in a formset, PermissionSetFormSet. This
+ explains some of the more perculiar code in here.
+
+ In t"""
+ id = forms.IntegerField(required=False, widget=forms.HiddenInput)
+ profiles = forms.ChoiceField(required=False)
+ permissions = forms.MultipleChoiceField(required=False, widget=forms.SelectMultiple)
+ delete = forms.BooleanField(required=False)
+
+ def __init__(self, *args, **kwargs):
+ super(PermissionSetForm, self).__init__(*args, **kwargs)
+
+ #
+ permission_choices = [(codename, _(permission.name)) for codename, permission in get_pootle_permissions().iteritems()]
+ # Used to render the correct choices
+ self['permissions'].field.widget.choices = permission_choices
+ # Used during validation to ensure that valid choices were selected
+ self['permissions'].field.choices = permission_choices
+
+ profile_choices = self.initial['profile_data']
+ # Used to render the correct choices
+ self['profiles'].field.widget.choices = profile_choices
+ # Used during validation to ensure that valid choices were selected
+ self['profiles'].field.choices = profile_choices
+ # Remove 'profile_data' lest we have to deal with weird an
+ # unexpected complaints from Django's data validation
+ # machinery.
+ del self.initial['profile_data']
+
+ def as_table(self):
+ params = {'id': self['id'].as_widget(),
+ 'permissions': self['permissions'].as_widget()}
+
+ if not self.is_new_user():
+ params.update({
+ 'username': self.initial['username'],
+ 'profiles': self['profiles'].as_hidden(),
+ 'delete': self['delete'].as_widget()})
+ else:
+ params.update({
+ 'delete': self['delete'].as_hidden(),})
+ if 'new' in self.initial:
+ params.update({ 'username': '',
+ 'profiles': self['profiles'].as_widget() })
+ else:
+ params.update({ 'username': self.initial['username'],
+ 'profiles': self['profiles'].as_hidden() })
+
+ return mark_safe('<tr><td>%(id)s%(username)s%(profiles)s</td><td>%(permissions)s</td><td>%(delete)s</td></tr>' % params)
+
+ def is_new_user(self):
+ return self.initial['id'] is None
+
+ #def clean(self):
+ # if self.is_new_user() and len(self.changed_data) > 0:
+ # return self['profiles'] != u'None'
+ # else:
+ # return True
+
+class BasePermissionSetFormSet(BaseFormSet):
+ def as_table(self):
+ return "<tr><th>%s</th><th>%s</th><th>%s</th></tr>%s" % (
+ _("Username"), _("Permissions"), _("Delete"),
+ super(BasePermissionSetFormSet, self).as_table())
+
+# See the comments in PermissionSetForm We don't want the
+# formset_factory to create empty extra forms. If extra > 0, then the
+# associated forms will not receive any initial data (i.e. the data
+# passed by the 'initial' parameter when creating a formset; for
+# example,
+# PermissionSetFormSet(initial=get_permission_data(translation_project))).
+# This is problematic for us, since we need to pass the choices to be
+# used in the dropdown boxes and selection boxes to the forms. The way
+# that I chose to do this was to pass the choice data in via the
+# initial parameter. This is then used in PermissionSetForm.__init__
+# to set the choices of the choice widgets. In other words, we want
+# extra==0, so that we can pass data to each form in the formset.
+PermissionSetFormSet = formset_factory(PermissionSetForm, BasePermissionSetFormSet, extra=0)
+
+def get_id(permission_set, profile_dict):
+ if permission_set.profile in profile_dict:
+ return permission_set.id
+ else:
+ return None
+
+def get_permission_data(directory):
+ # Get all the PermissionSet objects associated with the current directory
+ permission_sets = PermissionSet.objects.filter(directory=directory)
+ profile_permission_sets = dict((permission_set.profile, permission_set)
+ for permission_set in permission_sets)
+
+ # Get all profile objects which do not have PermissionSet objects
+ # in the current 'translation_project' pointing to them.
+ profiles_without_permissions = [profile for profile in PootleProfile.objects.all().order_by('user__username')
+ if profile not in profile_permission_sets]
+
+ # Build a list of initial data to be fed to a formset. Each entry
+ # here corresponds to an actual PermissionSet. Thus, we set 'id'
+ # to that of the PermissionSet in question. 'permissions' is a
+ # list of Permission codenames which have been enabled for this
+ # PermissionSet.
+ #
+ # profiles and profile_data are used to display a dropdown list of
+ # users without PermissionSet objects for the current
+ # 'translation_project'. Thus, they are used in the form which
+ # creates PermissionSet objects for new users. So we don't care
+ # about them for forms corresponding to existing PermissionSet
+ # objects. We include them so that Django's validation machinery
+ # won't complain that their values changed (this can quite
+ # possibly be removed without having Django complain, but that's
+ # for someone else to try).
+ permission_data = [{'id': get_id(permission_set, profile_permission_sets),
+ 'permissions': [permission.codename for permission in permission_set.positive_permissions.all()],
+ 'username': permission_set.profile.user.username,
+ 'profiles': permission_set.profile.id,
+ 'profile_data': [(permission_set.profile.id, permission_set.profile.user.username)]}
+ for permission_set in permission_sets]
+
+ # If there are any profiles which do not have PermissionSet
+ # objects associated with the current 'translation_project', then
+ # we want to display a form to make it possible to add them...
+ if len(profiles_without_permissions) > 0:
+ # Get the profile object for the user 'default'
+ default_profile = PootleProfile.objects.get(user__username='default')
+ default_permissions = get_matching_permissions(default_profile, directory)
+ # The form to add a new profile doesn't yet correspond to a
+ # PermissionSet object. Thus, 'id' can't have a valid
+ # PermissionSet id value.
+ #
+ # The selected 'permissions' should match the list of default
+ # permissions
+ permission_data.append({'id': None,
+ 'permissions': [permission for permission in default_permissions],
+ 'username': '',
+ 'profiles': u'None',
+ 'profile_data': [(u'None', '')] + [(profile.id, profile.user.username)
+ for profile in profiles_without_permissions],
+ 'new': True})
+ return permission_data
+
+def process_update(request, directory):
+ def find_updated_forms(formset):
+ deleted_forms = []
+ changed_forms = []
+ for form in formset.forms:
+ # If the user toggled the 'delete' checkbox, we'll roast
+ # the corresponding PermissionSet
+ if form['delete'].data:
+ deleted_forms.append(form)
+ # Otherwise, if the form contains any changed data, we'll
+ # have to update and save it.
+ elif len(form.changed_data) > 0:
+ changed_forms.append(form)
+ return deleted_forms, changed_forms
+
+ def get_permission_set(form):
+ if form.is_new_user():
+ permission_set = PermissionSet(profile_id=int(form['profiles'].data), directory=directory)
+ permission_set.save()
+ return permission_set
+ else:
+ return PermissionSet.objects.get(pk=form['id'].data)
+
+ if request.method == 'POST':
+ permission_set_formset = PermissionSetFormSet(data=request.POST, initial=get_permission_data(directory))
+ pootle_permissions = get_pootle_permissions()
+
+ # Check whether there are any validation errors in the form
+ # that the user submitted...
+ if permission_set_formset.is_valid():
+ deleted_forms, changed_forms = find_updated_forms(permission_set_formset)
+
+ for form in deleted_forms:
+ get_permission_set(form).delete()
+
+ for form in changed_forms:
+ permission_set = get_permission_set(form)
+ # pootle_permissions is a (permission codename ->
+ # PermissionSet) dict. We get the permission codenames
+ # from form['permissions'].data.
+
+ permission_set.positive_permissions = [pootle_permissions[codename] for codename in form['permissions'].data]
+ permission_set.save()
+
+ return PermissionSetFormSet(initial=get_permission_data(directory))
+ else:
+ # If the form validation failed, we'll return the old
+ # form, which will automatically print validation errors
+ # to the user when it's output again.
+ return permission_set_formset
+ else:
+ return PermissionSetFormSet(initial=get_permission_data(directory))
+
+
+@util.has_permission('administrate')
+def view(request, translation_project):
+ language = translation_project.language
+ project = translation_project.project
+ permission_set_formset = process_update(request, translation_project.directory)
+
+ if translation_project.file_style == "gnu":
+ filestyle_text = _("This is a GNU-style project (files named per language code).")
+ else:
+ filestyle_text = _("This is a standard style project (one directory per language).")
+
+ template_vars = {
+ "project": project,
+ "language": language,
+ "filestyle_text": filestyle_text,
+ "permissions_title": _("User Permissions"),
+ "username_title": _("Username"),
+ "permission_set_formset": permission_set_formset,
+ "adduser_text": _("(select to add user)"),
+ "search": search_forms.get_search_form(request),
+ "navitems": [navbar_dict.make_directory_navbar_dict(request, translation_project.directory)],
+ "feed_path": translation_project.directory.pootle_path[1:],
+ }
+ return render_to_response("language/tp_admin_permissions.html", template_vars, context_instance=RequestContext(request))
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/language/dispatch.py b/Pootle-2.0.0/local_apps/pootle_app/views/language/dispatch.py
new file mode 100644
index 0000000..afdb18b
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/language/dispatch.py
@@ -0,0 +1,130 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# Pootle is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# Pootle is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Pootle; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from pootle_app.models.permissions import check_permission
+from pootle_app import url_state, url_manip
+
+################################################################################
+
+class CommonState(url_state.State):
+ """Stores state common to project index pages and translation pages."""
+ editing = url_state.BooleanValue('editing')
+
+################################################################################
+
+class ProjectIndexState(CommonState):
+ show_checks = url_state.BooleanValue('show_checks')
+
+################################################################################
+
+class TranslatePageState(CommonState):
+ # Display state
+ view_mode = url_state.ChoiceValue('view_mode', ('view', 'review', 'translate', 'raw'))
+ # Position state
+ store = url_state.Value('store')
+ item = url_state.IntValue('item', 0)
+ # Search state
+ match_names = url_state.ListValue('match_names')
+
+def get_store(request):
+ basename = url_manip.basename(request.path_info)
+ if basename == 'translate.html':
+ if 'store' in request.POST:
+ return request.POST['store']
+ else:
+ return request.GET.get('store', '')
+
+ else:
+ return request.path_info
+
+################################################################################
+
+def translate(request, path, **kwargs):
+ params = TranslatePageState(request.GET, **kwargs)
+ # In Pootle, URLs ending in translate.html are used when the user
+ # translates all files in a directory (for example, if the user is
+ # going through all fuzzy translations in a directory). If this is
+ # the case, we need to pass the current store name in the 'store'
+ # GET variable so that Pootle will know where to continue from
+ # when the user clicks submit/skip/suggest on a translation
+ # unit. But otherwise the store name is the last component of the
+ # path name and we don't need to pass the 'store' GET variable.
+ if path[-1] == '/':
+ path = path + 'translate.html'
+ else:
+ params.store = None
+
+ if (check_permission('translate', request) or check_permission('suggest', request)) and \
+ 'view_mode' not in kwargs:
+ params.view_mode = 'translate'
+ return url_manip.make_url(path, params.encode())
+
+def review(request, path, **kwargs):
+ params = TranslatePageState(request.GET, **kwargs)
+ # In Pootle, URLs ending in translate.html are used when the user
+ # translates all files in a directory (for example, if the user is
+ # going through all fuzzy translations in a directory). If this is
+ # the case, we need to pass the current store name in the 'store'
+ # GET variable so that Pootle will know where to continue from
+ # when the user clicks submit/skip/suggest on a translation
+ # unit. But otherwise the store name is the last component of the
+ # path name and we don't need to pass the 'store' GET variable.
+ if path[-1] == '/':
+ path = path + 'translate.html'
+ else:
+ params.store = None
+
+ if 'view_mode' not in kwargs:
+ params.view_mode = 'review'
+ return url_manip.make_url(path, params.encode())
+
+def show_directory(request, directory_path, **kwargs):
+ params = ProjectIndexState(request.GET, **kwargs).encode()
+ return url_manip.make_url(directory_path, params)
+
+def translation_project_admin(translation_project):
+ return translation_project.directory.pootle_path + 'admin.html'
+
+def open_language(request, code):
+ return '/%s/' % code
+
+def open_translation_project(request, language_code, project_code):
+ return '/%s/%s/' % (language_code, project_code)
+
+def download_zip(request, path_obj):
+ if path_obj.is_dir:
+ current_folder = path_obj.pootle_path
+ else:
+ current_folder = path_obj.parent.pootle_path
+ # FIXME: ugly URL, django.core.urlresolvers.reverse() should work
+ archive_name = "%sexport/zip" % current_folder
+ return archive_name
+
+def export(request, pootle_path, format):
+ return '%s/export/%s' % (pootle_path, format)
+
+def commit(request, path_obj):
+ params = ProjectIndexState(request.GET).encode()
+ return url_manip.make_url(path_obj.pootle_path + '/commit', params)
+
+def update(request, path_obj):
+ params = ProjectIndexState(request.GET).encode()
+ return url_manip.make_url(path_obj.pootle_path + '/update', params)
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/language/item_dict.py b/Pootle-2.0.0/local_apps/pootle_app/views/language/item_dict.py
new file mode 100644
index 0000000..bb46167
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/language/item_dict.py
@@ -0,0 +1,301 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+"""Helper functions for the rendering of several items on the index views and
+similar pages."""
+
+from django.utils.translation import ugettext as _
+from django.utils.translation import ungettext
+
+from translate.storage import versioncontrol
+from pootle_app.models.permissions import check_permission
+from pootle_store.models import Store
+from pootle_app.views.language import dispatch
+
+################################################################################
+
+def get_item_summary(request, quick_stats, path_obj):
+ translated_words = quick_stats['translatedsourcewords']
+ total_words = quick_stats['totalsourcewords']
+ num_stores = Store.objects.filter(pootle_path__startswith=path_obj.pootle_path).count()
+ file_stats = ungettext("%d file", "%d files", num_stores, num_stores)
+ # The translated word counts
+ word_stats = _("%(translated)d/%(total)d words (%(translatedpercent)d%%) translated",
+ {"translated": translated_words,
+ "total": total_words,
+ "translatedpercent": quick_stats['translatedpercentage']}
+ )
+ # The translated unit counts
+ string_stats_text = _("%(translated)d/%(total)d strings",
+ {"translated": quick_stats['translated'],
+ "total": quick_stats['total']}
+ )
+ string_stats = '<span class="string-statistics">[%s]</span>' % string_stats_text
+ # The whole string of stats
+ return '%s %s %s' % (file_stats, word_stats, string_stats)
+
+def get_item_stats(request, quick_stats, path_obj, show_checks=False):
+ result = {
+ 'summary': get_item_summary(request, quick_stats, path_obj),
+ 'checks': [],
+ }
+ if show_checks:
+ result['checks'] = getcheckdetails(request, path_obj)
+ return result
+
+def getcheckdetails(request, path_obj):
+ """return a list of strings describing the results of
+ checks"""
+ checklinks = []
+ try:
+ property_stats = path_obj.getcompletestats(request.translation_project.checker)
+ total = property_stats['total']
+ keys = property_stats.keys()
+ keys.sort()
+ for checkname in keys:
+ if not checkname.startswith('check-'):
+ continue
+ checkcount = property_stats[checkname]
+ if total and checkcount:
+ stats = ungettext('%(checks)d string (%(checkspercent)d%%) failed',
+ '%(checks)d strings (%(checkspercent)d%%) failed', checkcount,
+ {"checks": checkcount, "checkspercent": (checkcount * 100) / total}
+ )
+ checklink = {'href': dispatch.review(request, path_obj.pootle_path, match_names=[checkname]),
+ 'text': checkname.replace('check-', '', 1),
+ 'stats': stats}
+ checklinks += [checklink]
+ except IOError:
+ pass
+ return checklinks
+
+################################################################################
+
+def review_link(request, path_obj):
+ try:
+ if path_obj.getcompletestats(request.translation_project.checker).get('check-hassuggestion', 0):
+ if check_permission('translate', request):
+ text = _('Review Suggestions')
+ else:
+ text = _('View Suggestions')
+ return {
+ 'href': dispatch.translate(request, path_obj.pootle_path, match_names=['check-hassuggestion']),
+ 'text': text }
+ except IOError:
+ pass
+
+def quick_link(request, path_obj):
+ try:
+ if path_obj.getquickstats()['translated'] < path_obj.getquickstats()['total']:
+ if check_permission('translate', request):
+ text = _('Quick Translate')
+ else:
+ text = _('View Untranslated')
+ return {
+ 'href': dispatch.translate(request, path_obj.pootle_path, match_names=['check-isfuzzy', 'untranslated']),
+ 'text': text }
+ except IOError:
+ pass
+
+def translate_all_link(request, path_obj):
+ #FIXME: what permissions to check for here?
+ return {
+ 'href': dispatch.translate(request, path_obj.pootle_path, match_names=[]),
+ 'text': _('Translate All') }
+
+def zip_link(request, path_obj):
+ if check_permission('archive', request):
+ text = _('ZIP of directory')
+ link = dispatch.download_zip(request, path_obj)
+ return {
+ 'href': link,
+ 'text': text,
+ }
+
+def po_link(request, path_obj):
+ href = dispatch.export(request, path_obj.pootle_path, 'po')
+ return {
+ 'href': href,
+ 'text': _('Download PO'),
+ }
+
+def xliff_link(request, path_obj):
+ text = _('Download XLIFF')
+ href = dispatch.export(request, path_obj.pootle_path, 'xlf')
+ return {
+ 'href': href,
+ 'text': text,
+
+ }
+
+def download_link(request, path_obj):
+ return {
+ 'href': '/export/%s' % path_obj.file.name,
+ 'text': _('Download'),
+ }
+
+def commit_link(request, path_obj):
+ if check_permission('commit', request) and versioncontrol.hasversioning(request.translation_project.abs_real_path):
+ link = dispatch.commit(request, path_obj)
+ text = _('Commit to VCS')
+ return {
+ 'href': link,
+ 'text': text,
+ 'link': link,
+ }
+
+def update_link(request, path_obj):
+ if check_permission('commit', request) and versioncontrol.hasversioning(request.translation_project.abs_real_path):
+ link = dispatch.update(request, path_obj)
+ text = _('Update from VCS')
+ return {
+ 'href': link,
+ 'text': text,
+ 'link': link,
+ }
+
+def _gen_link_list(request, path_obj, linkfuncs):
+ links = []
+ for linkfunc in linkfuncs:
+ link = linkfunc(request, path_obj)
+ if link is not None:
+ links.append(link)
+ return links
+
+def store_translate_links(request, path_obj):
+ """returns a list of links for store items in translate tab"""
+ linkfuncs = [quick_link, translate_all_link, update_link, commit_link, download_link]
+ return _gen_link_list(request, path_obj, linkfuncs)
+
+def store_review_links(request, path_obj):
+ """returns a list of links for store items in review tab"""
+ linkfuncs = [review_link, update_link, commit_link, download_link]
+ return _gen_link_list(request, path_obj, linkfuncs)
+
+def directory_translate_links(request, path_obj):
+ """returns a list of links for directory items in translate tab"""
+ return _gen_link_list(request, path_obj, [quick_link, translate_all_link, zip_link])
+
+def directory_review_links(request, path_obj):
+ """returns a list of links for directory items in review tab"""
+ return _gen_link_list(request, path_obj, [review_link, zip_link])
+
+
+################################################################################
+
+def nice_percentage(percentage):
+ """Return an integer percentage, but avoid returning 0% or 100% if it
+ might be misleading."""
+ # Let's try to be clever and make sure than anything above 0.0 and below 0.5
+ # will show as at least 1%, and anything above 99.5% and less than 100% will
+ # show as 100%.
+ if 99 < percentage < 100:
+ return 99
+ if 0 < percentage < 1:
+ return 1
+ return int(round(percentage))
+
+def add_percentages(quick_stats):
+ """Add percentages onto the raw stats dictionary."""
+ quick_stats['translatedpercentage'] = nice_percentage(100.0 * quick_stats['translatedsourcewords'] / max(quick_stats['totalsourcewords'], 1))
+ quick_stats['fuzzypercentage'] = nice_percentage(100.0 * quick_stats['fuzzysourcewords'] / max(quick_stats['totalsourcewords'], 1))
+ quick_stats['untranslatedpercentage'] = 100 - quick_stats['translatedpercentage'] - quick_stats['fuzzypercentage']
+ return quick_stats
+
+def stats_descriptions(quick_stats):
+ """Provides a dictionary with two textual descriptions of the work
+ outstanding."""
+
+ untranslated = quick_stats["untranslatedsourcewords"]
+ fuzzy = quick_stats["fuzzysourcewords"]
+ todo_words = untranslated + fuzzy
+ todo_text = ungettext("%d word needs attention",
+ "%d words need attention", todo_words, todo_words)
+
+ todo_tooltip = u""
+ untranslated_tooltip = ungettext("%d word untranslated", "%d words untranslated", untranslated, untranslated)
+ fuzzy_tooltip = ungettext("%d word needs review", "%d words need review", fuzzy, fuzzy)
+ # Firefox and Opera doesn't actually support newlines in tooltips, so we
+ # add some extra space to keep things readable
+ todo_tooltip = u" \n".join([untranslated_tooltip, fuzzy_tooltip])
+
+ return {
+ 'todo_text': todo_text,
+ 'todo_tooltip': todo_tooltip,
+ }
+
+def make_generic_item(request, path_obj, action, show_checks=False):
+ """Template variables for each row in the table.
+
+ make_directory_item() and make_store_item() will add onto these variables."""
+ try:
+ quick_stats = add_percentages(path_obj.getquickstats())
+ info = {
+ 'href': action,
+ 'data': quick_stats,
+ 'tooltip': _('%(percentage)d%% complete' %
+ {'percentage': quick_stats['translatedpercentage']}),
+ 'title': path_obj.name,
+ 'stats': get_item_stats(request, quick_stats, path_obj, show_checks),
+ }
+ errors = quick_stats.get('errors', 0)
+ if errors:
+ info['errortooltip'] = ungettext('Error reading %d file', 'Error reading %d files', errors, errors)
+ info.update(stats_descriptions(quick_stats))
+ except IOError, e:
+ info = {
+ 'href': action,
+ 'title': path_obj.name,
+ 'errortooltip': e.strerror,
+ 'data': {'errors': 1},
+ }
+ return info
+
+def make_directory_item(request, directory, links_required=None):
+ action = dispatch.show_directory(request, directory.pootle_path)
+ show_checks = links_required == 'review'
+ item = make_generic_item(request, directory, action, show_checks)
+ if links_required == 'translate':
+ item['actions'] = directory_translate_links(request, directory)
+ elif links_required == 'review':
+ item['actions'] = directory_review_links(request, directory)
+ else:
+ item['actions'] = []
+ item.update({
+ 'icon': 'folder',
+ 'isdir': True })
+ return item
+
+def make_store_item(request, store, links_required=None):
+ action = dispatch.translate(request, store.pootle_path)
+ show_checks = links_required == 'review'
+ item = make_generic_item(request, store, action, show_checks)
+ if links_required == 'translate':
+ item['actions'] = store_translate_links(request, store)
+ elif links_required == 'review':
+ item['actions'] = store_review_links(request, store)
+ else:
+ item['actions'] = []
+ item['href_todo'] = dispatch.review(request, store.pootle_path,
+ match_names=['check-isfuzzy,untranslated'])
+ item.update({
+ 'icon': 'file',
+ 'isfile': True })
+ return item
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/language/language_admin.py b/Pootle-2.0.0/local_apps/pootle_app/views/language/language_admin.py
new file mode 100644
index 0000000..feb5b02
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/language/language_admin.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# Pootle is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# Pootle is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Pootle; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from django.shortcuts import get_object_or_404, render_to_response
+from django.template import RequestContext
+from django.utils.translation import ugettext as _
+from django.core.exceptions import PermissionDenied
+
+from pootle_app.models.language import Language
+from pootle_app.models.permissions import get_matching_permissions, check_permission
+from pootle_app.models.profile import get_profile
+from pootle_app.views.language.admin_permissions import process_update as process_permission_update
+
+from pootle.i18n.gettext import tr_lang
+
+
+def view(request, language_code):
+ # Check if the user can access this view
+ language = get_object_or_404(Language, code=language_code)
+ request.permissions = get_matching_permissions(get_profile(request.user),
+ language.directory)
+ if not check_permission('administrate', request):
+ raise PermissionDenied(_("You do not have rights to administer this language."))
+
+ permission_set_formset = process_permission_update(request, language.directory)
+
+ template_vars = {
+ "language": { 'code': language_code,
+ 'name': tr_lang(language.fullname) },
+ "permissions_title": _("User Permissions"),
+ "username_title": _("Username"),
+ "permission_set_formset": permission_set_formset,
+ "adduser_text": _("(select to add user)"),
+ "hide_fileadmin_links": True,
+ "feed_path": '%s/' % language.code,
+ }
+ return render_to_response("language/language_admin.html", template_vars,
+ context_instance=RequestContext(request))
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/language/language_index.py b/Pootle-2.0.0/local_apps/pootle_app/views/language/language_index.py
new file mode 100644
index 0000000..9155125
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/language/language_index.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2004-2009 Zuza Software Foundation
+#
+# This file is part of translate.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+from django.shortcuts import get_object_or_404
+from django.utils.translation import ugettext as _
+from django.utils.translation import ungettext
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.core.exceptions import PermissionDenied
+
+from pootle_app.views.language.project_index import get_stats_headings
+from pootle_app.views.language.item_dict import nice_percentage, add_percentages, stats_descriptions
+from pootle_app.views import pagelayout
+from pootle_app.views.top_stats import gentopstats
+from pootle_app.models import Language, Submission
+
+from pootle.i18n.gettext import tr_lang
+
+from pootle_app.models.permissions import get_matching_permissions, check_permission
+from pootle_app.models.profile import get_profile
+
+def limit(query):
+ return query[:5]
+
+def get_last_action(translation_project):
+ try:
+ return Submission.objects.filter(translation_project=translation_project).latest()
+ except Submission.DoesNotExist:
+ return ''
+
+def make_project_item(translation_project):
+ project = translation_project.project
+ href = translation_project.pootle_path
+ projectstats = add_percentages(translation_project.getquickstats())
+ info = {
+ 'code': project.code,
+ 'href': href,
+ 'icon': 'folder',
+ 'title': project.fullname,
+ 'description': project.description,
+ 'data': projectstats,
+ 'lastactivity': get_last_action(translation_project),
+ 'isproject': True,
+ 'tooltip': _('%(percentage)d%% complete',
+ {'percentage': projectstats['translatedpercentage']})
+ }
+ errors = projectstats.get('errors', 0)
+ if errors:
+ info['errortooltip'] = ungettext('Error reading %d file', 'Error reading %d files', errors, errors)
+ info.update(stats_descriptions(projectstats))
+ return info
+
+def language_index(request, language_code):
+ language = get_object_or_404(Language, code=language_code)
+ request.permissions = get_matching_permissions(get_profile(request.user), language.directory)
+
+ if not check_permission("view", request):
+ raise PermissionDenied
+
+ projects = language.translationproject_set.all().order_by('project__fullname')
+ projectcount = len(projects)
+ items = (make_project_item(translate_project) for translate_project in projects)
+
+ totals = language.getquickstats()
+ average = nice_percentage(totals['translatedsourcewords'] * 100.0 / max(totals['totalsourcewords'], 1))
+ topstats = gentopstats(lambda query: query.filter(translation_project__language__code=language_code))
+
+ templatevars = {
+ 'language': {
+ 'code': language.code,
+ 'name': tr_lang(language.fullname),
+ 'stats': ungettext('%(projects)d project, %(average)d%% translated',
+ '%(projects)d projects, %(average)d%% translated',
+ projectcount, {"projects": projectcount, "average": average}),
+ },
+ 'feed_path': '%s/' % language.code,
+ 'projects': items,
+ 'statsheadings': get_stats_headings(),
+ 'topstats': topstats,
+ 'instancetitle': pagelayout.get_title(),
+ }
+ return render_to_response("language/language_index.html", templatevars, context_instance=RequestContext(request))
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/language/navbar_dict.py b/Pootle-2.0.0/local_apps/pootle_app/views/language/navbar_dict.py
new file mode 100644
index 0000000..3c97a7e
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/language/navbar_dict.py
@@ -0,0 +1,97 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+"""Helper methods for the navigation bar."""
+
+from django.utils.translation import ugettext as _
+
+from pootle.i18n.gettext import tr_lang
+
+from pootle_app import url_manip
+from pootle_app.models.permissions import check_permission
+from pootle_app.views.language import dispatch
+from pootle_app.views.language import item_dict
+from pootle_app.views.language.item_dict import directory_translate_links, directory_review_links
+
+
+def make_directory_pathlinks(request, project_url, url, links):
+ if url != project_url:
+ links.append({'href': dispatch.show_directory(request, url),
+ 'text': url_manip.basename(url)})
+ return make_directory_pathlinks(request, project_url, url_manip.parent(url), links)
+ else:
+ return list(reversed(links))
+
+def make_store_pathlinks(request, project_url, store, links):
+ links = make_directory_pathlinks(request, project_url, url_manip.parent(store.pootle_path), [])
+ links.append({'href': dispatch.translate(request, store.pootle_path),
+ 'text': store.name})
+ return links
+
+def make_directory_actions(request, links_required=None):
+ directory = request.translation_project.directory
+ if links_required == 'translate':
+ return directory_translate_links(request, directory)
+ elif links_required == 'review':
+ return directory_review_links(request, directory)
+
+
+def make_navbar_path_dict(request, path_links=None):
+ def make_admin(request):
+ if check_permission('admin', request):
+ return {'href': dispatch.translation_project_admin(request.translation_project),
+ 'text': _('Admin')}
+ else:
+ return None
+
+ language = request.translation_project.language
+ project = request.translation_project.project
+ return {
+ 'admin': make_admin(request),
+ 'language': {'href': dispatch.open_language(request, language.code),
+ 'text': tr_lang(language.fullname)},
+ 'project': {'href': dispatch.open_translation_project(request, language.code, project.code),
+ 'text': project.fullname},
+ 'pathlinks': path_links }
+
+def make_directory_navbar_dict(request, directory, links_required=None):
+ result = item_dict.make_directory_item(request, directory, links_required)
+ project_url = request.translation_project.directory.pootle_path
+ path_links = make_directory_pathlinks(request, project_url, directory.pootle_path, [])
+ if links_required:
+ actions = make_directory_actions(request, links_required)
+ else:
+ actions = []
+ result.update({
+ 'path': make_navbar_path_dict(request, path_links),
+ 'actions': actions })
+ del result['title']
+ return result
+
+def make_store_navbar_dict(request, store):
+ result = item_dict.make_store_item(request, store)
+ project_url = request.translation_project.directory.pootle_path
+ path_links = make_store_pathlinks(request, project_url, store, [])
+ result.update({
+ 'path': make_navbar_path_dict(request, path_links),
+ 'actions': {},
+ })
+ del result['title']
+ return result
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/language/project_index.py b/Pootle-2.0.0/local_apps/pootle_app/views/language/project_index.py
new file mode 100644
index 0000000..6258433
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/language/project_index.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+from django.utils.translation import ugettext as _
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.core.exceptions import PermissionDenied
+
+from pootle_app.models.permissions import get_matching_permissions, check_permission
+from pootle_app.models.profile import get_profile
+from pootle_app.views.base import BaseView
+from pootle_app.views.language import tp_common
+
+from pootle.i18n.gettext import tr_lang
+
+from pootle_app.views.language import dispatch, navbar_dict, search_forms
+
+################################################################################
+
+def get_stats_headings():
+ """returns a dictionary of localised headings"""
+ return {
+ "name": _("Name"),
+ "translated": _("Translated"),
+ "translatedpercentage": _("Translated percentage"),
+ "translatedwords": _("Translated words"),
+ "fuzzy": _("Fuzzy"),
+ "fuzzypercentage": _("Fuzzy percentage"),
+ "fuzzywords": _("Fuzzy words"),
+ "untranslated": _("Untranslated"),
+ "untranslatedpercentage": _("Untranslated percentage"),
+ "untranslatedwords": _("Untranslated words"),
+ "total": _("Total"),
+ "totalwords": _("Total Words"),
+ # l10n: noun. The graphical representation of translation status
+ "progress": _("Progress"),
+ "summary": _("Summary")
+ }
+
+################################################################################
+
+class ProjectIndexView(BaseView):
+ def GET(self, template_vars, request, translation_project, directory):
+ template_vars = super(ProjectIndexView, self).GET(template_vars, request)
+ request.permissions = get_matching_permissions(get_profile(request.user), translation_project.directory)
+ state = dispatch.ProjectIndexState(request.GET)
+ project = translation_project.project
+ language = translation_project.language
+
+ template_vars.update({
+ 'project': {"code": project.code, "name": project.fullname},
+ 'language': {"code": language.code, "name": tr_lang(language.fullname)},
+ 'search': search_forms.get_search_form(request),
+ 'children': tp_common.get_children(request, translation_project, directory),
+ 'navitems': [navbar_dict.make_directory_navbar_dict(request, directory)],
+ 'stats_headings': get_stats_headings(),
+ 'editing': state.editing,
+ 'topstats': tp_common.top_stats(translation_project),
+ 'feed_path': directory.pootle_path[1:],
+ })
+ return template_vars
+
+def view(request, translation_project, directory):
+ request.permissions = get_matching_permissions(get_profile(request.user),
+ translation_project.directory)
+ if not check_permission("view", request):
+ raise PermissionDenied(_("You do not have rights to access this translation project."))
+
+ view_obj = ProjectIndexView(forms=dict(upload=tp_common.UploadHandler,
+ update=tp_common.UpdateHandler))
+ return render_to_response("language/tp_overview.html",
+ view_obj(request, translation_project, directory),
+ context_instance=RequestContext(request))
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/language/search_forms.py b/Pootle-2.0.0/local_apps/pootle_app/views/language/search_forms.py
new file mode 100644
index 0000000..b05546d
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/language/search_forms.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+"""Helper methods for search functionality."""
+
+from django.utils.translation import ugettext_lazy as _
+from django import forms
+from django.utils.safestring import mark_safe
+from pootle_app.models.search import Search
+
+class SearchForm(forms.Form):
+ text = forms.CharField(widget=forms.TextInput(attrs={'size':'15'}))
+
+ def as_p(self):
+ return mark_safe('<label class="inputHint" for="%(for_label)s">%(title)s</label>%(text)s' % {
+ 'title': self.initial['title'],
+ 'for_label': self['text'].auto_id,
+ 'text': self['text'].as_widget()
+ })
+
+ def as_hidden(self):
+ return mark_safe(''.join(field.as_hidden() for field in self))
+
+class AdvancedSearchForm(forms.Form):
+ source = forms.BooleanField(label=_('Source Text'), required=False, initial=True)
+ target = forms.BooleanField(label=_('Target Text'), required=False, initial=True)
+ notes = forms.BooleanField(label=_('Comments'), required=False, initial= False)
+ locations = forms.BooleanField(label=_('Locations'), required=False, initial=False)
+
+ def as_hidden(self):
+ """Brain dead Django mungles rendering of checkboxes if initial values are routed via as_hidden
+ check http://code.djangoproject.com/ticket/9336 for more info"""
+
+ def field_hidden(field):
+ if field.data:
+ return '<input type="hidden" name="%s" value="True" id="id_%s" />' % (field.name, field.name)
+ else:
+ return ''
+
+ return mark_safe(''.join(field_hidden(field) for field in self))
+
+
+# TBD: Init the search forms from a SearchState object?
+def get_search_form(request, search_text=None):
+
+ search_form = None
+ advanced_search_form = None
+
+ if request.method == 'POST':
+ search_form = SearchForm(data=request.POST, initial={'title': _('Search'), 'text': search_text or ''})
+ if not search_form.is_valid():
+ search_form = None
+ advanced_search_form = AdvancedSearchForm(data=request.POST)
+ if not advanced_search_form.is_valid():
+ advanced_search_form = None
+
+ if search_form is None or advanced_search_form is None:
+ search_form = SearchForm(initial={'title': _('Search'), 'text': search_text or ''})
+ advanced_search_form = AdvancedSearchForm()
+
+ return {
+ 'search_form': SearchForm(data=request.POST,
+ initial={'title': _('Search'), 'text': search_text or ''}),
+ 'advanced_search_form': advanced_search_form,
+ 'advanced_search_title': _('Advanced Search'),
+ }
+
+def search_from_request(request):
+ def get_list(request, name):
+ try:
+ return request.GET[name].split(',')
+ except KeyError:
+ return []
+
+ def as_search_field_list(form):
+ if form.is_bound and form.is_valid():
+ return [key for key in form.cleaned_data if form.cleaned_data[key]]
+
+
+ search = get_search_form(request)
+
+ kwargs = {}
+ kwargs['match_names'] = get_list(request, 'match_names')
+ #FIXME: use cleaned_data
+ kwargs['search_text'] = search['search_form']['text'].data
+ kwargs['search_fields'] = as_search_field_list(search['advanced_search_form'])
+ kwargs['translation_project'] = request.translation_project
+ return Search(**kwargs)
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/language/tp_common.py b/Pootle-2.0.0/local_apps/pootle_app/views/language/tp_common.py
new file mode 100644
index 0000000..40fa745
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/language/tp_common.py
@@ -0,0 +1,288 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+import logging
+import os
+import StringIO
+import subprocess
+import zipfile
+
+from django import forms
+from django.utils.translation import ugettext_lazy as _
+from django.core.exceptions import PermissionDenied
+
+from translate.storage import factory, versioncontrol
+
+from pootle_app.lib import view_handler
+from pootle_app.project_tree import scan_translation_project_files
+from pootle_app.models.permissions import check_permission
+from pootle_app.models.signals import post_file_upload
+from pootle_app.views.language import item_dict
+from pootle_app.views.language import search_forms
+from pootle_app.views.top_stats import gentopstats
+from pootle_store.models import Store
+from pootle_store.util import absolute_real_path, relative_real_path
+
+def top_stats(translation_project):
+ return gentopstats(lambda query: query.filter(translation_project=translation_project))
+
+def get_children(request, translation_project, directory, links_required=None):
+ search = search_forms.search_from_request(request)
+ return [item_dict.make_directory_item(request, child_dir, links_required=links_required)
+ for child_dir in directory.child_dirs.all()] + \
+ [item_dict.make_store_item(request, child_store, links_required=links_required)
+ for child_store in directory.filter_stores(search).all()]
+
+def unix_to_host_path(p):
+ return os.sep.join(p.split('/'))
+
+def host_to_unix_path(p):
+ return '/'.join(p.split(os.sep))
+
+def get_upload_path(translation_project, relative_root_dir, local_filename):
+ """gets the path of a translation file being uploaded securely,
+ creating directories as neccessary"""
+ if os.path.basename(local_filename) != local_filename or local_filename.startswith("."):
+ raise ValueError(_("Invalid/insecure file name: %s", local_filename))
+ # XXX: Leakage of the project layout information outside of
+ # project_tree.py! The rest of Pootle shouldn't have to care
+ # whether something is GNU-style or not.
+ if translation_project.file_style == "gnu" and not translation_project.is_template_project:
+ if os.path.splitext(local_filename)[0] != translation_project.language.code:
+ raise ValueError(_("Invalid GNU-style file name: %(local_filename)s. It must match '%(langcode)s.%(filetype)s'.",
+ { 'local_filename': local_filename,
+ 'langcode': translation_project.language.code,
+ 'filetype': translation_project.project.localfiletype,
+ }))
+ dir_path = os.path.join(translation_project.real_path, unix_to_host_path(relative_root_dir))
+ return relative_real_path(os.path.join(dir_path, local_filename))
+
+def get_local_filename(translation_project, upload_filename):
+ base, ext = os.path.splitext(upload_filename)
+ new_ext = translation_project.project.localfiletype
+ if new_ext == 'po' and translation_project.is_template_project:
+ new_ext = 'pot'
+ return '%s.%s' % (base, new_ext)
+
+def unzip_external(request, relative_root_dir, django_file, overwrite):
+ from tempfile import mkdtemp, mkstemp
+ # Make a temporary directory to hold a zip file and its unzipped contents
+ tempdir = mkdtemp(prefix='pootle')
+ # Make a temporary file to hold the zip file
+ tempzipfd, tempzipname = mkstemp(prefix='pootle', suffix='.zip')
+ try:
+ # Dump the uploaded file to the temporary file
+ try:
+ os.write(tempzipfd, django_file.read())
+ finally:
+ os.close(tempzipfd)
+ # Unzip the temporary zip file
+ if subprocess.call(["unzip", tempzipname, "-d", tempdir]):
+ raise zipfile.BadZipfile(_("Error while extracting archive"))
+ # Enumerate the temporary directory...
+ for basedir, dirs, files in os.walk(tempdir):
+ for fname in files:
+ # Read the contents of a file...
+ fcontents = open(os.path.join(basedir, fname), 'rb').read()
+ # Get the filesystem path relative to the temporary directory
+ relative_host_dir = basedir[len(tempdir)+len(os.sep):]
+ # Construct a full UNIX path relative to the current
+ # translation project URL by attaching a UNIXified
+ # 'relative_host_dir' to the root relative path
+ # (i.e. the path from which the user is uploading the
+ # ZIP file.
+ sub_relative_root_dir = os.path.join(relative_root_dir, host_to_unix_path(relative_host_dir))
+ try:
+ upload_file(request, sub_relative_root_dir, fname, fcontents, overwrite)
+ except ValueError, e:
+ logging.error("error adding %s\t%s", fname, e)
+ finally:
+ # Clean up temporary file and directory used in try-block
+ import shutil
+ os.unlink(tempzipname)
+ shutil.rmtree(tempdir)
+
+def unzip_python(request, relative_root_dir, django_file, overwrite):
+ django_file.seek(0)
+ archive = zipfile.ZipFile(django_file, 'r')
+ # TODO: find a better way to return errors...
+ try:
+ for filename in archive.namelist():
+ try:
+ if filename[-1] != '/':
+ sub_relative_root_dir = os.path.join(relative_root_dir, os.path.dirname(filename))
+ newfile = StringIO.StringIO(archive.read(filename))
+ newfile.name = os.path.basename(filename)
+ upload_file(request, sub_relative_root_dir, newfile, overwrite)
+ except ValueError, e:
+ logging.error("error adding %s\t%s", filename, e)
+ finally:
+ archive.close()
+
+def upload_archive(request, directory, django_file, overwrite):
+ # First we try to use "unzip" from the system, otherwise fall back to using
+ # the slower zipfile module
+ try:
+ unzip_external(request, directory, django_file, overwrite)
+ except:
+ unzip_python(request, directory, django_file, overwrite)
+
+def overwrite_file(request, relative_root_dir, django_file, upload_path):
+ """overwrite with uploaded file"""
+ upload_dir = os.path.dirname(absolute_real_path(upload_path))
+ # Ensure that there is a directory into which we can dump the
+ # uploaded file.
+ if not os.path.exists(upload_dir):
+ os.makedirs(upload_dir)
+
+ # Get the file extensions of the uploaded filename and the
+ # current translation project
+ _upload_base, upload_ext = os.path.splitext(django_file.name)
+ _local_base, local_ext = os.path.splitext(upload_path)
+ # If the extension of the uploaded file matches the extension
+ # used in this translation project, then we simply write the
+ # file to the disc.
+ if upload_ext == local_ext:
+ outfile = open(absolute_real_path(upload_path), "wb")
+ try:
+ outfile.write(django_file.read())
+ finally:
+ outfile.close()
+ else:
+ # If the extension of the uploaded file does not match the
+ # extension of the current translation project, we create
+ # an empty file (with the right extension)...
+ empty_store = factory.getobject(absolute_real_path(upload_path))
+ # And save it...
+ empty_store.save()
+ scan_translation_project_files(request.translation_project)
+ # Then we open this newly created file and merge the
+ # uploaded file into it.
+ store = Store.objects.get(file=upload_path)
+ newstore = factory.getobject(django_file)
+ #FIXME: maybe there is a faster way to do this?
+ store.mergefile(newstore, request.user.username, allownewstrings=True, suggestions=False, notranslate=False, obsoletemissing=False)
+
+def upload_file(request, relative_root_dir, django_file, overwrite):
+ # for some reason factory checks explicitly for file existance and
+ # if file is open, which makes it impossible to work with Django's
+ # in memory uploads.
+ #
+ # setting _closed to False should work around this
+ #FIXME: hackish, does this have any undesirable side effect?
+ if getattr(django_file, '_closed', None) is None:
+ django_file._closed = False
+ # factory also checks for _mode
+ if getattr(django_file, '_mode', None) is None:
+ django_file._mode = 1
+ # mode is an attribute not a property in Django 1.1
+ if getattr(django_file, 'mode', None) is None:
+ django_file.mode = 1
+
+ local_filename = get_local_filename(request.translation_project, django_file.name)
+ # The full filesystem path to 'local_filename'
+ upload_path = get_upload_path(request.translation_project, relative_root_dir, local_filename)
+
+ file_exists = os.path.exists(absolute_real_path(upload_path))
+ if file_exists and overwrite == 'overwrite' and not check_permission('overwrite', request):
+ raise PermissionDenied(_("You do not have rights to overwrite files here."))
+ if not file_exists and not check_permission('administrate', request):
+ raise PermissionDenied(_("You do not have rights to upload new files here."))
+ if overwrite == 'merge' and not check_permission('translate', request):
+ raise PermissionDenied(_("You do not have rights to upload files here."))
+ if overwrite == 'suggest' and not check_permission('suggest', request):
+ raise PermissionDenied(_("You do not have rights to upload files here."))
+
+ if not file_exists or overwrite == 'overwrite':
+ overwrite_file(request, relative_root_dir, django_file, upload_path)
+ return
+
+ store = Store.objects.get(file=upload_path)
+ newstore = factory.getobject(django_file)
+
+ #FIXME: are we sure this is what we want to do? shouldn't we
+ # diffrentiate between structure changing uploads and mere
+ # pretranslate uploads?
+ suggestions = overwrite == 'merge'
+ notranslate = overwrite == 'suggest'
+ allownewstrings = check_permission('overwrite', request) or check_permission('administrate', request) or check_permission('commit', request)
+ obsoletemissing = allownewstrings and overwrite == 'merge'
+ store.mergefile(newstore, request.user.username, suggestions=suggestions, notranslate=notranslate,
+ allownewstrings=allownewstrings, obsoletemissing=obsoletemissing)
+
+class UpdateHandler(view_handler.Handler):
+ actions = [('do_update', _('Update all from version control'))]
+
+ class Form(forms.Form):
+ pass
+
+ def do_update(self, request, translation_project, directory):
+ translation_project.update_project(request)
+ return {}
+
+ @classmethod
+ def must_display(cls, request, *args, **kwargs):
+ return check_permission('commit', request) and \
+ versioncontrol.hasversioning(request.translation_project.abs_real_path)
+
+class UploadHandler(view_handler.Handler):
+ actions = [('do_upload', _('Upload'))]
+
+ @classmethod
+ def must_display(cls, request, *args, **kwargs):
+ return check_permission('translate', request)
+
+ def __init__(self, request, data=None, files=None):
+ choices = [('merge', _("Merge the file with the current file and turn conflicts into suggestions")),
+ ('suggest', _("Add all new translations as suggestions"))]
+ if check_permission('overwrite', request):
+ choices.insert(0, ('overwrite', _("Overwrite the current file if it exists")))
+
+ class UploadForm(forms.Form):
+ file = forms.FileField(required=True, label=_('File'))
+ overwrite = forms.ChoiceField(required=True, widget=forms.RadioSelect,
+ label='', choices=choices, initial='merge')
+
+ self.Form = UploadForm
+ super(UploadHandler, self).__init__(request, data, files)
+ self.form.allow_overwrite = check_permission('overwrite', request)
+ self.form.title = _("Upload File")
+
+ def do_upload(self, request, translation_project, directory):
+ if self.form.is_valid() and 'file' in request.FILES:
+ django_file = self.form.cleaned_data['file']
+ overwrite = self.form.cleaned_data['overwrite']
+ scan_translation_project_files(translation_project)
+ oldstats = translation_project.getquickstats()
+ # The URL relative to the URL of the translation project. Thus, if
+ # directory.pootle_path == /af/pootle/foo/bar, then
+ # relative_root_dir == foo/bar.
+ relative_root_dir = directory.pootle_path[len(translation_project.directory.pootle_path):]
+ if django_file.name.endswith('.zip'):
+ archive = True
+ upload_archive(request, relative_root_dir, django_file, overwrite)
+ else:
+ archive = False
+ upload_file(request, relative_root_dir, django_file, overwrite)
+ scan_translation_project_files(translation_project)
+ newstats = translation_project.getquickstats()
+ post_file_upload.send(sender=translation_project, user=request.user, oldstats=oldstats,
+ newstats=newstats, archive=archive)
+ return {'upload': self}
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/language/tp_review.py b/Pootle-2.0.0/local_apps/pootle_app/views/language/tp_review.py
new file mode 100644
index 0000000..dfb40c8
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/language/tp_review.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+from django.core.exceptions import PermissionDenied
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.utils.translation import ugettext as _
+
+from pootle_app.models.permissions import get_matching_permissions, check_permission
+from pootle_app.models.profile import get_profile
+from pootle_app.views.base import BaseView
+from pootle_app.views.language import navbar_dict
+from pootle_app.views.language import search_forms
+from pootle_app.views.language import tp_common
+
+from pootle.i18n.gettext import tr_lang
+
+class TPReviewView(BaseView):
+ def GET(self, template_vars, request, translation_project, directory):
+ template_vars = super(TPReviewView, self).GET(template_vars, request)
+ request.permissions = get_matching_permissions(get_profile(request.user), translation_project.directory)
+ project = translation_project.project
+ language = translation_project.language
+
+ template_vars.update({
+ 'project': {"code": project.code, "name": project.fullname},
+ 'language': {"code": language.code, "name": tr_lang(language.fullname)},
+ 'search': search_forms.get_search_form(request),
+ 'children': tp_common.get_children(request, translation_project, directory, links_required='review'),
+ 'navitems': [navbar_dict.make_directory_navbar_dict(request, directory, links_required='review')],
+ 'topstats': tp_common.top_stats(translation_project),
+ 'feed_path': directory.pootle_path[1:],
+ })
+ return template_vars
+
+def view(request, translation_project, directory):
+ request.permissions = get_matching_permissions(get_profile(request.user),
+ translation_project.directory)
+ if not check_permission("view", request):
+ raise PermissionDenied(_("You do not have rights to access review mode."))
+
+ view_obj = TPReviewView({})
+ return render_to_response("language/tp_review.html",
+ view_obj(request, translation_project, directory),
+ context_instance=RequestContext(request))
+
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/language/tp_translate.py b/Pootle-2.0.0/local_apps/pootle_app/views/language/tp_translate.py
new file mode 100644
index 0000000..b4972d3
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/language/tp_translate.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+from django.core.exceptions import PermissionDenied
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.utils.translation import ugettext as _
+
+from pootle_app.models.permissions import get_matching_permissions, check_permission
+from pootle_app.models.profile import get_profile
+from pootle_app.views.base import BaseView
+from pootle_app.views.language import navbar_dict
+from pootle_app.views.language import search_forms
+from pootle_app.views.language import tp_common
+
+from pootle.i18n.gettext import tr_lang
+
+class TPTranslateView(BaseView):
+ def GET(self, template_vars, request, translation_project, directory):
+ template_vars = super(TPTranslateView, self).GET(template_vars, request)
+ request.permissions = get_matching_permissions(get_profile(request.user), translation_project.directory)
+ project = translation_project.project
+ language = translation_project.language
+
+ template_vars.update({
+ 'project': {"code": project.code, "name": project.fullname},
+ 'language': {"code": language.code, "name": tr_lang(language.fullname)},
+ 'search': search_forms.get_search_form(request),
+ 'children': tp_common.get_children(request, translation_project, directory, links_required='translate'),
+ 'navitems': [navbar_dict.make_directory_navbar_dict(request, directory, links_required='translate')],
+ 'feed_path': directory.pootle_path[1:],
+ 'topstats': tp_common.top_stats(translation_project),
+ })
+ return template_vars
+
+def view(request, translation_project, directory):
+ request.permissions = get_matching_permissions(get_profile(request.user),
+ translation_project.directory)
+ if not check_permission("view", request):
+ raise PermissionDenied(_("You do not have rights to access translation mode."))
+
+ view_obj = TPTranslateView(forms=dict(upload=tp_common.UploadHandler,
+ update=tp_common.UpdateHandler))
+ return render_to_response("language/tp_translate.html",
+ view_obj(request, translation_project, directory),
+ context_instance=RequestContext(request))
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/language/translate_page.py b/Pootle-2.0.0/local_apps/pootle_app/views/language/translate_page.py
new file mode 100644
index 0000000..52dcea6
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/language/translate_page.py
@@ -0,0 +1,933 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2004-2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This file is somewhat based on the older Pootle/translatepage.py
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+import re
+import difflib
+import os
+
+from django.conf import settings
+from django.core.exceptions import PermissionDenied
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.utils.html import urlize
+from django.utils.translation import ugettext as _
+from django.utils.translation import ungettext
+
+from translate.storage import po
+from translate.misc.multistring import multistring
+
+from pootle.i18n.gettext import tr_lang, language_dir
+
+from pootle_store.models import Store
+
+from pootle_app.views import pagelayout
+from pootle_app.models import TranslationProject, Directory
+from pootle_app.models.profile import get_profile
+from pootle_app import unit_update
+from pootle_app.models.permissions import check_permission
+from pootle_app.views.language import dispatch, navbar_dict, search_forms
+
+xml_re = re.compile("&lt;.*?&gt;")
+
+def oddoreven(polarity):
+ if polarity % 2 == 0:
+ return "even"
+ elif polarity % 2 == 1:
+ return "odd"
+
+def get_alt_projects(request):
+ # do we have enabled alternative source language?
+ if settings.ENABLE_ALT_SRC:
+ # try to get the project if the user has chosen an alternate source language
+ return TranslationProject.objects.filter(language__in=get_profile(request.user).alt_src_langs.all(),
+ project=request.translation_project.project_id)
+ else:
+ return TranslationProject.objects.none()
+
+def add_file_links(request, store):
+ """adds a section on the current file, including any checks happening"""
+ state = dispatch.TranslatePageState(request.GET)
+ template_vars = {}
+ if store is not None:
+ if len(state.match_names) > 0:
+ checknames = \
+ ["<a href='http://translate.sourceforge.net/wiki/toolkit/pofilter_tests#%(checkname)s' \
+ title='%(checkname)s' target='_blank'>%(checkname)s</a>" % \
+ {"checkname": matchname.replace("check-", "", 1)} for matchname in state.match_names]
+ # TODO: put the following parameter in quotes, since it will be foreign in all target languages
+ # l10n: the parameter is the name of one of the quality checks, like "fuzzy"
+ template_vars["checking_text"] = _("checking %s", ", ".join(checknames))
+ return template_vars
+
+def get_rows_and_icon(request, profile):
+ state = dispatch.TranslatePageState(request.GET)
+ if state.view_mode == 'view':
+ return get_display_rows(profile), "file"
+ else:
+ return get_display_rows(profile), "edit"
+
+def get_page_links(request, store, pagesize, translations, item, first_item):
+ """gets links to other pages of items, based on the given baselink"""
+
+ pagelinks = []
+ pofilelen = store.file.getitemslen()
+
+ if pofilelen <= pagesize or item is None:
+ return pagelinks
+
+ lastitem = min(pofilelen - 1, first_item + pagesize - 1)
+ if pofilelen > pagesize and not item == 0:
+ pagelinks.append({"href": dispatch.translate(request, request.path_info, item=0),
+ # l10n: navigation link
+ "text": _("Start")})
+ if item > 0:
+ linkitem = max(item - pagesize, 0)
+ pagelinks.append({"href": dispatch.translate(request, request.path_info, item=linkitem),
+ # l10n: the parameter refers to the number of messages
+ "text": _("Previous %d", (item - linkitem))})
+
+ # l10n: the third parameter refers to the total number of messages in the file
+ pagelinks.append({"text": _("Items %(first)d to %(last)d of %(total)d",
+ {"first": first_item + 1, "last": lastitem + 1, "total": pofilelen})
+ })
+
+ if item + len(translations) < pofilelen:
+ linkitem = item + pagesize
+ itemcount = min(pofilelen - linkitem, pagesize)
+ pagelinks.append({"href": dispatch.translate(request, request.path_info, item=linkitem),
+ # l10n: the parameter refers to the number of messages
+ "text": _("Next %d", itemcount)})
+
+ if pofilelen > pagesize and (item + pagesize) < pofilelen:
+ pagelinks.append({"href": dispatch.translate(request, request.path_info,
+ item=max(pofilelen - 1, 0)),
+ # l10n: navigation link
+ "text": _("End")})
+
+ for n, pagelink in enumerate(pagelinks):
+ if n < len(pagelinks)-1:
+ pagelink["sep"] = " | "
+ else:
+ pagelink["sep"] = ""
+ return pagelinks
+
+
+def get_display_rows(profile):
+ """get the number of rows to display"""
+ rowsdesired = profile.unit_rows
+ maximum = 30
+ return min(rowsdesired, maximum)
+
+def get_units(store, item_start, item_stop):
+ return [store.file.store.units[index] for index in store.file.total[max(item_start, 0):item_stop]]
+
+def get_translations(request, profile, store, item):
+ """gets the list of translations desired for the view, and sets editable and firstitem parameters"""
+ state = dispatch.TranslatePageState(request.GET)
+ if store is None:
+ # editable, first item, items
+ return -1, item, []
+ elif state.view_mode == 'view':
+ rows = get_display_rows(profile)
+ return -1, item, get_units(store, item, item + rows)
+ else:
+ rows = get_display_rows(profile)
+ before = (rows / 2) - 1
+ first_item = max(item - before, 0)
+ last_item = first_item + rows
+ pofilelen = store.file.getitemslen()
+ if last_item > pofilelen:
+ last_item = pofilelen
+ first_item = max(last_item - rows, 0)
+ items = get_units(store, first_item, last_item)
+ return item, first_item, items
+
+def get_header_plural(request, store):
+ # get plural information from Language model
+ nplurals = request.translation_project.language.nplurals
+ plurals = request.translation_project.language.pluralequation
+
+ try:
+ # get plural information from Store
+ snplurals, splurals = store.file.store.getheaderplural()
+ if snplurals and snplurals.isdigit():
+ # file has plural information
+ #FIXME: should we check if file has correct language headers
+ nplurals = int(snplurals)
+ plurals = splurals
+ except:
+ # not a POHeader store
+ pass
+
+ return nplurals, plurals
+
+def ensure_trans_plurals(request, store, orig, trans):
+ nplurals, plurals = get_header_plural(request, store)
+ if len(orig) > 1:
+ if len(trans) != nplurals:
+ # Chop if in case it is too long
+ trans = trans[:nplurals]
+ trans.extend([u""] * (nplurals-len(trans)))
+ return trans
+
+def get_string_array(string):
+ if isinstance(string, multistring):
+ return string.strings
+ else:
+ return [string]
+
+def fancy_spaces(string):
+ """Returns the fancy spaces that are easily visible."""
+ spaces = string.group()
+ while spaces[0] in "\t\n\r":
+ spaces = spaces[1:]
+ return '<span class="translation-space"> </span>\n' * len(spaces)
+
+def add_fancy_spaces(text):
+ """Insert fancy spaces"""
+ if not text:
+ return u""
+ #More than two consecutive:
+ text = re.sub("[ ]{2,}", fancy_spaces, text)
+ #At start of string
+ text = re.sub("^[ ]+", fancy_spaces, text)
+ #After newline
+ text = re.sub("\\n([ ]+)", fancy_spaces, text)
+ #At end of string
+ text = re.sub("[ ]+$", fancy_spaces, text)
+ return text
+
+def replace_in_seq(text, *replacements):
+ if not text:
+ return u""
+ for original, replacement in replacements:
+ text = text.replace(original, replacement)
+ return text
+
+def escape_text(text, fancyspaces=True, stripescapes=False):
+ """Replace special characters &, <, >, add and handle escapes if asked."""
+ text = replace_in_seq(text,
+ ("&", "&amp;"), # Must be done first!
+ ("<", "&lt;"),
+ (">", "&gt;"))
+
+ if stripescapes:
+ text = replace_in_seq(text,
+ ("\n", '<br />'),
+ ("\r", '<br />'))
+ else:
+ fancyescape = lambda escape: \
+ '<span class="translation-highlight-escape">%s</span>' % escape
+ fancy_xml = lambda escape: \
+ '<span class="translation-highlight-html">%s</span>' % escape.group()
+ text = xml_re.sub(fancy_xml, text)
+ text = replace_in_seq(text,
+ ("\r\n", fancyescape('\\r\\n') + '<br />'),
+ ("\n", fancyescape('\\n') + '<br />'),
+ ("\r", fancyescape('\\r') + '<br />'),
+ ("\t", fancyescape('\\t')))
+ text = replace_in_seq(text, ("<br />", '<br />\n'))
+ # we don't need it at the end of the string
+
+ if text.endswith("<br />\n"):
+ text = text[:-len("<br />\n")]
+
+ if fancyspaces:
+ text = add_fancy_spaces(text)
+ return text
+
+def getorigdict(item, orig, editable):
+ if editable:
+ focus_class = "translate-original-focus"
+ else:
+ focus_class = ""
+ purefields = []
+ for pluralid, pluraltext in enumerate(orig):
+ pureid = "orig-pure%d-%d" % (item, pluralid)
+ purefields.append({"pureid": pureid, "name": pureid, "value": pluraltext})
+ origdict = {
+ "focus_class": focus_class,
+ "itemid": "orig%d" % item,
+ "pure": purefields,
+ "isplural": len(orig) > 1 or None,
+ "singular_title": _("Singular"),
+ "plural_title": _("Plural"),
+ }
+ if len(orig) > 1:
+ origdict["singular_text"] = escape_text(orig[0])
+ origdict["plural_text"] = escape_text(orig[1])
+ else:
+ origdict["text"] = escape_text(orig[0])
+ return origdict
+
+def get_terminology(request, store, item):
+ try:
+ term_matcher = request.translation_project.gettermmatcher()
+ if term_matcher is not None:
+ return term_matcher.matches(store.file.getitem(item).source)
+ else:
+ return []
+ except:
+ import logging
+ import traceback
+ logging.log(logging.ERROR, traceback.format_exc())
+ return []
+
+def remove_button_if_no_permission(label, buttons, request):
+ if label in buttons and not check_permission(label, request):
+ buttons.remove(label)
+
+def get_trans_buttons(request, translation_project, item, desiredbuttons):
+ """gets buttons for actions on translation"""
+ remove_button_if_no_permission("suggest", desiredbuttons, request)
+ remove_button_if_no_permission("translate", desiredbuttons, request)
+ specialchars = translation_project.language.specialchars
+ return {
+ "desired": desiredbuttons,
+ "item": item,
+ "specialchars": specialchars,
+ }
+
+def escape_for_textarea(text):
+ return replace_in_seq(text,
+ ("&", "&amp;"), # Must be done first!
+ ("<", "&lt;"),
+ (">", "&gt;"),
+ ("\r\n", '\\r\\n'),
+ ("\n", '\\n'),
+ ("\\n", '\\n\n'),
+ ("\t", '\\t'))
+
+def unescape_submition(text):
+ return replace_in_seq(text,
+ ("\t", ""),
+ ("\n", ""),
+ ("\r", ""),
+ ("\\t", "\t"),
+ ("\\n", "\n"),
+ ("\\r", "\r"))
+
+def get_edit_link(request, store, item):
+ """gets a link to edit the given item, if the user has permission"""
+ if check_permission("translate", request) or check_permission("suggest", request):
+ return {"href": dispatch.translate(request, store.pootle_path, view_mode='translate',
+ item=item, match_names=[]),
+ "linkid": "editlink%d" % item }
+ else:
+ return {}
+
+def get_trans_view(request, store, item, trans, textarea=False):
+ """returns a widget for viewing the given item's translation"""
+ if textarea:
+ escapefunction = escape_for_textarea
+ else:
+ escapefunction = escape_text
+ editlink = get_edit_link(request, store, item)
+ transdict = {"editlink": editlink}
+
+ if len(trans) > 1:
+ forms = []
+ for pluralitem, pluraltext in enumerate(trans):
+ form = {"title": _("Plural Form %d", pluralitem), "n": pluralitem, "text": escapefunction(pluraltext)}
+ forms.append(form)
+ transdict["forms"] = forms
+ elif trans:
+ transdict["text"] = escapefunction(trans[0])
+ else:
+ # Error, problem with plurals perhaps?
+ transdict["text"] = escapefunction(_("Translation not possible because plural information for your language is not available. Please contact the site administrator."))
+ transdict["error"] = True
+ return transdict
+
+def get_trans_edit(request, store, item, trans):
+ """returns a widget for editing the given item and translation"""
+ transdict = { "rows": 5 }
+ if check_permission("translate", request) or check_permission("suggest", request):
+ profile = get_profile(request.user)
+ transdict = { "rows": profile.input_height }
+ focusbox = ""
+ if len(trans) > 1:
+ buttons = get_trans_buttons(request, request.translation_project, item, ["translate", "suggest", "copy", "skip", "back"])
+ forms = []
+ for pluralitem, pluraltext in enumerate(trans):
+ pluralform = _("Plural Form %d", pluralitem)
+ pluraltext = escape_for_textarea(pluraltext)
+ textid = "trans%d-%d" % (item, pluralitem)
+ forms.append({"title": pluralform, "name": textid, "text": pluraltext, "n": pluralitem})
+ if not focusbox:
+ focusbox = textid
+ transdict["forms"] = forms
+ elif trans:
+ buttons = get_trans_buttons(request, request.translation_project, item, ["translate", "suggest", "copy", "skip", "back"])
+ transdict["text"] = escape_for_textarea(trans[0])
+ textid = "trans%d" % item
+ focusbox = textid
+ else:
+ # Perhaps there is no plural information available
+ buttons = get_trans_buttons(request, request.translation_project, item, ["skip", "back"])
+ # l10n: This is an error message that will display if the relevant problem occurs
+ transdict["text"] = escape_for_textarea(_("Translation not possible because plural information for your language is not available. Please contact the site administrator."))
+ textid = "trans%d" % item
+ transdict["error"] = True
+ focusbox = textid
+ transdict["focusbox"] = focusbox
+ else:
+ # TODO: work out how to handle this (move it up?)
+ transdict.update(get_trans_view(request, store, item, trans, textarea=True))
+ buttons = get_trans_buttons(request, request.translation_project, item, ["skip", "back"])
+ transdict["buttons"] = buttons
+ return transdict
+
+def highlight_diffs(text, diffs, issrc=True):
+ """highlights the differences in diffs in the text.
+ diffs should be list of diff opcodes
+ issrc specifies whether to use the src or destination positions in reconstructing the text
+ this escapes the text on the fly to prevent confusion in escaping the highlighting"""
+ if issrc:
+ diffstart = [(i1, 'start', tag) for (tag, i1, i2, j1, j2) in diffs if tag != 'equal']
+ diffstop = [(i2, 'stop', tag) for (tag, i1, i2, j1, j2) in diffs if tag != 'equal']
+ else:
+ diffstart = [(j1, 'start', tag) for (tag, i1, i2, j1, j2) in diffs if tag != 'equal']
+ diffstop = [(j2, 'stop', tag) for (tag, i1, i2, j1, j2) in diffs if tag != 'equal']
+ diffswitches = diffstart + diffstop
+ diffswitches.sort()
+ textdiff = ""
+ textnest = 0
+ textpos = 0
+ spanempty = False
+ for i, switch, tag in diffswitches:
+ textsection = escape_text(text[textpos:i], fancyspaces=False)
+ textdiff += textsection
+ if textsection:
+ spanempty = False
+ if switch == 'start':
+ textnest += 1
+ elif switch == 'stop':
+ textnest -= 1
+ if switch == 'start' and textnest == 1:
+ # start of a textition
+ textdiff += "<span class='translate-diff-%s'>" % tag
+ spanempty = True
+ elif switch == 'stop' and textnest == 0:
+ # start of an equals block
+ if spanempty:
+ # FIXME: work out why kid swallows empty spans, and browsers display them horribly, then remove this
+ textdiff += "()"
+ textdiff += "</span>"
+ textpos = i
+ textdiff += escape_text(text[textpos:], fancyspaces=False)
+ return textdiff
+
+def get_diff_codes(cmp1, cmp2):
+ """compares the two strings and returns opcodes"""
+ return difflib.SequenceMatcher(None, cmp1, cmp2).get_opcodes()
+
+def get_trans_review(request, store, item, trans, suggestions):
+ """returns a widget for reviewing the given item's suggestions"""
+ hasplurals = len(trans) > 1
+ diffcodes = {}
+ for pluralitem, pluraltrans in enumerate(trans):
+ if isinstance(pluraltrans, str):
+ trans[pluralitem] = pluraltrans.decode("utf-8")
+ for suggestion in suggestions:
+ for pluralitem, pluralsugg in enumerate(suggestion):
+ if isinstance(pluralsugg, str):
+ suggestion[pluralitem] = pluralsugg.decode("utf-8")
+ forms = []
+ for pluralitem, pluraltrans in enumerate(trans):
+ pluraldiffcodes = [get_diff_codes(pluraltrans, suggestion[pluralitem]) for suggestion in suggestions]
+ diffcodes[pluralitem] = pluraldiffcodes
+ combineddiffs = reduce(list.__add__, pluraldiffcodes, [])
+ transdiff = highlight_diffs(pluraltrans, combineddiffs, issrc=True)
+ form = {"n": pluralitem, "diff": transdiff, "title": None}
+ if hasplurals:
+ pluralform = _("Plural Form %d", pluralitem)
+ form["title"] = pluralform
+ forms.append(form)
+ transdict = {
+ "current_title": _("Current Translation:"),
+ "editlink": get_edit_link(request, store, item),
+ "forms": forms,
+ "isplural": hasplurals or None,
+ "itemid": "trans%d" % item,
+ }
+ suggitems = []
+ for suggid, msgstr in enumerate(suggestions):
+ suggestedby = store.getsuggester(item, suggid)
+ if len(suggestions) > 1:
+ if suggestedby:
+ # l10n: First parameter: number
+ # l10n: Second parameter: name of translator
+ suggtitle = _("Suggestion %(suggid)d by %(user)s:", {"suggid": (suggid+1), "user": suggestedby})
+ else:
+ suggtitle = _("Suggestion %d:", (suggid+1))
+ else:
+ if suggestedby:
+ # l10n: parameter: name of translator
+ suggtitle = _("Suggestion by %s:", suggestedby)
+ else:
+ suggtitle = _("Suggestion:")
+ forms = []
+ for pluralitem, pluraltrans in enumerate(trans):
+ pluralsuggestion = msgstr[pluralitem]
+ suggdiffcodes = diffcodes[pluralitem][suggid]
+ suggdiff = highlight_diffs(pluralsuggestion, suggdiffcodes, issrc=False)
+ if isinstance(pluralsuggestion, str):
+ pluralsuggestion = pluralsuggestion.decode("utf8")
+ form = {"diff": suggdiff}
+ form["suggid"] = "suggest%d-%d-%d" % (item, suggid, pluralitem)
+ form["value"] = pluralsuggestion
+ if hasplurals:
+ form["title"] = _("Plural Form %d", pluralitem)
+ forms.append(form)
+ suggdict = {
+ "title": suggtitle,
+ "author": suggestedby,
+ "forms": forms,
+ "suggid": "%d-%d" % (item, suggid),
+ "back": None,
+ "skip": None,
+ }
+ suggitems.append(suggdict)
+ # l10n: Button that takes the user to the previous unit or page
+ backbutton = {"item": item, "text": _("Back")}
+ skipbutton = {"item": item, "text": _("Skip")}
+ if suggitems:
+ suggitems[-1]["back"] = backbutton
+ suggitems[-1]["skip"] = skipbutton
+ transdict["suggtext"] = ungettext("%d suggestion",
+ "%d suggestions",
+ len(suggitems), len(suggitems))
+ else:
+ transdict["back"] = backbutton
+ transdict["skip"] = skipbutton
+ transdict["suggestions"] = suggitems
+ return transdict
+
+def get_translated_directory(target_language_code, root_directory, directory):
+ if directory.parent != root_directory:
+ translated_directory = get_translated_directory(target_language_code,
+ root_directory,
+ directory.parent)
+ return translated_directory.child_dirs.get(name=directory.name)
+ else:
+ return root_directory.child_dirs.get(name=target_language_code)
+
+def get_translated_store(alt_project, store):
+ """returns the file corresponding to store in the alternative TranslationProject"""
+ try:
+ translation_directory = get_translated_directory(alt_project.language.code,
+ Directory.objects.root,
+ store.parent)
+ if alt_project.project.get_treestyle() == 'gnu':
+ name = alt_project.language.code + os.extsep + alt_project.project.localfiletype
+ else:
+ name = store.name
+ try:
+ return translation_directory.child_stores.get(name=name)
+ except Store.DoesNotExist:
+ return None
+ except Directory.DoesNotExist:
+ return None
+
+def get_alt_src_dict(request, store, unit, alt_project):
+ alt_src_dict = {"available": False}
+ # TODO: handle plurals !!
+ if alt_project is not None:
+ #FIXME: we should bail out if alternative language == target language
+ language = alt_project.language
+ alt_src_dict.update({
+ "languagename": language.fullname,
+ "languagecode": language.code,
+ "dir": language_dir(language.code),
+ "title": tr_lang(language.fullname),
+ "available": True })
+ translated_store = get_translated_store(alt_project, store)
+ if translated_store is not None:
+ translated_store.file.store.require_index()
+
+ translated_unit = translated_store.file.store.findunit(unit.source)
+ if translated_unit is not None and translated_unit.istranslated():
+ if unit.hasplural():
+ unit_dict = {
+ "forms": [{"title": _("Plural Form %d", i),
+ "n": i,
+ "text": escape_text(text)}
+ for i, text in enumerate(translated_unit.target.strings)],
+ "isplural": True }
+ else:
+ unit_dict = {
+ "text": escape_text(translated_unit.target),
+ "isplural": False }
+
+ alt_src_dict.update(unit_dict)
+ else:
+ alt_src_dict["available"] = False
+ else:
+ alt_src_dict["available"] = False
+ return alt_src_dict
+
+def get_alt_src_list(request, store, unit):
+ return [get_alt_src_dict(request, store, unit, alt_project)
+ for alt_project in get_alt_projects(request)]
+
+def make_table(request, profile, store, item):
+ editable, first_item, translations = get_translations(request, profile, store, item)
+ state = dispatch.TranslatePageState(request.GET)
+ items = []
+ suggestions = {}
+ if (state.view_mode in ('review', 'translate')):
+ suggestions = {item: store.getsuggestions(item)}
+ for row, unit in enumerate(translations):
+ tmsuggestions = []
+ orig = get_string_array(unit.source)
+ trans = ensure_trans_plurals(request, store, orig, get_string_array(unit.target))
+ item = first_item + row
+ origdict = getorigdict(item, orig, item == editable)
+ transmerge = {}
+ suggestions[item] = store.getsuggestions(item)
+
+ message_context = ""
+ if item == editable:
+ translator_comments = unit.getnotes(origin="translator")
+ developer_comments = urlize(escape_text(unit.getnotes(origin="developer"), stripescapes=True))
+ locations = " ".join(unit.getlocations())
+ if isinstance(unit, po.pounit):
+ message_context = "".join(unit.getcontext())
+ tmsuggestions = store.gettmsuggestions(item)
+ tmsuggestions.extend(get_terminology(request, store, item))
+ transmerge = get_trans_edit(request, store, item, trans)
+ else:
+ translator_comments = unit.getnotes(origin="translator")
+ developer_comments = unit.getnotes(origin="developer")
+ locations = ""
+ transmerge = get_trans_view(request, store, item, trans)
+
+ itemsuggestions = []
+ for suggestion in suggestions[item]:
+ if suggestion.hasplural():
+ itemsuggestions.append(suggestion.target.strings)
+ else:
+ itemsuggestions.append([suggestion.target])
+ transreview = get_trans_review(request, store, item, trans, itemsuggestions)
+ if 'forms' in transmerge.keys():
+ for fnum in range(len(transmerge['forms'])):
+ transreview['forms'][fnum].update(transmerge['forms'][fnum])
+ elif 'text' in transmerge and not transmerge.get('error', False):
+ transreview['forms'][0]['text'] = transmerge['text']
+
+ transmerge.update(transreview)
+
+ transdict = {
+ "itemid": "trans%d" % item,
+ "focus_class": origdict["focus_class"],
+ "isplural": len(trans) > 1,
+ }
+ transdict.update(transmerge)
+ polarity = oddoreven(item)
+ if item == editable:
+ focus_class = "translate-focus"
+ else:
+ focus_class = ""
+
+ state_class = ""
+ fuzzy = None
+ if unit.isfuzzy():
+ state_class += "translate-translation-fuzzy"
+ fuzzy = "checked"
+
+ hassuggestion = len(transdict.get("suggestions", {})) > 0
+
+ itemdict = {
+ "itemid": item,
+ "orig": origdict,
+ "trans": transdict,
+ "polarity": polarity,
+ "focus_class": focus_class,
+ "editable": item == editable,
+ "state_class": state_class,
+ "fuzzy": fuzzy,
+ "translator_comments": translator_comments,
+ "developer_comments": developer_comments,
+ "locations": locations,
+ "message_context": message_context,
+ "tm": tmsuggestions,
+ "hassuggestion": hassuggestion
+ }
+
+ itemdict["altsrcs"] = []
+ # do we have enabled alternative source language?
+ if settings.ENABLE_ALT_SRC:
+ # get alternate source project information in a dictionary
+ if item == editable:
+ itemdict["altsrcs"] = get_alt_src_list(request, store, unit)
+
+ items.append(itemdict)
+ return items, translations, first_item
+
+keymatcher = re.compile("(\D+)([0-9\-]+)")
+
+def parsekey(key):
+ match = keymatcher.match(key)
+ if match:
+ keytype, itemcode = match.groups()
+ return keytype, itemcode
+ return None, None
+
+def dashsplit(item):
+ dashcount = item.count("-")
+ if dashcount == 2:
+ item, dashitem, subdashitem = item.split("-", 2)
+ return int(item), int(dashitem), int(subdashitem)
+ elif dashcount == 1:
+ item, dashitem = item.split("-", 1)
+ return int(item), int(dashitem), None
+ else:
+ return int(item), None, None
+
+def handle_skips(last_item, skips):
+ for item in skips:
+ last_item = item
+ return last_item
+
+def handle_backs(last_item, backs):
+ for item in backs:
+ last_item = item
+ return last_item
+
+def handle_suggestions(last_item, request, store, submitsuggests, skips, translations):
+ for item in submitsuggests:
+ if item in skips or item not in translations:
+ continue
+ value = translations[item]
+ unit_update.suggest_translation(store, item, value, request)
+ last_item = item
+ return last_item
+
+def handle_submits(last_item, request, store, submits, skips, translations, comments, fuzzies):
+ for item in submits:
+ if item in skips or item not in translations:
+ continue
+
+ newvalues = {}
+ newvalues["target"] = translations[item]
+ if isinstance(newvalues["target"], dict) and len(newvalues["target"]) == 1 and 0 in newvalues["target"]:
+ newvalues["target"] = newvalues["target"][0]
+
+ newvalues["fuzzy"] = False
+ if (fuzzies.get(item) == u'on'):
+ newvalues["fuzzy"] = True
+
+ translator_comments = comments.get(item)
+ if translator_comments:
+ newvalues["translator_comments"] = translator_comments
+
+ unit_update.update_translation(store, item, newvalues, request)
+ last_item = item
+ return last_item
+
+
+def process_post(request, store):
+ """receive any translations submitted by the user"""
+ post_dict = request.POST.copy()
+ backs = []
+ skips = []
+ submitsuggests = []
+ submits = []
+ translations = {}
+ suggestions = {}
+ comments = {}
+ fuzzies = {}
+ delkeys = []
+ for key, value in post_dict.iteritems():
+ keytype, item = parsekey(key)
+ if keytype is None:
+ continue
+ try:
+ item, dashitem, subdashitem = dashsplit(item)
+ except:
+ continue
+ if keytype == "skip":
+ skips.append(item)
+ elif keytype == "back":
+ backs.append(item)
+ elif keytype == "submitsuggest":
+ submitsuggests.append(item)
+ elif keytype == "submit":
+ submits.append(item)
+ elif keytype == "translator_comments":
+ # We need to remove carriage returns from the input.
+ value = value.replace("\r", "")
+ comments[item] = value
+ elif keytype == "fuzzy":
+ fuzzies[item] = value
+ elif keytype == "trans":
+ value = unescape_submition(value)
+ if dashitem is not None:
+ translations.setdefault(item, {})[dashitem] = value
+ else:
+ translations[item] = value
+ elif keytype == "suggest":
+ suggestions.setdefault((item, dashitem), {})[subdashitem] = value
+ elif keytype == "orig-pure":
+ # this is just to remove the hidden fields from the argdict
+ pass
+ else:
+ continue
+ delkeys.append(key)
+
+ for key in delkeys:
+ del post_dict[key]
+
+ prev_last_item = handle_backs(-1, backs)
+ last_item = handle_skips(-1, skips)
+ last_item = handle_suggestions(last_item, request, store, submitsuggests, skips, translations)
+ last_item = handle_submits(last_item, request, store, submits, skips, translations, comments, fuzzies)
+ return prev_last_item, last_item
+
+def process_post_main(store_name, item, request, next_store_item, prev_store_item):
+ store = Store.objects.get(pootle_path=store_name)
+ request.translation_project.indexer # Force initialization of the indexer
+ prev_item, next_item = process_post(request, store)
+
+ search = search_forms.search_from_request(request)
+ if next_item > -1:
+ return next_store_item(search, store_name, next_item + 1)
+ elif prev_item > -1:
+ return prev_store_item(search, store_name, prev_item - 1)
+ else:
+ return store, item
+
+def get_position(request, next_store_item, prev_store_item):
+ state = dispatch.TranslatePageState(request.GET)
+ store_name = dispatch.get_store(request)
+ item = state.item
+ if request.method == 'POST':
+ if 'new_search' in request.POST:
+ return next_store_item(search_forms.search_from_request(request), store_name, item)
+ else:
+ return process_post_main(store_name, item, request, next_store_item, prev_store_item)
+ else:
+ return next_store_item(search_forms.search_from_request(request), store_name, item)
+
+def get_failure_message(request):
+ # We are reviewing a check
+ if 'match_names' in request.GET:
+ if request.GET['match_names'] == u'check-isfuzzy,untranslated':
+ return _("End of Quick Translate.")
+ else:
+ return _("End of Quality Check Review.")
+ # This is a search
+ if 'new_search' in request.POST and 'store' not in request.POST:
+ return _("No file matched your query.")
+ return _("End of results.")
+
+def find_and_display(request, directory, next_store_item, prev_store_item):
+ try:
+ store, item = get_position(request, next_store_item, prev_store_item)
+ return view(request, directory, store, item)
+ except StopIteration:
+ return view(request, directory, None, 0, get_failure_message(request))
+
+def view(request, directory, store, item, stopped_by=None):
+ """the page which lets people edit translations"""
+ state = dispatch.TranslatePageState(request.GET)
+ if not check_permission("view", request):
+ raise PermissionDenied(_('You do not have rights to access view mode.'))
+
+ if store is not None:
+ formaction = dispatch.translate(request, request.path_info, store=store.pootle_path, item=0)
+ store_path = store.pootle_path
+ else:
+ formaction = ''
+ store_path = ''
+ if stopped_by is not None:
+ notice = stopped_by
+ else:
+ notice = {}
+ profile = get_profile(request.user)
+ translation_project = request.translation_project
+ language = translation_project.language
+ project = translation_project.project
+ if store is not None:
+ items, translations, first_item = make_table(request, profile, store, item)
+ navbar = navbar_dict.make_store_navbar_dict(request, store)
+ else:
+ items, translations, first_item = [], [], -1
+ navbar = navbar_dict.make_directory_navbar_dict(request, directory, links_required='translate')
+ pagelinks = None
+ rows, icon = get_rows_and_icon(request, profile)
+ navbar["icon"] = icon
+ if store is not None:
+ postats = store.getquickstats()
+ untranslated, fuzzy = postats["total"] - postats["translated"], postats["fuzzy"]
+ translated, total = postats["translated"], postats["total"]
+ pagelinks = get_page_links(request, store, rows, translations, item, first_item)
+
+ # templatising
+ instancetitle = _(settings.TITLE)
+ language_data = {"code": pagelayout.weblanguage(language.code),
+ "name": language.fullname,
+ "dir": language_dir(language.code)}
+ templatevars = {
+ "title_path": store_path,
+ "project": {"code": project.code,
+ "name": project.fullname},
+ "language": language_data,
+ "store": store_path,
+ # navigation bar
+ "navitems": [navbar],
+ "pagelinks": pagelinks,
+ # translation form
+ "actionurl": formaction,
+ "notice": notice,
+ # l10n: Heading above the table column with the source language
+ "original_title": _("Original"),
+ # l10n: Heading above the table column with the target language
+ "translation_title": _("Translation"),
+ "items": items,
+ "reviewmode": state.view_mode == 'review',
+ "accept_title": _("Accept suggestion"),
+ "reject_title": _("Reject suggestion"),
+ "fuzzytext": _("Fuzzy"),
+ # l10n: Heading above the textarea for translator comments.
+ "translator_comments_title": _("Translator Comments"),
+ # l10n: Heading above the comments extracted from the programing source code
+ "developer_comments_title": _("Developer Comments"),
+ # l10n: This heading refers to related translations and terminology
+ "related_title": _("Related"),
+ 'search': search_forms.get_search_form(request),
+ # general vars
+ "instancetitle": instancetitle,
+ "permissions": request.permissions,
+ "canedit": check_permission("translate", request) or check_permission("suggest", request),
+ "cantranslate": check_permission("translate", request),
+ "cansuggest": check_permission("suggest", request),
+ "canreview": check_permission("review", request),
+ # l10n: Button label
+ "accept_button": _("Accept"),
+ # l10n: Button label
+ "reject_button": _("Reject")
+ }
+
+ templatevars.update(add_file_links(request, store))
+ return render_to_response("language/translatepage.html", templatevars, RequestContext(request))
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/language/urls.py b/Pootle-2.0.0/local_apps/pootle_app/views/language/urls.py
new file mode 100644
index 0000000..e85bce8
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/language/urls.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# Pootle is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# Pootle is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Pootle; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('pootle_app.views.language',
+ (r'^(?P<language_code>[^/]*)([/](index.html)?)?$',
+ 'language_index.language_index'),
+ (r'^(?P<language_code>[^/]*)/admin.html$',
+ 'language_admin.view'),
+ (r'^(?P<language_code>[^/]*)/(?P<project_code>[^/]*)/((.*/)*)admin_permissions.html$',
+ 'view.translation_project_admin_permissions'),
+ (r'^(?P<language_code>[^/]*)/(?P<project_code>[^/]*)/((.*/)*)admin_files.html$',
+ 'view.translation_project_admin_files'),
+ (r'^(?P<language_code>[^/]*)/(?P<project_code>[^/]*)/(?P<dir_path>(.*/)*)translate.html$',
+ 'view.translate_page'),
+ (r'^(?P<language_code>[^/]*)/(?P<project_code>[^/]*)/(?P<file_path>.+)/review/(?P<item>\d+)/?$',
+ 'view.handle_suggestions'),
+ (r'^(?P<language_code>[^/]*)/(?P<project_code>[^/]*)/(?P<dir_path>(.*/)*)(index.html)?$',
+ 'view.project_index'),
+ (r'^(?P<language_code>[^/]*)/(?P<project_code>[^/]*)/(?P<dir_path>.*)edit.html$',
+ 'view.tp_translate'),
+ (r'^(?P<language_code>[^/]*)/(?P<project_code>[^/]*)/(?P<dir_path>.*)review.html$',
+ 'view.tp_review'),
+ (r'^(?P<language_code>[^/]*)/(?P<project_code>[^/]*)/(?P<file_path>.*)export/zip$',
+ 'view.export_zip'),
+ (r'^(?P<language_code>[^/]*)/(?P<project_code>[^/]*)/(?P<file_path>.*)/commit$',
+ 'view.commit_file'),
+ (r'^(?P<language_code>[^/]*)/(?P<project_code>[^/]*)/(?P<file_path>.*)/update$',
+ 'view.update_file'),
+ (r'^(?P<language_code>[^/]*)/(?P<project_code>[^/]*)/(?P<file_path>.+)/export/(?P<format>.+)$',
+ 'view.export'),
+ (r'^(?P<language_code>[^/]*)/(?P<project_code>[^/]*)/(?P<file_path>.+)?$',
+ 'view.handle_file'),
+)
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/language/view.py b/Pootle-2.0.0/local_apps/pootle_app/views/language/view.py
new file mode 100644
index 0000000..d9e51f7
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/language/view.py
@@ -0,0 +1,334 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008-2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+import cStringIO
+import os
+
+from django.shortcuts import get_object_or_404
+from django.http import HttpResponse
+from django.utils import simplejson
+from django.utils.translation import ugettext as _
+from django.utils.translation import ungettext as _N
+from django.core.exceptions import PermissionDenied
+
+from pootle_misc.baseurl import redirect
+from pootle_app.models import TranslationProject, Directory, store_iteration
+from pootle_store.models import Store
+from pootle_app.models.permissions import get_matching_permissions, check_permission
+from pootle_app.models.profile import get_profile
+from pootle_app.views.language import dispatch
+from pootle_app.convert import convert_table
+from pootle_app import unit_update
+
+from pootle_app.views.language.tp_translate import view as tp_translate_view
+from pootle_app.views.language.tp_review import view as tp_review_view
+from pootle_app.views.language.admin_permissions import view as tp_admin_permissions_view
+from pootle_app.views.language.admin_files import view as tp_admin_files_view
+
+from pootle_app.views.language.project_index import view as project_index_view
+from pootle_app.views.language.translate_page import find_and_display
+from pootle_app.views.language import search_forms
+
+from pootle_app.views.language.translate_page import get_diff_codes
+from pootle_app.views.language.translate_page import highlight_diffs
+from pootle_app.views.language.translate_page import get_string_array
+
+def get_translation_project(f):
+ def decorated_f(request, language_code, project_code, *args, **kwargs):
+ translation_project = get_object_or_404(TranslationProject, language__code=language_code, project__code=project_code)
+ return f(request, translation_project, *args, **kwargs)
+ return decorated_f
+
+def set_request_context(f):
+ def decorated_f(request, translation_project, *args, **kwargs):
+ # For now, all permissions in a translation project are
+ # relative to the root of that translation project.
+ request.permissions = get_matching_permissions(
+ get_profile(request.user), translation_project.directory)
+ request.translation_project = translation_project
+ return f(request, translation_project, *args, **kwargs)
+ return decorated_f
+
+################################################################################
+
+@get_translation_project
+@set_request_context
+def translation_project_admin_permissions(request, translation_project):
+ return tp_admin_permissions_view(request, translation_project)
+
+@get_translation_project
+@set_request_context
+def translation_project_admin_files(request, translation_project):
+ return tp_admin_files_view(request, translation_project)
+
+@get_translation_project
+@set_request_context
+def translate_page(request, translation_project, dir_path):
+ def next_store_item(search, store_name, item):
+ return store_iteration.get_next_match(directory,
+ store_name,
+ item,
+ search)
+
+ def prev_store_item(search, store_name, item):
+ return store_iteration.get_prev_match(directory,
+ store_name,
+ item,
+ search)
+
+ directory = translation_project.directory.get_relative(dir_path)
+ return find_and_display(request, directory, next_store_item, prev_store_item)
+
+
+@get_translation_project
+@set_request_context
+def project_index(request, translation_project, dir_path):
+ directory = get_object_or_404(Directory, pootle_path=translation_project.directory.pootle_path + dir_path)
+ return project_index_view(request, translation_project, directory)
+
+def handle_translation_file(request, translation_project, file_path):
+ pootle_path = translation_project.directory.pootle_path + (file_path or '')
+ store = get_object_or_404(Store, pootle_path=pootle_path)
+ def get_item(itr, item):
+ try:
+ return itr.next()
+ except StopIteration:
+ return item
+
+ def next_store_item(search, store_name, item):
+ if 0 <= item < store.getquickstats()['total']:
+ return store, get_item(search.next_matches(store, item), item - 1)
+ else:
+ return store, store.getquickstats()['total'] - 1
+
+ def prev_store_item(search, store_name, item):
+ if store.getquickstats()['total'] > item > 0:
+ return store, get_item(search.prev_matches(store, item), item + 1)
+ else:
+ return store, 0
+
+ return find_and_display(request, store.parent, next_store_item, prev_store_item)
+
+
+@get_translation_project
+@set_request_context
+def commit_file(request, translation_project, file_path):
+ if not check_permission("commit", request):
+ raise PermissionDenied(_("You do not have rights to commit files here"))
+ pootle_path = translation_project.directory.pootle_path + file_path
+ store = get_object_or_404(Store, pootle_path=pootle_path)
+ result = translation_project.commitpofile(request, store)
+ return redirect(dispatch.show_directory(request, translation_project.directory.pootle_path))
+
+@get_translation_project
+@set_request_context
+def update_file(request, translation_project, file_path):
+ if not check_permission("commit", request):
+ raise PermissionDenied(_("You do not have rights to update files here"))
+ pootle_path = translation_project.directory.pootle_path + file_path
+ store = get_object_or_404(Store, pootle_path=pootle_path)
+ result = translation_project.update_file(request, store)
+ return redirect(dispatch.show_directory(request, translation_project.directory.pootle_path))
+
+@get_translation_project
+@set_request_context
+def export_zip(request, translation_project, file_path):
+ if not check_permission("archive", request):
+ return redirect(translation_project.pootle_path,
+ message=_('You do not have the right to create ZIP archives.'))
+ pootle_path = translation_project.pootle_path + (file_path or '')
+ try:
+ path_obj = Directory.objects.get(pootle_path=pootle_path)
+ except Directory.DoesNotExist:
+ path_obj = get_object_or_404(Store, pootle_path=pootle_path[:-1])
+ stores = store_iteration.iter_stores(path_obj)
+ archivecontents = translation_project.get_archive(stores)
+ response = HttpResponse(archivecontents, content_type="application/zip")
+ if file_path.endswith("/"):
+ file_path = file_path[:-1]
+ fish, file_path = os.path.split(file_path)
+ archivename = '%s-%s' % (translation_project.project.code, translation_project.language.code)
+ archivename += file_path + '.zip'
+ response['Content-Disposition'] = 'attachment; filename=%s' % archivename
+ return response
+
+MIME_TYPES = {
+ "po": "text/x-gettext-translation; charset=%(encoding)s",
+ "csv": "text/csv; charset=%(encoding)s",
+ "xlf": "application/x-xliff; charset=%(encoding)s",
+ "ts": "application/x-linguist; charset=%(encoding)s",
+ "mo": "application/x-gettext-translation" }
+
+@get_translation_project
+@set_request_context
+def export(request, translation_project, file_path, format):
+ store = get_object_or_404(Store, pootle_path=translation_project.directory.pootle_path + file_path)
+ encoding = getattr(store.file.store, "encoding", "UTF-8")
+ content_type = MIME_TYPES[format] % dict(encoding=encoding)
+ if format == translation_project.project.localfiletype:
+ response = HttpResponse(str(store.file.store), content_type=content_type)
+ response['Content-Disposition'] = 'attachment; filename=%s' % store.name
+ else:
+ convert_func = convert_table[translation_project.project.localfiletype, format]
+ output_file = cStringIO.StringIO()
+ input_file = cStringIO.StringIO(str(store.file.store))
+ convert_func(input_file, output_file, None)
+ response = HttpResponse(output_file.getvalue(), content_type=content_type)
+ filename, ext = os.path.splitext(store.name)
+ filename += os.path.extsep + format
+ response['Content-Disposition'] = 'attachment; filename=%s' % filename
+ return response
+
+
+@get_translation_project
+@set_request_context
+def handle_file(request, translation_project, file_path):
+ return handle_translation_file(request, translation_project, file_path)
+
+@get_translation_project
+@set_request_context
+def handle_suggestions(request, translation_project, file_path, item):
+ """Handles accepts/rejects of suggestions selectively via AJAX, receiving
+ and sending data in JSON format.
+
+ Response attributes are described below:
+ * "status": Indicates the status after trying the action.
+ Possible values: "error", "success".
+ * "message": Status message of the transaction. Depending on the status
+ it will display an error message, or the number of
+ suggestions rejected/accepted.
+ * "diffs": Updated diff for the current translation after performing
+ an action. If there are no suggestions pending, an empty
+ dict will be returned."""
+ pootle_path = translation_project.pootle_path + file_path
+ store = Store.objects.get(pootle_path=pootle_path)
+ item = int(item)
+
+ def get_pending_suggestions(item):
+ """Gets pending suggestions for item in pofilename."""
+ itemsuggestions = []
+ suggestions = store.getsuggestions(item)
+ for suggestion in suggestions:
+ if suggestion.hasplural():
+ itemsuggestions.append(suggestion.target.strings)
+ else:
+ itemsuggestions.append([suggestion.target])
+ return itemsuggestions
+
+ def get_updated_diffs(trans, suggestions):
+ """Returns the diff between the current translation and the
+ suggestions available after performing an accept/reject
+ action.
+ If no suggestions are available anymore, just return an
+ empty list."""
+ # No suggestions left, no output at all
+ if len(suggs) == 0:
+ return []
+ else:
+ diffcodes = {}
+ forms = []
+ for pluralitem, pluraltrans in enumerate(trans):
+ pluraldiffcodes = [get_diff_codes(pluraltrans,
+ suggestion[pluralitem])
+ for suggestion in suggestions]
+ diffcodes[pluralitem] = pluraldiffcodes
+ combineddiffs = reduce(list.__add__, pluraldiffcodes, [])
+ transdiff = highlight_diffs(pluraltrans, combineddiffs,
+ issrc=True)
+ form = { "diff": transdiff }
+ forms.append(form)
+ return forms
+
+ response = {}
+ # Decode JSON data sent via POST
+ data = simplejson.loads(request.POST.get("data", "{}"))
+ if not data:
+ response["status"] = "error"
+ response["message"] = _("No suggestion data given.")
+ else:
+ response["del_ids"] = []
+ rejects = data.get("rejects", [])
+ reject_candidates = len(rejects)
+ reject_count = 0
+ accepts = data.get("accepts", [])
+ accept_candidates = len(accepts)
+ accept_count = 0
+
+ for sugg in accepts:
+ try:
+ unit_update.accept_suggestion(store, item, int(sugg["id"]),
+ sugg["newtrans"], request)
+ response["del_ids"].append((item, sugg["id"]))
+ response["accepted_id"] = (item, sugg["id"])
+ accept_count += 1
+ except ValueError, e:
+ # Probably an issue with "item". The exception might tell us
+ # everything we need, while no error will probably help the user
+ response["message"] = e
+ except PermissionDenied, e:
+ response["message"] = e
+
+
+ for sugg in reversed(rejects):
+ try:
+ unit_update.reject_suggestion(store, int(item), int(sugg["id"]),
+ sugg["newtrans"], request)
+ reject_count += 1
+ response["del_ids"].append((int(item), sugg["id"]))
+ except ValueError, e:
+ # Probably an issue with "item". The exception might tell us
+ # everything we need, while no error will probably help the user
+ response["message"] = e
+ except PermissionDenied, e:
+ response["message"] = e
+
+ response["status"] = (reject_candidates == reject_count and
+ accept_candidates == accept_count) and \
+ "success" or "error"
+
+ if response["status"] == "success":
+ amsg = ""
+ rmsg = ""
+ if accept_candidates != 0:
+ amsg = _("Suggestion accepted.")
+ if reject_candidates != 0:
+ rmsg = _N("Suggestion rejected.",
+ "%d suggestions rejected.",
+ reject_count, reject_count)
+ response["message"] = amsg + rmsg
+ # Get updated diffs
+ current_trans = get_string_array(store.file.getitem(item).target)
+ suggs = get_pending_suggestions(item)
+ response["diffs"] = get_updated_diffs(current_trans, suggs)
+
+ response = simplejson.dumps(response, indent=4)
+ return HttpResponse(response, mimetype="application/json")
+
+@get_translation_project
+@set_request_context
+def tp_translate(request, translation_project, dir_path):
+ directory = get_object_or_404(Directory, pootle_path=translation_project.directory.pootle_path + dir_path)
+ return tp_translate_view(request, translation_project, directory)
+
+@get_translation_project
+@set_request_context
+def tp_review(request, translation_project, dir_path):
+ directory = get_object_or_404(Directory, pootle_path=translation_project.directory.pootle_path + dir_path)
+ return tp_review_view(request, translation_project, directory)
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/pagelayout.py b/Pootle-2.0.0/local_apps/pootle_app/views/pagelayout.py
new file mode 100644
index 0000000..8532a15
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/pagelayout.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2004-2009 Zuza Software Foundation
+#
+# This file is part of Virtaal.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+from django.conf import settings
+from django.utils.html import escape
+from django.utils.translation import ugettext as _
+
+def get_title():
+ return _(settings.TITLE)
+
+def get_description():
+ return _(settings.DESCRIPTION)
+
+def getdoclang(language):
+ """Get the language code that the docs should be displayed in."""
+
+ # TODO: Determine the available languages programmatically.
+ available_languages = ['en']
+ if language in available_languages:
+ return language
+ else:
+ return 'en'
+
+def weblanguage(language):
+ """Reformats the language code from locale style (pt_BR) to web
+ style (pt-br)"""
+
+ return language.replace('_', '-')
+
+
+def completetemplatevars(templatevars, request):
+ """fill out default values for template variables"""
+
+ if not 'instancetitle' in templatevars:
+ templatevars['instancetitle'] = get_title()
+ templatevars['request'] = request
+ if not 'mediaurl' in templatevars:
+ templatevars['mediaurl'] = settings.MEDIA_URL
+ if not 'enablealtsrc' in templatevars:
+ templatevars['enablealtsrc'] = settings.ENABLE_ALT_SRC
+ templatevars['uilanguage'] = weblanguage(request.LANGUAGE_CODE)
+ try:
+ templatevars['username'] = templatevars['username']
+ except:
+ templatevars['username'] = ''
+ templatevars['canregister'] = settings.CAN_REGISTER
+ templatevars['current_url'] = request.path_info
+ if 'user' not in templatevars:
+ templatevars['user'] = request.user
+ if 'search' not in templatevars:
+ templatevars['search'] = None
+ templatevars['message'] = escape(request.GET.get('message', ''))
+
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/profile/__init__.py b/Pootle-2.0.0/local_apps/pootle_app/views/profile/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/profile/__init__.py
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/profile/view.py b/Pootle-2.0.0/local_apps/pootle_app/views/profile/view.py
new file mode 100644
index 0000000..1fa7b45
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/profile/view.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+"""This view defines the home / account pages for a user."""
+
+from django.forms import ModelForm
+from django.contrib.auth.models import User
+from django.utils.translation import ugettext as _
+
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.contrib.auth.decorators import login_required
+from django.forms.fields import email_re
+from django.forms.util import ErrorList
+
+from pootle_misc.baseurl import redirect
+
+def is_valid_email(email):
+ return (email_re.match(email) is not None)
+
+class UserForm(ModelForm):
+
+ def clean(self):
+ cleaned_data = self.cleaned_data
+ email = cleaned_data.get("email")
+
+ if not email or not is_valid_email(email):
+ msg = _('Enter a valid e-mail address.')
+ self._errors["email"] = ErrorList([msg])
+
+ # Always return the full collection of cleaned data.
+ return cleaned_data
+
+ class Meta:
+ model = User
+ fields = ('first_name', 'last_name', 'email')
+
+@login_required
+def edit_personal_info(request):
+ if request.POST:
+ post = request.POST.copy()
+ user_form = UserForm(post, instance=request.user)
+ if user_form.is_valid():
+ user_form.save()
+ response = redirect('/accounts/'+request.user.username)
+ else:
+ user_form = UserForm(instance=request.user)
+ template_vars = { "form": user_form }
+ response = render_to_response('profiles/edit_personal.html', template_vars , context_instance=RequestContext(request))
+ return response
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/project/__init__.py b/Pootle-2.0.0/local_apps/pootle_app/views/project/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/project/__init__.py
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/project/project_admin.py b/Pootle-2.0.0/local_apps/pootle_app/views/project/project_admin.py
new file mode 100644
index 0000000..4a32fe7
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/project/project_admin.py
@@ -0,0 +1,80 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008-2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+from django.utils.translation import ugettext as _
+from django import forms
+from django.forms.models import BaseModelFormSet
+
+from pootle_misc.baseurl import l
+
+from pootle_app.models import Project, TranslationProject
+from pootle_app import project_tree
+from pootle_app.views.admin import util
+
+class TranslationProjectFormSet(BaseModelFormSet):
+ def save_existing(self, form, instance, commit=True):
+ result = super(TranslationProjectFormSet, self).save_existing(form, instance, commit)
+ form.process_extra_fields()
+ return result
+
+ def save_new(self, form, commit=True):
+ result = super(TranslationProjectFormSet, self).save_new(form, commit)
+ form.process_extra_fields()
+ return result
+
+@util.user_is_admin
+def view(request, project_code):
+ current_project = Project.objects.get(code=project_code)
+ try:
+ template_translation_project = TranslationProject.objects.get(project=current_project, language__code='templates')
+ except TranslationProject.DoesNotExist:
+ template_translation_project = None
+
+ class TranslationProjectForm(forms.ModelForm):
+ if template_translation_project is not None:
+ update = forms.BooleanField(required=False, label=_("Update from templates"))
+ #FIXME: maybe we can detect if initialize is needed to avoid
+ # displaying it when not relevant
+ initialize = forms.BooleanField(required=False, label=_("Initialize"))
+ project = forms.ModelChoiceField(queryset=Project.objects.filter(pk=current_project.pk),
+ initial=current_project.pk, widget=forms.HiddenInput)
+ class Meta:
+ prefix = "existing_language"
+
+ def process_extra_fields(self):
+ if self.instance.pk is not None:
+ if self.cleaned_data.get('initialize', None):
+ self.instance.initialize()
+
+ if self.cleaned_data.get('update', None):
+ project_tree.convert_templates(template_translation_project, self.instance)
+
+
+ queryset = TranslationProject.objects.filter(project=current_project).order_by('pootle_path')
+
+ model_args = {}
+ model_args['project'] = { 'code': current_project.code,
+ 'name': current_project.fullname }
+ model_args['formid'] = "translation-projects"
+ model_args['submitname'] = "changetransprojects"
+ link = lambda instance: '<a href="%s">%s</a>' % (l(instance.pootle_path + 'admin_permissions.html'), instance.language)
+ return util.edit(request, 'project/project_admin.html', TranslationProject, model_args, link, linkfield="language",
+ queryset=queryset, can_delete=True, form=TranslationProjectForm, formset=TranslationProjectFormSet)
+
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/project/project_language_index.py b/Pootle-2.0.0/local_apps/pootle_app/views/project/project_language_index.py
new file mode 100644
index 0000000..ba60cf8
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/project/project_language_index.py
@@ -0,0 +1,95 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008-2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+import locale
+
+from django.utils.translation import ugettext as _
+from django.utils.translation import ungettext
+from django.shortcuts import get_object_or_404
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+
+from pootle_app.models import Project, Submission
+from pootle_app.views.language.project_index import get_stats_headings
+from pootle_app.views.language.item_dict import add_percentages, stats_descriptions
+from pootle.i18n.gettext import tr_lang
+from pootle_app.views.top_stats import gentopstats
+from pootle_app.views import pagelayout
+
+
+def limit(query):
+ return query[:5]
+
+def get_last_action(translation_project):
+ try:
+ return Submission.objects.filter(translation_project=translation_project).latest()
+ except Submission.DoesNotExist:
+ return ''
+
+def make_language_item(request, translation_project):
+ href = '/%s/%s/' % (translation_project.language.code, translation_project.project.code)
+ projectstats = add_percentages(translation_project.getquickstats())
+ info = {
+ 'code': translation_project.language.code,
+ 'href': href,
+ 'title': tr_lang(translation_project.language.fullname),
+ 'data': projectstats,
+ 'lastactivity': get_last_action(translation_project),
+ 'tooltip': _('%(percentage)d%% complete',
+ {'percentage': projectstats['translatedpercentage']})
+ }
+ errors = projectstats.get('errors', 0)
+ if errors:
+ info['errortooltip'] = ungettext('Error reading %d file', 'Error reading %d files', errors, errors)
+ info.update(stats_descriptions(projectstats))
+ return info
+
+
+def view(request, project_code, _path_var):
+ project = get_object_or_404(Project, code=project_code)
+ translation_projects = project.translationproject_set.all()
+ items = [make_language_item(request, translation_project) for translation_project in translation_projects]
+ items.sort(lambda x, y: locale.strcoll(x['title'], y['title']))
+ languagecount = len(translation_projects)
+ totals = add_percentages(project.getquickstats())
+ average = totals['translatedpercentage']
+
+ topstats = gentopstats(lambda query: query.filter(translation_project__project__code=project_code))
+
+ templatevars = {
+ 'project': {
+ 'code': project.code,
+ 'name': project.fullname,
+ 'stats': ungettext('%(languages)d language, %(average)d%% translated',
+ '%(languages)d languages, %(average)d%% translated',
+ languagecount, {"languages": languagecount, "average": average})
+ },
+ 'description': project.description,
+ 'adminlink': _('Admin'),
+ 'languages': items,
+ 'instancetitle': pagelayout.get_title(),
+ 'topstats': topstats,
+ 'statsheadings': get_stats_headings(),
+ 'translationlegend': {'translated': _('Translations are complete'),
+ 'fuzzy': _('Translations need to be checked (they are marked fuzzy)'
+ ), 'untranslated': _('Untranslated')},
+ }
+
+ return render_to_response('project/project.html', templatevars, context_instance=RequestContext(request))
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/project/projects_index.py b/Pootle-2.0.0/local_apps/pootle_app/views/project/projects_index.py
new file mode 100644
index 0000000..bf59609
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/project/projects_index.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008-2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+from django.utils.translation import ugettext as _
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+
+from pootle_app.views import pagelayout
+from pootle_app.models.profile import get_profile
+from pootle_app.views.index.index import getprojects
+from pootle_app.models.permissions import get_matching_permissions
+from pootle_app.views.top_stats import gentopstats
+from pootle_app.models import Directory
+
+def view(request):
+ request.permissions = get_matching_permissions(get_profile(request.user), Directory.objects.root)
+ topstats = gentopstats(lambda query: query)
+
+ templatevars = {
+ 'projectlink': _('Projects'),
+ 'projects': getprojects(request),
+ 'topstats': topstats,
+ 'instancetitle': pagelayout.get_title(),
+ 'translationlegend': {'translated': _('Translations are complete'),
+ 'fuzzy': _('Translations need to be checked (they are marked fuzzy)'
+ ), 'untranslated': _('Untranslated')},
+ }
+ return render_to_response('project/projects.html', templatevars, RequestContext(request))
+
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/project/urls.py b/Pootle-2.0.0/local_apps/pootle_app/views/project/urls.py
new file mode 100644
index 0000000..a013b77
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/project/urls.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 Zuza Software Foundation
+#
+# This file is part of translate.
+#
+# translate is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# translate is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with translate; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('pootle_app.views.project',
+ (r'^/?$|^/index.html$', 'projects_index.view'),
+ (r'^/([^/]*)/admin.html$', 'project_admin.view'),
+ (r'^/([^/]*)(/|/index.html)?$', 'project_language_index.view'),
+)
diff --git a/Pootle-2.0.0/local_apps/pootle_app/views/top_stats.py b/Pootle-2.0.0/local_apps/pootle_app/views/top_stats.py
new file mode 100644
index 0000000..63b87c4
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_app/views/top_stats.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# Pootle is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# Pootle is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Pootle; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from django.utils.translation import ugettext as _
+
+from pootle_app.models import Suggestion, Submission
+
+def map_num_contribs(sub, user):
+ user.num_contribs = sub.num_contribs
+ return user
+
+def suggesters_from_suggestions(sugs):
+ """Get the Users associated with the Suggestions. Also assign
+ the num_contribs attribute from the Suggestion to the User"""
+ return [map_num_contribs(sug, sug.suggester.user) for sug in sugs if sug.suggester]
+
+def reviewers_from_suggestions(sugs):
+ """Get the Users associated with the Suggestions. Also assign
+ the num_contribs attribute from the Suggestion to the User"""
+ return [map_num_contribs(sug, sug.reviewer.user) for sug in sugs if sug.reviewer]
+
+def users_from_submissions(subs):
+ """Get the Users associated with the Submissions. Also assign
+ the num_contribs attribute from the Submission to the User"""
+ return [map_num_contribs(sub, sub.submitter.user) for sub in subs]
+
+def gen_top_stat(data, header_label):
+ return {
+ 'data': data,
+ 'headerlabel': header_label }
+
+def limit(query):
+ return query[:5]
+
+def gentopstats(narrow_search_results):
+ """Generate the top contributor stats to be displayed
+ for an entire Pootle installation, a language or a project.
+ 'narrow_search_results' is a function taking a Django
+ query and should filter the results to give the results
+ for a particular project or language (or whatever is required).
+ For example the narrowing function
+ lambda query: query.filter(project='pootle', language='en')
+ will get the top contributor results for the project 'pootle'
+ in the language 'en'.
+
+ The output of this function looks something like this:
+ {'data': [],
+ 'headerlabel': u'Suggestions'},
+ {'data': [],
+ 'headerlabel': u'Reviews'},
+ {'data': [],
+ 'headerlabel': u'Submissions'}]
+ """
+ top_sugg = limit(narrow_search_results(Suggestion.objects.get_top_suggesters()))
+ top_review = limit(narrow_search_results(Suggestion.objects.get_top_reviewers()))
+ top_sub = limit(narrow_search_results(Submission.objects.get_top_submitters()))
+
+ return [
+ gen_top_stat(suggesters_from_suggestions(top_sugg), _('Suggestions')),
+ gen_top_stat(reviewers_from_suggestions(top_review), _('Reviews')),
+ gen_top_stat(users_from_submissions(top_sub), _('Submissions')) ]
diff --git a/Pootle-2.0.0/local_apps/pootle_autonotices/__init__.py b/Pootle-2.0.0/local_apps/pootle_autonotices/__init__.py
new file mode 100644
index 0000000..1164610
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_autonotices/__init__.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+from django.db.models.signals import pre_delete, post_init, pre_save, post_save
+from django.contrib.auth.models import User
+
+from pootle_app.models import Language, Project, TranslationProject
+import signals
+
+post_save.connect(signals.new_language, sender=Language)
+post_save.connect(signals.new_project, sender=Project)
+post_save.connect(signals.new_user, sender=User)
+post_save.connect(signals.new_translationproject, sender=TranslationProject)
+
+from pootle_app.models.signals import post_vc_update, post_vc_commit
+from pootle_app.models.signals import post_template_update, post_file_upload
+post_vc_update.connect(signals.updated_from_version_control)
+post_vc_commit.connect(signals.committed_to_version_control)
+post_template_update.connect(signals.updated_from_template)
+post_file_upload.connect(signals.file_uploaded)
+
+from pootle_store.signals import post_unit_update
+post_unit_update.connect(signals.unit_updated)
diff --git a/Pootle-2.0.0/local_apps/pootle_autonotices/signals.py b/Pootle-2.0.0/local_apps/pootle_autonotices/signals.py
new file mode 100644
index 0000000..a99fa2c
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_autonotices/signals.py
@@ -0,0 +1,131 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+"""Set of singal handlers for generating automatic notifications on system events"""
+
+import logging
+
+
+from pootle_notifications.models import Notice
+from pootle_app.models import Directory
+from pootle_app.models.profile import get_profile
+
+##### Model Events #####
+
+def new_object(created, message, parent):
+ if created:
+ notice = Notice(directory=parent, message=message)
+ notice.save()
+
+
+def new_language(sender, instance, created=False, **kwargs):
+ message = 'New language <a href="%s">%s</a> created.' % (instance.get_absolute_url(), instance.fullname)
+ new_object(created, message, instance.directory.parent)
+
+def new_project(sender, instance, created=False, **kwargs):
+ message = 'New project <a href="%s">%s</a> created.' % (instance.get_absolute_url(), instance.fullname)
+ new_object(created, message, parent=Directory.objects.root)
+
+def new_user(sender, instance, created=False, **kwargs):
+ # new user needs to be wrapped in a try block because it might be
+ # called before the rest of the models are loaded when first
+ # installing Pootle
+
+ try:
+ message = 'New user <a href="%s">%s</a> registered.' % (instance.get_profile().get_absolute_url(), instance.username)
+ new_object(created, message, parent=Directory.objects.root)
+ except:
+ pass
+
+def new_translationproject(sender, instance, created=False, **kwargs):
+ message = 'New project <a href="%s">%s</a> added to language <a href="%s">%s</a>.' % (
+ instance.get_absolute_url(), instance.project.fullname,
+ instance.language.get_absolute_url(), instance.language.fullname)
+ new_object(created, message, instance.directory.parent)
+
+
+##### TranslationProject Events #####
+
+from pootle_app.models.translation_project import stats_message
+
+def updated_from_template(sender, oldstats, newstats, **kwargs):
+ if oldstats == newstats:
+ # nothing changed, no need to report
+ return
+ message = 'Updated <a href="%s">%s</a> to latest template<br />' % (sender.get_absolute_url(), sender.fullname)
+ message += stats_message("Before update", oldstats) + "<br />"
+ message += stats_message("After update", newstats) + "<br />"
+ new_object(True, message, sender.directory)
+
+def updated_from_version_control(sender, oldstats, remotestats, newstats, **kwargs):
+ if oldstats == newstats:
+ # nothing changed, no need to report
+ return
+
+ message = 'Updated <a href="%s">%s</a> from version control<br />' % (sender.get_absolute_url(), sender.fullname)
+ message += stats_message("Before update", oldstats) + "<br />"
+ if not remotestats == newstats:
+ message += stats_message("Remote copy", remotestats) + "<br />"
+ message += stats_message("After update", newstats)
+ new_object(True, message, sender.directory)
+
+def committed_to_version_control(sender, store, stats, user, success, **kwargs):
+ message = '<a href="%s">%s</a> committed <a href="%s">%s</a> to version control' % (
+ user.get_absolute_url(), user.username,
+ store.get_absolute_url(), store.pootle_path)
+ message = stats_message(message, stats)
+ new_object(success, message, sender.directory)
+
+def file_uploaded(sender, oldstats, user, newstats, archive, **kwargs):
+ if oldstats == newstats:
+ logging.debug("file uploaded but stats didn't change")
+ return
+
+ if archive:
+ message = '<a href="%s">%s</a> uploaded an archive to <a href="%s">%s</a><br />' % (
+ get_profile(user).get_absolute_url(), user.username,
+ sender.get_absolute_url(), sender.fullname)
+ else:
+ message = '<a href="%s">%s</a> uploaded a file to <a href="%s">%s</a><br />' % (
+ get_profile(user).get_absolute_url(), user.username,
+ sender.get_absolute_url(), sender.fullname)
+
+ message += stats_message('Before upload', oldstats) + '<br />'
+ message += stats_message('After upload', newstats) + '<br />'
+ new_object(True, message, sender.directory)
+
+
+##### Store events #####
+
+def unit_updated(sender, oldstats, newstats, **kwargs):
+ if oldstats == newstats or oldstats['translatedsourcewords'] == oldstats['totalsourcewords']:
+ # file did not change or is already at 100%
+ return
+
+ if newstats['translatedsourcewords'] == newstats['totalsourcewords']:
+ # find parent translation project
+ directory = sender.parent
+ while not directory.is_translationproject() and not directory == Directory.objects.root:
+ directory = directory.parent
+
+ message = '<a href="%s">%s</a> fully translated</a><br />' % (sender.get_absolute_url(), sender.name)
+ message += stats_message("Project now at", directory.getquickstats())
+ new_object(True, message, directory)
+
diff --git a/Pootle-2.0.0/local_apps/pootle_misc/__init__.py b/Pootle-2.0.0/local_apps/pootle_misc/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_misc/__init__.py
diff --git a/Pootle-2.0.0/local_apps/pootle_misc/baseurl.py b/Pootle-2.0.0/local_apps/pootle_misc/baseurl.py
new file mode 100644
index 0000000..fa2e2b2
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_misc/baseurl.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008-2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+""" utility functions to help deploy Pootle under different url prefixes """
+
+import urllib
+
+from django.conf import settings
+from django.http import HttpResponseRedirect
+
+def l(path):
+ """ filter urls adding base_path prefix if required """
+ if path and path.startswith('/'):
+ base_url = getattr(settings, "SCRIPT_NAME", "")
+ #if not path.startswith(base_url):
+ return base_url + path
+ return path
+
+def abs_l(path):
+ """ filter paths adding full url prefix if required """
+ return settings.BASE_URL + path
+
+def m(path):
+ """ filter urls adding media url prefix if required """
+ return l(settings.MEDIA_URL + path)
+
+def redirect(url, **kwargs):
+ if len(kwargs) > 0:
+ return HttpResponseRedirect(l('%s?%s' % (url, urllib.urlencode(kwargs))))
+ else:
+ return HttpResponseRedirect(l(url))
diff --git a/Pootle-2.0.0/local_apps/pootle_misc/context_processors.py b/Pootle-2.0.0/local_apps/pootle_misc/context_processors.py
new file mode 100644
index 0000000..290a539
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_misc/context_processors.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+from django.conf import settings
+
+def sitesettings(request):
+ """exposes settings to templates"""
+ #FIXME: maybe we should expose relevant settings only?
+ return {'settings': settings }
diff --git a/Pootle-2.0.0/local_apps/pootle_misc/dbinit.py b/Pootle-2.0.0/local_apps/pootle_misc/dbinit.py
new file mode 100644
index 0000000..94b0893
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_misc/dbinit.py
@@ -0,0 +1,161 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008-2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+import sys
+
+from django.core.management import call_command
+from django.core.exceptions import ObjectDoesNotExist
+from django.contrib.auth.models import User
+
+from pootle.i18n.gettext import ugettext as _
+
+from pootle_app.models import Language, Project
+
+
+def header(exception):
+ text = """
+ <?xml version="1.0" encoding="UTF-8"?>
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+ <html>
+ <head>
+ <title>%(title)s</title>
+ <meta content="text/html; charset=utf-8" http-equiv="content-type" />
+ <style type="text/css">
+ body
+ {
+ background-color: #ffffff;
+ color: #000000;
+ font-family: Georgia, serif;
+ margin: 40px auto;
+ width: 740px;
+ }
+ h1
+ {
+ font-size: 185%%;
+ }
+ ul
+ {
+ list-style-type: square;
+ }
+ .error
+ {
+ background-color: inherit;
+ color: #d54e21;
+ font-weight: bold;
+ }
+ </style>
+ </head>
+ <body>
+ <h1>%(title)s</h1>
+ <p class="error">%(msg)s</p>
+ """ % {'title': _('Pootle: Install'),
+ 'msg': _('Error: "%s" while attempting to access the Pootle database, will try to initialize database.', exception)}
+ return text
+
+def syncdb():
+ text = u"""
+ <p>%s</p>
+ """ % _('Creating database tables...')
+ call_command('syncdb', interactive=False)
+ return text
+
+def initdb():
+ text = u"""
+ <p>%s</p>
+ """ % _('Creating default languages, projects and admin user')
+ call_command('initdb')
+ return text
+
+def stats_start():
+ text = u"""
+ <p>%s</p>
+ <ul>
+ """ % _('Calculating translation statistics, this will take a few minutes')
+ return text
+
+def stats_language(language):
+ text = u"""
+ <li>%s</li>
+ """ % _('%(language)s is %(percent)d%% complete',
+ {'language': language.localname(), 'percent': language.translated_percentage()})
+ return text
+
+def stats_project(project):
+ text = u"""
+ <li>%s</li>
+ """ % _('Project %(project)s is %(percent)d%% complete',
+ {'project': project.fullname, 'percent': project.translated_percentage()})
+ return text
+
+def stats_end():
+ text = u"""
+ </ul>
+ <p>%s</p>
+ """ % _('Done calculating statistics for default languages and projects')
+ return text
+
+
+def footer():
+ text = """
+ <p>%(endmsg)s</p>
+ <div><script>setTimeout("location.reload()", 10000)</script></div>
+ </body></html>
+ """ % { 'endmsg': _('Initialized database, you will be redirected to the front page in 10 seconds') }
+ return text
+
+def staggered_install(exception):
+ """Initialize the pootle database without displaying progress
+ reports for each step"""
+
+ # django's syncdb command prints progress repots to stdout, but
+ # mod_wsgi doesn't like stdout, so we reroute to stderr
+ stdout = sys.stdout
+ sys.stdout = sys.stderr
+
+ yield header(exception)
+
+ # try to build the database tables
+ yield syncdb()
+
+ # if this is a fresh install we should add some default languages
+ # and projects and a default admin account to make pootle more
+ # usable out of the box
+ #
+ # if there are no user accounts apart from defaults then assume
+ # it's fresh install
+ if User.objects.hide_defaults().count() == 0:
+ yield initdb()
+
+ # first time to visit the front page all stats for projects and
+ # languages will be calculated which can take forever, since users
+ # don't like webpages that take forever let's precalculate the
+ # stats here
+ yield stats_start()
+ for language in Language.objects.all():
+ yield stats_language(language)
+ for project in Project.objects.all():
+ yield stats_project(project)
+ yield stats_end()
+
+ yield footer()
+
+ # bring back stdout
+ sys.stdout = stdout
+ return
diff --git a/Pootle-2.0.0/local_apps/pootle_misc/middleware/__init__.py b/Pootle-2.0.0/local_apps/pootle_misc/middleware/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_misc/middleware/__init__.py
diff --git a/Pootle-2.0.0/local_apps/pootle_misc/middleware/baseurl.py b/Pootle-2.0.0/local_apps/pootle_misc/middleware/baseurl.py
new file mode 100644
index 0000000..a5c6640
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_misc/middleware/baseurl.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008-2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+from django.conf import settings
+
+class BaseUrlMiddleware(object):
+ def process_request(self, request):
+ """calculate settings.BASEURL based on HTTP headers"""
+ domain = None
+
+ if 'HTTP_HOST' in request.META:
+ domain = request.META['HTTP_HOST']
+
+ if 'SCRIPT_NAME' in request.META:
+ settings.SCRIPT_NAME = request.META['SCRIPT_NAME']
+ if domain is not None:
+ domain += request.META['SCRIPT_NAME']
+
+ if domain is not None:
+ if request.is_secure():
+ settings.BASE_URL = 'https://' + domain
+ else:
+ settings.BASE_URL = 'http://' + domain
+
+ #FIXME: DIRTY HACK ALERT if this works then something is
+ #wrong with the universe
+ # poison sites cache using detected domain
+ from django.contrib.sites import models as sites_models
+ sites_models.SITE_CACHE[settings.SITE_ID] = sites_models.Site(settings.SITE_ID,
+ request.META['HTTP_HOST'],
+ settings.TITLE)
diff --git a/Pootle-2.0.0/local_apps/pootle_misc/middleware/errorpages.py b/Pootle-2.0.0/local_apps/pootle_misc/middleware/errorpages.py
new file mode 100644
index 0000000..e165e1e
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_misc/middleware/errorpages.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008-2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+import traceback
+import sys
+
+from django.core.exceptions import PermissionDenied
+from django.http import HttpResponseForbidden, HttpResponseServerError
+from django.http import Http404
+from django.template.loader import render_to_string
+from django.template import RequestContext
+from django.utils.translation import ugettext as _
+from django.conf import settings
+from django.core.mail import mail_admins
+
+from pootle_misc.baseurl import l
+
+class ErrorPagesMiddleware(object):
+ """
+ Friendlier Error Pages
+ """
+ def process_exception(self, request, exception):
+ if isinstance(exception, Http404):
+ pass
+ elif isinstance(exception, PermissionDenied):
+ templatevars = { 'permission_error': unicode(exception.args[0]) }
+ if not request.user.is_authenticated():
+ login_msg = _('You need to <a href="%(login_link)s">login</a> to access this page.' % {
+ 'login_link': l("/accounts/login/") })
+ templatevars["login_message"] = login_msg
+ return HttpResponseForbidden(render_to_string('403.html', templatevars,
+ RequestContext(request)))
+ else:
+ #FIXME: implement better 500
+ tb = traceback.format_exc()
+ print >> sys.stderr, tb
+ if not settings.DEBUG:
+ try:
+ templatevars = {'exception': unicode(exception.args[0])}
+ if hasattr(exception, 'filename'):
+ templatevars['fserror'] = _('Error accessing %(filename)s, Filesystem sent error: %(errormsg)s',
+ {'filename': exception.filename, 'errormsg': exception.strerror})
+
+ # send email to admins with details about exception
+ subject = 'Error (%s IP): %s' % ((request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS and 'internal' or 'EXTERNAL'), request.path)
+ try:
+ request_repr = repr(request)
+ except:
+ request_repr = "Request repr() unavailable"
+ message = "%s\n\n%s\n\n%s" % (unicode(exception.args[0]), tb, request_repr)
+ mail_admins(subject, message, fail_silently=True)
+ return HttpResponseServerError(render_to_string('500.html', templatevars,
+ RequestContext(request)))
+ except:
+ # let's not confuse things by throwing an exception here
+ pass
diff --git a/Pootle-2.0.0/local_apps/pootle_misc/middleware/siteconfig.py b/Pootle-2.0.0/local_apps/pootle_misc/middleware/siteconfig.py
new file mode 100644
index 0000000..7db632b
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_misc/middleware/siteconfig.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008-2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+from django.http import HttpResponse
+from pootle_misc import siteconfig
+from pootle_misc import dbinit
+
+"""some unused http status code to mark the need to auto install"""
+
+INSTALL_STATUS_CODE = 613
+class SiteConfigMiddleware(object):
+ """
+ This middleware does two things, it reload djblet siteconfigs on
+ every request to ensure they're uptodate. but also works as an
+ early detection system for database errors.
+
+ It might seem strange that the middleware does these two unrelated
+ tasks, but since the only way to test the database is to run a
+ query, it would be too wasteful to add another query per request
+ when siteconfig already requires one.
+
+ """
+ def process_request(self, request):
+ """load site config, return a dummy response if database seems uninitialized"""
+ #FIXME: can't we find a more efficient method?
+ try:
+ siteconfig.load_site_config()
+ except Exception, e:
+ #HACKISH: since exceptions thrown by different databases
+ # do not share the same class heirarchy (DBAPI2 sucks) we
+ # have to check the class name instead (since python uses
+ # duck typing I will call this
+ # poking-the-duck-until-it-quacks-like-a-duck-test
+
+ if e.__class__.__name__ in ['OperationalError', 'ProgrammingError']:
+ # we can't build the database here cause caching
+ # middleware won't allow progressive loading of
+ # response so instead return an empty response marked
+ # with special status code INSTALL_STATUS_CODE
+
+ response = HttpResponse()
+ response.status_code = INSTALL_STATUS_CODE
+ response.exception = e
+ return response
+
+ def process_response(self, request, response):
+ """ this should be the last response processor to run, detect
+ dummy response with INSTALL_STATUS_CODE status code and start
+ db install process"""
+
+ if response.status_code == INSTALL_STATUS_CODE:
+ return HttpResponse(dbinit.staggered_install(response.exception))
+ else:
+ return response
+
diff --git a/Pootle-2.0.0/local_apps/pootle_misc/siteconfig.py b/Pootle-2.0.0/local_apps/pootle_misc/siteconfig.py
new file mode 100644
index 0000000..3d4f9ea
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_misc/siteconfig.py
@@ -0,0 +1,51 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2004-2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+# NOTE: Import this file in your urls.py or some place before
+# any code relying on settings is imported.
+
+from django.contrib.sites.models import Site
+
+from djblets.siteconfig.models import SiteConfiguration
+from djblets.siteconfig.django_settings import apply_django_settings, generate_defaults
+
+settings_map = {
+ # siteconfig key settings.py key
+ 'DESCRIPTION': 'DESCRIPTION',
+ 'TITLE' : 'TITLE',
+}
+
+defaults = generate_defaults(settings_map)
+
+def load_site_config():
+ """Sets up the SiteConfiguration, provides defaults and syncs settings."""
+ try:
+ siteconfig = SiteConfiguration.objects.get_current()
+ except SiteConfiguration.DoesNotExist:
+ # Either warn or just create the thing. Depends on your app
+ siteconfig = SiteConfiguration(site=Site.objects.get_current(),
+ version="1.0")
+ siteconfig.save()
+
+ # Code will go here for settings work in later examples.
+ if not siteconfig.get_defaults():
+ siteconfig.add_defaults(defaults)
+ apply_django_settings(siteconfig, settings_map)
+ return siteconfig
diff --git a/Pootle-2.0.0/local_apps/pootle_misc/templatetags/__init__.py b/Pootle-2.0.0/local_apps/pootle_misc/templatetags/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_misc/templatetags/__init__.py
diff --git a/Pootle-2.0.0/local_apps/pootle_misc/templatetags/baseurl.py b/Pootle-2.0.0/local_apps/pootle_misc/templatetags/baseurl.py
new file mode 100644
index 0000000..877fb72
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_misc/templatetags/baseurl.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008-2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+from django import template
+from django.template.defaultfilters import stringfilter
+
+from pootle_misc.baseurl import l, m, abs_l
+
+register = template.Library()
+register.filter('l', stringfilter(l))
+register.filter('m', stringfilter(m))
+register.filter('abs_l', stringfilter(abs_l))
diff --git a/Pootle-2.0.0/local_apps/pootle_misc/templatetags/cleanhtml.py b/Pootle-2.0.0/local_apps/pootle_misc/templatetags/cleanhtml.py
new file mode 100644
index 0000000..40d9e60
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_misc/templatetags/cleanhtml.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008-2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+
+from django import template
+from django.template.defaultfilters import stringfilter
+from django.utils.safestring import mark_safe
+
+try:
+ from lxml.html.clean import clean_html
+except ImportError:
+ clean_html = lambda text: text
+
+def clean_wrapper(text):
+ """wrapper around lxml's html cleaner that returns SafeStrings for
+ immediate rendering in templates"""
+ return mark_safe(clean_html(text))
+
+register = template.Library()
+register.filter('clean', stringfilter(clean_wrapper))
+
diff --git a/Pootle-2.0.0/local_apps/pootle_misc/templatetags/render_pager.py b/Pootle-2.0.0/local_apps/pootle_misc/templatetags/render_pager.py
new file mode 100644
index 0000000..86961b6
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_misc/templatetags/render_pager.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008-2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+from django.utils.translation import ugettext as _
+from django import template
+from django.utils.safestring import mark_safe
+
+def render_pager(pager):
+ """Render a pager block with next and previous links"""
+ if not pager.has_other_pages():
+ return ""
+
+ result = '<ul class="pager">'
+ if pager.has_previous():
+ result += '<li><a href="?page=1" class="nth-link">%s</a></li>' % _('First')
+ result += '<li><a href="?page=%d" class="prevnext-link">%s</a></li>' % (pager.previous_page_number(), _('Previous'))
+
+ start = max(1, pager.number - 4)
+ end = min(pager.paginator.num_pages, pager.number + 4)
+ if start > 1:
+ result += '<li>...</li>'
+ for i in range(start, end+1):
+ if i == pager.number:
+ result += '<li><span class="current-link">%s</span></li>' % i
+ else:
+ result += '<li><a href="?page=%d" class="number-link">%d</a></li>' % (i, i)
+ if end < pager.paginator.num_pages:
+ result += '<li>...</li>'
+
+ if pager.has_next():
+ result += '<li><a href="?page=%d" class="prevnext-link">%s</a></li>' % (pager.next_page_number(), _('Next'))
+ result += '<li><a href="?page=%d" class="nth-link">%s</a></li>' % (pager.paginator.num_pages, _('Last'))
+
+ result += '</ul>'
+ return mark_safe(result)
+
+register = template.Library()
+register.filter('render_pager', render_pager)
+
diff --git a/Pootle-2.0.0/local_apps/pootle_misc/util.py b/Pootle-2.0.0/local_apps/pootle_misc/util.py
new file mode 100644
index 0000000..11b5c44
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_misc/util.py
@@ -0,0 +1,52 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2004-2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+from django.core.cache import cache
+from django.conf import settings
+import logging
+
+def getfromcache(function, timeout=settings.OBJECT_CACHE_TIMEOUT):
+ def _getfromcache(instance, *args, **kwargs):
+ key = instance.pootle_path + ":" + function.__name__
+ result = cache.get(key)
+ if result is None:
+ logging.debug("cache miss for %s", key)
+ result = function(instance, *args, **kwargs)
+ cache.set(key, result, timeout)
+ return result
+ return _getfromcache
+
+def deletefromcache(sender, functions, **kwargs):
+ path = sender.pootle_path
+ path_parts = path.split("/")
+
+ # clean project cache
+ if len(path_parts):
+ key = "/projects/%s/" % path_parts[2]
+ for func in functions:
+ cache.delete(key + ":"+func)
+
+ # clean store and directory cache
+ while path_parts:
+ for func in functions:
+ cache.delete(path + ":"+func)
+ path_parts = path_parts[:-1]
+ path = "/".join(path_parts) + "/"
+
diff --git a/Pootle-2.0.0/local_apps/pootle_notifications/__init__.py b/Pootle-2.0.0/local_apps/pootle_notifications/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_notifications/__init__.py
diff --git a/Pootle-2.0.0/local_apps/pootle_notifications/feeds.py b/Pootle-2.0.0/local_apps/pootle_notifications/feeds.py
new file mode 100644
index 0000000..d3fdd06
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_notifications/feeds.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+from django.core.exceptions import PermissionDenied
+from django.contrib.syndication.feeds import Feed
+from django.http import HttpResponse
+from django.shortcuts import get_object_or_404
+
+from pootle_misc.baseurl import l
+from pootle_app.models import Directory
+from pootle_app.models.permissions import get_matching_permissions, check_permission
+from pootle_app.models.profile import get_profile
+
+from pootle_notifications.models import Notice
+from pootle_notifications.views import directory_to_title
+
+def view(request, path):
+ pootle_path = '/%s' % path
+ directory = get_object_or_404(Directory, pootle_path=pootle_path)
+
+ request.permissions = get_matching_permissions(get_profile(request.user), directory)
+ if not check_permission('view', request):
+ raise PermissionDenied
+
+ feedgen = NoticeFeed(pootle_path, request, directory).get_feed(path)
+ response = HttpResponse(mimetype=feedgen.mime_type)
+ feedgen.write(response, 'utf-8')
+ return response
+
+class NoticeFeed(Feed):
+ title_template = "notice_title.html"
+ description_template = "notice_body.html"
+ def __init__(self, slug, request, directory):
+ self.link = l(directory.pootle_path)
+ self.directory = directory
+ self.recusrive = request.GET.get('all', False)
+ super(NoticeFeed, self).__init__(slug, request)
+
+ def get_object(self, bits):
+ return self.directory
+
+ def title(self, directory):
+ return directory_to_title(self.request, directory)
+
+ def items(self, directory):
+ if self.recusrive:
+ return Notice.objects.filter(directory__pootle_path__startswith=directory.pootle_path).select_related('directory')[:30]
+ else:
+ return Notice.objects.filter(directory=directory).select_related('directory')[:30]
+
+ def item_pubdate(self, item):
+ return item.added
+
diff --git a/Pootle-2.0.0/local_apps/pootle_notifications/models.py b/Pootle-2.0.0/local_apps/pootle_notifications/models.py
new file mode 100644
index 0000000..3024d84
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_notifications/models.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+from django.db import models
+from django.utils.translation import ugettext_lazy as _
+
+from pootle_misc.baseurl import l
+
+class Notice(models.Model):
+ directory = models.ForeignKey('pootle_app.Directory', db_index=True)
+ message = models.TextField(_('Message'))
+ #l10n: The date that the news item was written
+ added = models.DateTimeField(_('Added'), auto_now_add=True, null=True, db_index=True)
+
+ def __unicode__(self):
+ return self.message
+
+ def get_absolute_url(self):
+ return l(self.directory.pootle_path + 'notices/%d' % self.id)
+
+ class Meta:
+ ordering = ["-added"]
+
diff --git a/Pootle-2.0.0/local_apps/pootle_notifications/templates/latest_news_snippet.html b/Pootle-2.0.0/local_apps/pootle_notifications/templates/latest_news_snippet.html
new file mode 100644
index 0000000..1466645
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_notifications/templates/latest_news_snippet.html
@@ -0,0 +1,11 @@
+{% load i18n cleanhtml %}
+
+<ul>
+{% if news_items %}
+ {% for item in news_items %}
+ <li class="newsitem">{{ item.message|clean }}</li>
+ {% endfor %}
+{% else %}
+ <li>{% trans "No news yet." %}</li>
+{% endif %}
+</ul>
diff --git a/Pootle-2.0.0/local_apps/pootle_notifications/templates/notice_body.html b/Pootle-2.0.0/local_apps/pootle_notifications/templates/notice_body.html
new file mode 100644
index 0000000..9699eef
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_notifications/templates/notice_body.html
@@ -0,0 +1,2 @@
+{% load cleanhtml %}
+{{ obj.message|safe|linebreaks|clean }}
diff --git a/Pootle-2.0.0/local_apps/pootle_notifications/templates/notice_title.html b/Pootle-2.0.0/local_apps/pootle_notifications/templates/notice_title.html
new file mode 100644
index 0000000..0d72005
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_notifications/templates/notice_title.html
@@ -0,0 +1 @@
+{{ obj.message|striptags|truncatewords:15 }}
diff --git a/Pootle-2.0.0/local_apps/pootle_notifications/templates/notices.html b/Pootle-2.0.0/local_apps/pootle_notifications/templates/notices.html
new file mode 100644
index 0000000..022960c
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_notifications/templates/notices.html
@@ -0,0 +1,91 @@
+{% extends "base.html" %}
+
+{% comment %}
+Oh yeah, I know, this template code is horrible, and actually there are
+two templates into one. For the future we have to take a different
+approach for this.
+{% endcomment %}
+
+{% load i18n baseurl cleanhtml %}
+
+{% get_current_language as LANGUAGE_CODE %}
+
+{% block title %}
+{% if is_language %}
+{{ block.super }}: {{ language.name }} » {% trans "News" %}
+{% else %}
+{{ block.super }}: {{ navitems.0.path.language.text }} » {{ navitems.0.path.project.text }} » {% trans "News" %}
+{% endif %}
+{% endblock %}
+
+{% block extra_head %}
+<link rel="alternate" type="application/rss+xml" title="{{ title }}" href="{% url pootle_notifications.feeds.view path %}" />
+{% endblock %}
+
+{% block bodyclass %}
+{% if is_language %}languagenews{% else %}tpnews{% endif %}
+{% endblock %}
+
+{% block nav-secondary %}
+{% if is_language %}
+<div id="breadcrumbs">
+ <h2 class="title"><a href="{% filter l %}/{{ language.code }}/{% endfilter %}">{{ language.name }}</a></h2>
+</div>
+{% else %}
+
+{% block search %}
+{% include "language/search.html" %}
+{% endblock %}
+
+{% block breadcrumbs %}
+<div id="breadcrumbs">
+ <h2 class="title">{% include "language/item_title.html" %}</h2>
+</div>
+{% endblock %}
+{% endif %}
+{% endblock %}
+
+{% block precontent %}
+{% if is_language %}
+ {% include "language_menu.html" %}
+{% else %}
+ {% include "tp_menu.html" %}
+{% endif %}
+{% endblock %}
+
+{% block content %}
+<div class="settings-container centered" lang="{{ LANGUAGE_CODE }}">
+{% if form %}
+ <h2>{{ title }}</h2>
+ <p>{{ success }}</p>
+ <form method="post" action="">
+ {{ form.as_p }}
+ <p>
+ <input type="submit" value="{% trans "Publish" %}" />
+ </p>
+ </form>
+{% endif %}
+
+ <h2>{% trans "Latest News" %} {{ name }}</h2>
+{% if notices %}
+ {% for notice in notices %}
+ <div class="newsitem">{{ notice.message|safe|linebreaks|clean }}</div>
+ <div class="newsitem-sep"></div>
+ {% endfor %}
+{% else %}
+ <p>{% trans "No news yet." %}</p>
+{% endif %}
+</div>
+{% endblock %}
+
+{% if not is_language %}
+{% block scripts_extra %}
+<script type="text/javascript" src='{{ "js/search.js"|m }}'></script>
+<script type="text/javascript" src='{{ "js/jquery/jquery.bidi.js"|m }}'></script>
+<script type="text/javascript">
+ $(document).ready(function() {
+ $(".newsitem address, .newsitem blockcode, .newsitem blockquote, .newsitem :header, .newsitem p, .newsitem pre, .newsitem li, .newsitem dt, .newsitem dd, .newsitem ul, .newsitem ol, .newsitem dl").filter(":not([dir])").bidi();
+ });
+</script>
+{% endblock %}
+{% endif %}
diff --git a/Pootle-2.0.0/local_apps/pootle_notifications/templates/viewnotice.html b/Pootle-2.0.0/local_apps/pootle_notifications/templates/viewnotice.html
new file mode 100644
index 0000000..d9db79e
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_notifications/templates/viewnotice.html
@@ -0,0 +1,12 @@
+{% extends "base.html" %}
+{% load i18n baseurl cleanhtml %}
+{% block content %}
+
+<h3 class="title">{{ title }}</h3>
+
+<div>
+{{ notice_message|safe|linebreaks|clean }}
+</div>
+
+{% endblock %}
+
diff --git a/Pootle-2.0.0/local_apps/pootle_notifications/templatetags/__init__.py b/Pootle-2.0.0/local_apps/pootle_notifications/templatetags/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_notifications/templatetags/__init__.py
diff --git a/Pootle-2.0.0/local_apps/pootle_notifications/templatetags/notification_tags.py b/Pootle-2.0.0/local_apps/pootle_notifications/templatetags/notification_tags.py
new file mode 100644
index 0000000..92e1d4c
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_notifications/templatetags/notification_tags.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+from django import template
+
+from pootle_app.models import Directory
+
+from pootle_notifications.models import Notice
+
+register = template.Library()
+
+
+@register.inclusion_tag('latest_news_snippet.html')
+def render_latest_news(path, num):
+ try:
+ directory = Directory.objects.get(pootle_path='/%s' % path)
+ except Directory.DoesNotExist:
+ return { 'news_items': None }
+ news_items = Notice.objects.filter(directory=directory)[:num]
+ return { 'news_items': news_items }
diff --git a/Pootle-2.0.0/local_apps/pootle_notifications/urls.py b/Pootle-2.0.0/local_apps/pootle_notifications/urls.py
new file mode 100644
index 0000000..273a71c
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_notifications/urls.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+from django.conf.urls.defaults import *
+
+
+urlpatterns = patterns('pootle_notifications',
+ (r'^(?P<path>.*)notices/rss.xml$', 'feeds.view'),
+ (r'^(?P<path>.*)notices/?$', 'views.view'),
+ (r'^(?P<path>.*)notices/(?P<notice_id>[0-9]+)/?$', 'views.view_notice_item'),
+)
diff --git a/Pootle-2.0.0/local_apps/pootle_notifications/views.py b/Pootle-2.0.0/local_apps/pootle_notifications/views.py
new file mode 100644
index 0000000..f3d2dc8
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_notifications/views.py
@@ -0,0 +1,129 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+from django.core.exceptions import PermissionDenied, ObjectDoesNotExist
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.utils.translation import ugettext as _
+from django.shortcuts import get_object_or_404
+from django import forms
+from django.forms import ModelForm
+
+from pootle.i18n.gettext import tr_lang
+
+from pootle_app.models import Directory
+from pootle_app.models.permissions import get_matching_permissions, check_permission
+from pootle_app.models.profile import get_profile
+
+from pootle_app.views.language import search_forms
+from pootle_app.views.language import navbar_dict
+
+from pootle_notifications.models import Notice
+
+def view(request, path):
+ #FIXME: why do we have leading and trailing slashes in pootle_path?
+ pootle_path = '/%s' % path
+
+ directory = get_object_or_404(Directory, pootle_path=pootle_path)
+
+ request.permissions = get_matching_permissions(get_profile(request.user), directory)
+
+ if not check_permission('view', request):
+ raise PermissionDenied
+
+ template_vars = {'path': path}
+
+ if check_permission('administrate', request):
+ template_vars['form'] = handle_form(request, directory)
+ template_vars['title'] = directory_to_title(request, directory)
+ if request.GET.get('all', False):
+ template_vars['notices'] = Notice.objects.filter(directory__pootle_path__startswith=directory.pootle_path).select_related('directory')[:30]
+ else:
+ template_vars['notices'] = Notice.objects.filter(directory=directory).select_related('directory')[:30]
+
+ if directory.is_language():
+ template_vars['is_language'] = True
+ template_vars['language'] = {'code': directory.language.code,
+ 'name': tr_lang(directory.language.fullname)}
+ else:
+ template_vars['is_language'] = False
+ try:
+ template_vars['search'] = search_forms.get_search_form(request)
+ request.translation_project = directory.get_translationproject()
+ template_vars['navitems'] = [navbar_dict.make_directory_navbar_dict(request, directory)]
+ except:
+ pass
+
+ return render_to_response('notices.html', template_vars, context_instance=RequestContext(request))
+
+def directory_to_title(request, directory):
+ """figures out if directory refers to a Language or
+ TranslationProject and returns appropriate string for use in
+ titles"""
+
+ try:
+ trans_vars = {
+ 'language': tr_lang(directory.language.fullname),
+ }
+ return _('News for %(language)s', trans_vars)
+ except ObjectDoesNotExist:
+ pass
+
+ try:
+ trans_vars = {
+ 'language': tr_lang(directory.translationproject.language.fullname),
+ 'project': directory.translationproject.project.fullname,
+ }
+ return _('News for the %(project)s project in %(language)s', trans_vars)
+ except ObjectDoesNotExist:
+ pass
+
+ return _('News for %(path)s',
+ {'path': directory.pootle_path})
+
+def handle_form(request, current_directory):
+ class NoticeForm(ModelForm):
+ directory = forms.ModelChoiceField(
+ queryset=Directory.objects.filter(pk=current_directory.pk),
+ initial=current_directory.pk, widget=forms.HiddenInput)
+
+ class Meta:
+ model = Notice
+
+ if request.method == 'POST':
+ form = NoticeForm(request.POST)
+ if form.is_valid():
+ form.save()
+ form = NoticeForm()
+ else:
+ form = NoticeForm()
+
+ return form
+
+
+def view_notice_item(request, path, notice_id):
+ notice = get_object_or_404(Notice, id=notice_id)
+ template_vars = {
+ "title" : _("View News Item"),
+ "notice_message" : notice.message,
+ }
+
+ return render_to_response('viewnotice.html', template_vars,
+ context_instance=RequestContext(request))
diff --git a/Pootle-2.0.0/local_apps/pootle_store/__init__.py b/Pootle-2.0.0/local_apps/pootle_store/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_store/__init__.py
diff --git a/Pootle-2.0.0/local_apps/pootle_store/fields.py b/Pootle-2.0.0/local_apps/pootle_store/fields.py
new file mode 100644
index 0000000..76ab93d
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_store/fields.py
@@ -0,0 +1,337 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+"""Utility classes for handling translation files."""
+
+import logging
+import time
+import os
+import shutil
+import tempfile
+
+from django.conf import settings
+from django.core.files import File
+from django.db.models.fields.files import FieldFile, FileField
+from django.utils.thread_support import currentThread
+
+from translate.storage import factory, statsdb, po, poheader
+from translate.misc.lru import LRUCachingDict
+
+from pootle_store.signals import translation_file_updated, post_unit_update
+
+from pootle.__version__ import sver as pootle_version
+
+x_generator = "Pootle %s" % pootle_version
+
+class StatsTuple(object):
+ """Encapsulates stats in the in memory cache, needed
+ since LRUCachingDict is based on a weakref.WeakValueDictionary
+ which cannot reference normal tuples"""
+ def __init__(self):
+ self.quickstats = None
+ self.stats = None
+ self.completestats = None
+ self.unitstats = None
+
+class StoreTuple(object):
+ """Encapsulates toolkit stores in the in memory cache, needed
+ since LRUCachingDict is based on a weakref.WeakValueDictionary
+ which cannot reference normal tuples"""
+ def __init__(self, store, mod_info):
+ self.store = store
+ self.mod_info = mod_info
+
+
+class TranslationStoreFile(File):
+ """A mixin for use alongside django.core.files.base.File, which provides
+ additional features for dealing with translation files."""
+
+ _stats = LRUCachingDict(settings.PARSE_POOL_SIZE * 5, settings.PARSE_POOL_CULL_FREQUENCY)
+ __statscache = {}
+
+ def _get_statscache(self):
+ """reuse statsdb database connection, keep a pool of one connection per thread"""
+ current_thread = currentThread()
+ if current_thread not in self.__statscache:
+ self.__statscache[current_thread] = statsdb.StatsCache(settings.STATS_DB_PATH)
+ return self.__statscache[current_thread]
+
+ _statscache = property(_get_statscache)
+
+ #FIXME: figure out what this checker thing is
+ checker = None
+
+ def _get_store(self):
+ """parse file and return TranslationStore object"""
+ if not hasattr(self, "_store"):
+ #FIXME: translate.storage.base.parsefile closes file for
+ #some weird reason, so we sprinkle with opens to make sure
+ #things workout
+ self.open()
+ self._store = factory.getobject(self)
+ self.open()
+ return self._store
+ store = property(_get_store)
+
+ def _get_filename(self):
+ return os.path.basename(self.name)
+ filename = property(_get_filename)
+
+ def savestore(self):
+ self.store.save()
+
+ def _guess_path(self):
+ """Most TranslationStoreFile objects will correspond to
+ TranslationStoreField instances and have a known path, however
+ standalone instances of TranslationStoreFile can come from in-memory
+ files or already open file descriptors with no sure way of obtaining a
+ path."""
+ #FIXME: is name the best substitute for path?
+ return self.name
+ path = property(_guess_path)
+
+
+ def getquickstats(self):
+ """Returns the quick statistics (totals only)."""
+ stats_tuple = self._stats.setdefault(self.path, StatsTuple())
+ if stats_tuple.quickstats is None:
+ stats_tuple.quickstats = self._statscache.filetotals(self.path, store=self._get_store) # or statsdb.emptyfiletotals()
+ return stats_tuple.quickstats
+
+ def getstats(self):
+ """Returns the unit states statistics only."""
+ stats_tuple = self._stats.setdefault(self.path, StatsTuple())
+ if stats_tuple.stats is None:
+ stats_tuple.stats = self._statscache.filestatestats(self.path, store=self._get_store)
+ return stats_tuple.stats
+
+ def getcompletestats(self, checker):
+ """Return complete stats including quality checks."""
+ stats_tuple = self._stats.setdefault(self.path, StatsTuple())
+ if stats_tuple.completestats is None:
+ stats_tuple.completestats = self._statscache.filestats(self.path, checker, store=self._get_store)
+ return stats_tuple.completestats
+
+ def getunitstats(self):
+ stats_tuple = self._stats.setdefault(self.path, StatsTuple())
+ if stats_tuple.unitstats is None:
+ stats_tuple.unitstats = self._statscache.unitstats(self.path, store=self._get_store)
+ return stats_tuple.unitstats
+
+ def reclassifyunit(self, item, checker):
+ """Reclassifies all the information in the database and self._stats
+ about the given unit."""
+ unit = self.getitem(item)
+ state = self._statscache.recacheunit(self.path, checker, unit)
+ #FIXME: can't we use state to update stats cache instead of invalidating it?
+ self._stats[self.path] = StatsTuple()
+ return state
+
+ def _get_total(self):
+ """Returns a list of translatable unit indices, useful for identifying
+ translatable units by their place in translation file (item number)."""
+ return self.getstats()['total']
+ total = property(_get_total)
+
+ def getitem(self, item):
+ """Returns a single unit based on the item number."""
+ return self.store.units[self.total[item]]
+
+ def getitemslen(self):
+ """The number of items in the file."""
+ return self.getquickstats()['total']
+
+ def updateunit(self, item, newvalues, checker, user=None, language=None):
+ """Updates a translation with a new target value, comments, or fuzzy
+ state."""
+ # operation replaces file, make sure we have latest copy
+ oldstats = self.getquickstats()
+ self._update_store_cache()
+ unit = self.getitem(item)
+
+ if newvalues.has_key('target'):
+ if not unit.hasplural() and not isinstance(newvalues['target'], basestring):
+ unit.target = newvalues['target'][0]
+ else:
+ unit.target = newvalues['target']
+ if newvalues.has_key('fuzzy'):
+ unit.markfuzzy(newvalues['fuzzy'])
+ if newvalues.has_key('translator_comments'):
+ unit.removenotes()
+ if newvalues['translator_comments']:
+ unit.addnote(newvalues['translator_comments'], origin="translator")
+
+ had_header = True
+ if isinstance(self.store, po.pofile):
+ had_header = self.store.header()
+ po_revision_date = time.strftime('%Y-%m-%d %H:%M') + poheader.tzstring()
+ headerupdates = {'PO_Revision_Date': po_revision_date,
+ 'X_Generator': x_generator}
+
+ if language is not None:
+ headerupdates['Language'] = language.code
+ if language.nplurals and language.pluralequation:
+ self.store.updateheaderplural(language.nplurals, language.pluralequation)
+
+ if user is not None:
+ headerupdates['Last_Translator'] = '%s <%s>' % (user.first_name, user.email)
+
+ self.store.updateheader(add=True, **headerupdates)
+
+ self.savestore()
+ if not had_header:
+ # if new header was added item indeces will be incorrect, flush stats caches
+ self._stats[self.path] = StatsTuple()
+ else:
+ self.reclassifyunit(item, checker)
+ newstats = self.getquickstats()
+ post_unit_update.send(sender=self.instance, oldstats=oldstats, newstats=newstats)
+
+
+ def addunit(self, unit):
+ """Wrapper around TranslationStore.addunit that updates sourceindex on
+ the fly.
+
+ Useful for avoiding rebuilding the index of pending files when new
+ suggestions are added."""
+ self.store.addunit(unit)
+ if hasattr(self.store, "sourceindex"):
+ self.store.add_unit_to_index(unit)
+
+ def removeunit(self, unit):
+ """Removes a unit from store, updates sourceindex on the fly.
+
+ Useful for avoiding rebuilding index of pending files when suggestions
+ are removed."""
+ self.store.units.remove(unit)
+ if hasattr(self.store, "sourceindex"):
+ self.store.remove_unit_from_index(unit)
+
+ def getpomtime(self):
+ return statsdb.get_mod_info(self.path)
+
+
+class TranslationStoreFieldFile(FieldFile, TranslationStoreFile):
+ """FieldFile is the File-like object of a FileField, that is found in a
+ TranslationStoreField."""
+
+ _store_cache = LRUCachingDict(settings.PARSE_POOL_SIZE, settings.PARSE_POOL_CULL_FREQUENCY)
+
+ # redundant redefinition of path to be the same as defined in
+ # FieldFile, added here for clarity since TranslationStoreFile
+ # uses a different method
+ path = property(FieldFile._get_path)
+
+ def _get_store(self):
+ """Get translation store from dictionary cache, populate if store not
+ already cached."""
+ #FIXME: when do we detect that file changed?
+ if not hasattr(self, "_store_tuple"):
+ self._update_store_cache()
+ return self._store_tuple.store
+
+
+ def _update_store_cache(self):
+ """Add translation store to dictionary cache, replace old cached
+ version if needed."""
+ mod_info = self.getpomtime()
+ if not hasattr(self, "_store_typle") or self._store_tuple.mod_info != mod_info:
+ try:
+ self._store_tuple = self._store_cache[self.path]
+ if self._store_tuple.mod_info != mod_info:
+ # if file is modified act as if it doesn't exist in cache
+ raise KeyError
+ except KeyError:
+ logging.debug("cache miss for %s", self.path)
+ self._store_tuple = StoreTuple(factory.getobject(self.path, ignore=self.field.ignore), mod_info)
+ self._store_cache[self.path] = self._store_tuple
+ self._stats[self.path] = StatsTuple()
+ translation_file_updated.send(sender=self, path=self.path)
+
+
+ def _touch_store_cache(self):
+ """Update stored mod_info without reparsing file."""
+ if hasattr(self, "_store_tuple"):
+ mod_info = self.getpomtime()
+ if self._store_tuple.mod_info != mod_info:
+ self._store_tuple.mod_info = mod_info
+ translation_file_updated.send(sender=self, path=self.path)
+ else:
+ #FIXME: do we really need that?
+ self._update_store_cache()
+
+
+ def _delete_store_cache(self):
+ """Remove translation store from cache."""
+ try:
+ del self._store_cache[self.path]
+ except KeyError:
+ pass
+
+ try:
+ del self._store_tuple
+ except AttributeError:
+ pass
+
+ try:
+ del self._stats[self.path]
+ except KeyError:
+ pass
+ translation_file_updated.send(sender=self, path=self.path)
+
+ store = property(_get_store)
+
+
+ def savestore(self):
+ """Saves to temporary file then moves over original file. This
+ way we avoid the need for locking."""
+ tmpfile, tmpfilename = tempfile.mkstemp(suffix=self.filename)
+ #FIXME: what if the file was modified before we save
+ self.store.savefile(tmpfilename)
+ shutil.move(tmpfilename, self.path)
+ self._touch_store_cache()
+
+ def save(self, name, content, save=True):
+ #FIXME: implement save to tmp file then move instead of directly saving
+ super(TranslationStoreFieldFile, self).save(name, content, save)
+ self._delete_store_cache()
+
+ def delete(self, save=True):
+ self._delete_store_cache()
+ if save:
+ super(TranslationStoreFieldFile, self).delete(save)
+
+
+class TranslationStoreField(FileField):
+ """This is the field class to represent a FileField in a model that
+ represents a translation store."""
+
+ attr_class = TranslationStoreFieldFile
+
+ #def formfield(self, **kwargs):
+ # defaults = {'form_class': FileField}
+ # defaults.update(kwargs)
+ # return super(TranslationStoreField, self).formfield(**defaults)
+
+ def __init__(self, ignore=None, **kwargs):
+ """ignore: postfix to be stripped from filename when trying to
+ determine file format for parsing, useful for .pending files"""
+ self.ignore = ignore
+ super(TranslationStoreField, self).__init__(**kwargs)
diff --git a/Pootle-2.0.0/local_apps/pootle_store/models.py b/Pootle-2.0.0/local_apps/pootle_store/models.py
new file mode 100644
index 0000000..e14ba00
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_store/models.py
@@ -0,0 +1,369 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008-2009 Zuza Software Foundation
+#
+# This file is part of Pootle.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+import os
+import logging
+import re
+
+from django.db import models
+from django.conf import settings
+from django.utils.translation import ugettext_lazy as _
+from django.core.files.storage import FileSystemStorage
+
+from translate.storage import po
+
+from pootle_misc.util import getfromcache, deletefromcache
+from pootle_misc.baseurl import l
+from pootle_app.models.directory import Directory
+from pootle_store.fields import TranslationStoreField
+from pootle_store.signals import translation_file_updated
+
+# custom storage otherwise djago assumes all files are uploads headed to
+# media dir
+fs = FileSystemStorage(location=settings.PODIRECTORY)
+
+# regexp to parse suggester name from msgidcomment
+suggester_regexp = re.compile(r'suggested by (.*) \[[-0-9]+\]')
+
+class Store(models.Model):
+ """A model representing a translation store (i.e. a PO or XLIFF file)."""
+ is_dir = False
+
+ file = TranslationStoreField(upload_to="fish", max_length=255, storage=fs, db_index=True, null=False, editable=False)
+ pending = TranslationStoreField(ignore='.pending', upload_to="fish", max_length=255, storage=fs, editable=False)
+ tm = TranslationStoreField(ignore='.tm', upload_to="fish", max_length=255, storage=fs, editable=False)
+ parent = models.ForeignKey(Directory, related_name='child_stores', db_index=True, editable=False)
+ pootle_path = models.CharField(max_length=255, null=False, unique=True, db_index=True, verbose_name=_("Path"))
+ name = models.CharField(max_length=128, null=False, editable=False)
+
+ class Meta:
+ ordering = ['pootle_path']
+ unique_together = ('parent', 'name')
+
+ def handle_file_update(self, sender, **kwargs):
+ deletefromcache(self, ["getquickstats", "getcompletestats"])
+
+ def _get_abs_real_path(self):
+ return self.file.path
+
+ abs_real_path = property(_get_abs_real_path)
+
+ def _get_real_path(self):
+ return self.file.name
+
+ real_path = property(_get_real_path)
+
+ def __unicode__(self):
+ return self.name
+
+ def get_absolute_url(self):
+ return l(self.pootle_path)
+
+ @getfromcache
+ def getquickstats(self):
+ # convert result to normal dicts for later operations
+ return dict(self.file.getquickstats())
+
+ @getfromcache
+ def getcompletestats(self, checker):
+ #FIXME: figure out our own checker?
+ stats = {}
+ for key, value in self.file.getcompletestats(checker).iteritems():
+ stats[key] = len(value)
+ return stats
+
+ def initpending(self, create=False):
+ """initialize pending translations file if needed"""
+ #FIXME: we parse file just to find if suggestions can be
+ #stored in format, maybe we should store TranslationStore
+ #class and query it for such info
+ if self.file.store.suggestions_in_format:
+ # suggestions can be stored in the translation file itself
+ return
+
+ pending_name = self.file.name + os.extsep + 'pending'
+ pending_path = os.path.join(settings.PODIRECTORY, pending_name)
+ if self.pending:
+ # pending file already referencing in db, but does it
+ # really exist
+ if os.path.exists(self.pending.path):
+ # pending file exists
+ self.pending._update_store_cache()
+ return
+ elif not create:
+ # pending file doesn't exist anymore
+ self.pending = None
+ self.save()
+
+ # check if pending file already exists, just in case it was
+ # added outside of pootle
+ if not os.path.exists(pending_path) and create:
+ # we only create the file if asked, typically before
+ # adding a suggestion
+ store = po.pofile()
+ store.makeheader(charset='UTF-8', encoding='8bit')
+ store.savefile(pending_path)
+
+ if os.path.exists(pending_path):
+ self.pending = pending_name
+ self.save()
+ self.pending._update_store_cache()
+ translation_file_updated.connect(self.handle_file_update, sender=self.pending)
+
+ def getsuggestions_unit(self, unit):
+ if self.file.store.suggestions_in_format:
+ return unit.getalttrans()
+ else:
+ self.initpending()
+ if self.pending:
+ self.pending.store.require_index()
+ suggestions = self.pending.store.findunits(unit.source)
+ if suggestions is not None:
+ return suggestions
+ return []
+
+ def getsuggestions(self, item):
+ unit = self.file.getitem(item)
+ return self.getsuggestions_unit(unit)
+
+
+ def suggestion_is_unique(self, unit, newtarget):
+ """check for duplicate suggestions"""
+ if unit.target == newtarget:
+ return False
+
+ for suggestion in self.getsuggestions_unit(unit):
+ if suggestion.target == newtarget:
+ return False
+
+ return True
+
+ def addunitsuggestion(self, unit, newunit, username):
+ """adds suggestion for the given unit"""
+ if not self.suggestion_is_unique(unit, newunit.target):
+ return
+
+ if self.file.store.suggestions_in_format:
+ unit.addalttrans(newunit.target, origin=username)
+ else:
+ newunit = self.pending.store.UnitClass.buildfromunit(newunit)
+ if username is not None:
+ newunit.msgidcomment = 'suggested by %s [%d]' % (username, hash(newunit.target))
+ self.pending.addunit(newunit)
+
+
+ def addsuggestion(self, item, suggtarget, username, checker=None):
+ """adds a new suggestion for the given item"""
+ unit = self.file.getitem(item)
+
+ if self.file.store.suggestions_in_format:
+ # probably xliff, which can't do unit copies and doesn't
+ # need a unit to add suggestions anyway. so let's shortcut
+ # and insert suggestion here
+ if self.suggestion_is_unique(unit, suggtarget):
+ unit.addalttrans(suggtarget, origin=username)
+ self.file.savestore()
+ else:
+ newpo = unit.copy()
+ newpo.target = suggtarget
+ newpo.markfuzzy(False)
+
+ self.initpending(create=True)
+ self.addunitsuggestion(unit, newpo, username)
+ self.pending.savestore()
+
+ if checker is not None:
+ self.file.reclassifyunit(item, checker)
+
+
+ def _deletesuggestion(self, item, suggestion):
+ if self.file.store.suggestions_in_format:
+ unit = self.file.getitem(item)
+ unit.delalttrans(suggestion)
+ else:
+ try:
+ self.pending.removeunit(suggestion)
+ except ValueError:
+ logging.error('Found an index error attempting to delete a suggestion: %s', suggestion)
+ return # TODO: Print a warning for the user.
+
+ def deletesuggestion(self, item, suggitem, newtrans, checker):
+ """removes the suggestion from the pending file"""
+ suggestions = self.getsuggestions(item)
+
+ try:
+ # first try to use index
+ suggestion = self.getsuggestions(item)[suggitem]
+ if suggestion.hasplural() and suggestion.target.strings == newtrans or \
+ not suggestion.hasplural() and suggestion.target == newtrans[0]:
+ self._deletesuggestion(item, suggestion)
+ else:
+ # target doesn't match suggested translation, index is
+ # incorrect
+ raise IndexError
+ except IndexError:
+ logging.debug('Found an index error attempting to delete suggestion %d\n looking for item by target', suggitem)
+ # see if we can find the correct suggestion by searching
+ # for target text
+ for suggestion in suggestions:
+ if suggestion.hasplural() and suggestion.target.strings == newtrans or \
+ not suggestion.hasplural() and suggestion.target == newtrans[0]:
+ self._deletesuggestion(item, suggestion)
+ break
+
+ if self.file.store.suggestions_in_format:
+ self.file.savestore()
+ else:
+ self.pending.savestore()
+ self.file.reclassifyunit(item, checker)
+
+
+ def getsuggester(self, item, suggitem):
+ """returns who suggested the given item's suggitem if
+ recorded, else None"""
+
+ unit = self.getsuggestions(item)[suggitem]
+ if self.file.store.suggestions_in_format:
+ return unit.xmlelement.get('origin')
+
+ else:
+ suggestedby = suggester_regexp.search(unit.msgidcomment)
+ if suggestedby:
+ return suggestedby.group(1)
+ return None
+
+
+ def mergefile(self, newfile, username, allownewstrings, suggestions, notranslate, obsoletemissing):
+ """make sure each msgid is unique ; merge comments etc from
+ duplicates into original"""
+ self.file._update_store_cache()
+ self.file.store.require_index()
+ newfile.require_index()
+
+ old_ids = set(self.file.store.id_index.keys())
+ new_ids = set(newfile.id_index.keys())
+
+ if allownewstrings:
+ new_units = (newfile.findid(uid) for uid in new_ids - old_ids)
+ for unit in new_units:
+ self.file.store.addunit(self.file.store.UnitClass.buildfromunit(unit))
+
+ if obsoletemissing:
+ old_units = (self.file.store.findid(uid) for uid in old_ids - new_ids)
+ for unit in old_units:
+ unit.makeobsolete()
+
+ if notranslate or suggestions:
+ self.initpending(create=True)
+
+ shared_units = ((self.file.store.findid(uid), newfile.findid(uid)) for uid in old_ids & new_ids)
+ for oldunit, newunit in shared_units:
+ if not newunit.istranslated():
+ continue
+
+ if notranslate or oldunit.istranslated() and suggestions:
+ self.addunitsuggestion(oldunit, newunit, username)
+ else:
+ oldunit.merge(newunit)
+
+ if (suggestions or notranslate) and not self.file.store.suggestions_in_format:
+ self.pending.savestore()
+
+ if not isinstance(newfile, po.pofile) or notranslate or suggestions:
+ # TODO: We don't support updating the header yet.
+ self.file.savestore()
+ return
+
+ # Let's update selected header entries. Only the ones
+ # listed below, and ones that are empty in self can be
+ # updated. The check in header_order is just a basic
+ # sanity check so that people don't insert garbage.
+ updatekeys = [
+ 'Content-Type',
+ 'POT-Creation-Date',
+ 'Last-Translator',
+ 'Project-Id-Version',
+ 'PO-Revision-Date',
+ 'Language-Team',
+ ]
+ headerstoaccept = {}
+ ownheader = self.file.store.parseheader()
+ for (key, value) in newfile.parseheader().items():
+ if key in updatekeys or (not key in ownheader
+ or not ownheader[key]) and key in po.pofile.header_order:
+ headerstoaccept[key] = value
+ self.file.store.updateheader(add=True, **headerstoaccept)
+
+ # Now update the comments above the header:
+ header = self.file.store.header()
+ newheader = newfile.header()
+ if header is None and not newheader is None:
+ header = self.file.store.UnitClass('', encoding=self.file.store._encoding)
+ header.target = ''
+ if header:
+ header._initallcomments(blankall=True)
+ if newheader:
+ for i in range(len(header.allcomments)):
+ header.allcomments[i].extend(newheader.allcomments[i])
+
+ self.file.savestore()
+
+
+ def inittm(self):
+ """initialize translation memory file if needed"""
+ if self.tm and os.path.exists(self.tm.path):
+ return
+
+ tm_filename = self.file.path + os.extsep + 'tm'
+ if os.path.exists(tm_filename):
+ self.tm = tm_filename
+ self.save()
+
+ def gettmsuggestions(self, item):
+ """find all the tmsuggestion items submitted for the given
+ item"""
+
+ self.inittm()
+ if self.tm:
+ unit = self.file.getitem(item)
+ locations = unit.getlocations()
+ # TODO: review the matching method. We can't simply use the
+ # location index, because we want multiple matches.
+ suggestpos = [suggestpo for suggestpo in self.tm.store.units
+ if suggestpo.getlocations() == locations]
+ return suggestpos
+ return []
+
+def set_store_pootle_path(sender, instance, **kwargs):
+ instance.pootle_path = '%s%s' % (instance.parent.pootle_path, instance.name)
+models.signals.pre_save.connect(set_store_pootle_path, sender=Store)
+
+def store_post_init(sender, instance, **kwargs):
+ translation_file_updated.connect(instance.handle_file_update, sender=instance.file)
+ if instance.pending is not None:
+ #FIXME: we probably want another method for pending, to avoid
+ # invalidating stats that are not affected by suggestions
+ translation_file_updated.connect(instance.handle_file_update, sender=instance.pending)
+
+models.signals.post_init.connect(store_post_init, sender=Store)
+
+def store_post_delete(sender, instance, **kwargs):
+ deletefromcache(instance, ["getquickstats", "getcompletestats"])
+models.signals.post_delete.connect(store_post_delete, sender=Store)
diff --git a/Pootle-2.0.0/local_apps/pootle_store/signals.py b/Pootle-2.0.0/local_apps/pootle_store/signals.py
new file mode 100644
index 0000000..48944e1
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_store/signals.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2009 Zuza Software Foundation
+#
+# This file is part of translate.
+#
+# translate is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# translate is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with translate; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from django.dispatch import Signal
+
+translation_file_updated = Signal(providing_args=["path"])
+post_unit_update = Signal(providing_args=["oldstats", "newstats"])
diff --git a/Pootle-2.0.0/local_apps/pootle_store/util.py b/Pootle-2.0.0/local_apps/pootle_store/util.py
new file mode 100644
index 0000000..2772152
--- /dev/null
+++ b/Pootle-2.0.0/local_apps/pootle_store/util.py
@@ -0,0 +1,87 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2004-2006 Zuza Software Foundation
+#
+# This file is part of translate.
+#
+# translate is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# translate is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with translate; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import os
+
+from django.conf import settings
+
+def add_trailing_slash(path):
+ """If path does not end with /, add it and return."""
+
+ if path[-1] == os.sep:
+ return path
+ else:
+ return path + os.sep
+
+
+def relative_real_path(p):
+ if p.startswith(settings.PODIRECTORY):
+ return p[len(add_trailing_slash(settings.PODIRECTORY)):]
+ else:
+ return p
+
+
+def absolute_real_path(p):
+ if not p.startswith(settings.PODIRECTORY):
+ return os.path.join(settings.PODIRECTORY, p)
+ else:
+ return p
+
+def dictsum(x, y):
+ return dict( (n, x.get(n, 0)+y.get(n, 0)) for n in set(x)|set(y) )
+
+def statssum(queryset, empty_stats=None):
+ if empty_stats is None:
+ empty_stats = {'fuzzy': 0,
+ 'fuzzysourcewords': 0,
+ 'review': 0,
+ 'total': 0,
+ 'totalsourcewords': 0,
+ 'translated': 0,
+ 'translatedsourcewords': 0,
+ 'translatedtargetwords': 0,
+ 'untranslated': 0,
+ 'untranslatedsourcewords': 0,
+ 'errors': 0}
+ totals = empty_stats
+ for item in queryset:
+ try:
+ totals = dictsum(totals, item.getquickstats())
+ except:
+ totals['errors'] += 1
+ return totals
+
+def completestatssum(queryset, checker, empty_stats=None):
+ if empty_stats is None:
+ empty_stats = {u'check-hassuggestion': 0,
+ u'check-isfuzzy': 0,
+ 'fuzzy': 0,
+ 'total': 0,
+ 'translated': 0,
+ 'untranslated': 0,
+ 'errors': 0}
+ totals = empty_stats
+ for item in queryset:
+ try:
+ totals = dictsum(totals, item.getcompletestats(checker))
+ except:
+ totals['errors'] += 1
+ return totals