Changeset - ad4a680113b7
[Not reviewed]
beta
0 3 0
Marcin Kuzminski - 13 years ago 2013-05-11 23:19:06
marcin@python-works.com
Gist: implemented delete of gists by owner, or super admin
3 files changed with 33 insertions and 7 deletions:
0 comments (0 inline, 0 general)
rhodecode/controllers/admin/gists.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
"""
 
    rhodecode.controllers.admin.gist
 
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
    gist controller for RhodeCode
 

	
 
    :created_on: May 9, 2013
 
    :author: marcink
 
    :copyright: (C) 2010-2013 Marcin Kuzminski <marcin@python-works.com>
 
    :license: GPLv3, see COPYING for more details.
 
"""
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
import time
 
import logging
 
import traceback
 
import formencode
 
from formencode import htmlfill
 

	
 
from pylons import request, tmpl_context as c, url
 
from pylons.controllers.util import abort, redirect
 
from pylons.i18n.translation import _
 

	
 
from rhodecode.model.forms import GistForm
 
from rhodecode.model.gist import GistModel
 
from rhodecode.model.meta import Session
 
from rhodecode.model.db import Gist
 
from rhodecode.lib import helpers as h
 
from rhodecode.lib.base import BaseController, render
 
from rhodecode.lib.auth import LoginRequired, NotAnonymous
 
from rhodecode.lib.utils2 import safe_str, safe_int, time_to_datetime
 
from rhodecode.lib.helpers import Page
 
from webob.exc import HTTPNotFound
 
from webob.exc import HTTPNotFound, HTTPForbidden
 
from sqlalchemy.sql.expression import or_
 
from rhodecode.lib.vcs.exceptions import VCSError
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class GistsController(BaseController):
 
    """REST Controller styled on the Atom Publishing Protocol"""
 

	
 
    def __load_defaults(self):
 
        c.lifetime_values = [
 
            (str(-1), _('forever')),
 
            (str(5), _('5 minutes')),
 
            (str(60), _('1 hour')),
 
            (str(60 * 24), _('1 day')),
 
            (str(60 * 24 * 30), _('1 month')),
 
        ]
 
        c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
 

	
 
    @LoginRequired()
 
    def index(self, format='html'):
 
        """GET /admin/gists: All items in the collection"""
 
        # url('gists')
 
        c.show_private = request.GET.get('private') and c.rhodecode_user.username != 'default'
 
        gists = Gist().query()\
 
            .filter(or_(Gist.gist_expires == -1, Gist.gist_expires >= time.time()))\
 
            .order_by(Gist.created_on.desc())
 
        if c.show_private:
 
            c.gists = gists.filter(Gist.gist_type == Gist.GIST_PRIVATE)\
 
                             .filter(Gist.gist_owner == c.rhodecode_user.user_id)
 
        else:
 
            c.gists = gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)
 
        p = safe_int(request.GET.get('page', 1), 1)
 
        c.gists_pager = Page(c.gists, page=p, items_per_page=10)
 
        return render('admin/gists/index.html')
 

	
 
    @LoginRequired()
 
    @NotAnonymous()
 
    def create(self):
 
        """POST /admin/gists: Create a new item"""
 
        # url('gists')
 
        self.__load_defaults()
 
        gist_form = GistForm([x[0] for x in c.lifetime_values])()
 
        try:
 
            form_result = gist_form.to_python(dict(request.POST))
 
            #TODO: multiple files support, from the form
 
            nodes = {
 
                form_result['filename'] or 'gistfile1.txt': {
 
@@ -106,75 +106,85 @@ class GistsController(BaseController):
 
            Session().commit()
 
            new_gist_id = gist.gist_access_id
 
        except formencode.Invalid, errors:
 
            defaults = errors.value
 

	
 
            return formencode.htmlfill.render(
 
                render('admin/gists/new.html'),
 
                defaults=defaults,
 
                errors=errors.error_dict or {},
 
                prefix_error=False,
 
                encoding="UTF-8"
 
            )
 

	
 
        except Exception, e:
 
            log.error(traceback.format_exc())
 
            h.flash(_('Error occurred during gist creation'), category='error')
 
            return redirect(url('new_gist'))
 
        return redirect(url('gist', id=new_gist_id))
 

	
 
    @LoginRequired()
 
    @NotAnonymous()
 
    def new(self, format='html'):
 
        """GET /admin/gists/new: Form to create a new item"""
 
        # url('new_gist')
 
        self.__load_defaults()
 
        return render('admin/gists/new.html')
 

	
 
    @LoginRequired()
 
    @NotAnonymous()
 
    def update(self, id):
 
        """PUT /admin/gists/id: Update an existing item"""
 
        # Forms posted to this method should contain a hidden field:
 
        #    <input type="hidden" name="_method" value="PUT" />
 
        # Or using helpers:
 
        #    h.form(url('gist', id=ID),
 
        #           method='put')
 
        # url('gist', id=ID)
 

	
 
    @LoginRequired()
 
    @NotAnonymous()
 
    def delete(self, id):
 
        """DELETE /admin/gists/id: Delete an existing item"""
 
        # Forms posted to this method should contain a hidden field:
 
        #    <input type="hidden" name="_method" value="DELETE" />
 
        # Or using helpers:
 
        #    h.form(url('gist', id=ID),
 
        #           method='delete')
 
        # url('gist', id=ID)
 
        gist = GistModel().get_gist(id)
 
        owner = gist.gist_owner == c.rhodecode_user.user_id
 
        if h.HasPermissionAny('hg.admin')() or owner:
 
            GistModel().delete(gist)
 
            Session().commit()
 
            h.flash(_('Deleted gist %s') % gist.gist_access_id, category='success')
 
        else:
 
            raise HTTPForbidden()
 

	
 
        return redirect(url('gists'))
 

	
 
    @LoginRequired()
 
    def show(self, id, format='html'):
 
        """GET /admin/gists/id: Show a specific item"""
 
        # url('gist', id=ID)
 
        gist_id = id
 
        c.gist = Gist.get_or_404(gist_id)
 

	
 
        #check if this gist is not expired
 
        if c.gist.gist_expires != -1:
 
            if time.time() > c.gist.gist_expires:
 
                log.error('Gist expired at %s' %
 
                          (time_to_datetime(c.gist.gist_expires)))
 
                raise HTTPNotFound()
 
        try:
 
            c.file_changeset, c.files = GistModel().get_gist_files(gist_id)
 
        except VCSError:
 
            log.error(traceback.format_exc())
 
            raise HTTPNotFound()
 

	
 
        return render('admin/gists/show.html')
 

	
 
    @LoginRequired()
 
    @NotAnonymous()
 
    def edit(self, id, format='html'):
 
        """GET /admin/gists/id/edit: Form to edit an existing item"""
 
        # url('edit_gist', id=ID)
rhodecode/templates/admin/gists/show.html
Show inline comments
 
@@ -3,85 +3,87 @@
 

	
 
<%def name="title()">
 
    ${_('gist')}:${c.gist.gist_access_id} &middot; ${c.rhodecode_name}
 
</%def>
 

	
 
<%def name="breadcrumbs_links()">
 
    ${_('Gist')} &middot; gist:${c.gist.gist_access_id}
 
</%def>
 

	
 
<%def name="page_nav()">
 
    ${self.menu('gists')}
 
</%def>
 

	
 
<%def name="main()">
 
<div class="box">
 
    <!-- box / title -->
 
    <div class="title">
 
        ${self.breadcrumbs()}
 
        %if c.rhodecode_user.username != 'default':
 
        <ul class="links">
 
          <li>
 
             <span>${h.link_to(_(u'Create new gist'), h.url('new_gist'))}</span>
 
          </li>
 
        </ul>
 
        %endif
 
    </div>
 
    <div class="table">
 
        <div id="files_data">
 
            <div id="body" class="codeblock">
 
                <div class="code-header">
 
                    <div class="stats">
 
                        <div class="left" style="margin: -4px 0px 0px 0px">
 
                          %if c.gist.gist_type == 'public':
 
                            <div class="ui-btn green badge">${_('Public gist')}</div>
 
                          %else:
 
                            <div class="ui-btn yellow badge">${_('Private gist')}</div>
 
                          %endif
 
                        </div>
 
                       <span style="color: #AAA">
 
                         %if c.gist.gist_expires == -1:
 
                          ${_('Expires')}: ${_('never')}
 
                         %else:
 
                          ${_('Expires')}: ${h.age(h.time_to_datetime(c.gist.gist_expires))}
 
                         %endif
 
                       </span>
 
                        <div class="left item last">${c.gist.gist_description}</div>
 
                        <div class="buttons">
 
                          ## only owner should see that
 
                          %if c.gist.owner.username == c.rhodecode_user.username:
 
                          %if h.HasPermissionAny('hg.admin')() or c.gist.gist_owner == c.rhodecode_user.user_id:
 
                            ##${h.link_to(_('Edit'),h.url(''),class_="ui-btn")}
 
                            ##${h.link_to(_('Delete'),h.url(''),class_="ui-btn red")}
 
                            ${h.form(url('gist', id=c.gist.gist_id),method='delete')}
 
                                ${h.submit('remove_gist', _('Delete'),class_="ui-btn red",onclick="return confirm('"+_('Confirm to delete this gist')+"');")}
 
                            ${h.end_form()}
 
                          %endif
 
                        </div>
 
                    </div>
 

	
 
                    <div class="author">
 
                        <div class="gravatar">
 
                            <img alt="gravatar" src="${h.gravatar_url(h.email_or_none(c.file_changeset.author),16)}"/>
 
                        </div>
 
                        <div title="${c.file_changeset.author}" class="user">${h.person(c.file_changeset.author)} - ${_('created')} ${h.age(c.file_changeset.date)}</div>
 
                    </div>
 
                    <div class="commit">${h.urlify_commit(c.file_changeset.message,c.repo_name)}</div>
 
                </div>
 
            </div>
 

	
 
               ## iterate over the files
 
               % for file in c.files:
 
               <div style="border: 1px solid #EEE;margin-top:20px">
 
                <div id="${h.FID('G', file.path)}" class="stats" style="border-bottom: 1px solid #DDD;padding: 8px 14px;">
 
                    <b>${file.path}</b>
 
                    ##<div class="buttons">
 
                    ##   ${h.link_to(_('Show as raw'),h.url(''),class_="ui-btn")}
 
                    ##</div>
 
                </div>
 
                <div class="code-body">
 
                    ${h.pygmentize(file,linenos=True,anchorlinenos=True,lineanchors='L',cssclass="code-highlight")}
 
                </div>
 
              </div>
 
               %endfor
 
        </div>
 
    </div>
 

	
 

	
 
</div>
 
</%def>
rhodecode/tests/functional/test_admin_gists.py
Show inline comments
 
from rhodecode.tests import *
 
from rhodecode.model.gist import GistModel
 
from rhodecode.model.meta import Session
 
from rhodecode.model.db import User, Gist
 

	
 

	
 
def _create_gist(f_name, content='some gist', lifetime=-1,
 
                 description='gist-desc', gist_type='public'):
 
                 description='gist-desc', gist_type='public',
 
                 owner=TEST_USER_ADMIN_LOGIN):
 
    gist_mapping = {
 
        f_name: {'content': content}
 
    }
 
    user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
 
    user = User.get_by_username(owner)
 
    gist = GistModel().create(description, owner=user,
 
                       gist_mapping=gist_mapping, gist_type=gist_type,
 
                       lifetime=lifetime)
 
    Session().commit()
 
    return gist
 

	
 

	
 
class TestGistsController(TestController):
 

	
 
    def tearDown(self):
 
        for g in Gist.get_all():
 
            GistModel().delete(g)
 
        Session().commit()
 

	
 
    def test_index(self):
 
        self.log_user()
 
        response = self.app.get(url('gists'))
 
        # Test response...
 
        response.mustcontain('There are no gists yet')
 

	
 
        _create_gist('gist1')
 
        _create_gist('gist2', lifetime=1400)
 
        _create_gist('gist3', description='gist3-desc')
 
        _create_gist('gist4', gist_type='private')
 
        response = self.app.get(url('gists'))
 
        # Test response...
 
        response.mustcontain('gist:1')
 
        response.mustcontain('gist:2')
 
        response.mustcontain('Expires: in 23 hours')  # we don't care about the end
 
        response.mustcontain('gist:3')
 
        response.mustcontain('gist3-desc')
 
        response.mustcontain(no=['gist:4'])
 

	
 
    def test_index_private_gists(self):
 
        self.log_user()
 
        gist = _create_gist('gist5', gist_type='private')
 
        response = self.app.get(url('gists', private=1))
 
        # Test response...
 

	
 
        #and privates
 
        response.mustcontain('gist:%s' % gist.gist_access_id)
 

	
 
    def test_create_missing_description(self):
 
        self.log_user()
 
        response = self.app.post(url('gists'),
 
                                 params={'lifetime': -1}, status=200)
 

	
 
        response.mustcontain('Missing value')
 
@@ -64,62 +65,75 @@ class TestGistsController(TestController
 
        response = self.app.post(url('gists'),
 
                                 params={'lifetime': -1,
 
                                         'content': 'gist test',
 
                                         'filename': 'foo',
 
                                         'public': 'public'},
 
                                 status=302)
 
        response = response.follow()
 
        response.mustcontain('added file: foo')
 
        response.mustcontain('gist test')
 
        response.mustcontain('<div class="ui-btn green badge">Public gist</div>')
 

	
 
    def test_create_private(self):
 
        self.log_user()
 
        response = self.app.post(url('gists'),
 
                                 params={'lifetime': -1,
 
                                         'content': 'private gist test',
 
                                         'filename': 'private-foo',
 
                                         'private': 'private'},
 
                                 status=302)
 
        response = response.follow()
 
        response.mustcontain('added file: private-foo<')
 
        response.mustcontain('private gist test')
 
        response.mustcontain('<div class="ui-btn yellow badge">Private gist</div>')
 

	
 
    def test_create_with_description(self):
 
        self.log_user()
 
        response = self.app.post(url('gists'),
 
                                 params={'lifetime': -1,
 
                                         'content': 'gist test',
 
                                         'filename': 'foo-desc',
 
                                         'description': 'gist-desc',
 
                                         'public': 'public'},
 
                                 status=302)
 
        response = response.follow()
 
        response.mustcontain('added file: foo-desc')
 
        response.mustcontain('gist test')
 
        response.mustcontain('gist-desc')
 
        response.mustcontain('<div class="ui-btn green badge">Public gist</div>')
 

	
 
    def test_new(self):
 
        self.log_user()
 
        response = self.app.get(url('new_gist'))
 

	
 
    def test_update(self):
 
        self.skipTest('not implemented')
 
        response = self.app.put(url('gist', id=1))
 

	
 
    def test_delete(self):
 
        self.skipTest('not implemented')
 
        response = self.app.delete(url('gist', id=1))
 
        self.log_user()
 
        gist = _create_gist('delete-me')
 
        response = self.app.delete(url('gist', id=gist.gist_id))
 
        self.checkSessionFlash(response, 'Deleted gist %s' % gist.gist_id)
 

	
 
    def test_delete_normal_user_his_gist(self):
 
        self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
 
        gist = _create_gist('delete-me', owner=TEST_USER_REGULAR_LOGIN)
 
        response = self.app.delete(url('gist', id=gist.gist_id))
 
        self.checkSessionFlash(response, 'Deleted gist %s' % gist.gist_id)
 

	
 
    def test_delete_normal_user_not_his_own_gist(self):
 
        self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
 
        gist = _create_gist('delete-me')
 
        response = self.app.delete(url('gist', id=gist.gist_id), status=403)
 

	
 
    def test_show(self):
 
        gist = _create_gist('gist-show-me')
 
        response = self.app.get(url('gist', id=gist.gist_access_id))
 
        response.mustcontain('added file: gist-show-me<')
 
        response.mustcontain('test_admin (RhodeCode Admin) - created')
 
        response.mustcontain('gist-desc')
 
        response.mustcontain('<div class="ui-btn green badge">Public gist</div>')
 

	
 
    def test_edit(self):
 
        self.skipTest('not implemented')
 
        response = self.app.get(url('edit_gist', id=1))
0 comments (0 inline, 0 general)