{"action":"created","comment":{"url":"https://api.github.com/repos/translate/pootle/pulls/comments/49188120","id":49188120,"diff_hunk":"@@ -43,120 +57,323 @@ def admin_permissions(request, translation_project):\n                        'translation_projects/admin/permissions.html', ctx)\n \n \n-@get_path_obj\n-@permission_required('view')\n-@get_resource\n-def browse(request, translation_project, dir_path, filename=None):\n-    project = translation_project.project\n-    language = translation_project.language\n-\n-    directory = request.directory\n-    store = request.store\n-    is_admin = check_permission('administrate', request)\n-\n-    ctx, cookie_data = get_sidebar_announcements_context(\n-        request,\n-        (project, language, translation_project, ),\n-    )\n-\n-    ctx.update(get_browser_context(request))\n-\n-    # TODO improve plugin logic\n-    if \"import_export\" in settings.INSTALLED_APPS:\n-        if request.user.is_authenticated():\n-            if check_permission('translate', request):\n-                ctx.update(handle_upload_form(request, project))\n-            ctx.update({'display_download': True,\n-                        'has_sidebar': True})\n-\n-    stats = request.resource_obj.get_stats()\n-\n-    if store is None:\n-        table_fields = ['name', 'progress', 'total', 'need-translation',\n-                        'suggestions', 'critical', 'last-updated', 'activity']\n-        ctx.update({\n-            'table': {\n-                'id': 'tp',\n-                'fields': table_fields,\n-                'headings': get_table_headings(table_fields),\n-                'items': get_children(directory),\n-            }\n-        })\n-\n-        if 'virtualfolder' in settings.INSTALLED_APPS:\n-            vfolders = get_vfolders(directory, all_vfolders=is_admin)\n-            if len(vfolders) > 0:\n-                table_fields = ['name', 'priority', 'progress', 'total',\n-                                'need-translation', 'suggestions', 'critical',\n-                                'last-updated', 'activity']\n-                ctx.update({\n-                    'vfolders': {\n-                        'id': 'vfolders',\n-                        'fields': table_fields,\n-                        'headings': get_table_headings(table_fields),\n-                        'items': vfolders,\n-                    },\n-                })\n-\n-                # FIXME: set vfolders stats in the resource, don't inject them\n-                # here.\n-                stats['vfolders'] = {}\n-\n-                for vfolder_treeitem in directory.vf_treeitems.iterator():\n-                    if (request.user.is_superuser or\n-                            vfolder_treeitem.is_visible):\n-                        stats['vfolders'][vfolder_treeitem.code] = \\\n-                            vfolder_treeitem.get_stats(include_children=False)\n-\n-    ctx.update({\n-        'parent': get_parent(directory if store is None else store),\n-        'translation_project': translation_project,\n-        'project': project,\n-        'language': language,\n-        'stats': jsonify(stats),\n-        'is_admin': is_admin,\n-        'is_store': store is not None,\n-\n-        'browser_extends': 'translation_projects/base.html',\n-    })\n-\n-    response = render(request, 'browser/index.html', ctx)\n-\n-    if cookie_data:\n-        response.set_cookie(SIDEBAR_COOKIE_NAME, cookie_data)\n-\n-    return response\n-\n-\n-@get_path_obj\n-@permission_required('view')\n-@get_resource\n-def translate(request, translation_project, dir_path, filename):\n-    project = translation_project.project\n-\n-    ctx = get_translation_context(request)\n-\n-    ctx.update({\n-        'language': translation_project.language,\n-        'project': project,\n-        'translation_project': translation_project,\n-\n-        'editor_extends': 'translation_projects/base.html',\n-    })\n-\n-    return render(request, \"editor/main.html\", ctx)\n-\n-\n-@get_path_obj\n-@permission_required('view')\n-@get_resource\n-def export_view(request, translation_project, dir_path, filename=None):\n-    \"\"\"Displays a list of units with filters applied.\"\"\"\n-    ctx = get_export_view_context(request)\n-    ctx.update({\n-        'source_language': translation_project.project.source_language,\n-        'language': translation_project.language,\n-        'project': translation_project.project,\n-    })\n-\n-    return render(request, 'editor/export_view.html', ctx)\n+def redirect_to_tp_on_404(f):\n+\n+    @functools.wraps(f)\n+    def method_wrapper(self, request, *args, **kwargs):\n+        User = get_user_model()\n+        request.profile = User.get(self.request.user)\n+        try:\n+            request.permissions = get_matching_permissions(\n+                request.profile,\n+                self.permission_context) or []\n+        except Http404 as e:\n+            if (kwargs[\"dir_path\"] or kwargs.get(\"filename\", None)):\n+                try:\n+                    TranslationProject.objects.get(\n+                        project__code=kwargs[\"project_code\"],\n+                        language__code=kwargs[\"language_code\"])\n+                    # the TP exists so redirect to it\n+                    return redirect(\n+                        reverse(\n+                            'pootle-tp-browse',\n+                            kwargs={\n+                                k: v\n+                                for k, v\n+                                in kwargs.items()\n+                                if k in [\n+                                    \"language_code\",\n+                                    \"project_code\"]}))\n+                except TranslationProject.DoesNotExist:\n+                    pass\n+\n+            # if we get here - the TP does not exist\n+            user_choice = self.request.COOKIES.get(\n+                'user-choice', None)\n+            if user_choice:\n+                url = None\n+                if user_choice == 'language':\n+                    url = reverse(\n+                        'pootle-language-browse',\n+                        args=[kwargs[\"language_code\"]])\n+                elif user_choice == \"project\":\n+                    url = reverse(\n+                        'pootle-project-browse',\n+                        args=[kwargs[\"project_code\"], '', ''])\n+                if url:\n+                    response = redirect(url)\n+                    response.delete_cookie('user-choice')\n+                    return response\n+            raise e\n+        return f(self, request, *args, **kwargs)\n+    return method_wrapper\n+\n+\n+class TPMixin(object):\n+\n+    @redirect_to_tp_on_404\n+    @set_permissions\n+    @requires_permission(\"view\")\n+    def dispatch(self, request, *args, **kwargs):\n+        # get funky with the request 8/\n+        return super(TPMixin, self).dispatch(request, *args, **kwargs)\n+\n+    @property\n+    def ctx_path(self):\n+        return self.tp.pootle_path\n+\n+    @property\n+    def resource_path(self):\n+        return self.object.pootle_path.replace(self.ctx_path, \"\")\n+\n+    @property\n+    def dir_path(self):\n+        return self.resource_path\n+\n+    @cached_property\n+    def tp(self):\n+        return self.object.translation_project\n+\n+    @cached_property\n+    def project(self):\n+        return self.tp.project\n+\n+    @cached_property\n+    def language(self):\n+        return self.tp.language\n+\n+    @cached_property\n+    def sidebar_announcements(self):\n+        return get_sidebar_announcements_context(\n+            self.request,\n+            (self.project, self.language, self.tp))\n+\n+\n+class TPDirectoryMixin(TPMixin):\n+    model = Directory\n+    browse_url_path = \"pootle-tp-browse\"\n+    export_url_path = \"pootle-tp-export\"\n+    translate_url_path = \"pootle-tp-translate\"\n+\n+    @lru_cache()\n+    def get_object(self):\n+        return get_object_or_404(\n+            Directory.objects.all(),\n+            pootle_path=self.path)\n+\n+    @property\n+    def url_kwargs(self):\n+        return {\n+            \"language_code\": self.language.code,\n+            \"project_code\": self.project.code,\n+            \"dir_path\": self.dir_path}\n+\n+\n+class TPStoreMixin(TPMixin):\n+    model = Store\n+    browse_url_path = \"pootle-tp-store-browse\"\n+    export_url_path = \"pootle-tp-store-export\"\n+    translate_url_path = \"pootle-tp-store-translate\"\n+    is_store = True\n+\n+    @property\n+    def permission_context(self):\n+        return self.get_object().parent\n+\n+    @property\n+    def dir_path(self):\n+        return self.resource_path.replace(self.object.name, \"\")\n+\n+    @property\n+    def url_kwargs(self):\n+        return {\n+            \"language_code\": self.language.code,\n+            \"project_code\": self.project.code,\n+            \"dir_path\": self.dir_path,\n+            \"filename\": self.object.name}\n+\n+    def get_object(self):\n+        path = (\n+            \"/%(language_code)s/%(project_code)s/%(dir_path)s%(filename)s\"\n+            % self.kwargs)\n+        return get_object_or_404(\n+            Store.objects.select_related(\"parent\"),","path":"pootle/apps/pootle_translationproject/views.py","position":308,"original_position":308,"commit_id":"6c1d640239586576bbb5e620cb75278db0af8b03","original_commit_id":"cae5dd7bce6adfc1ea44f1655b207513210c2e1a","user":{"login":"phlax","id":454682,"avatar_url":"https://avatars.githubusercontent.com/u/454682?v=3","gravatar_id":"","url":"https://api.github.com/users/phlax","html_url":"https://github.com/phlax","followers_url":"https://api.github.com/users/phlax/followers","following_url":"https://api.github.com/users/phlax/following{/other_user}","gists_url":"https://api.github.com/users/phlax/gists{/gist_id}","starred_url":"https://api.github.com/users/phlax/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/phlax/subscriptions","organizations_url":"https://api.github.com/users/phlax/orgs","repos_url":"https://api.github.com/users/phlax/repos","events_url":"https://api.github.com/users/phlax/events{/privacy}","received_events_url":"https://api.github.com/users/phlax/received_events","type":"User","site_admin":false},"body":"cool - ill test and add","created_at":"2016-01-08T13:38:00Z","updated_at":"2016-01-08T13:38:00Z","html_url":"https://github.com/translate/pootle/pull/4303#discussion_r49188120","pull_request_url":"https://api.github.com/repos/translate/pootle/pulls/4303","_links":{"self":{"href":"https://api.github.com/repos/translate/pootle/pulls/comments/49188120"},"html":{"href":"https://github.com/translate/pootle/pull/4303#discussion_r49188120"},"pull_request":{"href":"https://api.github.com/repos/translate/pootle/pulls/4303"}}},"pull_request":{"url":"https://api.github.com/repos/translate/pootle/pulls/4303","id":54188968,"html_url":"https://github.com/translate/pootle/pull/4303","diff_url":"https://github.com/translate/pootle/pull/4303.diff","patch_url":"https://github.com/translate/pootle/pull/4303.patch","issue_url":"https://api.github.com/repos/translate/pootle/issues/4303","number":4303,"state":"open","locked":false,"title":"View classes","user":{"login":"phlax","id":454682,"avatar_url":"https://avatars.githubusercontent.com/u/454682?v=3","gravatar_id":"","url":"https://api.github.com/users/phlax","html_url":"https://github.com/phlax","followers_url":"https://api.github.com/users/phlax/followers","following_url":"https://api.github.com/users/phlax/following{/other_user}","gists_url":"https://api.github.com/users/phlax/gists{/gist_id}","starred_url":"https://api.github.com/users/phlax/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/phlax/subscriptions","organizations_url":"https://api.github.com/users/phlax/orgs","repos_url":"https://api.github.com/users/phlax/repos","events_url":"https://api.github.com/users/phlax/events{/privacy}","received_events_url":"https://api.github.com/users/phlax/received_events","type":"User","site_admin":false},"body":"requires #4322 ","created_at":"2015-12-19T23:33:23Z","updated_at":"2016-01-08T13:38:00Z","closed_at":null,"merged_at":null,"merge_commit_sha":"68e892fa1ccf8d475b1f3cac2668a6eda4ae69ad","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/translate/pootle/pulls/4303/commits","review_comments_url":"https://api.github.com/repos/translate/pootle/pulls/4303/comments","review_comment_url":"https://api.github.com/repos/translate/pootle/pulls/comments{/number}","comments_url":"https://api.github.com/repos/translate/pootle/issues/4303/comments","statuses_url":"https://api.github.com/repos/translate/pootle/statuses/6c1d640239586576bbb5e620cb75278db0af8b03","head":{"label":"phlax:view_classes","ref":"view_classes","sha":"6c1d640239586576bbb5e620cb75278db0af8b03","user":{"login":"phlax","id":454682,"avatar_url":"https://avatars.githubusercontent.com/u/454682?v=3","gravatar_id":"","url":"https://api.github.com/users/phlax","html_url":"https://github.com/phlax","followers_url":"https://api.github.com/users/phlax/followers","following_url":"https://api.github.com/users/phlax/following{/other_user}","gists_url":"https://api.github.com/users/phlax/gists{/gist_id}","starred_url":"https://api.github.com/users/phlax/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/phlax/subscriptions","organizations_url":"https://api.github.com/users/phlax/orgs","repos_url":"https://api.github.com/users/phlax/repos","events_url":"https://api.github.com/users/phlax/events{/privacy}","received_events_url":"https://api.github.com/users/phlax/received_events","type":"User","site_admin":false},"repo":{"id":39886550,"name":"pootle","full_name":"phlax/pootle","owner":{"login":"phlax","id":454682,"avatar_url":"https://avatars.githubusercontent.com/u/454682?v=3","gravatar_id":"","url":"https://api.github.com/users/phlax","html_url":"https://github.com/phlax","followers_url":"https://api.github.com/users/phlax/followers","following_url":"https://api.github.com/users/phlax/following{/other_user}","gists_url":"https://api.github.com/users/phlax/gists{/gist_id}","starred_url":"https://api.github.com/users/phlax/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/phlax/subscriptions","organizations_url":"https://api.github.com/users/phlax/orgs","repos_url":"https://api.github.com/users/phlax/repos","events_url":"https://api.github.com/users/phlax/events{/privacy}","received_events_url":"https://api.github.com/users/phlax/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/phlax/pootle","description":"Online translation tool","fork":true,"url":"https://api.github.com/repos/phlax/pootle","forks_url":"https://api.github.com/repos/phlax/pootle/forks","keys_url":"https://api.github.com/repos/phlax/pootle/keys{/key_id}","collaborators_url":"https://api.github.com/repos/phlax/pootle/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/phlax/pootle/teams","hooks_url":"https://api.github.com/repos/phlax/pootle/hooks","issue_events_url":"https://api.github.com/repos/phlax/pootle/issues/events{/number}","events_url":"https://api.github.com/repos/phlax/pootle/events","assignees_url":"https://api.github.com/repos/phlax/pootle/assignees{/user}","branches_url":"https://api.github.com/repos/phlax/pootle/branches{/branch}","tags_url":"https://api.github.com/repos/phlax/pootle/tags","blobs_url":"https://api.github.com/repos/phlax/pootle/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/phlax/pootle/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/phlax/pootle/git/refs{/sha}","trees_url":"https://api.github.com/repos/phlax/pootle/git/trees{/sha}","statuses_url":"https://api.github.com/repos/phlax/pootle/statuses/{sha}","languages_url":"https://api.github.com/repos/phlax/pootle/languages","stargazers_url":"https://api.github.com/repos/phlax/pootle/stargazers","contributors_url":"https://api.github.com/repos/phlax/pootle/contributors","subscribers_url":"https://api.github.com/repos/phlax/pootle/subscribers","subscription_url":"https://api.github.com/repos/phlax/pootle/subscription","commits_url":"https://api.github.com/repos/phlax/pootle/commits{/sha}","git_commits_url":"https://api.github.com/repos/phlax/pootle/git/commits{/sha}","comments_url":"https://api.github.com/repos/phlax/pootle/comments{/number}","issue_comment_url":"https://api.github.com/repos/phlax/pootle/issues/comments{/number}","contents_url":"https://api.github.com/repos/phlax/pootle/contents/{+path}","compare_url":"https://api.github.com/repos/phlax/pootle/compare/{base}...{head}","merges_url":"https://api.github.com/repos/phlax/pootle/merges","archive_url":"https://api.github.com/repos/phlax/pootle/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/phlax/pootle/downloads","issues_url":"https://api.github.com/repos/phlax/pootle/issues{/number}","pulls_url":"https://api.github.com/repos/phlax/pootle/pulls{/number}","milestones_url":"https://api.github.com/repos/phlax/pootle/milestones{/number}","notifications_url":"https://api.github.com/repos/phlax/pootle/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/phlax/pootle/labels{/name}","releases_url":"https://api.github.com/repos/phlax/pootle/releases{/id}","created_at":"2015-07-29T10:00:35Z","updated_at":"2015-07-29T10:00:39Z","pushed_at":"2016-01-08T13:04:32Z","git_url":"git://github.com/phlax/pootle.git","ssh_url":"git@github.com:phlax/pootle.git","clone_url":"https://github.com/phlax/pootle.git","svn_url":"https://github.com/phlax/pootle","homepage":"http://pootle.translatehouse.org","size":82058,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"translate:master","ref":"master","sha":"e8a234a54545dad4eccdb515ff007c3389aeb09b","user":{"login":"translate","id":1538178,"avatar_url":"https://avatars.githubusercontent.com/u/1538178?v=3","gravatar_id":"","url":"https://api.github.com/users/translate","html_url":"https://github.com/translate","followers_url":"https://api.github.com/users/translate/followers","following_url":"https://api.github.com/users/translate/following{/other_user}","gists_url":"https://api.github.com/users/translate/gists{/gist_id}","starred_url":"https://api.github.com/users/translate/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/translate/subscriptions","organizations_url":"https://api.github.com/users/translate/orgs","repos_url":"https://api.github.com/users/translate/repos","events_url":"https://api.github.com/users/translate/events{/privacy}","received_events_url":"https://api.github.com/users/translate/received_events","type":"Organization","site_admin":false},"repo":{"id":3769702,"name":"pootle","full_name":"translate/pootle","owner":{"login":"translate","id":1538178,"avatar_url":"https://avatars.githubusercontent.com/u/1538178?v=3","gravatar_id":"","url":"https://api.github.com/users/translate","html_url":"https://github.com/translate","followers_url":"https://api.github.com/users/translate/followers","following_url":"https://api.github.com/users/translate/following{/other_user}","gists_url":"https://api.github.com/users/translate/gists{/gist_id}","starred_url":"https://api.github.com/users/translate/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/translate/subscriptions","organizations_url":"https://api.github.com/users/translate/orgs","repos_url":"https://api.github.com/users/translate/repos","events_url":"https://api.github.com/users/translate/events{/privacy}","received_events_url":"https://api.github.com/users/translate/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/translate/pootle","description":"Online translation tool","fork":false,"url":"https://api.github.com/repos/translate/pootle","forks_url":"https://api.github.com/repos/translate/pootle/forks","keys_url":"https://api.github.com/repos/translate/pootle/keys{/key_id}","collaborators_url":"https://api.github.com/repos/translate/pootle/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/translate/pootle/teams","hooks_url":"https://api.github.com/repos/translate/pootle/hooks","issue_events_url":"https://api.github.com/repos/translate/pootle/issues/events{/number}","events_url":"https://api.github.com/repos/translate/pootle/events","assignees_url":"https://api.github.com/repos/translate/pootle/assignees{/user}","branches_url":"https://api.github.com/repos/translate/pootle/branches{/branch}","tags_url":"https://api.github.com/repos/translate/pootle/tags","blobs_url":"https://api.github.com/repos/translate/pootle/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/translate/pootle/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/translate/pootle/git/refs{/sha}","trees_url":"https://api.github.com/repos/translate/pootle/git/trees{/sha}","statuses_url":"https://api.github.com/repos/translate/pootle/statuses/{sha}","languages_url":"https://api.github.com/repos/translate/pootle/languages","stargazers_url":"https://api.github.com/repos/translate/pootle/stargazers","contributors_url":"https://api.github.com/repos/translate/pootle/contributors","subscribers_url":"https://api.github.com/repos/translate/pootle/subscribers","subscription_url":"https://api.github.com/repos/translate/pootle/subscription","commits_url":"https://api.github.com/repos/translate/pootle/commits{/sha}","git_commits_url":"https://api.github.com/repos/translate/pootle/git/commits{/sha}","comments_url":"https://api.github.com/repos/translate/pootle/comments{/number}","issue_comment_url":"https://api.github.com/repos/translate/pootle/issues/comments{/number}","contents_url":"https://api.github.com/repos/translate/pootle/contents/{+path}","compare_url":"https://api.github.com/repos/translate/pootle/compare/{base}...{head}","merges_url":"https://api.github.com/repos/translate/pootle/merges","archive_url":"https://api.github.com/repos/translate/pootle/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/translate/pootle/downloads","issues_url":"https://api.github.com/repos/translate/pootle/issues{/number}","pulls_url":"https://api.github.com/repos/translate/pootle/pulls{/number}","milestones_url":"https://api.github.com/repos/translate/pootle/milestones{/number}","notifications_url":"https://api.github.com/repos/translate/pootle/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/translate/pootle/labels{/name}","releases_url":"https://api.github.com/repos/translate/pootle/releases{/id}","created_at":"2012-03-19T23:06:03Z","updated_at":"2016-01-07T14:51:48Z","pushed_at":"2016-01-08T13:04:34Z","git_url":"git://github.com/translate/pootle.git","ssh_url":"git@github.com:translate/pootle.git","clone_url":"https://github.com/translate/pootle.git","svn_url":"https://github.com/translate/pootle","homepage":"http://pootle.translatehouse.org","size":83541,"stargazers_count":564,"watchers_count":564,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":169,"mirror_url":null,"open_issues_count":279,"forks":169,"open_issues":279,"watchers":564,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/translate/pootle/pulls/4303"},"html":{"href":"https://github.com/translate/pootle/pull/4303"},"issue":{"href":"https://api.github.com/repos/translate/pootle/issues/4303"},"comments":{"href":"https://api.github.com/repos/translate/pootle/issues/4303/comments"},"review_comments":{"href":"https://api.github.com/repos/translate/pootle/pulls/4303/comments"},"review_comment":{"href":"https://api.github.com/repos/translate/pootle/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/translate/pootle/pulls/4303/commits"},"statuses":{"href":"https://api.github.com/repos/translate/pootle/statuses/6c1d640239586576bbb5e620cb75278db0af8b03"}}}}