| 1 | # -*- coding: utf-8 -*- |
|---|
| 2 | # |
|---|
| 3 | # Copyright (C) 2016 Cinc |
|---|
| 4 | # All rights reserved. |
|---|
| 5 | # |
|---|
| 6 | # This software is licensed as described in the file COPYING.txt, which |
|---|
| 7 | # you should have received as part of this distribution. |
|---|
| 8 | # |
|---|
| 9 | # Author: Cinc |
|---|
| 10 | # |
|---|
| 11 | |
|---|
| 12 | from trac.admin import IAdminPanelProvider |
|---|
| 13 | from trac.core import Component, implements |
|---|
| 14 | from trac.util.translation import _ |
|---|
| 15 | from trac.web.chrome import add_notice, add_script, add_script_data, add_stylesheet, add_warning |
|---|
| 16 | from .model import ReviewDataModel, ReviewFileModel |
|---|
| 17 | from .repo import insert_project_files, repo_path_exists |
|---|
| 18 | |
|---|
| 19 | __author__ = 'Cinc' |
|---|
| 20 | __license__ = "BSD" |
|---|
| 21 | |
|---|
| 22 | class PeerReviewFileAdmin(Component): |
|---|
| 23 | """Admin panel to specify files belonging to a project. |
|---|
| 24 | |
|---|
| 25 | [[BR]] |
|---|
| 26 | You may define a project identifier and a root folder from the repository holding all the files |
|---|
| 27 | of a project using this admin panel. When saving the information all the files in the folder hierarchy are hashed |
|---|
| 28 | and file name, revision, hash and project name are inserted in the database. |
|---|
| 29 | |
|---|
| 30 | Using the file information it is possible to create reports (see TracReports for more information) like which |
|---|
| 31 | files may need a review and more. |
|---|
| 32 | """ |
|---|
| 33 | implements(IAdminPanelProvider) |
|---|
| 34 | |
|---|
| 35 | # IAdminPanelProvider methods |
|---|
| 36 | |
|---|
| 37 | def get_admin_panels(self, req): |
|---|
| 38 | if 'CODE_REVIEW_DEV' in req.perm: |
|---|
| 39 | yield ('codereview', 'Code review', 'projectfiles', 'Project Files') |
|---|
| 40 | |
|---|
| 41 | def render_admin_panel(self, req, cat, page, path_info): |
|---|
| 42 | |
|---|
| 43 | def remove_project_info(rem_name): |
|---|
| 44 | # Remove project name info |
|---|
| 45 | rev_data = ReviewDataModel(self.env) |
|---|
| 46 | rev_data.clear_props() |
|---|
| 47 | rev_data['data'] = rem_name |
|---|
| 48 | rev_data['data_key'] = 'name' |
|---|
| 49 | for item in rev_data.list_matching_objects(): |
|---|
| 50 | item.delete() |
|---|
| 51 | # Remove info about project like rootfolder, extensions, revision, repo |
|---|
| 52 | rev_data = ReviewDataModel(self.env) |
|---|
| 53 | rev_data.clear_props() |
|---|
| 54 | rev_data['data_key'] = rem_name |
|---|
| 55 | for item in rev_data.list_matching_objects(): |
|---|
| 56 | item.delete() |
|---|
| 57 | ReviewFileModel.delete_files_by_project_name(self.env, rem_name) |
|---|
| 58 | |
|---|
| 59 | def add_project_info(): |
|---|
| 60 | def _insert_project_info(type_, key_, val): |
|---|
| 61 | rev_data = ReviewDataModel(self.env) |
|---|
| 62 | rev_data['type'] = type_ |
|---|
| 63 | rev_data['data_key'] = key_ |
|---|
| 64 | rev_data['data'] = val |
|---|
| 65 | rev_data.insert() |
|---|
| 66 | _insert_project_info('fileproject', 'name', name) |
|---|
| 67 | _insert_project_info('rootfolder', name, rootfolder) |
|---|
| 68 | _insert_project_info('extensions', name, exts) |
|---|
| 69 | _insert_project_info('repo', name, reponame) |
|---|
| 70 | _insert_project_info('revision', name, rev) |
|---|
| 71 | |
|---|
| 72 | def create_ext_list(ext_str): |
|---|
| 73 | """Create a list of extensions from a string. |
|---|
| 74 | |
|---|
| 75 | Double ',', trailing ',' and empty extensions are filtered out. Extensions not starting with '.' |
|---|
| 76 | are ignored. |
|---|
| 77 | |
|---|
| 78 | @return: unfiltered extension list, filtered extension list |
|---|
| 79 | """ |
|---|
| 80 | if not ext_str: |
|---|
| 81 | return [], [] |
|---|
| 82 | ext_list = [ext.strip() for ext in ext_str.split(',') if ext.strip()] # filter trailing ',', double ','and empty exts |
|---|
| 83 | return [ext for ext in ext_str.split(',') if ext], [ext.lower() for ext in ext_list if ext[0] == '.'] |
|---|
| 84 | |
|---|
| 85 | req.perm.require('CODE_REVIEW_DEV') |
|---|
| 86 | |
|---|
| 87 | name = req.args.get('projectname') or path_info |
|---|
| 88 | rootfolder = req.args.get('rootfolder') |
|---|
| 89 | reponame = req.args.get('reponame', '') |
|---|
| 90 | rev = req.args.get('rev', None) |
|---|
| 91 | exts = req.args.get('extensions', '') |
|---|
| 92 | follow_ext = req.args.get('follow_ext', False) |
|---|
| 93 | ext_list, ext_filtered = create_ext_list(exts) |
|---|
| 94 | sel = req.args.get('sel', []) # For removal |
|---|
| 95 | if type(sel) is not list: |
|---|
| 96 | sel = [sel] |
|---|
| 97 | |
|---|
| 98 | all_proj = ReviewDataModel.all_file_project_data(self.env) |
|---|
| 99 | |
|---|
| 100 | if req.method=='POST': |
|---|
| 101 | if req.args.get('add'): |
|---|
| 102 | def do_redirect(): |
|---|
| 103 | req.redirect(req.href.admin(cat, page, projectname=name, |
|---|
| 104 | rootfolder=rootfolder, |
|---|
| 105 | extensions=exts, |
|---|
| 106 | repo=reponame, |
|---|
| 107 | rev=rev, |
|---|
| 108 | error=1)) |
|---|
| 109 | if not name: |
|---|
| 110 | add_warning(req, _("You need to specify a project name.")) |
|---|
| 111 | do_redirect() |
|---|
| 112 | if name in all_proj: |
|---|
| 113 | add_warning(req, _("The project identifier already exists.")) |
|---|
| 114 | do_redirect() |
|---|
| 115 | if not repo_path_exists(self.env, rootfolder, reponame): |
|---|
| 116 | add_warning(req, _("The given root folder can't be found in the repository or it is a file.")) |
|---|
| 117 | do_redirect() |
|---|
| 118 | if len(ext_list) != len(ext_filtered): |
|---|
| 119 | add_warning(req, _("Some extensions are not valid.")) |
|---|
| 120 | do_redirect() |
|---|
| 121 | add_project_info() |
|---|
| 122 | errors, num_files = insert_project_files(self, rootfolder, name, ext_filtered, follow_ext, rev=rev, reponame=reponame) |
|---|
| 123 | add_notice(req, _("The project has been added. %s files belonging to the project have been added " |
|---|
| 124 | "to the database"), num_files) |
|---|
| 125 | for err in errors: |
|---|
| 126 | add_warning(req, err) |
|---|
| 127 | elif req.args.get('save'): |
|---|
| 128 | def do_redirect_save(): |
|---|
| 129 | req.redirect(req.href.admin(cat, page, path_info, |
|---|
| 130 | projectname=name, |
|---|
| 131 | rootfolder=rootfolder, |
|---|
| 132 | extensions=exts, |
|---|
| 133 | repo=reponame, |
|---|
| 134 | rev=rev, |
|---|
| 135 | error=1)) |
|---|
| 136 | |
|---|
| 137 | if not req.args.get('projectname'): |
|---|
| 138 | add_warning(req, _("No project name given. The old name was inserted again.")) |
|---|
| 139 | add_warning(req, _("No changes have been saved.")) |
|---|
| 140 | do_redirect_save() |
|---|
| 141 | if name != path_info: |
|---|
| 142 | if name in all_proj: |
|---|
| 143 | add_warning(req, _("The project identifier already exists.")) |
|---|
| 144 | do_redirect_save() |
|---|
| 145 | if not repo_path_exists(self.env, rootfolder, reponame): |
|---|
| 146 | add_warning(req, _("The given root folder %s can't be found in the repository or it is a file."), |
|---|
| 147 | rootfolder) |
|---|
| 148 | do_redirect_save() |
|---|
| 149 | if len(ext_list) != len(ext_filtered): |
|---|
| 150 | add_warning(req, _("Some extensions are not valid. %s"), exts) |
|---|
| 151 | do_redirect_save() |
|---|
| 152 | # Handle change. We remove all data for old name and recreate it using the new one |
|---|
| 153 | remove_project_info(path_info) |
|---|
| 154 | add_project_info() |
|---|
| 155 | errors, num_files = insert_project_files(self, rootfolder, name, ext_filtered, follow_ext, rev=rev, reponame=reponame) |
|---|
| 156 | add_notice(req, _("Your changes have been changed. %s files belonging to the project have been added " |
|---|
| 157 | "to the database"), num_files) |
|---|
| 158 | for err in errors: |
|---|
| 159 | add_warning(req, err) |
|---|
| 160 | elif req.args.get('remove'): |
|---|
| 161 | for rem_name in sel: |
|---|
| 162 | remove_project_info(rem_name) |
|---|
| 163 | |
|---|
| 164 | req.redirect(req.href.admin(cat, page)) |
|---|
| 165 | |
|---|
| 166 | all_proj_lst = [[key, value] for key, value in all_proj.items()] |
|---|
| 167 | data = {'view': 'detail' if path_info else 'list', |
|---|
| 168 | 'projects': sorted(all_proj_lst, key=lambda item: item[0]), |
|---|
| 169 | 'projectname': name, |
|---|
| 170 | } |
|---|
| 171 | if(path_info): |
|---|
| 172 | data['view_project'] = path_info |
|---|
| 173 | view_proj = all_proj[path_info] |
|---|
| 174 | # With V3.1 the following was added to the saved information for multi repo support. |
|---|
| 175 | # It isn't available for old projects. |
|---|
| 176 | if 'repo' not in view_proj: |
|---|
| 177 | view_proj['repo'] = '' |
|---|
| 178 | if 'revision' not in view_proj: |
|---|
| 179 | view_proj['revision'] = '' |
|---|
| 180 | data.update({ |
|---|
| 181 | 'rootfolder': rootfolder or view_proj['rootfolder'], |
|---|
| 182 | 'extensions': exts or view_proj['extensions'], |
|---|
| 183 | 'reponame': reponame or view_proj['repo'], |
|---|
| 184 | 'revision': rev or view_proj['revision'], |
|---|
| 185 | }) |
|---|
| 186 | else: |
|---|
| 187 | data.update({ |
|---|
| 188 | 'rootfolder': rootfolder, |
|---|
| 189 | 'extensions': exts, |
|---|
| 190 | 'reponame': reponame, |
|---|
| 191 | 'revision': rev |
|---|
| 192 | }) |
|---|
| 193 | add_stylesheet(req, 'common/css/browser.css') |
|---|
| 194 | add_stylesheet(req, 'hw/css/admin_file.css') |
|---|
| 195 | add_script_data(req, {'repo_browser': self.env.href.adminrepobrowser(data['rootfolder'], |
|---|
| 196 | repo=data['reponame'], |
|---|
| 197 | rev=data['revision']), |
|---|
| 198 | 'show_repo_idx': path_info == None if 'error' not in req.args else False} |
|---|
| 199 | ) |
|---|
| 200 | add_script(req, 'hw/js/admin_files.js') |
|---|
| 201 | return 'admin_files.html', data |
|---|