##// END OF EJS Templates
Implemented permissions into hg app, secured admin controllers, templates and repository specific controllers
marcink -
r318:fdf9f6ee default
parent child Browse files
Show More
@@ -1,46 +1,46 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # branches controller for pylons
3 # branches controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
5
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
20 """
20 """
21 Created on April 21, 2010
21 Created on April 21, 2010
22 branches controller for pylons
22 branches controller for pylons
23 @author: marcink
23 @author: marcink
24 """
24 """
25 from pylons import tmpl_context as c
25 from pylons import tmpl_context as c, request
26 from pylons_app.lib.auth import LoginRequired
26 from pylons_app.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
27 from pylons_app.lib.base import BaseController, render
27 from pylons_app.lib.base import BaseController, render
28 from pylons_app.model.hg_model import HgModel
28 from pylons_app.model.hg_model import HgModel
29 import logging
29 import logging
30
31 log = logging.getLogger(__name__)
30 log = logging.getLogger(__name__)
32
31
33 class BranchesController(BaseController):
32 class BranchesController(BaseController):
34
33
35 @LoginRequired()
34 @LoginRequired()
35 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', 'repository.admin')
36 def __before__(self):
36 def __before__(self):
37 super(BranchesController, self).__before__()
37 super(BranchesController, self).__before__()
38
38
39 def index(self):
39 def index(self):
40 hg_model = HgModel()
40 hg_model = HgModel()
41 c.repo_info = hg_model.get_repo(c.repo_name)
41 c.repo_info = hg_model.get_repo(c.repo_name)
42 c.repo_branches = {}
42 c.repo_branches = {}
43 for name, hash in c.repo_info.branches.items():
43 for name, hash_ in c.repo_info.branches.items():
44 c.repo_branches[name] = c.repo_info.get_changeset(hash)
44 c.repo_branches[name] = c.repo_info.get_changeset(hash_)
45
45
46 return render('branches/branches.html')
46 return render('branches/branches.html')
@@ -1,88 +1,90 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # changelog controller for pylons
3 # changelog controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 from json import dumps
6 from mercurial.graphmod import colored, CHANGESET, revisions as graph_rev
7 from pylons import request, session, tmpl_context as c
8 from pylons_app.lib.auth import LoginRequired
9 from pylons_app.lib.base import BaseController, render
10 from pylons_app.model.hg_model import HgModel
11 from webhelpers.paginate import Page
12 import logging
13
5
14 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
15 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
16 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
17 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
18 #
10 #
19 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
14 # GNU General Public License for more details.
23 #
15 #
24 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
25 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
27 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
28 """
20 """
29 Created on April 21, 2010
21 Created on April 21, 2010
30 changelog controller for pylons
22 changelog controller for pylons
31 @author: marcink
23 @author: marcink
32 """
24 """
33 log = logging.getLogger(__name__)
25 from json import dumps
26 from mercurial.graphmod import colored, CHANGESET, revisions as graph_rev
27 from pylons import request, session, tmpl_context as c
28 from pylons_app.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
29 from pylons_app.lib.base import BaseController, render
30 from pylons_app.model.hg_model import HgModel
31 from webhelpers.paginate import Page
32 import logging
33 log = logging.getLogger(__name__)
34
34
35 class ChangelogController(BaseController):
35 class ChangelogController(BaseController):
36
36
37 @LoginRequired()
37 @LoginRequired()
38 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
39 'repository.admin')
38 def __before__(self):
40 def __before__(self):
39 super(ChangelogController, self).__before__()
41 super(ChangelogController, self).__before__()
40
42
41 def index(self):
43 def index(self):
42 limit = 100
44 limit = 100
43 default = 20
45 default = 20
44 if request.params.get('size'):
46 if request.params.get('size'):
45 try:
47 try:
46 int_size = int(request.params.get('size'))
48 int_size = int(request.params.get('size'))
47 except ValueError:
49 except ValueError:
48 int_size = default
50 int_size = default
49 int_size = int_size if int_size <= limit else limit
51 int_size = int_size if int_size <= limit else limit
50 c.size = int_size
52 c.size = int_size
51 session['changelog_size'] = c.size
53 session['changelog_size'] = c.size
52 session.save()
54 session.save()
53 else:
55 else:
54 c.size = int(session.get('changelog_size', default))
56 c.size = int(session.get('changelog_size', default))
55
57
56 changesets = HgModel().get_repo(c.repo_name)
58 changesets = HgModel().get_repo(c.repo_name)
57
59
58 p = int(request.params.get('page', 1))
60 p = int(request.params.get('page', 1))
59 c.total_cs = len(changesets)
61 c.total_cs = len(changesets)
60 c.pagination = Page(changesets, page=p, item_count=c.total_cs,
62 c.pagination = Page(changesets, page=p, item_count=c.total_cs,
61 items_per_page=c.size)
63 items_per_page=c.size)
62
64
63 self._graph(changesets, c.size, p)
65 self._graph(changesets, c.size, p)
64
66
65 return render('changelog/changelog.html')
67 return render('changelog/changelog.html')
66
68
67
69
68 def _graph(self, repo, size, p):
70 def _graph(self, repo, size, p):
69 revcount = size
71 revcount = size
70 if not repo.revisions:return dumps([]), 0
72 if not repo.revisions:return dumps([]), 0
71
73
72 max_rev = repo.revisions[-1]
74 max_rev = repo.revisions[-1]
73 offset = 1 if p == 1 else ((p - 1) * revcount)
75 offset = 1 if p == 1 else ((p - 1) * revcount)
74 rev_start = repo.revisions[(-1 * offset)]
76 rev_start = repo.revisions[(-1 * offset)]
75
77
76 revcount = min(max_rev, revcount)
78 revcount = min(max_rev, revcount)
77 rev_end = max(0, rev_start - revcount)
79 rev_end = max(0, rev_start - revcount)
78 dag = graph_rev(repo.repo, rev_start, rev_end)
80 dag = graph_rev(repo.repo, rev_start, rev_end)
79
81
80 c.dag = tree = list(colored(dag))
82 c.dag = tree = list(colored(dag))
81 data = []
83 data = []
82 for (id, type, ctx, vtx, edges) in tree:
84 for (id, type, ctx, vtx, edges) in tree:
83 if type != CHANGESET:
85 if type != CHANGESET:
84 continue
86 continue
85 data.append(('', vtx, edges))
87 data.append(('', vtx, edges))
86
88
87 c.jsdata = dumps(data)
89 c.jsdata = dumps(data)
88
90
@@ -1,96 +1,97 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # changeset controller for pylons
3 # changeset controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 from pylons import tmpl_context as c, url
6 from pylons.controllers.util import redirect
7 from pylons_app.lib.auth import LoginRequired
8 from pylons_app.lib.base import BaseController, render
9 from pylons_app.model.hg_model import HgModel
10 from vcs.exceptions import RepositoryError
11 from vcs.nodes import FileNode
12 from vcs.utils import diffs as differ
13 import logging
14 import traceback
15
5
16 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
17 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
18 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
19 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
20 #
10 #
21 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
22 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
23 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 # GNU General Public License for more details.
14 # GNU General Public License for more details.
25 #
15 #
26 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
27 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
28 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
29 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
30 """
20 """
31 Created on April 25, 2010
21 Created on April 25, 2010
32 changeset controller for pylons
22 changeset controller for pylons
33 @author: marcink
23 @author: marcink
34 """
24 """
35
25 from pylons import tmpl_context as c, url, request
26 from pylons.controllers.util import redirect
27 from pylons_app.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
28 from pylons_app.lib.base import BaseController, render
29 from pylons_app.model.hg_model import HgModel
30 from vcs.exceptions import RepositoryError
31 from vcs.nodes import FileNode
32 from vcs.utils import diffs as differ
33 import logging
34 import traceback
36
35
37 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
38
37
39 class ChangesetController(BaseController):
38 class ChangesetController(BaseController):
40
39
41 @LoginRequired()
40 @LoginRequired()
41 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
42 'repository.admin')
42 def __before__(self):
43 def __before__(self):
43 super(ChangesetController, self).__before__()
44 super(ChangesetController, self).__before__()
44
45
45 def index(self, revision):
46 def index(self, revision):
46 hg_model = HgModel()
47 hg_model = HgModel()
47 try:
48 try:
48 c.changeset = hg_model.get_repo(c.repo_name).get_changeset(revision)
49 c.changeset = hg_model.get_repo(c.repo_name).get_changeset(revision)
49 except RepositoryError:
50 except RepositoryError:
50 log.error(traceback.format_exc())
51 log.error(traceback.format_exc())
51 return redirect(url('hg_home'))
52 return redirect(url('hg_home'))
52 else:
53 else:
53 try:
54 try:
54 c.changeset_old = c.changeset.parents[0]
55 c.changeset_old = c.changeset.parents[0]
55 except IndexError:
56 except IndexError:
56 c.changeset_old = None
57 c.changeset_old = None
57 c.changes = []
58 c.changes = []
58
59
59 for node in c.changeset.added:
60 for node in c.changeset.added:
60 filenode_old = FileNode(node.path, '')
61 filenode_old = FileNode(node.path, '')
61 if filenode_old.is_binary or node.is_binary:
62 if filenode_old.is_binary or node.is_binary:
62 diff = 'binary file'
63 diff = 'binary file'
63 else:
64 else:
64 f_udiff = differ.get_udiff(filenode_old, node)
65 f_udiff = differ.get_udiff(filenode_old, node)
65 diff = differ.DiffProcessor(f_udiff).as_html()
66 diff = differ.DiffProcessor(f_udiff).as_html()
66 try:
67 try:
67 diff = unicode(diff)
68 diff = unicode(diff)
68 except:
69 except:
69 log.warning('Decoding failed of %s', filenode_old)
70 log.warning('Decoding failed of %s', filenode_old)
70 log.warning('Decoding failed of %s', node)
71 log.warning('Decoding failed of %s', node)
71 diff = 'unsupported type'
72 diff = 'unsupported type'
72 cs1 = None
73 cs1 = None
73 cs2 = node.last_changeset.raw_id
74 cs2 = node.last_changeset.raw_id
74 c.changes.append(('added', node, diff, cs1, cs2))
75 c.changes.append(('added', node, diff, cs1, cs2))
75
76
76 for node in c.changeset.changed:
77 for node in c.changeset.changed:
77 filenode_old = c.changeset_old.get_node(node.path)
78 filenode_old = c.changeset_old.get_node(node.path)
78 if filenode_old.is_binary or node.is_binary:
79 if filenode_old.is_binary or node.is_binary:
79 diff = 'binary file'
80 diff = 'binary file'
80 else:
81 else:
81 f_udiff = differ.get_udiff(filenode_old, node)
82 f_udiff = differ.get_udiff(filenode_old, node)
82 diff = differ.DiffProcessor(f_udiff).as_html()
83 diff = differ.DiffProcessor(f_udiff).as_html()
83 try:
84 try:
84 diff = unicode(diff)
85 diff = unicode(diff)
85 except:
86 except:
86 log.warning('Decoding failed of %s', filenode_old)
87 log.warning('Decoding failed of %s', filenode_old)
87 log.warning('Decoding failed of %s', node)
88 log.warning('Decoding failed of %s', node)
88 diff = 'unsupported type'
89 diff = 'unsupported type'
89 cs1 = filenode_old.last_changeset.raw_id
90 cs1 = filenode_old.last_changeset.raw_id
90 cs2 = node.last_changeset.raw_id
91 cs2 = node.last_changeset.raw_id
91 c.changes.append(('changed', node, diff, cs1, cs2))
92 c.changes.append(('changed', node, diff, cs1, cs2))
92
93
93 for node in c.changeset.removed:
94 for node in c.changeset.removed:
94 c.changes.append(('removed', node, None, None, None))
95 c.changes.append(('removed', node, None, None, None))
95
96
96 return render('changeset/changeset.html')
97 return render('changeset/changeset.html')
@@ -1,193 +1,194 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # files controller for pylons
3 # files controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 from mercurial import archival
5
6 from pylons import request, response, session, tmpl_context as c, url
7 from pylons.controllers.util import redirect
8 from pylons_app.lib.auth import LoginRequired
9 from pylons_app.lib.base import BaseController, render
10 from pylons_app.lib.utils import EmptyChangeset
11 from pylons_app.model.hg_model import HgModel
12 from vcs.exceptions import RepositoryError, ChangesetError
13 from vcs.nodes import FileNode
14 from vcs.utils import diffs as differ
15 import logging
16 import pylons_app.lib.helpers as h
17 import tempfile
18
19 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
20 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
21 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
22 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
23 #
10 #
24 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
25 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
26 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 # GNU General Public License for more details.
14 # GNU General Public License for more details.
28 #
15 #
29 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
30 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
31 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
32 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
33 """
20 """
34 Created on April 21, 2010
21 Created on April 21, 2010
35 files controller for pylons
22 files controller for pylons
36 @author: marcink
23 @author: marcink
37 """
24 """
38
25 from mercurial import archival
26 from pylons import request, response, session, tmpl_context as c, url
27 from pylons.controllers.util import redirect
28 from pylons_app.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
29 from pylons_app.lib.base import BaseController, render
30 from pylons_app.lib.utils import EmptyChangeset, get_repo_slug
31 from pylons_app.model.hg_model import HgModel
32 from vcs.exceptions import RepositoryError, ChangesetError
33 from vcs.nodes import FileNode
34 from vcs.utils import diffs as differ
35 import logging
36 import pylons_app.lib.helpers as h
37 import tempfile
39
38
40 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
41
40
42 class FilesController(BaseController):
41 class FilesController(BaseController):
43
42
44 @LoginRequired()
43 @LoginRequired()
44 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
45 'repository.admin')
45 def __before__(self):
46 def __before__(self):
46 super(FilesController, self).__before__()
47 super(FilesController, self).__before__()
47
48
48 def index(self, repo_name, revision, f_path):
49 def index(self, repo_name, revision, f_path):
49 hg_model = HgModel()
50 hg_model = HgModel()
50 c.repo = repo = hg_model.get_repo(c.repo_name)
51 c.repo = repo = hg_model.get_repo(c.repo_name)
51 revision = request.POST.get('at_rev', None) or revision
52 revision = request.POST.get('at_rev', None) or revision
52
53
53 def get_next_rev(cur):
54 def get_next_rev(cur):
54 max_rev = len(c.repo.revisions) - 1
55 max_rev = len(c.repo.revisions) - 1
55 r = cur + 1
56 r = cur + 1
56 if r > max_rev:
57 if r > max_rev:
57 r = max_rev
58 r = max_rev
58 return r
59 return r
59
60
60 def get_prev_rev(cur):
61 def get_prev_rev(cur):
61 r = cur - 1
62 r = cur - 1
62 return r
63 return r
63
64
64 c.f_path = f_path
65 c.f_path = f_path
65
66
66
67
67 try:
68 try:
68 cur_rev = repo.get_changeset(revision).revision
69 cur_rev = repo.get_changeset(revision).revision
69 prev_rev = repo.get_changeset(get_prev_rev(cur_rev)).raw_id
70 prev_rev = repo.get_changeset(get_prev_rev(cur_rev)).raw_id
70 next_rev = repo.get_changeset(get_next_rev(cur_rev)).raw_id
71 next_rev = repo.get_changeset(get_next_rev(cur_rev)).raw_id
71
72
72 c.url_prev = url('files_home', repo_name=c.repo_name,
73 c.url_prev = url('files_home', repo_name=c.repo_name,
73 revision=prev_rev, f_path=f_path)
74 revision=prev_rev, f_path=f_path)
74 c.url_next = url('files_home', repo_name=c.repo_name,
75 c.url_next = url('files_home', repo_name=c.repo_name,
75 revision=next_rev, f_path=f_path)
76 revision=next_rev, f_path=f_path)
76
77
77 c.changeset = repo.get_changeset(revision)
78 c.changeset = repo.get_changeset(revision)
78
79
79
80
80 c.cur_rev = c.changeset.raw_id
81 c.cur_rev = c.changeset.raw_id
81 c.rev_nr = c.changeset.revision
82 c.rev_nr = c.changeset.revision
82 c.files_list = c.changeset.get_node(f_path)
83 c.files_list = c.changeset.get_node(f_path)
83 c.file_history = self._get_history(repo, c.files_list, f_path)
84 c.file_history = self._get_history(repo, c.files_list, f_path)
84
85
85 except (RepositoryError, ChangesetError):
86 except (RepositoryError, ChangesetError):
86 c.files_list = None
87 c.files_list = None
87
88
88 return render('files/files.html')
89 return render('files/files.html')
89
90
90 def rawfile(self, repo_name, revision, f_path):
91 def rawfile(self, repo_name, revision, f_path):
91 hg_model = HgModel()
92 hg_model = HgModel()
92 c.repo = hg_model.get_repo(c.repo_name)
93 c.repo = hg_model.get_repo(c.repo_name)
93 file_node = c.repo.get_changeset(revision).get_node(f_path)
94 file_node = c.repo.get_changeset(revision).get_node(f_path)
94 response.content_type = file_node.mimetype
95 response.content_type = file_node.mimetype
95 response.content_disposition = 'attachment; filename=%s' \
96 response.content_disposition = 'attachment; filename=%s' \
96 % f_path.split('/')[-1]
97 % f_path.split('/')[-1]
97 return file_node.content
98 return file_node.content
98
99
99 def annotate(self, repo_name, revision, f_path):
100 def annotate(self, repo_name, revision, f_path):
100 hg_model = HgModel()
101 hg_model = HgModel()
101 c.repo = hg_model.get_repo(c.repo_name)
102 c.repo = hg_model.get_repo(c.repo_name)
102 cs = c.repo.get_changeset(revision)
103 cs = c.repo.get_changeset(revision)
103 c.file = cs.get_node(f_path)
104 c.file = cs.get_node(f_path)
104 c.file_msg = cs.get_file_message(f_path)
105 c.file_msg = cs.get_file_message(f_path)
105 c.cur_rev = cs.raw_id
106 c.cur_rev = cs.raw_id
106 c.f_path = f_path
107 c.f_path = f_path
107 c.annotate = cs.get_file_annotate(f_path)
108 c.annotate = cs.get_file_annotate(f_path)
108 return render('files/files_annotate.html')
109 return render('files/files_annotate.html')
109
110
110 def archivefile(self, repo_name, revision, fileformat):
111 def archivefile(self, repo_name, revision, fileformat):
111 archive_specs = {
112 archive_specs = {
112 '.tar.bz2': ('application/x-tar', 'tbz2'),
113 '.tar.bz2': ('application/x-tar', 'tbz2'),
113 '.tar.gz': ('application/x-tar', 'tgz'),
114 '.tar.gz': ('application/x-tar', 'tgz'),
114 '.zip': ('application/zip', 'zip'),
115 '.zip': ('application/zip', 'zip'),
115 }
116 }
116 if not archive_specs.has_key(fileformat):
117 if not archive_specs.has_key(fileformat):
117 return 'Unknown archive type %s' % fileformat
118 return 'Unknown archive type %s' % fileformat
118
119
119 def read_in_chunks(file_object, chunk_size=1024 * 40):
120 def read_in_chunks(file_object, chunk_size=1024 * 40):
120 """Lazy function (generator) to read a file piece by piece.
121 """Lazy function (generator) to read a file piece by piece.
121 Default chunk size: 40k."""
122 Default chunk size: 40k."""
122 while True:
123 while True:
123 data = file_object.read(chunk_size)
124 data = file_object.read(chunk_size)
124 if not data:
125 if not data:
125 break
126 break
126 yield data
127 yield data
127
128
128 archive = tempfile.TemporaryFile()
129 archive = tempfile.TemporaryFile()
129 repo = HgModel().get_repo(repo_name).repo
130 repo = HgModel().get_repo(repo_name).repo
130 fname = '%s-%s%s' % (repo_name, revision, fileformat)
131 fname = '%s-%s%s' % (repo_name, revision, fileformat)
131 archival.archive(repo, archive, revision, archive_specs[fileformat][1],
132 archival.archive(repo, archive, revision, archive_specs[fileformat][1],
132 prefix='%s-%s' % (repo_name, revision))
133 prefix='%s-%s' % (repo_name, revision))
133 response.content_type = archive_specs[fileformat][0]
134 response.content_type = archive_specs[fileformat][0]
134 response.content_disposition = 'attachment; filename=%s' % fname
135 response.content_disposition = 'attachment; filename=%s' % fname
135 archive.seek(0)
136 archive.seek(0)
136 return read_in_chunks(archive)
137 return read_in_chunks(archive)
137
138
138 def diff(self, repo_name, f_path):
139 def diff(self, repo_name, f_path):
139 hg_model = HgModel()
140 hg_model = HgModel()
140 diff1 = request.GET.get('diff1')
141 diff1 = request.GET.get('diff1')
141 diff2 = request.GET.get('diff2')
142 diff2 = request.GET.get('diff2')
142 c.action = action = request.GET.get('diff')
143 c.action = action = request.GET.get('diff')
143 c.no_changes = diff1 == diff2
144 c.no_changes = diff1 == diff2
144 c.f_path = f_path
145 c.f_path = f_path
145 c.repo = hg_model.get_repo(c.repo_name)
146 c.repo = hg_model.get_repo(c.repo_name)
146
147
147 try:
148 try:
148 if diff1 not in ['', None, 'None', '0' * 12]:
149 if diff1 not in ['', None, 'None', '0' * 12]:
149 c.changeset_1 = c.repo.get_changeset(diff1)
150 c.changeset_1 = c.repo.get_changeset(diff1)
150 node1 = c.changeset_1.get_node(f_path)
151 node1 = c.changeset_1.get_node(f_path)
151 else:
152 else:
152 c.changeset_1 = EmptyChangeset()
153 c.changeset_1 = EmptyChangeset()
153 node1 = FileNode('.', '')
154 node1 = FileNode('.', '')
154 if diff2 not in ['', None, 'None', '0' * 12]:
155 if diff2 not in ['', None, 'None', '0' * 12]:
155 c.changeset_2 = c.repo.get_changeset(diff2)
156 c.changeset_2 = c.repo.get_changeset(diff2)
156 node2 = c.changeset_2.get_node(f_path)
157 node2 = c.changeset_2.get_node(f_path)
157 else:
158 else:
158 c.changeset_2 = EmptyChangeset()
159 c.changeset_2 = EmptyChangeset()
159 node2 = FileNode('.', '')
160 node2 = FileNode('.', '')
160 except RepositoryError:
161 except RepositoryError:
161 return redirect(url('files_home',
162 return redirect(url('files_home',
162 repo_name=c.repo_name, f_path=f_path))
163 repo_name=c.repo_name, f_path=f_path))
163
164
164 c.diff1 = 'r%s:%s' % (c.changeset_1.revision, c.changeset_1.raw_id)
165 c.diff1 = 'r%s:%s' % (c.changeset_1.revision, c.changeset_1.raw_id)
165 c.diff2 = 'r%s:%s' % (c.changeset_2.revision, c.changeset_2.raw_id)
166 c.diff2 = 'r%s:%s' % (c.changeset_2.revision, c.changeset_2.raw_id)
166 f_udiff = differ.get_udiff(node1, node2)
167 f_udiff = differ.get_udiff(node1, node2)
167
168
168 diff = differ.DiffProcessor(f_udiff)
169 diff = differ.DiffProcessor(f_udiff)
169
170
170 if action == 'download':
171 if action == 'download':
171 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
172 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
172 response.content_type = 'text/plain'
173 response.content_type = 'text/plain'
173 response.content_disposition = 'attachment; filename=%s' \
174 response.content_disposition = 'attachment; filename=%s' \
174 % diff_name
175 % diff_name
175 return diff.raw_diff()
176 return diff.raw_diff()
176
177
177 elif action == 'raw':
178 elif action == 'raw':
178 c.cur_diff = '<pre class="raw">%s</pre>' % h.escape(diff.raw_diff())
179 c.cur_diff = '<pre class="raw">%s</pre>' % h.escape(diff.raw_diff())
179 elif action == 'diff':
180 elif action == 'diff':
180 c.cur_diff = diff.as_html()
181 c.cur_diff = diff.as_html()
181
182
182 return render('files/file_diff.html')
183 return render('files/file_diff.html')
183
184
184 def _get_history(self, repo, node, f_path):
185 def _get_history(self, repo, node, f_path):
185 from vcs.nodes import NodeKind
186 from vcs.nodes import NodeKind
186 if not node.kind is NodeKind.FILE:
187 if not node.kind is NodeKind.FILE:
187 return []
188 return []
188 changesets = node.history
189 changesets = node.history
189 hist_l = []
190 hist_l = []
190 for chs in changesets:
191 for chs in changesets:
191 n_desc = 'r%s:%s' % (chs.revision, chs._short)
192 n_desc = 'r%s:%s' % (chs.revision, chs._short)
192 hist_l.append((chs._short, n_desc,))
193 hist_l.append((chs._short, n_desc,))
193 return hist_l
194 return hist_l
@@ -1,90 +1,90 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # permissions controller for pylons
3 # permissions controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
5
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
20 """
20 """
21 Created on April 27, 2010
21 Created on April 27, 2010
22 permissions controller for pylons
22 permissions controller for pylons
23 @author: marcink
23 @author: marcink
24 """
24 """
25 from formencode import htmlfill
25 from formencode import htmlfill
26 from pylons import request, session, tmpl_context as c, url
26 from pylons import request, session, tmpl_context as c, url
27 from pylons.controllers.util import abort, redirect
27 from pylons.controllers.util import abort, redirect
28 from pylons.i18n.translation import _
28 from pylons.i18n.translation import _
29 from pylons_app.lib import helpers as h
29 from pylons_app.lib import helpers as h
30 from pylons_app.lib.auth import LoginRequired, HasPermissionAllDecorator
30 from pylons_app.lib.auth import LoginRequired, HasPermissionAllDecorator
31 from pylons_app.lib.base import BaseController, render
31 from pylons_app.lib.base import BaseController, render
32 from pylons_app.model.db import User, UserLog
32 from pylons_app.model.db import User, UserLog
33 from pylons_app.model.forms import UserForm
33 from pylons_app.model.forms import UserForm
34 from pylons_app.model.user_model import UserModel
34 from pylons_app.model.user_model import UserModel
35 import formencode
35 import formencode
36 import logging
36 import logging
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40 class PermissionsController(BaseController):
40 class PermissionsController(BaseController):
41 """REST Controller styled on the Atom Publishing Protocol"""
41 """REST Controller styled on the Atom Publishing Protocol"""
42 # To properly map this controller, ensure your config/routing.py
42 # To properly map this controller, ensure your config/routing.py
43 # file has a resource setup:
43 # file has a resource setup:
44 # map.resource('permission', 'permissions')
44 # map.resource('permission', 'permissions')
45
45
46 @LoginRequired()
46 @LoginRequired()
47 @HasPermissionAllDecorator('hg.admin')
47 #@HasPermissionAllDecorator('hg.admin')
48 def __before__(self):
48 def __before__(self):
49 c.admin_user = session.get('admin_user')
49 c.admin_user = session.get('admin_user')
50 c.admin_username = session.get('admin_username')
50 c.admin_username = session.get('admin_username')
51 super(PermissionsController, self).__before__()
51 super(PermissionsController, self).__before__()
52
52
53 def index(self, format='html'):
53 def index(self, format='html'):
54 """GET /permissions: All items in the collection"""
54 """GET /permissions: All items in the collection"""
55 # url('permissions')
55 # url('permissions')
56 return render('admin/permissions/permissions.html')
56 return render('admin/permissions/permissions.html')
57
57
58 def create(self):
58 def create(self):
59 """POST /permissions: Create a new item"""
59 """POST /permissions: Create a new item"""
60 # url('permissions')
60 # url('permissions')
61
61
62 def new(self, format='html'):
62 def new(self, format='html'):
63 """GET /permissions/new: Form to create a new item"""
63 """GET /permissions/new: Form to create a new item"""
64 # url('new_permission')
64 # url('new_permission')
65
65
66 def update(self, id):
66 def update(self, id):
67 """PUT /permissions/id: Update an existing item"""
67 """PUT /permissions/id: Update an existing item"""
68 # Forms posted to this method should contain a hidden field:
68 # Forms posted to this method should contain a hidden field:
69 # <input type="hidden" name="_method" value="PUT" />
69 # <input type="hidden" name="_method" value="PUT" />
70 # Or using helpers:
70 # Or using helpers:
71 # h.form(url('permission', id=ID),
71 # h.form(url('permission', id=ID),
72 # method='put')
72 # method='put')
73 # url('permission', id=ID)
73 # url('permission', id=ID)
74
74
75 def delete(self, id):
75 def delete(self, id):
76 """DELETE /permissions/id: Delete an existing item"""
76 """DELETE /permissions/id: Delete an existing item"""
77 # Forms posted to this method should contain a hidden field:
77 # Forms posted to this method should contain a hidden field:
78 # <input type="hidden" name="_method" value="DELETE" />
78 # <input type="hidden" name="_method" value="DELETE" />
79 # Or using helpers:
79 # Or using helpers:
80 # h.form(url('permission', id=ID),
80 # h.form(url('permission', id=ID),
81 # method='delete')
81 # method='delete')
82 # url('permission', id=ID)
82 # url('permission', id=ID)
83
83
84 def show(self, id, format='html'):
84 def show(self, id, format='html'):
85 """GET /permissions/id: Show a specific item"""
85 """GET /permissions/id: Show a specific item"""
86 # url('permission', id=ID)
86 # url('permission', id=ID)
87
87
88 def edit(self, id, format='html'):
88 def edit(self, id, format='html'):
89 """GET /permissions/id/edit: Form to edit an existing item"""
89 """GET /permissions/id/edit: Form to edit an existing item"""
90 # url('edit_permission', id=ID)
90 # url('edit_permission', id=ID)
@@ -1,194 +1,197 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # repos controller for pylons
3 # repos controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 # This program is free software; you can redistribute it and/or
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; version 2
7 # as published by the Free Software Foundation; version 2
8 # of the License or (at your opinion) any later version of the license.
8 # of the License or (at your opinion) any later version of the license.
9 #
9 #
10 # This program is distributed in the hope that it will be useful,
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
13 # GNU General Public License for more details.
14 #
14 #
15 # You should have received a copy of the GNU General Public License
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # MA 02110-1301, USA.
18 # MA 02110-1301, USA.
19 """
19 """
20 Created on April 7, 2010
20 Created on April 7, 2010
21 admin controller for pylons
21 admin controller for pylons
22 @author: marcink
22 @author: marcink
23 """
23 """
24 from formencode import htmlfill
24 from operator import itemgetter
25 from operator import itemgetter
25 from pylons import request, response, session, tmpl_context as c, url, \
26 from pylons import request, response, session, tmpl_context as c, url
26 app_globals as g
27 from pylons.controllers.util import abort, redirect
27 from pylons.controllers.util import abort, redirect
28 from pylons.i18n.translation import _
28 from pylons.i18n.translation import _
29 from pylons_app.lib import helpers as h
29 from pylons_app.lib import helpers as h
30 from pylons_app.lib.auth import LoginRequired
30 from pylons_app.lib.auth import LoginRequired, HasPermissionAllDecorator
31 from pylons_app.lib.base import BaseController, render
31 from pylons_app.lib.base import BaseController, render
32 from pylons_app.lib.utils import invalidate_cache
32 from pylons_app.lib.utils import invalidate_cache
33 from pylons_app.model.repo_model import RepoModel
33 from pylons_app.model.forms import RepoForm
34 from pylons_app.model.hg_model import HgModel
34 from pylons_app.model.hg_model import HgModel
35 from pylons_app.model.forms import RepoForm
35 from pylons_app.model.repo_model import RepoModel
36 from pylons_app.model.meta import Session
37 import formencode
36 import formencode
38 from formencode import htmlfill
39 import logging
37 import logging
40 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
41
39
42 class ReposController(BaseController):
40 class ReposController(BaseController):
43 """REST Controller styled on the Atom Publishing Protocol"""
41 """REST Controller styled on the Atom Publishing Protocol"""
44 # To properly map this controller, ensure your config/routing.py
42 # To properly map this controller, ensure your config/routing.py
45 # file has a resource setup:
43 # file has a resource setup:
46 # map.resource('repo', 'repos')
44 # map.resource('repo', 'repos')
45
47 @LoginRequired()
46 @LoginRequired()
47 @HasPermissionAllDecorator('hg.admin')
48 def __before__(self):
48 def __before__(self):
49 c.admin_user = session.get('admin_user')
49 c.admin_user = session.get('admin_user')
50 c.admin_username = session.get('admin_username')
50 c.admin_username = session.get('admin_username')
51 super(ReposController, self).__before__()
51 super(ReposController, self).__before__()
52
52
53 def index(self, format='html'):
53 def index(self, format='html'):
54 """GET /repos: All items in the collection"""
54 """GET /repos: All items in the collection"""
55 # url('repos')
55 # url('repos')
56 cached_repo_list = HgModel().get_repos()
56 cached_repo_list = HgModel().get_repos()
57 c.repos_list = sorted(cached_repo_list, key=itemgetter('name_sort'))
57 c.repos_list = sorted(cached_repo_list, key=itemgetter('name_sort'))
58 return render('admin/repos/repos.html')
58 return render('admin/repos/repos.html')
59
59
60 def create(self):
60 def create(self):
61 """POST /repos: Create a new item"""
61 """POST /repos: Create a new item"""
62 # url('repos')
62 # url('repos')
63 repo_model = RepoModel()
63 repo_model = RepoModel()
64 _form = RepoForm()()
64 _form = RepoForm()()
65 try:
65 try:
66 form_result = _form.to_python(dict(request.POST))
66 form_result = _form.to_python(dict(request.POST))
67 repo_model.create(form_result, c.hg_app_user)
67 repo_model.create(form_result, c.hg_app_user)
68 invalidate_cache('cached_repo_list')
68 invalidate_cache('cached_repo_list')
69 h.flash(_('created repository %s') % form_result['repo_name'],
69 h.flash(_('created repository %s') % form_result['repo_name'],
70 category='success')
70 category='success')
71
71
72 except formencode.Invalid as errors:
72 except formencode.Invalid as errors:
73 c.form_errors = errors.error_dict
73 c.form_errors = errors.error_dict
74 c.new_repo = errors.value['repo_name']
74 c.new_repo = errors.value['repo_name']
75 return htmlfill.render(
75 return htmlfill.render(
76 render('admin/repos/repo_add.html'),
76 render('admin/repos/repo_add.html'),
77 defaults=errors.value,
77 defaults=errors.value,
78 encoding="UTF-8")
78 encoding="UTF-8")
79
79
80 except Exception:
80 except Exception:
81 h.flash(_('error occured during creation of repository %s') \
81 h.flash(_('error occured during creation of repository %s') \
82 % form_result['repo_name'], category='error')
82 % form_result['repo_name'], category='error')
83
83
84 return redirect('repos')
84 return redirect('repos')
85
85
86 def new(self, format='html'):
86 def new(self, format='html'):
87 """GET /repos/new: Form to create a new item"""
87 """GET /repos/new: Form to create a new item"""
88 new_repo = request.GET.get('repo', '')
88 new_repo = request.GET.get('repo', '')
89 c.new_repo = h.repo_name_slug(new_repo)
89 c.new_repo = h.repo_name_slug(new_repo)
90
90
91 return render('admin/repos/repo_add.html')
91 return render('admin/repos/repo_add.html')
92
92
93 def update(self, repo_name):
93 def update(self, repo_name):
94 """PUT /repos/repo_name: Update an existing item"""
94 """PUT /repos/repo_name: Update an existing item"""
95 # Forms posted to this method should contain a hidden field:
95 # Forms posted to this method should contain a hidden field:
96 # <input type="hidden" name="_method" value="PUT" />
96 # <input type="hidden" name="_method" value="PUT" />
97 # Or using helpers:
97 # Or using helpers:
98 # h.form(url('repo', repo_name=ID),
98 # h.form(url('repo', repo_name=ID),
99 # method='put')
99 # method='put')
100 # url('repo', repo_name=ID)
100 # url('repo', repo_name=ID)
101 repo_model = RepoModel()
101 repo_model = RepoModel()
102 _form = RepoForm(edit=True)()
102 _form = RepoForm(edit=True)()
103 try:
103 try:
104 form_result = _form.to_python(dict(request.POST))
104 form_result = _form.to_python(dict(request.POST))
105 repo_model.update(repo_name, form_result)
105 repo_model.update(repo_name, form_result)
106 invalidate_cache('cached_repo_list')
106 invalidate_cache('cached_repo_list')
107 h.flash(_('Repository %s updated succesfully' % repo_name), category='success')
107 h.flash(_('Repository %s updated succesfully' % repo_name),
108 category='success')
108
109
109 except formencode.Invalid as errors:
110 except formencode.Invalid as errors:
110 c.repo_info = repo_model.get(repo_name)
111 c.repo_info = repo_model.get(repo_name)
111 c.users_array = repo_model.get_users_js()
112 c.users_array = repo_model.get_users_js()
112 errors.value.update({'user':c.repo_info.user.username})
113 errors.value.update({'user':c.repo_info.user.username})
113 c.form_errors = errors.error_dict
114 c.form_errors = errors.error_dict
114 return htmlfill.render(
115 return htmlfill.render(
115 render('admin/repos/repo_edit.html'),
116 render('admin/repos/repo_edit.html'),
116 defaults=errors.value,
117 defaults=errors.value,
117 encoding="UTF-8")
118 encoding="UTF-8")
118 except Exception:
119 except Exception:
119 h.flash(_('error occured during update of repository %s') \
120 h.flash(_('error occured during update of repository %s') \
120 % form_result['repo_name'], category='error')
121 % form_result['repo_name'], category='error')
121 return redirect(url('repos'))
122 return redirect(url('repos'))
122
123
123 def delete(self, repo_name):
124 def delete(self, repo_name):
124 """DELETE /repos/repo_name: Delete an existing item"""
125 """DELETE /repos/repo_name: Delete an existing item"""
125 # Forms posted to this method should contain a hidden field:
126 # Forms posted to this method should contain a hidden field:
126 # <input type="hidden" name="_method" value="DELETE" />
127 # <input type="hidden" name="_method" value="DELETE" />
127 # Or using helpers:
128 # Or using helpers:
128 # h.form(url('repo', repo_name=ID),
129 # h.form(url('repo', repo_name=ID),
129 # method='delete')
130 # method='delete')
130 # url('repo', repo_name=ID)
131 # url('repo', repo_name=ID)
131
132
132 repo_model = RepoModel()
133 repo_model = RepoModel()
133 repo = repo_model.get(repo_name)
134 repo = repo_model.get(repo_name)
134 if not repo:
135 if not repo:
135 h.flash(_('%s repository is not mapped to db perhaps'
136 h.flash(_('%s repository is not mapped to db perhaps'
136 ' it was moved or renamed from the filesystem'
137 ' it was moved or renamed from the filesystem'
137 ' please run the application again'
138 ' please run the application again'
138 ' in order to rescan repositories') % repo_name, category='error')
139 ' in order to rescan repositories') % repo_name,
140 category='error')
139
141
140 return redirect(url('repos'))
142 return redirect(url('repos'))
141 try:
143 try:
142 repo_model.delete(repo)
144 repo_model.delete(repo)
143 invalidate_cache('cached_repo_list')
145 invalidate_cache('cached_repo_list')
144 h.flash(_('deleted repository %s') % repo_name, category='success')
146 h.flash(_('deleted repository %s') % repo_name, category='success')
145 except Exception:
147 except Exception:
146 h.flash(_('An error occured during deletion of %s') % repo_name,
148 h.flash(_('An error occured during deletion of %s') % repo_name,
147 category='error')
149 category='error')
148
150
149 return redirect(url('repos'))
151 return redirect(url('repos'))
150
152
151 def delete_perm_user(self, repo_name):
153 def delete_perm_user(self, repo_name):
152 """
154 """
153 DELETE an existing repository permission user
155 DELETE an existing repository permission user
154 @param repo_name:
156 @param repo_name:
155 """
157 """
156
158
157 try:
159 try:
158 repo_model = RepoModel()
160 repo_model = RepoModel()
159 repo_model.delete_perm_user(request.POST, repo_name)
161 repo_model.delete_perm_user(request.POST, repo_name)
160 except Exception as e:
162 except Exception as e:
161 h.flash(_('An error occured during deletion of repository user'),
163 h.flash(_('An error occured during deletion of repository user'),
162 category='error')
164 category='error')
163
165
164
166
165 def show(self, repo_name, format='html'):
167 def show(self, repo_name, format='html'):
166 """GET /repos/repo_name: Show a specific item"""
168 """GET /repos/repo_name: Show a specific item"""
167 # url('repo', repo_name=ID)
169 # url('repo', repo_name=ID)
168
170
169 def edit(self, repo_name, format='html'):
171 def edit(self, repo_name, format='html'):
170 """GET /repos/repo_name/edit: Form to edit an existing item"""
172 """GET /repos/repo_name/edit: Form to edit an existing item"""
171 # url('edit_repo', repo_name=ID)
173 # url('edit_repo', repo_name=ID)
172 repo_model = RepoModel()
174 repo_model = RepoModel()
173 c.repo_info = repo = repo_model.get(repo_name)
175 c.repo_info = repo = repo_model.get(repo_name)
174 if not repo:
176 if not repo:
175 h.flash(_('%s repository is not mapped to db perhaps'
177 h.flash(_('%s repository is not mapped to db perhaps'
176 ' it was created or renamed from the filesystem'
178 ' it was created or renamed from the filesystem'
177 ' please run the application again'
179 ' please run the application again'
178 ' in order to rescan repositories') % repo_name, category='error')
180 ' in order to rescan repositories') % repo_name,
181 category='error')
179
182
180 return redirect(url('repos'))
183 return redirect(url('repos'))
181 defaults = c.repo_info.__dict__
184 defaults = c.repo_info.__dict__
182 defaults.update({'user':c.repo_info.user.username})
185 defaults.update({'user':c.repo_info.user.username})
183 c.users_array = repo_model.get_users_js()
186 c.users_array = repo_model.get_users_js()
184
187
185 for p in c.repo_info.repo2perm:
188 for p in c.repo_info.repo2perm:
186 defaults.update({'perm_%s' % p.user.username:
189 defaults.update({'perm_%s' % p.user.username:
187 p.permission.permission_name})
190 p.permission.permission_name})
188
191
189 return htmlfill.render(
192 return htmlfill.render(
190 render('admin/repos/repo_edit.html'),
193 render('admin/repos/repo_edit.html'),
191 defaults=defaults,
194 defaults=defaults,
192 encoding="UTF-8",
195 encoding="UTF-8",
193 force_defaults=False
196 force_defaults=False
194 )
197 )
@@ -1,48 +1,49 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # shortlog controller for pylons
3 # shortlog controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
5
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
20 """
20 """
21 Created on April 18, 2010
21 Created on April 18, 2010
22 shortlog controller for pylons
22 shortlog controller for pylons
23 @author: marcink
23 @author: marcink
24 """
24 """
25 from pylons import tmpl_context as c, request
25 from pylons import tmpl_context as c, request
26 from pylons_app.lib.auth import LoginRequired
26 from pylons_app.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
27 from pylons_app.lib.base import BaseController, render
27 from pylons_app.lib.base import BaseController, render
28 from pylons_app.model.hg_model import HgModel
28 from pylons_app.model.hg_model import HgModel
29 from webhelpers.paginate import Page
29 from webhelpers.paginate import Page
30 import logging
30 import logging
31
32 log = logging.getLogger(__name__)
31 log = logging.getLogger(__name__)
33
32
34 class ShortlogController(BaseController):
33 class ShortlogController(BaseController):
35
34
36 @LoginRequired()
35 @LoginRequired()
36 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
37 'repository.admin')
37 def __before__(self):
38 def __before__(self):
38 super(ShortlogController, self).__before__()
39 super(ShortlogController, self).__before__()
39
40
40 def index(self):
41 def index(self):
41 p = int(request.params.get('page', 1))
42 p = int(request.params.get('page', 1))
42 repo = HgModel().get_repo(c.repo_name)
43 repo = HgModel().get_repo(c.repo_name)
43 c.repo_changesets = Page(repo, page=p, items_per_page=20)
44 c.repo_changesets = Page(repo, page=p, items_per_page=20)
44 c.shortlog_data = render('shortlog/shortlog_data.html')
45 c.shortlog_data = render('shortlog/shortlog_data.html')
45 if request.params.get('partial'):
46 if request.params.get('partial'):
46 return c.shortlog_data
47 return c.shortlog_data
47 r = render('shortlog/shortlog.html')
48 r = render('shortlog/shortlog.html')
48 return r
49 return r
@@ -1,59 +1,60 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # summary controller for pylons
3 # summary controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
5
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
20 """
20 """
21 Created on April 18, 2010
21 Created on April 18, 2010
22 summary controller for pylons
22 summary controller for pylons
23 @author: marcink
23 @author: marcink
24 """
24 """
25 from pylons import tmpl_context as c, request
25 from pylons import tmpl_context as c, request
26 from pylons_app.lib.auth import LoginRequired
26 from pylons_app.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
27 from pylons_app.lib.base import BaseController, render
27 from pylons_app.lib.base import BaseController, render
28 from pylons_app.model.hg_model import HgModel
28 from pylons_app.model.hg_model import HgModel
29 from webhelpers.paginate import Page
29 from webhelpers.paginate import Page
30 import logging
30 import logging
31
32 log = logging.getLogger(__name__)
31 log = logging.getLogger(__name__)
33
32
34 class SummaryController(BaseController):
33 class SummaryController(BaseController):
35
34
36 @LoginRequired()
35 @LoginRequired()
36 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
37 'repository.admin')
37 def __before__(self):
38 def __before__(self):
38 super(SummaryController, self).__before__()
39 super(SummaryController, self).__before__()
39
40
40 def index(self):
41 def index(self):
41 hg_model = HgModel()
42 hg_model = HgModel()
42 c.repo_info = hg_model.get_repo(c.repo_name)
43 c.repo_info = hg_model.get_repo(c.repo_name)
43 c.repo_changesets = Page(list(c.repo_info[:10]), page=1, items_per_page=20)
44 c.repo_changesets = Page(list(c.repo_info[:10]), page=1, items_per_page=20)
44 e = request.environ
45 e = request.environ
45 uri = u'%(protocol)s://%(user)s@%(host)s/%(repo_name)s' % {
46 uri = u'%(protocol)s://%(user)s@%(host)s/%(repo_name)s' % {
46 'protocol': e.get('wsgi.url_scheme'),
47 'protocol': e.get('wsgi.url_scheme'),
47 'user':str(c.hg_app_user.username),
48 'user':str(c.hg_app_user.username),
48 'host':e.get('HTTP_HOST'),
49 'host':e.get('HTTP_HOST'),
49 'repo_name':c.repo_name, }
50 'repo_name':c.repo_name, }
50 c.clone_repo_url = uri
51 c.clone_repo_url = uri
51 c.repo_tags = {}
52 c.repo_tags = {}
52 for name, hash in c.repo_info.tags.items()[:10]:
53 for name, hash in c.repo_info.tags.items()[:10]:
53 c.repo_tags[name] = c.repo_info.get_changeset(hash)
54 c.repo_tags[name] = c.repo_info.get_changeset(hash)
54
55
55 c.repo_branches = {}
56 c.repo_branches = {}
56 for name, hash in c.repo_info.branches.items()[:10]:
57 for name, hash in c.repo_info.branches.items()[:10]:
57 c.repo_branches[name] = c.repo_info.get_changeset(hash)
58 c.repo_branches[name] = c.repo_info.get_changeset(hash)
58
59
59 return render('summary/summary.html')
60 return render('summary/summary.html')
@@ -1,46 +1,46 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # tags controller for pylons
3 # tags controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
5
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
20 """
20 """
21 Created on April 21, 2010
21 Created on April 21, 2010
22 tags controller for pylons
22 tags controller for pylons
23 @author: marcink
23 @author: marcink
24 """
24 """
25 from pylons import tmpl_context as c
25 from pylons import tmpl_context as c, request
26 from pylons_app.lib.auth import LoginRequired
26 from pylons_app.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
27 from pylons_app.lib.base import BaseController, render
27 from pylons_app.lib.base import BaseController, render
28 from pylons_app.model.hg_model import HgModel
28 from pylons_app.model.hg_model import HgModel
29 import logging
29 import logging
30
31 log = logging.getLogger(__name__)
30 log = logging.getLogger(__name__)
32
31
33 class TagsController(BaseController):
32 class TagsController(BaseController):
34
33
35 @LoginRequired()
34 @LoginRequired()
35 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', 'repository.admin')
36 def __before__(self):
36 def __before__(self):
37 super(TagsController, self).__before__()
37 super(TagsController, self).__before__()
38
38
39 def index(self):
39 def index(self):
40 hg_model = HgModel()
40 hg_model = HgModel()
41 c.repo_info = hg_model.get_repo(c.repo_name)
41 c.repo_info = hg_model.get_repo(c.repo_name)
42 c.repo_tags = {}
42 c.repo_tags = {}
43 for name, hash in c.repo_info.tags.items():
43 for name, hash in c.repo_info.tags.items():
44 c.repo_tags[name] = c.repo_info.get_changeset(hash)
44 c.repo_tags[name] = c.repo_info.get_changeset(hash)
45
45
46 return render('tags/tags.html')
46 return render('tags/tags.html')
@@ -1,149 +1,155 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # users controller for pylons
3 # users controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
5
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
20 """
20 """
21 Created on April 4, 2010
21 Created on April 4, 2010
22 users controller for pylons
22 users controller for pylons
23 @author: marcink
23 @author: marcink
24 """
24 """
25 from formencode import htmlfill
25 from formencode import htmlfill
26 from pylons import request, session, tmpl_context as c, url
26 from pylons import request, session, tmpl_context as c, url
27 from pylons.controllers.util import abort, redirect
27 from pylons.controllers.util import abort, redirect
28 from pylons.i18n.translation import _
28 from pylons.i18n.translation import _
29 from pylons_app.lib import helpers as h
29 from pylons_app.lib import helpers as h
30 from pylons_app.lib.auth import LoginRequired, HasPermissionAllDecorator
30 from pylons_app.lib.auth import LoginRequired, HasPermissionAllDecorator
31 from pylons_app.lib.base import BaseController, render
31 from pylons_app.lib.base import BaseController, render
32 from pylons_app.model.db import User, UserLog
32 from pylons_app.model.db import User, UserLog
33 from pylons_app.model.forms import UserForm
33 from pylons_app.model.forms import UserForm
34 from pylons_app.model.user_model import UserModel
34 from pylons_app.model.user_model import UserModel, DefaultUserException
35 import formencode
35 import formencode
36 import logging
36 import logging
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40 class UsersController(BaseController):
40 class UsersController(BaseController):
41 """REST Controller styled on the Atom Publishing Protocol"""
41 """REST Controller styled on the Atom Publishing Protocol"""
42 # To properly map this controller, ensure your config/routing.py
42 # To properly map this controller, ensure your config/routing.py
43 # file has a resource setup:
43 # file has a resource setup:
44 # map.resource('user', 'users')
44 # map.resource('user', 'users')
45
45
46 @LoginRequired()
46 @LoginRequired()
47 @HasPermissionAllDecorator('hg.admin')
47 @HasPermissionAllDecorator('hg.admin')
48 def __before__(self):
48 def __before__(self):
49 c.admin_user = session.get('admin_user')
49 c.admin_user = session.get('admin_user')
50 c.admin_username = session.get('admin_username')
50 c.admin_username = session.get('admin_username')
51 super(UsersController, self).__before__()
51 super(UsersController, self).__before__()
52
52
53
53
54 def index(self, format='html'):
54 def index(self, format='html'):
55 """GET /users: All items in the collection"""
55 """GET /users: All items in the collection"""
56 # url('users')
56 # url('users')
57
57
58 c.users_list = self.sa.query(User).all()
58 c.users_list = self.sa.query(User).all()
59 return render('admin/users/users.html')
59 return render('admin/users/users.html')
60
60
61 def create(self):
61 def create(self):
62 """POST /users: Create a new item"""
62 """POST /users: Create a new item"""
63 # url('users')
63 # url('users')
64
64
65 user_model = UserModel()
65 user_model = UserModel()
66 login_form = UserForm()()
66 login_form = UserForm()()
67 try:
67 try:
68 form_result = login_form.to_python(dict(request.POST))
68 form_result = login_form.to_python(dict(request.POST))
69 user_model.create(form_result)
69 user_model.create(form_result)
70 h.flash(_('created user %s') % form_result['username'],
70 h.flash(_('created user %s') % form_result['username'],
71 category='success')
71 category='success')
72 except formencode.Invalid as errors:
72 except formencode.Invalid as errors:
73 c.form_errors = errors.error_dict
73 c.form_errors = errors.error_dict
74 return htmlfill.render(
74 return htmlfill.render(
75 render('admin/users/user_add.html'),
75 render('admin/users/user_add.html'),
76 defaults=errors.value,
76 defaults=errors.value,
77 encoding="UTF-8")
77 encoding="UTF-8")
78 except Exception:
78 except Exception:
79 h.flash(_('error occured during creation of user %s') \
79 h.flash(_('error occured during creation of user %s') \
80 % form_result['username'], category='error')
80 % form_result['username'], category='error')
81 return redirect(url('users'))
81 return redirect(url('users'))
82
82
83 def new(self, format='html'):
83 def new(self, format='html'):
84 """GET /users/new: Form to create a new item"""
84 """GET /users/new: Form to create a new item"""
85 # url('new_user')
85 # url('new_user')
86 return render('admin/users/user_add.html')
86 return render('admin/users/user_add.html')
87
87
88 def update(self, id):
88 def update(self, id):
89 """PUT /users/id: Update an existing item"""
89 """PUT /users/id: Update an existing item"""
90 # Forms posted to this method should contain a hidden field:
90 # Forms posted to this method should contain a hidden field:
91 # <input type="hidden" name="_method" value="PUT" />
91 # <input type="hidden" name="_method" value="PUT" />
92 # Or using helpers:
92 # Or using helpers:
93 # h.form(url('user', id=ID),
93 # h.form(url('user', id=ID),
94 # method='put')
94 # method='put')
95 # url('user', id=ID)
95 # url('user', id=ID)
96 user_model = UserModel()
96 user_model = UserModel()
97 _form = UserForm(edit=True)()
97 _form = UserForm(edit=True)()
98 try:
98 try:
99 form_result = _form.to_python(dict(request.POST))
99 form_result = _form.to_python(dict(request.POST))
100 user_model.update(id, form_result)
100 user_model.update(id, form_result)
101 h.flash(_('User updated succesfully'), category='success')
101 h.flash(_('User updated succesfully'), category='success')
102
102
103 except formencode.Invalid as errors:
103 except formencode.Invalid as errors:
104 c.user = user_model.get_user(id)
104 c.user = user_model.get_user(id)
105 c.form_errors = errors.error_dict
105 c.form_errors = errors.error_dict
106 return htmlfill.render(
106 return htmlfill.render(
107 render('admin/users/user_edit.html'),
107 render('admin/users/user_edit.html'),
108 defaults=errors.value,
108 defaults=errors.value,
109 encoding="UTF-8")
109 encoding="UTF-8")
110 except Exception:
110 except Exception:
111 h.flash(_('error occured during update of user %s') \
111 h.flash(_('error occured during update of user %s') \
112 % form_result['username'], category='error')
112 % form_result['username'], category='error')
113
113
114 return redirect(url('users'))
114 return redirect(url('users'))
115
115
116 def delete(self, id):
116 def delete(self, id):
117 """DELETE /users/id: Delete an existing item"""
117 """DELETE /users/id: Delete an existing item"""
118 # Forms posted to this method should contain a hidden field:
118 # Forms posted to this method should contain a hidden field:
119 # <input type="hidden" name="_method" value="DELETE" />
119 # <input type="hidden" name="_method" value="DELETE" />
120 # Or using helpers:
120 # Or using helpers:
121 # h.form(url('user', id=ID),
121 # h.form(url('user', id=ID),
122 # method='delete')
122 # method='delete')
123 # url('user', id=ID)
123 # url('user', id=ID)
124 user_model = UserModel()
124 user_model = UserModel()
125 try:
125 try:
126 user_model.delete(id)
126 user_model.delete(id)
127 h.flash(_('sucessfully deleted user'), category='success')
127 h.flash(_('sucessfully deleted user'), category='success')
128 except DefaultUserException as e:
129 h.flash(str(e), category='warning')
128 except Exception:
130 except Exception:
129 h.flash(_('An error occured during deletion of user'),
131 h.flash(_('An error occured during deletion of user'),
130 category='error')
132 category='error')
131
132 return redirect(url('users'))
133 return redirect(url('users'))
133
134
134 def show(self, id, format='html'):
135 def show(self, id, format='html'):
135 """GET /users/id: Show a specific item"""
136 """GET /users/id: Show a specific item"""
136 # url('user', id=ID)
137 # url('user', id=ID)
137
138
138
139
139 def edit(self, id, format='html'):
140 def edit(self, id, format='html'):
140 """GET /users/id/edit: Form to edit an existing item"""
141 """GET /users/id/edit: Form to edit an existing item"""
141 # url('edit_user', id=ID)
142 # url('edit_user', id=ID)
142 c.user = self.sa.query(User).get(id)
143 c.user = self.sa.query(User).get(id)
144 if c.user.username == 'default':
145 h.flash(_("You can't edit this user since it's"
146 " crucial for entire application"), category='warning')
147 return redirect(url('users'))
148
143 defaults = c.user.__dict__
149 defaults = c.user.__dict__
144 return htmlfill.render(
150 return htmlfill.render(
145 render('admin/users/user_edit.html'),
151 render('admin/users/user_edit.html'),
146 defaults=defaults,
152 defaults=defaults,
147 encoding="UTF-8",
153 encoding="UTF-8",
148 force_defaults=False
154 force_defaults=False
149 )
155 )
@@ -1,159 +1,163 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3 <html xmlns="http://www.w3.org/1999/xhtml" id="mainhtml">
3 <html xmlns="http://www.w3.org/1999/xhtml" id="mainhtml">
4 <head>
4 <head>
5 <link rel="icon" href="/images/hgicon.png" type="image/png" />
5 <link rel="icon" href="/images/hgicon.png" type="image/png" />
6 <meta name="robots" content="index, nofollow"/>
6 <meta name="robots" content="index, nofollow"/>
7 <title>${next.title()}</title>
7 <title>${next.title()}</title>
8 ##For future use yui reset for cross browser compatability.
8 ##For future use yui reset for cross browser compatability.
9 ##<link rel="stylesheet" href="/js/yui/reset-fonts-grids/reset-fonts-grids.css" type="text/css" />
9 ##<link rel="stylesheet" href="/js/yui/reset-fonts-grids/reset-fonts-grids.css" type="text/css" />
10 ${self.css()}
10 ${self.css()}
11 ${self.js()}
11 ${self.js()}
12 </head>
12 </head>
13
13
14 <body class="mainbody">
14 <body class="mainbody">
15 <div id="container">
15 <div id="container">
16 <div class="page-header">
16 <div class="page-header">
17 <h1 class="breadcrumbs">${next.breadcrumbs()}</h1>
17 <h1 class="breadcrumbs">${next.breadcrumbs()}</h1>
18 ${self.page_nav()}
18 ${self.page_nav()}
19 <div class="flash_msg">
19 <div class="flash_msg">
20 <% messages = h.flash.pop_messages() %>
20 <% messages = h.flash.pop_messages() %>
21 % if messages:
21 % if messages:
22 <ul id="flash-messages">
22 <ul id="flash-messages">
23 % for message in messages:
23 % for message in messages:
24 <li class="${message.category}_msg">${message}</li>
24 <li class="${message.category}_msg">${message}</li>
25 % endfor
25 % endfor
26 </ul>
26 </ul>
27 % endif
27 % endif
28 </div>
28 </div>
29 <div id="main">
29 <div id="main">
30 ${next.main()}
30 ${next.main()}
31 <script type="text/javascript">${h.tooltip.activate()}</script>
31 <script type="text/javascript">${h.tooltip.activate()}</script>
32 </div>
32 </div>
33 <div class="page-footer">
33 <div class="page-footer">
34 Hg App ${c.hg_app_version} &copy; 2010 by Marcin Kuzminski
34 Hg App ${c.hg_app_version} &copy; 2010 by Marcin Kuzminski
35 </div>
35 </div>
36
36
37 <div id="powered-by">
37 <div id="powered-by">
38 <p>
38 <p>
39 <a href="http://mercurial.selenic.com/" title="Mercurial">
39 <a href="http://mercurial.selenic.com/" title="Mercurial">
40 <img src="/images/hglogo.png" width="75" height="90" alt="mercurial"/></a>
40 <img src="/images/hglogo.png" width="75" height="90" alt="mercurial"/></a>
41 </p>
41 </p>
42 </div>
42 </div>
43
43
44 <div id="corner-top-left"></div>
44 <div id="corner-top-left"></div>
45 <div id="corner-top-right"></div>
45 <div id="corner-top-right"></div>
46 <div id="corner-bottom-left"></div>
46 <div id="corner-bottom-left"></div>
47 <div id="corner-bottom-right"></div>
47 <div id="corner-bottom-right"></div>
48
48
49 </div>
49 </div>
50 </body>
50 </body>
51 </html>
51 </html>
52
52
53 ### MAKO DEFS ###
53 ### MAKO DEFS ###
54
54
55 <%def name="page_nav()">
55 <%def name="page_nav()">
56 ${self.menu()}
56 ${self.menu()}
57 ${self.submenu()}
57 ${self.submenu()}
58 </%def>
58 </%def>
59
59
60 <%def name="menu(current)">
60 <%def name="menu(current)">
61 <%
61 <%
62 def is_current(selected):
62 def is_current(selected):
63 if selected == current:
63 if selected == current:
64 return "class='current'"
64 return "class='current'"
65 %>
65 %>
66 %if current not in ['home','admin']:
66 %if current not in ['home','admin']:
67 ##regular menu
67 ##regular menu
68 <script type="text/javascript">
68 <script type="text/javascript">
69 YAHOO.util.Event.onDOMReady(function(){
69 YAHOO.util.Event.onDOMReady(function(){
70 YAHOO.util.Event.addListener('repo_switcher','click',function(){
70 YAHOO.util.Event.addListener('repo_switcher','click',function(){
71 if(YAHOO.util.Dom.hasClass('repo_switcher','selected')){
71 if(YAHOO.util.Dom.hasClass('repo_switcher','selected')){
72 YAHOO.util.Dom.setStyle('switch_repos','display','none');
72 YAHOO.util.Dom.setStyle('switch_repos','display','none');
73 YAHOO.util.Dom.setStyle('repo_switcher','background','');
73 YAHOO.util.Dom.setStyle('repo_switcher','background','');
74 YAHOO.util.Dom.removeClass('repo_switcher','selected');
74 YAHOO.util.Dom.removeClass('repo_switcher','selected');
75 YAHOO.util.Dom.get('repo_switcher').removeAttribute('style');
75 YAHOO.util.Dom.get('repo_switcher').removeAttribute('style');
76 }
76 }
77 else{
77 else{
78 YAHOO.util.Dom.setStyle('switch_repos','display','');
78 YAHOO.util.Dom.setStyle('switch_repos','display','');
79 YAHOO.util.Dom.setStyle('repo_switcher','background','#FFFFFF');
79 YAHOO.util.Dom.setStyle('repo_switcher','background','#FFFFFF');
80 YAHOO.util.Dom.setStyle('repo_switcher','color','#556CB5');
80 YAHOO.util.Dom.setStyle('repo_switcher','color','#556CB5');
81 YAHOO.util.Dom.addClass('repo_switcher','selected');
81 YAHOO.util.Dom.addClass('repo_switcher','selected');
82 }
82 }
83 });
83 });
84 YAHOO.util.Event.addListener('repos_list','change',function(e){
84 YAHOO.util.Event.addListener('repos_list','change',function(e){
85 var wa = YAHOO.util.Dom.get('repos_list').value;
85 var wa = YAHOO.util.Dom.get('repos_list').value;
86
86
87 var url = "${h.url('summary_home',repo_name='__REPLACE__')}".replace('__REPLACE__',wa);
87 var url = "${h.url('summary_home',repo_name='__REPLACE__')}".replace('__REPLACE__',wa);
88 window.location = url;
88 window.location = url;
89 })
89 })
90 });
90 });
91 </script>
91 </script>
92 <ul class="page-nav">
92 <ul class="page-nav">
93 <li>
93 <li>
94 <a id="repo_switcher" title="${_('Switch repository')}" href="#">&darr;</a>
94 <a id="repo_switcher" title="${_('Switch repository')}" href="#">&darr;</a>
95 <div id="switch_repos" style="display:none;position: absolute;height: 25px">
95 <div id="switch_repos" style="display:none;position: absolute;height: 25px">
96 <select id="repos_list" size="=10" style="min-width: 150px">
96 <select id="repos_list" size="=10" style="min-width: 150px">
97 %for repo in sorted(x.name.lower() for x in c.cached_repo_list.values()):
97 %for repo in sorted(x.name.lower() for x in c.cached_repo_list.values()):
98 <option value="${repo}">${repo}</option>
98 <option value="${repo}">${repo}</option>
99 %endfor
99 %endfor
100 </select>
100 </select>
101 </div>
101 </div>
102 </li>
102 </li>
103 <li ${is_current('summary')}>${h.link_to(_('summary'),h.url('summary_home',repo_name=c.repo_name))}</li>
103 <li ${is_current('summary')}>${h.link_to(_('summary'),h.url('summary_home',repo_name=c.repo_name))}</li>
104 <li ${is_current('shortlog')}>${h.link_to(_('shortlog'),h.url('shortlog_home',repo_name=c.repo_name))}</li>
104 <li ${is_current('shortlog')}>${h.link_to(_('shortlog'),h.url('shortlog_home',repo_name=c.repo_name))}</li>
105 <li ${is_current('changelog')}>${h.link_to(_('changelog'),h.url('changelog_home',repo_name=c.repo_name))}</li>
105 <li ${is_current('changelog')}>${h.link_to(_('changelog'),h.url('changelog_home',repo_name=c.repo_name))}</li>
106 <li ${is_current('branches')}>${h.link_to(_('branches'),h.url('branches_home',repo_name=c.repo_name))}</li>
106 <li ${is_current('branches')}>${h.link_to(_('branches'),h.url('branches_home',repo_name=c.repo_name))}</li>
107 <li ${is_current('tags')}>${h.link_to(_('tags'),h.url('tags_home',repo_name=c.repo_name))}</li>
107 <li ${is_current('tags')}>${h.link_to(_('tags'),h.url('tags_home',repo_name=c.repo_name))}</li>
108 <li ${is_current('files')}>${h.link_to(_('files'),h.url('files_home',repo_name=c.repo_name))}</li>
108 <li ${is_current('files')}>${h.link_to(_('files'),h.url('files_home',repo_name=c.repo_name))}</li>
109 <li>${h.link_to(_('settings'),h.url('edit_repo',repo_name=c.repo_name))}</li>
109 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
110 <li>${h.link_to(_('settings'),h.url('edit_repo',repo_name=c.repo_name))}</li>
111 %endif
110 </ul>
112 </ul>
111 %else:
113 %else:
112 ##Root menu
114 ##Root menu
113 <ul class="page-nav">
115 <ul class="page-nav">
114 <li ${is_current('home')}>${h.link_to(_('Home'),h.url('/'))}</li>
116 <li ${is_current('home')}>${h.link_to(_('Home'),h.url('/'))}</li>
115 <li ${is_current('admin')}>${h.link_to(_('Admin'),h.url('admin_home'))}</li>
117 %if h.HasPermissionAll('hg.admin')('access admin main page'):
118 <li ${is_current('admin')}>${h.link_to(_('Admin'),h.url('admin_home'))}</li>
119 %endif
116 <li class="logout">${h.link_to(u'Logout',h.url('logout_home'))}</li>
120 <li class="logout">${h.link_to(u'Logout',h.url('logout_home'))}</li>
117 </ul>
121 </ul>
118 %endif
122 %endif
119 </div>
123 </div>
120 </%def>
124 </%def>
121 <%def name="submenu(current=None)">
125 <%def name="submenu(current=None)">
122 <%
126 <%
123 def is_current(selected):
127 def is_current(selected):
124 if selected == current:
128 if selected == current:
125 return "class='current_submenu'"
129 return "class='current_submenu'"
126 %>
130 %>
127 %if current != None:
131 %if current != None:
128 <div>
132 <div>
129 <ul class="submenu">
133 <ul class="submenu">
130 <li ${is_current('repos')}>${h.link_to(u'repos',h.url('repos'),class_='repos')}</li>
134 <li ${is_current('repos')}>${h.link_to(u'repos',h.url('repos'),class_='repos')}</li>
131 <li ${is_current('users')}>${h.link_to(u'users',h.url('users'),class_='users')}</li>
135 <li ${is_current('users')}>${h.link_to(u'users',h.url('users'),class_='users')}</li>
132 <li ${is_current('permissions')}>${h.link_to(u'permissions',h.url('permissions'),class_='permissions')}</li>
136 ##comented for now<li ${is_current('permissions')}>${h.link_to(u'permissions',h.url('permissions'),class_='permissions')}</li>
133 </ul>
137 </ul>
134 </div>
138 </div>
135 %endif
139 %endif
136 </%def>
140 </%def>
137
141
138
142
139 <%def name="css()">
143 <%def name="css()">
140 <link rel="stylesheet" href="/css/monoblue_custom.css" type="text/css" />
144 <link rel="stylesheet" href="/css/monoblue_custom.css" type="text/css" />
141 </%def>
145 </%def>
142
146
143 <%def name="js()">
147 <%def name="js()">
144 <script type="text/javascript" src="/js/yui/utilities/utilities.js"></script>
148 <script type="text/javascript" src="/js/yui/utilities/utilities.js"></script>
145 <script type="text/javascript" src="/js/yui/container/container-min.js"></script>
149 <script type="text/javascript" src="/js/yui/container/container-min.js"></script>
146 <script type="text/javascript" src="/js/yui/datasource/datasource-min.js"></script>
150 <script type="text/javascript" src="/js/yui/datasource/datasource-min.js"></script>
147 <script type="text/javascript" src="/js/yui/autocomplete/autocomplete-min.js"></script>
151 <script type="text/javascript" src="/js/yui/autocomplete/autocomplete-min.js"></script>
148 </%def>
152 </%def>
149
153
150 <!-- DEFINITION OF FORM ERROR FETCHER -->
154 <!-- DEFINITION OF FORM ERROR FETCHER -->
151 <%def name="get_form_error(element)">
155 <%def name="get_form_error(element)">
152 %if hasattr(c,'form_errors') and type(c.form_errors) == dict:
156 %if hasattr(c,'form_errors') and type(c.form_errors) == dict:
153 %if c.form_errors.get(element,False):
157 %if c.form_errors.get(element,False):
154 <span class="error-message">
158 <span class="error-message">
155 ${c.form_errors.get(element,'')}
159 ${c.form_errors.get(element,'')}
156 </span>
160 </span>
157 %endif
161 %endif
158 %endif
162 %endif
159 </%def> No newline at end of file
163 </%def>
@@ -1,53 +1,55 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base/base.html"/>
2 <%inherit file="base/base.html"/>
3 <%def name="title()">
3 <%def name="title()">
4 ${c.repos_prefix} Mercurial Repositories
4 ${c.repos_prefix} Mercurial Repositories
5 </%def>
5 </%def>
6 <%def name="breadcrumbs()">
6 <%def name="breadcrumbs()">
7 ${c.repos_prefix} Mercurial Repositories
7 ${c.repos_prefix} Mercurial Repositories
8 </%def>
8 </%def>
9 <%def name="page_nav()">
9 <%def name="page_nav()">
10 ${self.menu('home')}
10 ${self.menu('home')}
11 </%def>
11 </%def>
12 <%def name="main()">
12 <%def name="main()">
13 <%def name="get_sort(name)">
13 <%def name="get_sort(name)">
14 <%name_slug = name.lower().replace(' ','_') %>
14 <%name_slug = name.lower().replace(' ','_') %>
15 %if name_slug == c.cs_slug:
15 %if name_slug == c.cs_slug:
16 <span style="font-weight: bold;text-decoration: underline;">${name}</span>
16 <span style="font-weight: bold;text-decoration: underline;">${name}</span>
17 %else:
17 %else:
18 <span style="font-weight: bold">${name}</span>
18 <span style="font-weight: bold">${name}</span>
19 %endif
19 %endif
20 <a style="color:#FFF" href="?sort=${name_slug}">&darr;</a>
20 <a style="color:#FFF" href="?sort=${name_slug}">&darr;</a>
21 <a style="color:#FFF" href="?sort=-${name_slug}">&uarr;</a>
21 <a style="color:#FFF" href="?sort=-${name_slug}">&uarr;</a>
22 </%def>
22 </%def>
23 <table class="table_disp">
23 <table class="table_disp">
24 <tr class="header">
24 <tr class="header">
25 <td>${get_sort(_('Name'))}</td>
25 <td>${get_sort(_('Name'))}</td>
26 <td>${get_sort(_('Description'))}</td>
26 <td>${get_sort(_('Description'))}</td>
27 <td>${get_sort(_('Last change'))}</td>
27 <td>${get_sort(_('Last change'))}</td>
28 <td>${get_sort(_('Tip'))}</td>
28 <td>${get_sort(_('Tip'))}</td>
29 <td>${get_sort(_('Contact'))}</td>
29 <td>${get_sort(_('Contact'))}</td>
30 <td>${_('RSS')}</td>
30 <td>${_('RSS')}</td>
31 <td>${_('Atom')}</td>
31 <td>${_('Atom')}</td>
32 </tr>
32 </tr>
33 %for cnt,repo in enumerate(c.repos_list):
33 %for cnt,repo in enumerate(c.repos_list):
34 %if h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(repo['name'],'main page check'):
34 <tr class="parity${cnt%2}">
35 <tr class="parity${cnt%2}">
35 <td>${h.link_to(repo['name'],
36 <td>${h.link_to(repo['name'],
36 h.url('summary_home',repo_name=repo['name']))}</td>
37 h.url('summary_home',repo_name=repo['name']))}</td>
37 <td title="${repo['description']}">${h.truncate(repo['description'],60)}</td>
38 <td title="${repo['description']}">${h.truncate(repo['description'],60)}</td>
38 <td>${h.age(repo['last_change'])}</td>
39 <td>${h.age(repo['last_change'])}</td>
39 <td>${h.link_to_if(repo['rev']>=0,'r%s:%s' % (repo['rev'],repo['tip']),
40 <td>${h.link_to_if(repo['rev']>=0,'r%s:%s' % (repo['rev'],repo['tip']),
40 h.url('changeset_home',repo_name=repo['name'],revision=repo['tip']),
41 h.url('changeset_home',repo_name=repo['name'],revision=repo['tip']),
41 class_="tooltip",
42 class_="tooltip",
42 tooltip_title=h.tooltip(repo['last_msg']))}</td>
43 tooltip_title=h.tooltip(repo['last_msg']))}</td>
43 <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
44 <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
44 <td>
45 <td>
45 <a title="${_('Subscribe to %s rss feed')%repo['name']}" class="rss_logo" href="${h.url('rss_feed_home',repo_name=repo['name'])}"></a>
46 <a title="${_('Subscribe to %s rss feed')%repo['name']}" class="rss_logo" href="${h.url('rss_feed_home',repo_name=repo['name'])}"></a>
46 </td>
47 </td>
47 <td>
48 <td>
48 <a title="${_('Subscribe to %s atom feed')%repo['name']}" class="atom_logo" href="${h.url('atom_feed_home',repo_name=repo['name'])}"></a>
49 <a title="${_('Subscribe to %s atom feed')%repo['name']}" class="atom_logo" href="${h.url('atom_feed_home',repo_name=repo['name'])}"></a>
49 </td>
50 </td>
50 </tr>
51 </tr>
52 %endif
51 %endfor
53 %endfor
52 </table>
54 </table>
53 </%def>
55 </%def>
General Comments 0
You need to be logged in to leave comments. Login now