Changeset - 49eb69d78988
[Not reviewed]
beta
0 9 3
Marcin Kuzminski - 15 years ago 2010-11-22 03:57:47
marcin@python-works.com
implemented user dashboards, and following system.
12 files changed with 384 insertions and 16 deletions:
0 comments (0 inline, 0 general)
rhodecode/config/routing.py
Show inline comments
 
@@ -124,6 +124,14 @@ def make_map(config):
 
        m.connect('admin_home', '', action='index')#main page
 
        m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
 
                  action='add_repo')
 

	
 

	
 
    #USER JOURNAL
 
    map.connect('journal', '/_admin/journal', controller='journal',)
 
    map.connect('toggle_following', '/_admin/toggle_following', controller='journal',
 
                action='toggle_following', conditions=dict(method=["POST"]))
 

	
 

	
 
    #SEARCH
 
    map.connect('search', '/_admin/search', controller='search',)
 
    map.connect('search_repo', '/_admin/search/{search_repo:.*}', controller='search')
rhodecode/controllers/journal.py
Show inline comments
 
new file 100644
 
#!/usr/bin/env python
 
# encoding: utf-8
 
# journal controller for pylons
 
# Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
 
# 
 
# 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; version 2
 
# of the License or (at your opinion) any later version of the license.
 
# 
 
# 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, write to the Free Software
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 
# MA  02110-1301, USA.
 
"""
 
Created on November 21, 2010
 
journal controller for pylons
 
@author: marcink
 
"""
 

	
 
from pylons import request, response, session, tmpl_context as c, url
 
from pylons.controllers.util import abort, redirect
 
from rhodecode.lib.auth import LoginRequired
 
from rhodecode.lib.base import BaseController, render
 
from rhodecode.lib.helpers import get_token
 
from rhodecode.model.db import UserLog, UserFollowing
 
from rhodecode.model.scm import ScmModel
 
import logging
 
from paste.httpexceptions import HTTPInternalServerError, HTTPNotFound
 

	
 
log = logging.getLogger(__name__)
 

	
 
class JournalController(BaseController):
 

	
 

	
 
    @LoginRequired()
 
    def __before__(self):
 
        super(JournalController, self).__before__()
 

	
 
    def index(self):
 
        # Return a rendered template
 

	
 
        c.following = self.sa.query(UserFollowing)\
 
            .filter(UserFollowing.user_id == c.rhodecode_user.user_id).all()
 

	
 

	
 
        c.journal = self.sa.query(UserLog)\
 
            .order_by(UserLog.action_date.desc())\
 
            .all()
 
        return render('/journal.html')
 

	
 

	
 
    def toggle_following(self):
 
        print c.rhodecode_user
 

	
 
        if request.POST.get('auth_token') == get_token():
 
            scm_model = ScmModel()
 

	
 
            user_id = request.POST.get('follows_user_id')
 
            if user_id:
 
                try:
 
                    scm_model.toggle_following_user(user_id,
 
                                                    c.rhodecode_user.user_id)
 
                    return 'ok'
 
                except:
 
                    raise HTTPInternalServerError()
 

	
 
            repo_id = request.POST.get('follows_repo_id')
 
            if repo_id:
 
                try:
 
                    scm_model.toggle_following_repo(repo_id,
 
                                                    c.rhodecode_user.user_id)
 
                    return 'ok'
 
                except:
 
                    raise HTTPInternalServerError()
 

	
 

	
 

	
 
        raise HTTPInternalServerError()
rhodecode/controllers/summary.py
Show inline comments
 
@@ -52,8 +52,10 @@ class SummaryController(BaseController):
 
        super(SummaryController, self).__before__()
 

	
 
    def index(self):
 
        hg_model = ScmModel()
 
        c.repo_info = hg_model.get_repo(c.repo_name)
 
        scm_model = ScmModel()
 
        c.repo_info = scm_model.get_repo(c.repo_name)
 
        c.following = scm_model.is_following_repo(c.repo_name,
 
                                             c.rhodecode_user.user_id)
 
        def url_generator(**kw):
 
            return url('shortlog_home', repo_name=c.repo_name, **kw)
 

	
rhodecode/lib/helpers.py
Show inline comments
 
@@ -3,6 +3,8 @@
 
Consists of functions to typically be used within templates, but also
 
available to Controllers. This module is available to both as 'h'.
 
"""
 
import random
 
import hashlib
 
from pygments.formatters import HtmlFormatter
 
from pygments import highlight as code_highlight
 
from pylons import url, app_globals as g
 
@@ -36,6 +38,24 @@ def _reset(name, value=None, id=NotGiven
 

	
 
reset = _reset
 

	
 

	
 
def get_token():
 
    """Return the current authentication token, creating one if one doesn't
 
    already exist.
 
    """
 
    token_key = "_authentication_token"
 
    from pylons import session
 
    if not token_key in session:
 
        try:
 
            token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
 
        except AttributeError: # Python < 2.4
 
            token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
 
        session[token_key] = token
 
        if hasattr(session, 'save'):
 
            session.save()
 
    return session[token_key]
 

	
 

	
 
#Custom helpers here :)
 
class _Link(object):
 
    '''
 
@@ -415,10 +435,10 @@ def action_parser(user_log):
 
                cs_links += html_tmpl % (', '.join(r for r in revs[revs_limit:]),
 
                                         _('and %s more revisions') \
 
                                            % (len(revs) - revs_limit))
 
                
 

	
 
            return literal(cs_links)
 
        return ''
 
    
 

	
 
    def get_fork_name():
 
        if action == 'user_forked_repo':
 
            from rhodecode.model.scm import ScmModel
rhodecode/model/db.py
Show inline comments
 
@@ -49,6 +49,7 @@ class User(Base):
 
    user_perms = relation('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
 

	
 
    repositories = relation('Repository')
 
    user_followers = relation('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
 

	
 
    @LazyProperty
 
    def full_contact(self):
 
@@ -101,6 +102,9 @@ class Repository(Base):
 
    repo_to_perm = relation('RepoToPerm', cascade='all')
 
    stats = relation('Statistics', cascade='all', uselist=False)
 

	
 
    repo_followers = relation('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
 

	
 

	
 
    def __repr__(self):
 
        return "<Repository('%s:%s')>" % (self.repo_id, self.repo_name)
 

	
 
@@ -148,6 +152,23 @@ class Statistics(Base):
 

	
 
    repository = relation('Repository', single_parent=True)
 

	
 
class UserFollowing(Base):
 
    __tablename__ = 'user_followings'
 
    __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
 
                      UniqueConstraint('user_id', 'follows_user_id')
 
                      , {'useexisting':True})
 

	
 
    user_following_id = Column("user_following_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
 
    user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
 
    follows_repo_id = Column("follows_repository_id", INTEGER(), ForeignKey(u'repositories.repo_id'), nullable=True, unique=None, default=None)
 
    follows_user_id = Column("follows_user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=True, unique=None, default=None)
 

	
 
    user = relation('User', primaryjoin='User.user_id==UserFollowing.user_id')
 

	
 
    follows_user = relation('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
 
    follows_repository = relation('Repository')
 

	
 

	
 
class CacheInvalidation(Base):
 
    __tablename__ = 'cache_invalidation'
 
    __table_args__ = (UniqueConstraint('cache_key'), {'useexisting':True})
 
@@ -163,4 +184,4 @@ class CacheInvalidation(Base):
 
        self.cache_active = False
 

	
 
    def __repr__(self):
 
        return "<CacheInvaidation('%s:%s')>" % (self.cache_id, self.cache_key)
 
        return "<CacheInvalidation('%s:%s')>" % (self.cache_id, self.cache_key)
rhodecode/model/scm.py
Show inline comments
 
@@ -29,7 +29,8 @@ from rhodecode.lib import helpers as h
 
from rhodecode.lib.auth import HasRepoPermissionAny
 
from rhodecode.lib.utils import get_repos, make_ui
 
from rhodecode.model import meta
 
from rhodecode.model.db import Repository, User, RhodeCodeUi, CacheInvalidation
 
from rhodecode.model.db import Repository, User, RhodeCodeUi, CacheInvalidation, \
 
    UserFollowing
 
from rhodecode.model.caching_query import FromCache
 
from sqlalchemy.orm import joinedload
 
from sqlalchemy.orm.session import make_transient
 
@@ -219,7 +220,79 @@ class ScmModel(object):
 
            self.sa.rollback()
 

	
 

	
 
    def toggle_following_repo(self, follow_repo_id, user_id):
 

	
 
        f = self.sa.query(UserFollowing)\
 
            .filter(UserFollowing.follows_repo_id == follow_repo_id)\
 
            .filter(UserFollowing.user_id == user_id).scalar()
 

	
 
        if f is not None:
 
            try:
 
                self.sa.delete(f)
 
                self.sa.commit()
 
                return
 
            except:
 
                log.error(traceback.format_exc())
 
                self.sa.rollback()
 
                raise
 

	
 

	
 
        try:
 
            f = UserFollowing()
 
            f.user_id = user_id
 
            f.follows_repo_id = follow_repo_id
 
            self.sa.add(f)
 
            self.sa.commit()
 
        except:
 
            log.error(traceback.format_exc())
 
            self.sa.rollback()
 
            raise
 

	
 
    def toggle_following_user(self, follow_user_id , user_id):
 
        f = self.sa.query(UserFollowing)\
 
            .filter(UserFollowing.follows_user_id == follow_user_id)\
 
            .filter(UserFollowing.user_id == user_id).scalar()
 

	
 
        if f is not None:
 
            try:
 
                self.sa.delete(f)
 
                self.sa.commit()
 
                return
 
            except:
 
                log.error(traceback.format_exc())
 
                self.sa.rollback()
 
                raise
 

	
 
        try:
 
            f = UserFollowing()
 
            f.user_id = user_id
 
            f.follows_user_id = follow_user_id
 
            self.sa.add(f)
 
            self.sa.commit()
 
        except:
 
            log.error(traceback.format_exc())
 
            self.sa.rollback()
 
            raise
 

	
 
    def is_following_repo(self, repo_name, user_id):
 
        r = self.sa.query(Repository)\
 
            .filter(Repository.repo_name == repo_name).scalar()
 

	
 
        f = self.sa.query(UserFollowing)\
 
            .filter(UserFollowing.follows_repository == r)\
 
            .filter(UserFollowing.user_id == user_id).scalar()
 

	
 
        return f is not None
 

	
 
    def is_following_user(self, username, user_id):
 
        u = self.sa.query(User)\
 
            .filter(User.username == username).scalar()
 

	
 
        f = self.sa.query(UserFollowing)\
 
            .filter(UserFollowing.follows_user == u)\
 
            .filter(UserFollowing.user_id == user_id).scalar()
 

	
 
        return f is not None
 

	
 

	
 
    def _should_invalidate(self, repo_name):
rhodecode/public/css/style.css
Show inline comments
 
@@ -1776,6 +1776,26 @@ background:#556CB5;
 
color:#FFF;
 
}
 
 
.follow{
 
background:url("../images/icons/heart_add.png") no-repeat scroll 3px;
 
height: 16px;
 
width: 20px;
 
cursor: pointer;
 
display: block;
 
float: right;
 
margin-top: 2px;
 
}
 
 
.following{
 
background:url("../images/icons/heart_delete.png") no-repeat scroll 3px;
 
height: 16px;
 
width: 20px;
 
cursor: pointer;
 
display: block;
 
float: right;
 
margin-top: 2px;
 
}
 
 
.add_icon {
 
background:url("../images/icons/add.png") no-repeat scroll 3px;
 
height:16px;
rhodecode/templates/base/base.html
Show inline comments
 
@@ -239,6 +239,15 @@
 
                </li>
 
                
 
                <li>
 
                    <a title="${_('Journal')}"  href="${h.url('journal')}">
 
                    <span class="icon">
 
                        <img src="/images/icons/book.png" alt="${_('Journal')}" />
 
                    </span>
 
                    <span>${_('Journal')}</span>                 
 
                    </a>        
 
                </li>
 
                                
 
                <li>
 
                    <a title="${_('Search')}"  href="${h.url('search')}">
 
                    <span class="icon">
 
                        <img src="/images/icons/search_16.png" alt="${_('Search')}" />
 
@@ -286,6 +295,50 @@
 
<script type="text/javascript" src="/js/yui2a.js"></script>
 
<!--[if IE]><script language="javascript" type="text/javascript" src="/js/excanvas.min.js"></script><![endif]-->
 
<script type="text/javascript" src="/js/yui.flot.js"></script>
 

	
 
<script type="text/javascript">
 
var base_url  ='/_admin/toggle_following';
 
var YUC = YAHOO.util.Connect;
 
var YUD = YAHOO.util.Dom;
 

	
 

	
 
function onSuccess(){
 
	
 
	var f = YUD.get('follow_toggle');
 
    if(f.getAttribute('class')=='follow'){
 
        f.setAttribute('class','following');
 
        f.setAttribute('title',"${_('Stop following this repository')}");
 
        
 
    }
 
    else{
 
        f.setAttribute('class','follow');
 
        f.setAttribute('title',"${_('Start following this repository')}");
 
    }
 
}
 

	
 
function toggleFollowingUser(fallows_user_id,token){
 
    args = 'follows_user_id='+fallows_user_id;
 
    args+= '&auth_token='+token;
 
    YUC.asyncRequest('POST',base_url,{
 
        success:function(o){
 
        	onSuccess();
 
        }
 
    },args); return false;
 
}
 

	
 

	
 
function toggleFollowingRepo(fallows_repo_id,token){
 
    args = 'follows_repo_id='+fallows_repo_id;
 
    args+= '&auth_token='+token;
 
    YUC.asyncRequest('POST',base_url,{
 
        success:function(o){
 
        	onSuccess();
 
        }
 
    },args); return false;
 
}    
 
</script>
 

	
 

	
 
</%def>
 

	
 
<%def name="breadcrumbs()">
rhodecode/templates/journal.html
Show inline comments
 
new file 100644
 
## -*- coding: utf-8 -*-
 
<%inherit file="base/base.html"/>
 
<%def name="title()">
 
    ${_('Journal')} - ${c.rhodecode_name}
 
</%def>
 
<%def name="breadcrumbs()">
 
	${c.rhodecode_name}
 
</%def>
 
<%def name="page_nav()">
 
	${self.menu('home')}
 
</%def>
 
<%def name="main()">
 
	
 
    <div class="box box-left">
 
	    <!-- box / title -->
 
	    <div class="title">
 
	        <h5>${_('Journal')}</h5>
 
	    </div>
 
	    <div>
 
	    %if c.journal:
 
            %for entry in c.journal:
 
            <div style="padding:10px">
 
                <div class="gravatar">
 
                    <img alt="gravatar" src="${h.gravatar_url(entry.user.email)}"/>
 
                </div>
 
                <div>${entry.user.name} ${entry.user.lastname}</div>
 
                <div style="padding-left: 45px;">${h.action_parser(entry)} <br/>
 
                <b>
 
		        %if entry.repository:
 
		          ${h.link_to(entry.repository.repo_name,
 
		                      h.url('summary_home',repo_name=entry.repository.repo_name))}
 
		        %else:
 
		          ${entry.repository_name}
 
		        %endif             
 
                </b> - <span title="${entry.action_date}">${h.age(entry.action_date)}</span>
 
                </div>
 
            </div>
 
            <div style="clear:both;border-bottom:1px dashed #DDD;padding:3px 3px;margin:0px 10px 0px 10px"></div>
 
            %endfor
 
        %else:
 
            ${_('No entries yet')}
 
        %endif   
 
	    </div>
 
    </div>
 
    
 
    <div class="box box-right">
 
        <!-- box / title -->
 
        <div class="title">
 
            <h5>${_('Following')}</h5>
 
        </div>
 
        <div>
 
        %if c.following:
 
            %for entry in c.following:
 
                <div>
 
                    %if entry.follows_user_id:
 
                      <img alt="" src="/images/icons/user.png"/>
 
                      
 
                      ${entry.follows_user.username}
 
                    %endif
 
                    
 
                    %if entry.follows_repo_id:
 
                    
 
                      %if entry.follows_repository.private:
 
                        <img alt="" src="/images/icons/lock_closed.png"/>
 
                      %else:
 
                        <img alt="" src="/images/icons/lock_open.png"/>
 
                      %endif
 
                      
 
                      ${h.link_to(entry.follows_repository.repo_name,h.url('summary_home',
 
                        repo_name=entry.follows_repository.repo_name))}
 
                      
 
                    %endif
 
                </div>
 
            %endfor
 
        %else:
 
            ${_('You are not following any users or repositories')}
 
        %endif               
 
        </div>
 
    </div>
 
</%def>    
rhodecode/templates/summary/summary.html
Show inline comments
 
@@ -55,6 +55,16 @@ E.onDOMReady(function(e){
 
	                <img style="margin-bottom:2px" class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="/images/icons/lock_open.png"/>
 
	             %endif
 
			      <span style="font-size: 1.6em;font-weight: bold;vertical-align: baseline;">${c.repo_info.name}</span>
 
			      
 
			      %if c.following:
 
                  <span id="follow_toggle" class="following" title="${_('Stop following this repository')}"
 
                        onclick="javascript:toggleFollowingRepo(${c.repo_info.dbrepo.repo_id},'${str(h.get_token())}')">
 
                  </span>			      
 
			      %else:
 
			      <span id="follow_toggle" class="follow" title="${_('Start following this repository')}"
 
			            onclick="javascript:toggleFollowingRepo(${c.repo_info.dbrepo.repo_id},'${str(h.get_token())}')">
 
			      </span>
 
			      %endif
 
			      <br/>
 
		            %if c.repo_info.dbrepo.fork:
 
		            	<span style="margin-top:5px">
rhodecode/tests/functional/test_files.py
Show inline comments
 
@@ -73,10 +73,6 @@ class TestFilesController(TestController
 
                                    revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc',
 
                                    f_path='vcs/nodes.py'))
 

	
 

	
 

	
 
        #tests...
 

	
 
        #test or history
 
        assert """<select id="diff1" name="diff1">
 
<option selected="selected" value="8911406ad776fdd3d0b9932a2e89677e57405a48">r167:8911406ad776</option>
 
@@ -131,12 +127,6 @@ removed extra unicode conversion in diff
 
                                    f_path='vcs/nodes.py'))
 

	
 

	
 

	
 

	
 

	
 

	
 
        print response.body
 

	
 
        assert """<option selected="selected" value="8911406ad776fdd3d0b9932a2e89677e57405a48">r167:8911406ad776</option>
 
<option value="aa957ed78c35a1541f508d2ec90e501b0a9e3167">r165:aa957ed78c35</option>
 
<option value="48e11b73e94c0db33e736eaeea692f990cb0b5f1">r140:48e11b73e94c</option>
rhodecode/tests/functional/test_journal.py
Show inline comments
 
new file 100644
 
from rhodecode.tests import *
 

	
 
class TestJournalController(TestController):
 

	
 
    def test_index(self):
 
        response = self.app.get(url(controller='journal', action='index'))
 
        # Test response...
0 comments (0 inline, 0 general)