diff --git a/README.rst b/README.rst --- a/README.rst +++ b/README.rst @@ -8,19 +8,19 @@ browser/management tool with a built in It works on http/https and has a built in permission/authentication system with the ability to authenticate via LDAP. -RhodeCode is similar in some respects to github or bitbucket, -however RhodeCode can be run as standalone hosted application on your own server. It is open source -and donation ware and focuses more on providing a customized, self administered -interface for Mercurial(and soon GIT) repositories. RhodeCode is powered by a vcs_ -library that Lukasz Balcerzak and I created to handle multiple different version -control systems. +RhodeCode is similar in some respects to github or bitbucket_, +however RhodeCode can be run as standalone hosted application on your own server. +It is open source and donation ware and focuses more on providing a customized, +self administered interface for Mercurial(and soon GIT) repositories. +RhodeCode is powered by a vcs_ library that Lukasz Balcerzak and I created to +handle multiple different version control systems. RhodeCode uses `Semantic Versioning `_ RhodeCode demo -------------- -http://hg.python-works.com +http://demo.rhodecode.org The default access is anonymous but you can login to an administrative account using the following credentials: @@ -31,8 +31,8 @@ using the following credentials: Source code ----------- -The latest source for RhodeCode can be obtained from my own RhodeCode instance -https://rhodecode.org +The latest source for RhodeCode can be obtained from official RhodeCode instance +https://hg.rhodecode.org Rarely updated source code and issue tracker is available at bitbcuket http://bitbucket.org/marcinkuzminski/rhodecode @@ -123,6 +123,7 @@ have sphinx installed you can install it .. _python: http://www.python.org/ .. _django: http://www.djangoproject.com/ .. _mercurial: http://mercurial.selenic.com/ +.. _bitbucket: http://bitbucket.org/ .. _subversion: http://subversion.tigris.org/ .. _git: http://git-scm.com/ .. _celery: http://celeryproject.org/ diff --git a/docs/changelog.rst b/docs/changelog.rst --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -3,6 +3,28 @@ Changelog ========= + +1.1.5 (**2011-03-1X**) +====================== + +news +---- + +- basic windows support, by exchanging pybcrypt into sha256 for windows only + highly inspired by idea of mantis406 + +fixes +----- + +- fixed sorting by author in main page +- fixed crashes with diffs on binary files +- fixed #131 problem with boolean values for LDAP +- fixed #122 mysql problems thanks to striker69 +- fixed problem with errors on calling raw/raw_files/annotate functions + with unknown revisions +- fixed returned rawfiles attachment names with international character +- cleaned out docs, big thanks to Jason Harris + 1.1.4 (**2011-02-19**) ====================== diff --git a/docs/contributing.rst b/docs/contributing.rst --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -7,13 +7,19 @@ If you would like to contribute to Rhode greatly appreciated! Could I request that you make your source contributions by first forking the -RhodeCode repository on bitbucket +RhodeCode repository on bitbucket_ https://bitbucket.org/marcinkuzminski/rhodecode and then make your changes to -your forked repository. Finally, when you are finished making a change, please -send me a pull request. +your forked repository. Please post all fixes into **BETA** branch since your +fix might be already fixed there and i try to merge all fixes from beta into +stable, and not the other way. Finally, when you are finished making a change, +please send me a pull request. To run RhodeCode in a development version you always need to install the tip version of RhodeCode and the VCS library. | Thank you for any contributions! -| Marcin \ No newline at end of file +| Marcin + + + +.. _bitbucket: http://bitbucket.org/ diff --git a/docs/index.rst b/docs/index.rst --- a/docs/index.rst +++ b/docs/index.rst @@ -48,6 +48,7 @@ Other topics .. _python: http://www.python.org/ .. _django: http://www.djangoproject.com/ .. _mercurial: http://mercurial.selenic.com/ +.. _bitbucket: http://bitbucket.org/ .. _subversion: http://subversion.tigris.org/ .. _git: http://git-scm.com/ .. _celery: http://celeryproject.org/ diff --git a/docs/installation.rst b/docs/installation.rst --- a/docs/installation.rst +++ b/docs/installation.rst @@ -9,12 +9,13 @@ together with celery you have to install recommended one is rabbitmq_ to make the async tasks work. Of course RhodeCode works in sync mode also and then you do not have to install -any third party applications. However, using Celery_ will give you a large speed improvement when using -many big repositories. If you plan to use RhodeCode for say 7 to 10 small repositories, RhodeCode -will perform perfectly well without celery running. +any third party applications. However, using Celery_ will give you a large +speed improvement when using many big repositories. If you plan to use +RhodeCode for say 7 to 10 small repositories, RhodeCode will perform perfectly +well without celery running. -If you make the decision to run RhodeCode with celery make sure you run celeryd using paster -and message broker together with the application. +If you make the decision to run RhodeCode with celery make sure you run +celeryd using paster and message broker together with the application. Installing RhodeCode from Cheese Shop ------------------------------------- diff --git a/docs/setup.rst b/docs/setup.rst --- a/docs/setup.rst +++ b/docs/setup.rst @@ -348,19 +348,19 @@ Troubleshooting double check the root path for your http setup. It should point to for example: /home/my-virtual-python/lib/python2.6/site-packages/rhodecode/public - -| + +| :Q: **Can't install celery/rabbitmq** :A: Don't worry RhodeCode works without them too. No extra setup is required. | - + :Q: **Long lasting push timeouts?** :A: Make sure you set a longer timeouts in your proxy/fcgi settings, timeouts are caused by https server and not RhodeCode. - -| + +| :Q: **Large pushes timeouts?** :A: Make sure you set a proper max_body_size for the http server. diff --git a/docs/upgrade.rst b/docs/upgrade.rst --- a/docs/upgrade.rst +++ b/docs/upgrade.rst @@ -7,7 +7,8 @@ Upgrading from Cheese Shop -------------------------- .. note:: - Firstly, it is recommended that you **always** perform a database backup before doing an upgrade. + Firstly, it is recommended that you **always** perform a database backup + before doing an upgrade. The easiest way to upgrade ``rhodecode`` is to run:: @@ -24,15 +25,16 @@ Then make sure you run the following com This will display any changes made by the new version of RhodeCode to your current configuration. It will try to perform an automerge. It's always better -to make a backup of your configuration file before hand and recheck the content after the automerge. +to make a backup of your configuration file before hand and recheck the +content after the automerge. .. note:: The next steps only apply to upgrading from non bugfix releases eg. from any minor or major releases. Bugfix releases (eg. 1.1.2->1.1.3) will not have any database schema changes or whoosh library updates. -It is also recommended that you rebuild the whoosh index after upgrading since the new whoosh -version could introduce some incompatible index changes. +It is also recommended that you rebuild the whoosh index after upgrading since +the new whoosh version could introduce some incompatible index changes. The final step is to upgrade the database. To do this simply run:: @@ -40,8 +42,8 @@ The final step is to upgrade the databas paster upgrade-db production.ini This will upgrade the schema and update some of the defaults in the database, -and will always recheck the settings of the application, if there are no new options -that need to be set. +and will always recheck the settings of the application, if there are no new +options that need to be set. .. _virtualenv: http://pypi.python.org/pypi/virtualenv diff --git a/rhodecode/__init__.py b/rhodecode/__init__.py --- a/rhodecode/__init__.py +++ b/rhodecode/__init__.py @@ -25,11 +25,12 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. - +import platform -VERSION = (1, 1, 4) +VERSION = (1, 1, 5) __version__ = '.'.join((str(each) for each in VERSION[:4])) __dbversion__ = 2 #defines current db version for migrations +__platform__ = platform.system() try: from rhodecode.lib.utils import get_current_revision diff --git a/rhodecode/config/deployment.ini_tmpl b/rhodecode/config/deployment.ini_tmpl --- a/rhodecode/config/deployment.ini_tmpl +++ b/rhodecode/config/deployment.ini_tmpl @@ -71,7 +71,7 @@ celery.result.serialier = json celeryd.concurrency = 2 #celeryd.log.file = celeryd.log celeryd.log.level = debug -celeryd.max.tasks.per.child = 3 +celeryd.max.tasks.per.child = 1 #tasks will never be sent to the queue, but executed locally instead. celery.always.eager = false diff --git a/rhodecode/controllers/admin/settings.py b/rhodecode/controllers/admin/settings.py --- a/rhodecode/controllers/admin/settings.py +++ b/rhodecode/controllers/admin/settings.py @@ -140,8 +140,8 @@ class SettingsController(BaseController) except: log.error(traceback.format_exc()) - h.flash(_('error occurred during updating application settings'), - category='error') + h.flash(_('error occurred during updating' + ' application settings'), category='error') self.sa.rollback() diff --git a/rhodecode/controllers/files.py b/rhodecode/controllers/files.py --- a/rhodecode/controllers/files.py +++ b/rhodecode/controllers/files.py @@ -7,7 +7,7 @@ :created_on: Apr 21, 2010 :author: marcink - :copyright: (C) 2009-2010 Marcin Kuzminski + :copyright: (C) 2009-2011 Marcin Kuzminski :license: GPLv3, see COPYING for more details. """ # This program is free software; you can redistribute it and/or @@ -55,9 +55,30 @@ class FilesController(BaseController): super(FilesController, self).__before__() c.cut_off_limit = self.cut_off_limit + def __get_cs_or_redirect(self, rev, repo_name): + """ + Safe way to get changeset if error occur it redirects to tip with + proper message + + :param rev: revision to fetch + :param repo_name: repo name to redirect after + """ + + _repo = ScmModel().get_repo(c.repo_name) + try: + return _repo.get_changeset(rev) + except EmptyRepositoryError, e: + h.flash(_('There are no files yet'), category='warning') + redirect(h.url('summary_home', repo_name=repo_name)) + + except RepositoryError, e: + h.flash(str(e), category='warning') + redirect(h.url('files_home', repo_name=repo_name, revision='tip')) + def index(self, repo_name, revision, f_path): - hg_model = ScmModel() - c.repo = hg_model.get_repo(c.repo_name) + cs = self.__get_cs_or_redirect(revision, repo_name) + c.repo = ScmModel().get_repo(c.repo_name) + revision = request.POST.get('at_rev', None) or revision def get_next_rev(cur): @@ -72,68 +93,64 @@ class FilesController(BaseController): return r c.f_path = f_path + c.changeset = cs + cur_rev = c.changeset.revision + prev_rev = c.repo.get_changeset(get_prev_rev(cur_rev)).raw_id + next_rev = c.repo.get_changeset(get_next_rev(cur_rev)).raw_id + c.url_prev = url('files_home', repo_name=c.repo_name, + revision=prev_rev, f_path=f_path) + c.url_next = url('files_home', repo_name=c.repo_name, + revision=next_rev, f_path=f_path) try: - c.changeset = c.repo.get_changeset(revision) - cur_rev = c.changeset.revision - prev_rev = c.repo.get_changeset(get_prev_rev(cur_rev)).raw_id - next_rev = c.repo.get_changeset(get_next_rev(cur_rev)).raw_id - - c.url_prev = url('files_home', repo_name=c.repo_name, - revision=prev_rev, f_path=f_path) - c.url_next = url('files_home', repo_name=c.repo_name, - revision=next_rev, f_path=f_path) - - try: - c.files_list = c.changeset.get_node(f_path) - c.file_history = self._get_history(c.repo, c.files_list, f_path) - except RepositoryError, e: - h.flash(str(e), category='warning') - redirect(h.url('files_home', repo_name=repo_name, revision=revision)) - - except EmptyRepositoryError, e: - h.flash(_('There are no files yet'), category='warning') - redirect(h.url('summary_home', repo_name=repo_name)) - + c.files_list = c.changeset.get_node(f_path) + c.file_history = self._get_history(c.repo, c.files_list, f_path) except RepositoryError, e: h.flash(str(e), category='warning') - redirect(h.url('files_home', repo_name=repo_name, revision='tip')) - + redirect(h.url('files_home', repo_name=repo_name, + revision=revision)) return render('files/files.html') def rawfile(self, repo_name, revision, f_path): - hg_model = ScmModel() - c.repo = hg_model.get_repo(c.repo_name) - file_node = c.repo.get_changeset(revision).get_node(f_path) + cs = self.__get_cs_or_redirect(revision, repo_name) + try: + file_node = cs.get_node(f_path) + except RepositoryError, e: + h.flash(str(e), category='warning') + redirect(h.url('files_home', repo_name=repo_name, + revision=cs.raw_id)) + + fname = f_path.split('/')[-1].encode('utf8', 'replace') + + response.content_disposition = 'attachment; filename=%s' % fname response.content_type = file_node.mimetype - response.content_disposition = 'attachment; filename=%s' \ - % f_path.split('/')[-1] return file_node.content def raw(self, repo_name, revision, f_path): - hg_model = ScmModel() - c.repo = hg_model.get_repo(c.repo_name) - file_node = c.repo.get_changeset(revision).get_node(f_path) + cs = self.__get_cs_or_redirect(revision, repo_name) + try: + file_node = cs.get_node(f_path) + except RepositoryError, e: + h.flash(str(e), category='warning') + redirect(h.url('files_home', repo_name=repo_name, + revision=cs.raw_id)) + response.content_type = 'text/plain' - return file_node.content def annotate(self, repo_name, revision, f_path): - hg_model = ScmModel() - c.repo = hg_model.get_repo(c.repo_name) - + cs = self.__get_cs_or_redirect(revision, repo_name) try: - c.cs = c.repo.get_changeset(revision) - c.file = c.cs.get_node(f_path) + c.file = cs.get_node(f_path) except RepositoryError, e: h.flash(str(e), category='warning') - redirect(h.url('files_home', repo_name=repo_name, revision=revision)) + redirect(h.url('files_home', repo_name=repo_name, revision=cs.raw_id)) - c.file_history = self._get_history(c.repo, c.file, f_path) - + c.file_history = self._get_history(ScmModel().get_repo(c.repo_name), c.file, f_path) + c.cs = cs c.f_path = f_path return render('files/files_annotate.html') @@ -201,25 +218,34 @@ class FilesController(BaseController): response.content_type = 'text/plain' response.content_disposition = 'attachment; filename=%s' \ % diff_name + if node1.is_binary or node2.is_binary: + return _('binary file changed') return diff.raw_diff() elif c.action == 'raw': response.content_type = 'text/plain' + if node1.is_binary or node2.is_binary: + return _('binary file changed') return diff.raw_diff() elif c.action == 'diff': if node1.size > self.cut_off_limit or node2.size > self.cut_off_limit: c.cur_diff = _('Diff is to big to display') + elif node1.is_binary or node2.is_binary: + c.cur_diff = _('Binary file') else: c.cur_diff = diff.as_html() else: #default option if node1.size > self.cut_off_limit or node2.size > self.cut_off_limit: c.cur_diff = _('Diff is to big to display') + elif node1.is_binary or node2.is_binary: + c.cur_diff = _('Binary file') else: c.cur_diff = diff.as_html() - if not c.cur_diff: c.no_changes = True + if not c.cur_diff: + c.no_changes = True return render('files/file_diff.html') def _get_history(self, repo, node, f_path): @@ -250,9 +276,3 @@ class FilesController(BaseController): hist_l.append(tags_group) return hist_l - -# [ -# ([("u1", "User1"), ("u2", "User2")], "Users"), -# ([("g1", "Group1"), ("g2", "Group2")], "Groups") -# ] - diff --git a/rhodecode/controllers/home.py b/rhodecode/controllers/home.py --- a/rhodecode/controllers/home.py +++ b/rhodecode/controllers/home.py @@ -43,7 +43,7 @@ class HomeController(BaseController): super(HomeController, self).__before__() def index(self): - sortables = ['name', 'description', 'last_change', 'tip', 'contact'] + sortables = ['name', 'description', 'last_change', 'tip', 'owner'] current_sort = request.GET.get('sort', 'name') current_sort_slug = current_sort.replace('-', '') diff --git a/rhodecode/controllers/journal.py b/rhodecode/controllers/journal.py --- a/rhodecode/controllers/journal.py +++ b/rhodecode/controllers/journal.py @@ -26,9 +26,12 @@ # MA 02110-1301, USA. import logging -from sqlalchemy import or_ +import traceback from pylons import request, response, session, tmpl_context as c, url +from paste.httpexceptions import HTTPInternalServerError, HTTPBadRequest + +from sqlalchemy import or_ from rhodecode.lib.auth import LoginRequired, NotAnonymous from rhodecode.lib.base import BaseController, render @@ -36,8 +39,6 @@ from rhodecode.lib.helpers import get_to from rhodecode.model.db import UserLog, UserFollowing from rhodecode.model.scm import ScmModel -from paste.httpexceptions import HTTPInternalServerError - log = logging.getLogger(__name__) class JournalController(BaseController): @@ -81,6 +82,7 @@ class JournalController(BaseController): c.rhodecode_user.user_id) return 'ok' except: + log.error(traceback.format_exc()) raise HTTPInternalServerError() repo_id = request.POST.get('follows_repo_id') @@ -90,8 +92,9 @@ class JournalController(BaseController): c.rhodecode_user.user_id) return 'ok' except: + log.error(traceback.format_exc()) raise HTTPInternalServerError() - raise HTTPInternalServerError() + raise HTTPBadRequest() diff --git a/rhodecode/lib/__init__.py b/rhodecode/lib/__init__.py --- a/rhodecode/lib/__init__.py +++ b/rhodecode/lib/__init__.py @@ -26,4 +26,21 @@ # MA 02110-1301, USA. def str2bool(v): - return v.lower() in ["yes", "true", "t", "1"] if v else None + if isinstance(v, (str, unicode)): + obj = v.strip().lower() + if obj in ['true', 'yes', 'on', 'y', 't', '1']: + return True + elif obj in ['false', 'no', 'off', 'n', 'f', '0']: + return False + else: + raise ValueError("String is not true/false: %r" % obj) + return bool(obj) + +def generate_api_key(username, salt=None): + from tempfile import _RandomNameSequence + import hashlib + + if salt is None: + salt = _RandomNameSequence().next() + + return hashlib.sha1(username + salt).hexdigest() diff --git a/rhodecode/lib/auth.py b/rhodecode/lib/auth.py --- a/rhodecode/lib/auth.py +++ b/rhodecode/lib/auth.py @@ -1,8 +1,14 @@ -#!/usr/bin/env python -# encoding: utf-8 -# authentication and permission libraries -# Copyright (C) 2009-2010 Marcin Kuzminski -# +# -*- coding: utf-8 -*- +""" + rhodecode.lib.auth + ~~~~~~~~~~~~~~~~~~ + + authentication and permission libraries + + :created_on: Apr 4, 2010 + :copyright: (c) 2010 by marcink. + :license: LICENSE_NAME, see LICENSE_FILE 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; version 2 @@ -17,26 +23,34 @@ # 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 April 4, 2010 -@author: marcink -""" +import random +import logging +import traceback + +from decorator import decorator + from pylons import config, session, url, request from pylons.controllers.util import abort, redirect -from rhodecode.lib.exceptions import * +from pylons.i18n.translation import _ + +from rhodecode import __platform__ + +if __platform__ == 'Windows': + from hashlib import sha256 +if __platform__ in ('Linux', 'Darwin'): + import bcrypt + +from rhodecode.lib import str2bool +from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError from rhodecode.lib.utils import get_repo_slug from rhodecode.lib.auth_ldap import AuthLdap + from rhodecode.model import meta from rhodecode.model.user import UserModel -from rhodecode.model.caching_query import FromCache -from rhodecode.model.db import User, RepoToPerm, Repository, Permission, \ - UserToPerm -import bcrypt -from decorator import decorator -import logging -import random -import traceback +from rhodecode.model.db import Permission, RepoToPerm, Repository, \ + User, UserToPerm + log = logging.getLogger(__name__) @@ -65,15 +79,46 @@ class PasswordGenerator(object): self.passwd = ''.join([random.choice(type) for _ in xrange(len)]) return self.passwd +class RhodeCodeCrypto(object): + + @classmethod + def hash_string(cls, str_): + """ + Cryptographic function used for password hashing based on pybcrypt + or pycrypto in windows + + :param password: password to hash + """ + if __platform__ == 'Windows': + return sha256(str_).hexdigest() + elif __platform__ in ('Linux', 'Darwin'): + return bcrypt.hashpw(str_, bcrypt.gensalt(10)) + else: + raise Exception('Unknown or unsupported platform %s' % __platform__) + + @classmethod + def hash_check(cls, password, hashed): + """ + Checks matching password with it's hashed value, runs different + implementation based on platform it runs on + + :param password: password + :param hashed: password in hashed form + """ + + if __platform__ == 'Windows': + return sha256(password).hexdigest() == hashed + elif __platform__ in ('Linux', 'Darwin'): + return bcrypt.hashpw(password, hashed) == hashed + else: + raise Exception('Unknown or unsupported platform %s' % __platform__) + def get_crypt_password(password): - """Cryptographic function used for password hashing based on sha1 - :param password: password to hash - """ - return bcrypt.hashpw(password, bcrypt.gensalt(10)) + return RhodeCodeCrypto.hash_string(password) def check_password(password, hashed): - return bcrypt.hashpw(password, hashed) == hashed + return RhodeCodeCrypto.hash_check(password, hashed) def authfunc(environ, username, password): """ @@ -126,7 +171,7 @@ def authenticate(username, password): #====================================================================== # FALLBACK TO LDAP AUTH IN ENABLE #====================================================================== - if ldap_settings.get('ldap_active', False): + if str2bool(ldap_settings.get('ldap_active')): log.debug("Authenticating user using ldap") kwargs = { 'server':ldap_settings.get('ldap_host', ''), @@ -134,7 +179,7 @@ def authenticate(username, password): 'port':ldap_settings.get('ldap_port'), 'bind_dn':ldap_settings.get('ldap_dn_user'), 'bind_pass':ldap_settings.get('ldap_dn_pass'), - 'use_ldaps':ldap_settings.get('ldap_ldaps'), + 'use_ldaps':str2bool(ldap_settings.get('ldap_ldaps')), 'ldap_version':3, } log.debug('Checking for ldap authentication') diff --git a/rhodecode/lib/auth_ldap.py b/rhodecode/lib/auth_ldap.py --- a/rhodecode/lib/auth_ldap.py +++ b/rhodecode/lib/auth_ldap.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # encoding: utf-8 # ldap authentication lib -# Copyright (C) 2009-2010 Marcin Kuzminski +# Copyright (C) 2009-2011 Marcin Kuzminski # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff --git a/rhodecode/lib/db_manage.py b/rhodecode/lib/db_manage.py --- a/rhodecode/lib/db_manage.py +++ b/rhodecode/lib/db_manage.py @@ -60,39 +60,19 @@ class DbManage(object): init_model(engine) self.sa = meta.Session() - def check_for_db(self, override): - db_path = jn(self.root, self.dbname) - if self.dburi.startswith('sqlite'): - log.info('checking for existing db in %s', db_path) - if os.path.isfile(db_path): - - self.db_exists = True - if not override: - raise Exception('database already exists') - return 'sqlite' - if self.dburi.startswith('postgresql'): - self.db_exists = True - return 'postgresql' - - def create_tables(self, override=False): """Create a auth database """ - db_type = self.check_for_db(override) - if self.db_exists: - log.info("database exist and it's going to be destroyed") - if self.tests: - destroy = True - else: - destroy = ask_ok('Are you sure to destroy old database ? [y/n]') - if not destroy: - sys.exit() - if self.db_exists and destroy: - if db_type == 'sqlite': - os.remove(jn(self.root, self.dbname)) - if db_type == 'postgresql': - meta.Base.metadata.drop_all() + log.info("Any existing database is going to be destroyed") + if self.tests: + destroy = True + else: + destroy = ask_ok('Are you sure to destroy old database ? [y/n]') + if not destroy: + sys.exit() + if destroy: + meta.Base.metadata.drop_all() checkfirst = not override meta.Base.metadata.create_all(checkfirst=checkfirst) @@ -322,10 +302,14 @@ class DbManage(object): """Creates ldap settings""" try: - for k in ['ldap_active', 'ldap_host', 'ldap_port', 'ldap_ldaps', - 'ldap_dn_user', 'ldap_dn_pass', 'ldap_base_dn']: + for k, v in [('ldap_active', 'false'), + ('ldap_host', ''), + ('ldap_port', '389'), + ('ldap_ldaps', 'false'), + ('ldap_dn_user', ''), ('ldap_dn_pass', ''), + ('ldap_base_dn', '')]: - setting = RhodeCodeSettings(k, '') + setting = RhodeCodeSettings(k, v) self.sa.add(setting) self.sa.commit() except: diff --git a/rhodecode/lib/helpers.py b/rhodecode/lib/helpers.py --- a/rhodecode/lib/helpers.py +++ b/rhodecode/lib/helpers.py @@ -230,6 +230,8 @@ tooltip = _ToolTip() class _FilesBreadCrumbs(object): def __call__(self, repo_name, rev, paths): + if isinstance(paths, str): + paths = paths.decode('utf-8', 'replace') url_l = [link_to(repo_name, url('files_home', repo_name=repo_name, revision=rev, f_path=''))] @@ -483,7 +485,7 @@ def action_parser_icon(user_log): if len(x) > 1: action, action_params = x - tmpl = """%s""" + tmpl = """%s""" map = {'user_deleted_repo':'database_delete.png', 'user_created_repo':'database_add.png', 'user_forked_repo':'arrow_divide.png', @@ -550,6 +552,6 @@ def changed_tooltip(nodes): suf = '' if len(nodes) > 30: suf = '
' + _(' and %s more') % (len(nodes) - 30) - return literal(pref + '
'.join([x.path for x in nodes[:30]]) + suf) + return literal(pref + '
'.join([x.path.decode('utf-8', 'replace') for x in nodes[:30]]) + suf) else: return ': ' + _('No Files') diff --git a/rhodecode/model/db.py b/rhodecode/model/db.py --- a/rhodecode/model/db.py +++ b/rhodecode/model/db.py @@ -30,51 +30,20 @@ from datetime import date from sqlalchemy import * from sqlalchemy.exc import DatabaseError -from sqlalchemy.orm import relationship, backref, class_mapper -from sqlalchemy.orm.session import Session +from sqlalchemy.orm import relationship, backref +from sqlalchemy.orm.interfaces import MapperExtension -from rhodecode.model.meta import Base +from rhodecode.model.meta import Base, Session log = logging.getLogger(__name__) -class BaseModel(object): - @classmethod - def _get_keys(cls): - """return column names for this model """ - return class_mapper(cls).c.keys() - - def get_dict(self): - """return dict with keys and values corresponding - to this model data """ - - d = {} - for k in self._get_keys(): - d[k] = getattr(self, k) - return d - - def get_appstruct(self): - """return list with keys and values tupples corresponding - to this model data """ - - l = [] - for k in self._get_keys(): - l.append((k, getattr(self, k),)) - return l - - def populate_obj(self, populate_dict): - """populate model with data from given populate_dict""" - - for k in self._get_keys(): - if k in populate_dict: - setattr(self, k, populate_dict[k]) - -class RhodeCodeSettings(Base, BaseModel): +class RhodeCodeSettings(Base): __tablename__ = 'rhodecode_settings' __table_args__ = (UniqueConstraint('app_settings_name'), {'useexisting':True}) app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - app_settings_name = Column("app_settings_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - app_settings_value = Column("app_settings_value", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) def __init__(self, k='', v=''): self.app_settings_name = k @@ -84,27 +53,27 @@ class RhodeCodeSettings(Base, BaseModel) return "<%s('%s:%s')>" % (self.__class__.__name__, self.app_settings_name, self.app_settings_value) -class RhodeCodeUi(Base, BaseModel): +class RhodeCodeUi(Base): __tablename__ = 'rhodecode_ui' __table_args__ = {'useexisting':True} ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - ui_section = Column("ui_section", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_key = Column("ui_key", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_value = Column("ui_value", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True) -class User(Base, BaseModel): +class User(Base): __tablename__ = 'users' __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'useexisting':True}) user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - username = Column("username", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - password = Column("password", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) active = Column("active", Boolean(), nullable=True, unique=None, default=None) admin = Column("admin", Boolean(), nullable=True, unique=None, default=False) - name = Column("name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - lastname = Column("lastname", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - email = Column("email", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None) is_ldap = Column("is_ldap", Boolean(), nullable=False, unique=None, default=False) @@ -118,6 +87,10 @@ class User(Base, BaseModel): def full_contact(self): return '%s %s <%s>' % (self.name, self.lastname, self.email) + @property + def short_contact(self): + return '%s %s' % (self.name, self.lastname) + @property def is_admin(self): @@ -127,6 +100,11 @@ class User(Base, BaseModel): return "<%s('id:%s:%s')>" % (self.__class__.__name__, self.user_id, self.username) + @classmethod + def by_username(cls, username): + return Session.query(cls).filter(cls.username == username).one() + + def update_lastlogin(self): """Update user lastlogin""" @@ -140,15 +118,15 @@ class User(Base, BaseModel): session.rollback() -class UserLog(Base, BaseModel): +class UserLog(Base): __tablename__ = 'user_logs' __table_args__ = {'useexisting':True} user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) repository_id = Column("repository_id", Integer(length=None, convert_unicode=False, assert_unicode=None), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) - repository_name = Column("repository_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - user_ip = Column("user_ip", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - action = Column("action", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None) @property @@ -158,16 +136,16 @@ class UserLog(Base, BaseModel): user = relationship('User') repository = relationship('Repository') -class Repository(Base, BaseModel): +class Repository(Base): __tablename__ = 'repositories' __table_args__ = (UniqueConstraint('repo_name'), {'useexisting':True},) repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - repo_name = Column("repo_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) - repo_type = Column("repo_type", String(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg') + repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) + repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg') user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) private = Column("private", Boolean(), nullable=True, unique=None, default=None) enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True) - description = Column("description", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None) user = relationship('User') @@ -178,23 +156,23 @@ class Repository(Base, BaseModel): repo_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all') logs = relationship('UserLog', cascade='all') - + def __repr__(self): return "<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id, self.repo_name) -class Permission(Base, BaseModel): +class Permission(Base): __tablename__ = 'permissions' __table_args__ = {'useexisting':True} permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - permission_name = Column("permission_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - permission_longname = Column("permission_longname", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) def __repr__(self): return "<%s('%s:%s')>" % (self.__class__.__name__, self.permission_id, self.permission_name) -class RepoToPerm(Base, BaseModel): +class RepoToPerm(Base): __tablename__ = 'repo_to_perm' __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'useexisting':True}) repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) @@ -206,7 +184,7 @@ class RepoToPerm(Base, BaseModel): permission = relationship('Permission') repository = relationship('Repository') -class UserToPerm(Base, BaseModel): +class UserToPerm(Base): __tablename__ = 'user_to_perm' __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'useexisting':True}) user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) @@ -216,19 +194,19 @@ class UserToPerm(Base, BaseModel): user = relationship('User') permission = relationship('Permission') -class Statistics(Base, BaseModel): +class Statistics(Base): __tablename__ = 'statistics' __table_args__ = (UniqueConstraint('repository_id'), {'useexisting':True}) stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - repository_id = Column("repository_id", Integer(), ForeignKey(u'repositories.repo_id'), nullable=False, unique=True, default=None) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None) stat_on_revision = Column("stat_on_revision", Integer(), nullable=False) - commit_activity = Column("commit_activity", LargeBinary(), nullable=False)#JSON data + commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data - languages = Column("languages", LargeBinary(), nullable=False)#JSON data + languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data repository = relationship('Repository', single_parent=True) -class UserFollowing(Base, BaseModel): +class UserFollowing(Base): __tablename__ = 'user_followings' __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'), UniqueConstraint('user_id', 'follows_user_id') @@ -244,12 +222,12 @@ class UserFollowing(Base, BaseModel): follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id') follows_repository = relationship('Repository', order_by='Repository.repo_name') -class CacheInvalidation(Base, BaseModel): +class CacheInvalidation(Base): __tablename__ = 'cache_invalidation' __table_args__ = (UniqueConstraint('cache_key'), {'useexisting':True}) cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - cache_key = Column("cache_key", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - cache_args = Column("cache_args", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False) @@ -262,10 +240,10 @@ class CacheInvalidation(Base, BaseModel) return "<%s('%s:%s')>" % (self.__class__.__name__, self.cache_id, self.cache_key) -class DbMigrateVersion(Base, BaseModel): +class DbMigrateVersion(Base): __tablename__ = 'db_migrate_version' __table_args__ = {'useexisting':True} - repository_id = Column('repository_id', String(250), primary_key=True) + repository_id = Column('repository_id', String(255), primary_key=True) repository_path = Column('repository_path', Text) version = Column('version', Integer) diff --git a/rhodecode/model/meta.py b/rhodecode/model/meta.py --- a/rhodecode/model/meta.py +++ b/rhodecode/model/meta.py @@ -1,8 +1,10 @@ """SQLAlchemy Metadata and Session object""" from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import scoped_session, sessionmaker +from sqlalchemy.orm import scoped_session, sessionmaker, class_mapper +from beaker import cache + from rhodecode.model import caching_query -from beaker import cache + # Beaker CacheManager. A home base for cache configurations. cache_manager = cache.CacheManager() @@ -17,10 +19,52 @@ Session = scoped_session( ) ) +class BaseModel(object): + """Base Model for all classess + + """ + + @classmethod + def _get_keys(cls): + """return column names for this model """ + return class_mapper(cls).c.keys() + + def get_dict(self): + """return dict with keys and values corresponding + to this model data """ + + d = {} + for k in self._get_keys(): + d[k] = getattr(self, k) + return d + + def get_appstruct(self): + """return list with keys and values tupples corresponding + to this model data """ + + l = [] + for k in self._get_keys(): + l.append((k, getattr(self, k),)) + return l + + def populate_obj(self, populate_dict): + """populate model with data from given populate_dict""" + + for k in self._get_keys(): + if k in populate_dict: + setattr(self, k, populate_dict[k]) + + @classmethod + def query(cls): + return Session.query(cls) + + @classmethod + def get(cls, id_): + return Session.query(cls).get(id_) + + # The declarative Base -Base = declarative_base() -#For another db... -#Base2 = declarative_base() +Base = declarative_base(cls=BaseModel) #to use cache use this in query #.options(FromCache("sqlalchemy_cache_type", "cachekey")) diff --git a/rhodecode/model/scm.py b/rhodecode/model/scm.py --- a/rhodecode/model/scm.py +++ b/rhodecode/model/scm.py @@ -146,6 +146,7 @@ class ScmModel(BaseModel): tmp_d['rev'] = tip.revision tmp_d['contact'] = repo.dbrepo.user.full_contact tmp_d['contact_sort'] = tmp_d['contact'] + tmp_d['owner_sort'] = tmp_d['contact'] tmp_d['repo_archives'] = list(repo._get_archives()) tmp_d['last_msg'] = tip.message tmp_d['repo'] = repo diff --git a/setup.py b/setup.py --- a/setup.py +++ b/setup.py @@ -1,19 +1,19 @@ import sys +from rhodecode import get_version +from rhodecode import __platform__ + py_version = sys.version_info -from rhodecode import get_version - requirements = [ "Pylons==1.0.0", "WebHelpers==1.2", "SQLAlchemy==0.6.6", - "Mako==0.3.6", - "vcs==0.1.10", - "pygments==1.3.1", + "Mako==0.4.0", + "vcs==0.1.11", + "pygments==1.4.0", "mercurial==1.7.5", "whoosh==1.3.4", - "celery==2.1.4", - "py-bcrypt", + "celery==2.2.4", "babel", ] @@ -25,10 +25,14 @@ classifiers = ['Development Status :: 5 'Operating System :: OS Independent', 'Programming Language :: Python', ] -if sys.version_info < (2, 6): +if py_version < (2, 6): requirements.append("simplejson") requirements.append("pysqlite") +if __platform__ in ('Linux', 'Darwin'): + requirements.append("py-bcrypt") + + #additional files from project that goes somewhere in the filesystem #relative to sys.prefix data_files = [] @@ -38,6 +42,10 @@ package_data = {'rhodecode': ['i18n/*/LC description = ('Mercurial repository browser/management with ' 'build in push/pull server and full text search') +keywords = ' '.join (['rhodecode', 'rhodiumcode', 'mercurial', 'git', + 'repository management', 'hgweb replacement' + 'hgwebdir', 'gitweb replacement', 'serving hgweb', + ]) #long description try: readme_file = 'README.rst' @@ -66,7 +74,7 @@ setup( version=get_version(), description=description, long_description=long_description, - keywords='rhodiumcode mercurial web hgwebdir gitweb git replacement serving hgweb rhodecode', + keywords=keywords, license='BSD', author='Marcin Kuzminski', author_email='marcin@python-works.com',