diff --git a/.hgignore b/.hgignore --- a/.hgignore +++ b/.hgignore @@ -1,5 +1,11 @@ +syntax: glob +*.pyc +*.swp syntax: regexp +^build +^docs/build/ +^docs/_build/ ^data$ ^\.settings$ ^\.project$ @@ -7,4 +13,4 @@ syntax: regexp ^rhodecode\.db$ ^test\.db$ ^repositories\.config$ -^RhodeCode\.egg-info$ \ No newline at end of file +^RhodeCode\.egg-info$ diff --git a/MANIFEST.in b/MANIFEST.in --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,5 @@ include rhodecode/config/deployment.ini_tmpl +include rhodecode/lib/dbmigrate/migrate.cfg include README.rst recursive-include rhodecode/i18n/ * @@ -7,7 +8,7 @@ recursive-include rhodecode/i18n/ * recursive-include rhodecode/public/css * recursive-include rhodecode/public/images * #js -include rhodecode/public/js/yui2.js +include rhodecode/public/js/yui2a.js include rhodecode/public/js/excanvas.min.js include rhodecode/public/js/yui.flot.js include rhodecode/public/js/graph.js diff --git a/README.rst b/README.rst --- a/README.rst +++ b/README.rst @@ -1,23 +1,25 @@ -RhodeCode (RhodiumCode) -======================= +================================================= +Welcome to RhodeCode (RhodiumCode) documentation! +================================================= -``RhodeCode`` (formerly hg-app) is Pylons based repository management and -serving for mercurial_. It's similar to github or bitbucket, but it's suppose to run -as standalone app, it's open source and focuses more on restricted access to repositories -There's no default free access to RhodeCode You have to create an account in order -to use the application. It's powered by vcs_ library that we created to handle -many various version control systems. +``RhodeCode`` (formerly hg-app) is Pylons framework based Mercurial repository +browser/management with build in push/pull server and full text search. +It works on http/https, has build in permission/authentication(+ldap) features +It's similar to github or bitbucket, but it's suppose to run as standalone +hosted application, it's open source and focuses more on restricted access to +repositories. It's powered by vcs_ library that me and Lukasz Balcerzak created +to handle many various version control systems. RhodeCode uses `Semantic Versioning `_ - RhodeCode demo -------------- http://hg.python-works.com -The default access is +The default access is anonymous but You can login to administrative account +using those credentials - username: demo - password: demo @@ -25,14 +27,14 @@ The default access is Source code ----------- -Source code is along with issue tracker is available at +The most up to date sources can be obtained from my own RhodeCode instance +https://rhodecode.org + +Rarely updated source code and issue tracker is available at bitbcuket http://bitbucket.org/marcinkuzminski/rhodecode -Also a source codes can be obtained from demo rhodecode instance -http://hg.python-works.com/rhodecode/summary - -Instalation ------------ +Installation +------------ Please visit http://packages.python.org/RhodeCode/installation.html @@ -40,41 +42,49 @@ Instalation Features -------- -- Has it's own middleware to handle mercurial_ protocol request. Each request - can be logged and authenticated. Runs on threads unlikely to hgweb You can - make multiple pulls/pushes simultaneous. Supports http/https -- Full permissions and authentication per project private/read/write/admin. - One account for web interface and mercurial_ push/pull/clone. +- Has it's own middleware to handle mercurial_ protocol request. + Each request can be logged and authenticated. Runs on threads unlikely to + hgweb. You can make multiple pulls/pushes simultaneous. Supports http/https + and ldap +- Full permissions (private/read/write/admin) and authentication per project. + One account for web interface and mercurial_ push/pull/clone operations. - Mako templates let's you customize look and feel of application. - Beautiful diffs, annotations and source codes all colored by pygments. - Mercurial_ branch graph and yui-flot powered graphs with zooming and statistics -- Admin interface with user/permission management. User activity journal logs - pulls, pushes, forks,registrations. Possible to disable built in hooks +- Admin interface with user/permission management. Admin activity journal, logs + pulls, pushes, forks, registrations and other actions made by all users. - Server side forks, it's possible to fork a project and hack it free without - breaking the main. -- Full text search on source codes, search on file names. All powered by whoosh - and build in indexing daemons + breaking the main repository. +- Full text search powered by Whoosh on source codes, and file names. + Build in indexing daemons, with optional incremental index build (no external search servers required all in one application) -- Rss / atom feeds, gravatar support, download sources as zip/tarballs +- Setup project descriptions and info inside built in db for easy, non + file-system operations +- Inteligent cache with invalidation after push or project change, provides high + performance and always up to date data. +- Rss / atom feeds, gravatar support, download sources as zip/tar/gz - Async tasks for speed and performance using celery_ (works without them too) - Backup scripts can do backup of whole app and send it over scp to desired - location -- Setup project descriptions and info inside built in db for easy, non - file-system operations -- Added cache with invalidation on push/repo management for high performance and - always up to date data. -- Based on pylons 1.0 / sqlalchemy 0.6 / sqlite + location +- Based on pylons / sqlalchemy / sqlite / whoosh / vcs -Incoming --------- +.. include:: ./docs/screenshots.rst + + +Incoming / Plans +---------------- +- project grouping +- User groups/teams - code review (probably based on hg-review) -- full git_ support, with push/pull server +- full git_ support, with push/pull server (currently in beta tests) +- redmine integration +- public accessible activity feeds - commit based build in wiki system - clone points and cloning from remote repositories into rhodecode (git_ and mercurial_) -- some cache optimizations +- more statistics and graph (global annotation + some more statistics) - other cools stuff that i can figure out (or You can help me figure out) License @@ -83,8 +93,18 @@ License ``rhodecode`` is released under GPL_ license. -Documentation -------------- +Mailing group Q&A +----------------- + +join the `Google group `_ + +open an issue at `issue tracker `_ + +join #rhodecode on FreeNode (irc.freenode.net) +or use http://webchat.freenode.net/?channels=rhodecode for web access to irc. + +Online documentation +-------------------- Online documentation for current version is available at http://packages.python.org/RhodeCode/. @@ -92,13 +112,3 @@ Documentation make html -.. _virtualenv: http://pypi.python.org/pypi/virtualenv -.. _python: http://www.python.org/ -.. _django: http://www.djangoproject.com/ -.. _mercurial: http://mercurial.selenic.com/ -.. _subversion: http://subversion.tigris.org/ -.. _git: http://git-scm.com/ -.. _celery: http://celeryproject.org/ -.. _Sphinx: http://sphinx.pocoo.org/ -.. _GPL: http://www.gnu.org/licenses/gpl.html -.. _vcs: http://pypi.python.org/pypi/vcs \ No newline at end of file diff --git a/celeryconfig.py b/celeryconfig.py deleted file mode 100644 --- a/celeryconfig.py +++ /dev/null @@ -1,77 +0,0 @@ -# List of modules to import when celery starts. -import sys -import os -import ConfigParser -root = os.getcwd() - -PYLONS_CONFIG_NAME = 'production.ini' - -sys.path.append(root) -config = ConfigParser.ConfigParser({'here':root}) -config.read('%s/%s' % (root, PYLONS_CONFIG_NAME)) -PYLONS_CONFIG = config - -CELERY_IMPORTS = ("rhodecode.lib.celerylib.tasks",) - -## Result store settings. -CELERY_RESULT_BACKEND = "database" -CELERY_RESULT_DBURI = dict(config.items('app:main'))['sqlalchemy.db1.url'] -CELERY_RESULT_SERIALIZER = 'json' - - -BROKER_CONNECTION_MAX_RETRIES = 30 - -## Broker settings. -BROKER_HOST = "localhost" -BROKER_PORT = 5672 -BROKER_VHOST = "rabbitmqhost" -BROKER_USER = "rabbitmq" -BROKER_PASSWORD = "qweqwe" - -## Worker settings -## If you're doing mostly I/O you can have more processes, -## but if mostly spending CPU, try to keep it close to the -## number of CPUs on your machine. If not set, the number of CPUs/cores -## available will be used. -CELERYD_CONCURRENCY = 2 -# CELERYD_LOG_FILE = "celeryd.log" -CELERYD_LOG_LEVEL = "DEBUG" -CELERYD_MAX_TASKS_PER_CHILD = 3 - -#Tasks will never be sent to the queue, but executed locally instead. -CELERY_ALWAYS_EAGER = False -if PYLONS_CONFIG_NAME == 'test.ini': - #auto eager for tests - CELERY_ALWAYS_EAGER = True - -#=============================================================================== -# EMAIL SETTINGS -#=============================================================================== -pylons_email_config = dict(config.items('DEFAULT')) - -CELERY_SEND_TASK_ERROR_EMAILS = True - -#List of (name, email_address) tuples for the admins that should receive error e-mails. -ADMINS = [('Administrator', pylons_email_config.get('email_to'))] - -#The e-mail address this worker sends e-mails from. Default is "celery@localhost". -SERVER_EMAIL = pylons_email_config.get('error_email_from') - -#The mail server to use. Default is "localhost". -MAIL_HOST = pylons_email_config.get('smtp_server') - -#Username (if required) to log on to the mail server with. -MAIL_HOST_USER = pylons_email_config.get('smtp_username') - -#Password (if required) to log on to the mail server with. -MAIL_HOST_PASSWORD = pylons_email_config.get('smtp_password') - -MAIL_PORT = pylons_email_config.get('smtp_port') - - -#=============================================================================== -# INSTRUCTIONS FOR RABBITMQ -#=============================================================================== -# rabbitmqctl add_user rabbitmq qweqwe -# rabbitmqctl add_vhost rabbitmqhost -# rabbitmqctl set_permissions -p rabbitmqhost rabbitmq ".*" ".*" ".*" diff --git a/development.ini b/development.ini --- a/development.ini +++ b/development.ini @@ -1,6 +1,6 @@ ################################################################################ ################################################################################ -# rhodecode - Pylons environment configuration # +# RhodeCode - Pylons environment configuration # # # # The %(here)s variable will be replaced with the parent directory of this file# ################################################################################ @@ -9,8 +9,8 @@ debug = true ################################################################################ ## Uncomment and replace with the address which should receive ## -## any error reports after application crash ## -## Additionally those settings will be used by rhodecode mailing system ## +## any error reports after application crash ## +## Additionally those settings will be used by RhodeCode mailing system ## ################################################################################ #email_to = admin@localhost #error_email_from = paste_error@localhost @@ -19,22 +19,23 @@ debug = true #smtp_server = mail.server.com #smtp_username = -#smtp_password = +#smtp_password = #smtp_port = -#smtp_use_tls = +#smtp_use_tls = false +#smtp_use_ssl = true [server:main] ##nr of threads to spawn threadpool_workers = 5 -##max request before +##max request before thread respawn threadpool_max_requests = 6 ##option to use threads of process use_threadpool = false use = egg:Paste#http -host = 127.0.0.1 +host = 0.0.0.0 port = 5000 [app:main] @@ -43,6 +44,35 @@ full_stack = true static_files = true lang=en cache_dir = %(here)s/data +index_dir = %(here)s/data/index +cut_off_limit = 256000 + +#################################### +### CELERY CONFIG #### +#################################### +use_celery = false +broker.host = localhost +broker.vhost = rabbitmqhost +broker.port = 5672 +broker.user = rabbitmq +broker.password = qweqwe + +celery.imports = rhodecode.lib.celerylib.tasks + +celery.result.backend = amqp +celery.result.dburi = amqp:// +celery.result.serialier = json + +#celery.send.task.error.emails = true +#celery.amqp.task.result.expires = 18000 + +celeryd.concurrency = 2 +#celeryd.log.file = celeryd.log +celeryd.log.level = debug +celeryd.max.tasks.per.child = 3 + +#tasks will never be sent to the queue, but executed locally instead. +celery.always.eager = false #################################### ### BEAKER CACHE #### @@ -60,9 +90,8 @@ beaker.cache.short_term.expire=60 beaker.cache.long_term.type=memory beaker.cache.long_term.expire=36000 - beaker.cache.sql_cache_short.type=memory -beaker.cache.sql_cache_short.expire=5 +beaker.cache.sql_cache_short.expire=10 beaker.cache.sql_cache_med.type=memory beaker.cache.sql_cache_med.expire=360 @@ -74,7 +103,7 @@ beaker.cache.sql_cache_long.expire=3600 ### BEAKER SESSION #### #################################### ## Type of storage used for the session, current types are -## "dbm", "file", "memcached", "database", and "memory". +## dbm, file, memcached, database, and memory. ## The storage uses the Container API ##that is also used by the cache system. beaker.session.type = file @@ -116,7 +145,7 @@ sqlalchemy.convert_unicode = true ### LOGGING CONFIGURATION #### ################################ [loggers] -keys = root, routes, rhodecode, sqlalchemy +keys = root, routes, rhodecode, sqlalchemy,beaker,templates [handlers] keys = console @@ -136,6 +165,19 @@ level = DEBUG handlers = console qualname = routes.middleware # "level = DEBUG" logs the route matched and routing variables. +propagate = 0 + +[logger_beaker] +level = ERROR +handlers = console +qualname = beaker.container +propagate = 0 + +[logger_templates] +level = INFO +handlers = console +qualname = pylons.templating +propagate = 0 [logger_rhodecode] level = DEBUG diff --git a/docs/api/index.rst b/docs/api/index.rst new file mode 100644 --- /dev/null +++ b/docs/api/index.rst @@ -0,0 +1,10 @@ +.. _api: + +API Reference +============= + +.. toctree:: + :maxdepth: 3 + + models + \ No newline at end of file diff --git a/docs/api/models.rst b/docs/api/models.rst new file mode 100644 --- /dev/null +++ b/docs/api/models.rst @@ -0,0 +1,19 @@ +.. _models: + +The :mod:`models` Module +======================== + +.. automodule:: rhodecode.model + :members: + +.. automodule:: rhodecode.model.permission + :members: + +.. automodule:: rhodecode.model.repo + :members: + +.. automodule:: rhodecode.model.scm + :members: + +.. automodule:: rhodecode.model.user + :members: diff --git a/docs/changelog.rst b/docs/changelog.rst --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -3,22 +3,88 @@ Changelog ========= -1.0.2 (**2010-11-XX**) +1.1.0 (**2010-12-18**) ---------------------- +:status: in-progress +:branch: beta + +news +++++ + +- rewrite of internals for vcs >=0.1.10 +- uses mercurial 1.7 with dotencode disabled for maintaining compatibility + with older clients +- anonymous access, authentication via ldap +- performance upgrade for cached repos list - each repository has it's own + cache that's invalidated when needed. +- performance upgrades on repositories with large amount of commits (20K+) +- main page quick filter for filtering repositories +- user dashboards with ability to follow chosen repositories actions +- sends email to admin on new user registration +- added cache/statistics reset options into repository settings +- more detailed action logger (based on hooks) with pushed changesets lists + and options to disable those hooks from admin panel +- introduced new enhanced changelog for merges that shows more accurate results +- new improved and faster code stats (based on pygments lexers mapping tables, + showing up to 10 trending sources for each repository. Additionally stats + can be disabled in repository settings. +- gui optimizations, fixed application width to 1024px +- added cut off (for large files/changesets) limit into config files +- whoosh, celeryd, upgrade moved to paster command +- other than sqlite database backends can be used + +fixes ++++++ + +- fixes #61 forked repo was showing only after cache expired +- fixes #76 no confirmation on user deletes +- fixes #66 Name field misspelled +- fixes #72 block user removal when he owns repositories +- fixes #69 added password confirmation fields +- fixes #87 RhodeCode crashes occasionally on updating repository owner +- fixes #82 broken annotations on files with more than 1 blank line at the end +- a lot of fixes and tweaks for file browser +- fixed detached session issues +- fixed when user had no repos he would see all repos listed in my account +- fixed ui() instance bug when global hgrc settings was loaded for server + instance and all hgrc options were merged with our db ui() object +- numerous small bugfixes + +(special thanks for TkSoh for detailed feedback) + + +1.0.2 (**2010-11-12**) +---------------------- + +news +++++ + +- tested under python2.7 +- bumped sqlalchemy and celery versions + +fixes ++++++ + - fixed #59 missing graph.js - fixed repo_size crash when repository had broken symlinks - fixed python2.5 crashes. -- tested under python2.7 -- bumped sqlalcehmy and celery versions + 1.0.1 (**2010-11-10**) ---------------------- +news +++++ + +- small css updated + +fixes ++++++ + - fixed #53 python2.5 incompatible enumerate calls - fixed #52 disable mercurial extension for web - fixed #51 deleting repositories don't delete it's dependent objects -- small css updated 1.0.0 (**2010-11-02**) @@ -52,3 +118,4 @@ Changelog - Disabled dirsize in file browser, it's causing nasty bug when dir renames occure. After vcs is fixed it'll be put back again. - templating/css rewrites, optimized css. + diff --git a/docs/conf.py b/docs/conf.py --- a/docs/conf.py +++ b/docs/conf.py @@ -16,7 +16,7 @@ import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) +sys.path.insert(0, os.path.abspath('..')) # -- General configuration ----------------------------------------------------- diff --git a/docs/contributing.rst b/docs/contributing.rst new file mode 100644 --- /dev/null +++ b/docs/contributing.rst @@ -0,0 +1,9 @@ +.. _contributing: + +Contributing in RhodeCode +========================= + +If You would like to contribute to RhodeCode, please contact me, any help is +greatly appreciated. + +Thank You. diff --git a/docs/enable_git.rst b/docs/enable_git.rst new file mode 100644 --- /dev/null +++ b/docs/enable_git.rst @@ -0,0 +1,21 @@ +.. _enable_git: + +Enabling GIT support (beta) +=========================== + + +Git support in RhodeCode 1.1 was disabled due to some instability issues, but +If You would like to test it fell free to re-enable it. To enable GIT just +uncomment git line in rhodecode/__init__.py file + +.. code-block:: python + + BACKENDS = { + 'hg': 'Mercurial repository', + #'git': 'Git repository', + } + +.. note:: + Please note that it's not fully stable and it might crash (that's why it + was disabled), so be careful about enabling git support. Don't use it in + production ! \ No newline at end of file diff --git a/docs/images/screenshot1_main_page.png b/docs/images/screenshot1_main_page.png index 4bec6af17df332ce0ed74963f6a2c128bc3d1afa..69574ef777876c964ee4566105cc8204ba90bc46 GIT binary patch literal 125025 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 z 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�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 Jtb;kk{SPQX-LL=v diff --git a/docs/images/screenshot2_summary_page.png b/docs/images/screenshot2_summary_page.png index bae85edffa8cbd7d70ceade63b270e1a7079ab7e..19509c3ec9c57cf6d941c2c831cad61df2111009 GIT binary patch literal 141838 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

n*vJYzYMFtTGrjTIZX2hqeSNo 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

JL}?^z`)SkwryAqocJdv{L^y=xiYAb$bKD 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 diff --git a/docs/images/screenshot3_changelog_page.png b/docs/images/screenshot3_changelog_page.png new file mode 100644 index 0000000000000000000000000000000000000000..9e2a3d13060a3b883f31078fda97aa1b74e5a3d3 GIT binary patch literal 185199 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%0DgakUCaVW0!8c8FDy4r4=t|Geq-GJ9yUZm_K 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

QtCfaQ=~R}Wl%GaMyZ;Ww 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
sS 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

m;d+^qVKu?`$VZlDPT#`kVPi!2-#_Pz+O@c_ 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 LUbITsF!28YN3h?; diff --git a/docs/index.rst b/docs/index.rst --- a/docs/index.rst +++ b/docs/index.rst @@ -1,107 +1,43 @@ .. _index: -Welcome to RhodeCode (RhodiumCode) documentation! -================================================= - -``RhodeCode`` (formerly hg-app) is Pylons based repository management and -serving for mercurial_. It's similar to github or bitbucket, but it's suppose to run -as standalone app, it's open source and focuses more on restricted access to repositories -There's no default free access to RhodeCode You have to create an account in order -to use the application. It's powered by vcs_ library that we created to handle -many various version control systems. - -RhodeCode uses `Semantic Versioning `_ - - -RhodeCode demo --------------- - -http://hg.python-works.com - -The default access is - -- username: demo -- password: demo - -Source code ------------ - -Source code is along with issue tracker is available at -http://bitbucket.org/marcinkuzminski/rhodecode - -Also a source codes can be obtained from demo rhodecode instance -http://hg.python-works.com/rhodecode/summary - -Features --------- - -- Has it's own middleware to handle mercurial_ protocol request. Each request - can be logged and authenticated. Runs on threads unlikely to hgweb You can - make multiple pulls/pushes simultaneous. Supports http/https -- Full permissions and authentication per project private/read/write/admin. - One account for web interface and mercurial_ push/pull/clone. -- Mako templates let's you customize look and feel of application. -- Beautiful diffs, annotations and source codes all colored by pygments. -- Mercurial_ branch graph and yui-flot powered graphs with zooming and statistics -- Admin interface with user/permission management. User activity journal logs - pulls, pushes, forks,registrations. Possible to disable built in hooks -- Server side forks, it's possible to fork a project and hack it free without - breaking the main. -- Full text search on source codes, search on file names. All powered by whoosh - and build in indexing daemons - (no external search servers required all in one application) -- Rss / atom feeds, gravatar support, download sources as zip/tarballs -- Async tasks for speed and performance using celery_ (works without them too) -- Backup scripts can do backup of whole app and send it over scp to desired - location -- Setup project descriptions and info inside built in db for easy, non - file-system operations -- Added cache with invalidation on push/repo management for high performance and - always up to date data. -- Based on pylons 1.0 / sqlalchemy 0.6 / sqlite - - -.. figure:: images/screenshot1_main_page.png - :align: left - - Main page of RhodeCode - -.. figure:: images/screenshot2_summary_page.png - :align: left - - Summary page - - -Incoming --------- - -- code review (probably based on hg-review) -- full git_ support, with push/pull server -- commit based build in wiki system -- clone points and cloning from remote repositories into rhodecode - (git_ and mercurial_) -- more statistics and graph (global annotation + some more statistics) -- user customized activity dashboards -- some cache optimizations -- other cools stuff that i can figure out (or You can help me figure out) - -License -------- - -``rhodecode`` is released under GPL_ license. - +.. include:: ./../README.rst Documentation ------------- +**Installation:** + .. toctree:: :maxdepth: 1 installation + setup upgrade - setup + +**Usage** + +.. toctree:: + :maxdepth: 1 + + enable_git + statistics + +**Develop** + +.. toctree:: + :maxdepth: 1 + + contributing changelog +**API** + +.. toctree:: + :maxdepth: 2 + + api/index + + Other topics ------------ diff --git a/docs/installation.rst b/docs/installation.rst --- a/docs/installation.rst +++ b/docs/installation.rst @@ -5,31 +5,20 @@ Installation ``RhodeCode`` is written entirely in Python, but in order to use it's full potential there are some third-party requirements. When RhodeCode is used -together with celery_ You have to install some kind of message broker, +together with celery You have to install some kind of message broker, recommended one is rabbitmq_ to make the async tasks work. Of course RhodeCode works in sync mode also, then You don't have to install any third party apps. Celery_ will give You large speed improvement when using -many big repositories. If You plan to use it for 5 or 10 small repositories, it +many big repositories. If You plan to use it for 7 or 10 small repositories, it will work just fine without celery running. -After You decide to Run it with celery make sure You run celeryd and -message broker together with the application. - -Requirements for Celery ------------------------ - -**Message Broker** - -- preferred is `RabbitMq `_ -- possible other is `Redis `_ - -For installation instructions You can visit: -http://ask.github.com/celery/getting-started/index.html -It's very nice tutorial how to start celery_ with rabbitmq_ +After You decide to Run it with celery make sure You run celeryd using paster +and message broker together with the application. Install from Cheese Shop ------------------------ +Rhodecode requires python 2.x greater than version 2.5 Easiest way to install ``rhodecode`` is to run:: @@ -42,7 +31,7 @@ Or:: If you prefer to install manually simply grab latest release from http://pypi.python.org/pypi/rhodecode, decompres archive and run:: - python setup.py install + python setup.py install Step by step installation example @@ -62,7 +51,7 @@ Step by step installation example :: - source /var/www/rhodecode-venv/bin/activate + source activate /var/www/rhodecode-venv/bin/activate - Make a folder for rhodecode somewhere on the filesystem for example @@ -80,8 +69,28 @@ Step by step installation example - this will install rhodecode together with pylons and all other required python libraries +Requirements for Celery (optional) +---------------------------------- + +.. note:: + Installing message broker and using celery is optional, RhodeCode will + work without them perfectly fine. + + +**Message Broker** + +- preferred is `RabbitMq `_ +- possible other is `Redis `_ + +For installation instructions You can visit: +http://ask.github.com/celery/getting-started/index.html +It's very nice tutorial how to start celery_ with rabbitmq_ + You can now proceed to :ref:`setup` +----------------------------------- + + .. _virtualenv: http://pypi.python.org/pypi/virtualenv .. _python: http://www.python.org/ diff --git a/docs/screenshots.rst b/docs/screenshots.rst new file mode 100644 --- /dev/null +++ b/docs/screenshots.rst @@ -0,0 +1,13 @@ +.. _screenshots: + +.. figure:: images/screenshot1_main_page.png + + Main page of RhodeCode + +.. figure:: images/screenshot2_summary_page.png + + Summary page + +.. figure:: images/screenshot3_changelog_page.png + + Changelog with DAG graph \ No newline at end of file diff --git a/docs/setup.rst b/docs/setup.rst --- a/docs/setup.rst +++ b/docs/setup.rst @@ -7,13 +7,20 @@ Setup Setting up the application -------------------------- +First You'll ned to create RhodeCode config file. Run the following command +to do this + :: paster make-config RhodeCode production.ini - This will create `production.ini` config inside the directory - this config contain various settings for rhodecode, e.g port, email settings - static files, cache and logging. + this config contains various settings for RhodeCode, e.g proxy port, + email settings, usage of static files, cache, celery settings and logging. + + + +Next we need to create the database. :: @@ -24,55 +31,136 @@ Setting up the application existing ones. RhodeCode will simply add all new found repositories to it's database. Also make sure You specify correct path to repositories. - Remember that the given path for mercurial_ repositories must be write - accessible for the application. It's very important since RhodeCode web interface - will work even without such an access but, when trying to do a push it'll - eventually fail with permission denied errors. -- Run + accessible for the application. It's very important since RhodeCode web + interface will work even without such an access but, when trying to do a + push it'll eventually fail with permission denied errors. + +You are ready to use rhodecode, to run it simply execute :: paster serve production.ini -- This command runs the rhodecode server the app should be available at the +- This command runs the RhodeCode server the app should be available at the 127.0.0.1:5000. This ip and port is configurable via the production.ini - file created in previous step + file created in previous step - Use admin account you created to login. - Default permissions on each repository is read, and owner is admin. So - remember to update these if needed. + remember to update these if needed. In the admin panel You can toggle ldap, + anonymous, permissions settings. As well as edit more advanced options on + users and repositories -Note ----- + +Setting up Whoosh full text search +---------------------------------- -RhodeCode when running without the celery it's running all it's task in sync -mode, for first few times when visiting summary page You can notice few -slow downs, this is due the statistics building it's cache. After all changesets -are parsed it'll take the stats from cache and run much faster. Each summary -page display parse at most 250 changesets in order to not stress the cpu, so -the full stats are going to be loaded after total_number_of_changesets/250 -summary page visits. +Index for whoosh can be build starting from version 1.1 using paster command +passing repo locations to index, as well as Your config file that stores +whoosh index files locations. There is possible to pass `-f` to the options +to enable full index rebuild. Without that indexing will run always in in +incremental mode. + +:: + paster make-index production.ini --repo-location= - -Setting up Whoosh ------------------ +for full index rebuild You can use + +:: + + paster make-index production.ini -f --repo-location= - For full text search You can either put crontab entry for +This command can be run even from crontab in order to do periodical +index builds and keep Your index always up to date. An example entry might +look like this + :: - python /var/www/rhodecode//lib/indexers/daemon.py incremental + /path/to/python/bin/paster /path/to/rhodecode/production.ini --repo-location= -When using incremental mode whoosh will check last modification date of each file -and add it to reindex if newer file is available. Also indexing daemon checks -for removed files and removes them from index. Sometime You might want to rebuild -index from scratch, in admin panel You can check `build from scratch` flag -and in standalone daemon You can pass `full` instead on incremental to build -remove previous index and build new one. +When using incremental(default) mode whoosh will check last modification date +of each file and add it to reindex if newer file is available. Also indexing +daemon checks for removed files and removes them from index. + +Sometime You might want to rebuild index from scratch. You can do that using +the `-f` flag passed to paster command or, in admin panel You can check +`build from scratch` flag. + + +Setting up LDAP support +----------------------- + +RhodeCode starting from version 1.1 supports ldap authentication. In order +to use ldap, You have to install python-ldap package. This package is available +via pypi, so You can install it by running + +:: + + easy_install python-ldap + +:: + + pip install python-ldap + +.. note:: + python-ldap requires some certain libs on Your system, so before installing + it check that You have at least `openldap`, and `sasl` libraries. + +ldap settings are located in admin->ldap section, + +Here's a typical ldap setup:: + + Enable ldap = checked #controls if ldap access is enabled + Host = host.domain.org #actual ldap server to connect + Port = 389 or 689 for ldaps #ldap server ports + Enable LDAPS = unchecked #enable disable ldaps + Account = #access for ldap server(if required) + Password = #password for ldap server(if required) + Base DN = uid=%(user)s,CN=users,DC=host,DC=domain,DC=org + + +`Account` and `Password` are optional, and used for two-phase ldap +authentication so those are credentials to access Your ldap, if it doesn't +support anonymous search/user lookups. + +Base DN must have %(user)s template inside, it's a placer where Your uid used +to login would go, it allows admins to specify not standard schema for uid +variable + +If all data are entered correctly, and `python-ldap` is properly installed +Users should be granted to access RhodeCode wit ldap accounts. When +logging at the first time an special ldap account is created inside RhodeCode, +so You can control over permissions even on ldap users. If such user exists +already in RhodeCode database ldap user with the same username would be not +able to access RhodeCode. + +If You have problems with ldap access and believe You entered correct +information check out the RhodeCode logs,any error messages sent from +ldap will be saved there. + + + +Setting Up Celery +----------------- + +Since version 1.1 celery is configured by the rhodecode ini configuration files +simply set use_celery=true in the ini file then add / change the configuration +variables inside the ini file. + +Remember that the ini files uses format with '.' not with '_' like celery +so for example setting `BROKER_HOST` in celery means setting `broker.host` in +the config file. + +In order to make start using celery run:: + paster celeryd + Nginx virtual host example -------------------------- -Sample config for nginx:: +Sample config for nginx using proxy:: server { listen 80; @@ -122,6 +210,16 @@ in production.ini file:: To not have the statics served by the application. And improve speed. +Apache reverse proxy +-------------------- +Tutorial can be found here +http://wiki.pylonshq.com/display/pylonscookbook/Apache+as+a+reverse+proxy+for+Pylons + + +Apache's example FCGI config +---------------------------- + +TODO ! Other configuration files ------------------------- @@ -132,6 +230,29 @@ http://hg.python-works.com/rhodecode/fil and also an celeryconfig file can be use from here: http://hg.python-works.com/rhodecode/files/tip/celeryconfig.py +Troubleshooting +--------------- + +- missing static files ? + + - make sure either to set the `static_files = true` in the .ini file or + 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 + +- can't install celery/rabbitmq + + - don't worry RhodeCode works without them too. No extra setup required + +- long lasting push timeouts ? + + - make sure You set a longer timeouts in Your proxy/fcgi settings, timeouts + are caused by https server and not RhodeCode + +- large pushes timeouts ? + + - make sure You set a proper max_body_size for the http server + .. _virtualenv: http://pypi.python.org/pypi/virtualenv diff --git a/docs/statistics.rst b/docs/statistics.rst new file mode 100644 --- /dev/null +++ b/docs/statistics.rst @@ -0,0 +1,32 @@ +.. _statistics: + + +Statistics +========== + +RhodeCode statistics system is heavy on resources, so in order to keep a +balance between the usability and performance statistics are cached inside db +and are gathered incrementally, this is how RhodeCode does this: + +With Celery disabled +++++++++++++++++++++ + +- on each first visit on summary page a set of 250 commits are parsed and + updates statistics cache +- this happens on each single visit of statistics page until all commits are + fetched. Statistics are kept cached until some more commits are added to + repository, in such case RhodeCode will fetch only the ones added and will + update it's cache. + + +With Celery enabled ++++++++++++++++++++ + +- on first visit on summary page RhodeCode will create task that will execute + on celery workers, that will gather all stats until all commits are parsed, + each task will parse 250 commits, and run next task to parse next 250 + commits, until all are parsed. + +.. note:: + In any time You can disable statistics on each repository in repository edit + form in admin panel, just uncheck the statistics checkbox. \ No newline at end of file diff --git a/docs/theme/nature/layout.html b/docs/theme/nature/layout.html new file mode 100644 --- /dev/null +++ b/docs/theme/nature/layout.html @@ -0,0 +1,14 @@ +{% extends "basic/layout.html" %} + +{% block sidebarlogo %} +

Support my development effort.

+
+
+ + + + +
+
+{% endblock %}} diff --git a/docs/upgrade.rst b/docs/upgrade.rst --- a/docs/upgrade.rst +++ b/docs/upgrade.rst @@ -22,7 +22,26 @@ Then make sure You run from the installa paster make-config RhodeCode production.ini This will display any changes made from new version of RhodeCode To your -current config. And tries to do an automerge. +current config. And tries to do an automerge. It's always better to do a backup +of config file and recheck the content after merge. + +It's also good to rebuild the whoosh index since after upgrading the whoosh +version there could be introduced incompatible index changes. + + +The last step is to upgrade the database. To do this simply run + +:: + + paster upgrade-db production.ini + +This will upgrade schema, as well as update some default on the database, +always recheck the settings of the application, if there are no new options +that need to be set. + +.. note:: + Always perform a database backup before doing upgrade. + .. _virtualenv: http://pypi.python.org/pypi/virtualenv diff --git a/production.ini b/production.ini --- a/production.ini +++ b/production.ini @@ -22,6 +22,7 @@ debug = true #smtp_password = #smtp_port = #smtp_use_tls = false +#smtp_use_ssl = true [server:main] ##nr of threads to spawn @@ -43,6 +44,35 @@ full_stack = true static_files = false lang=en cache_dir = %(here)s/data +index_dir = %(here)s/data/index +cut_off_limit = 256000 + +#################################### +### CELERY CONFIG #### +#################################### +use_celery = false +broker.host = localhost +broker.vhost = rabbitmqhost +broker.port = 5672 +broker.user = rabbitmq +broker.password = qweqwe + +celery.imports = rhodecode.lib.celerylib.tasks + +celery.result.backend = amqp +celery.result.dburi = amqp:// +celery.result.serialier = json + +#celery.send.task.error.emails = true +#celery.amqp.task.result.expires = 18000 + +celeryd.concurrency = 2 +#celeryd.log.file = celeryd.log +celeryd.log.level = debug +celeryd.max.tasks.per.child = 3 + +#tasks will never be sent to the queue, but executed locally instead. +celery.always.eager = false #################################### ### BEAKER CACHE #### @@ -136,6 +166,7 @@ level = INFO handlers = console qualname = routes.middleware # "level = DEBUG" logs the route matched and routing variables. +propagate = 0 [logger_rhodecode] level = DEBUG diff --git a/rhodecode/__init__.py b/rhodecode/__init__.py --- a/rhodecode/__init__.py +++ b/rhodecode/__init__.py @@ -1,8 +1,16 @@ -#!/usr/bin/env python -# encoding: utf-8 -# RhodeCode, a web based repository management based on pylons -# Copyright (C) 2009-2010 Marcin Kuzminski -# +# -*- coding: utf-8 -*- +""" + rhodecode.__init__ + ~~~~~~~~~~~~~~~~~~ + + RhodeCode, a web based repository management based on pylons + versioning implementation: http://semver.org/ + + :created_on: Apr 9, 2010 + :author: marcink + :copyright: (C) 2009-2010 Marcin Kuzminski + :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; version 2 @@ -17,19 +25,28 @@ # 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 9, 2010 -RhodeCode, a web based repository management based on pylons -versioning implementation: http://semver.org/ -@author: marcink -""" + + +VERSION = (1, 1, 0) +__version__ = '.'.join((str(each) for each in VERSION[:4])) +__dbversion__ = 2 #defines current db version for migrations -VERSION = (1, 0, 2,) +try: + from rhodecode.lib.utils import get_current_revision + _rev = get_current_revision() +except ImportError: + #this is needed when doing some setup.py operations + _rev = False -__version__ = '.'.join((str(each) for each in VERSION[:4])) +if len(VERSION) > 3 and _rev: + __version__ += ' [rev:%s]' % _rev[0] def get_version(): - """ - Returns shorter version (digit parts only) as string. - """ + """Returns shorter version (digit parts only) as string.""" + return '.'.join((str(each) for each in VERSION[:3])) + +BACKENDS = { + 'hg': 'Mercurial repository', + #'git': 'Git repository', +} 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 @@ -1,6 +1,6 @@ ################################################################################ ################################################################################ -# rhodecode - Pylons environment configuration # +# RhodeCode - Pylons environment configuration # # # # The %(here)s variable will be replaced with the parent directory of this file# ################################################################################ @@ -10,7 +10,7 @@ debug = true ################################################################################ ## Uncomment and replace with the address which should receive ## ## any error reports after application crash ## -## Additionally those settings will be used by rhodecode mailing system ## +## Additionally those settings will be used by RhodeCode mailing system ## ################################################################################ #email_to = admin@localhost #error_email_from = paste_error@localhost @@ -22,13 +22,14 @@ debug = true #smtp_password = #smtp_port = #smtp_use_tls = false +#smtp_use_ssl = true [server:main] ##nr of threads to spawn threadpool_workers = 5 ##max request before thread respawn -threadpool_max_requests = 2 +threadpool_max_requests = 10 ##option to use threads of process use_threadpool = true @@ -43,7 +44,36 @@ full_stack = true static_files = true lang=en cache_dir = %(here)s/data +index_dir = %(here)s/data/index app_instance_uuid = ${app_instance_uuid} +cut_off_limit = 256000 + +#################################### +### CELERY CONFIG #### +#################################### +use_celery = false +broker.host = localhost +broker.vhost = rabbitmqhost +broker.port = 5672 +broker.user = rabbitmq +broker.password = qweqwe + +celery.imports = rhodecode.lib.celerylib.tasks + +celery.result.backend = amqp +celery.result.dburi = amqp:// +celery.result.serialier = json + +#celery.send.task.error.emails = true +#celery.amqp.task.result.expires = 18000 + +celeryd.concurrency = 2 +#celeryd.log.file = celeryd.log +celeryd.log.level = debug +celeryd.max.tasks.per.child = 3 + +#tasks will never be sent to the queue, but executed locally instead. +celery.always.eager = false #################################### ### BEAKER CACHE #### @@ -62,7 +92,7 @@ beaker.cache.long_term.type=memory beaker.cache.long_term.expire=36000 beaker.cache.sql_cache_short.type=memory -beaker.cache.sql_cache_short.expire=5 +beaker.cache.sql_cache_short.expire=10 beaker.cache.sql_cache_med.type=memory beaker.cache.sql_cache_med.expire=360 @@ -136,6 +166,7 @@ level = INFO handlers = console qualname = routes.middleware # "level = DEBUG" logs the route matched and routing variables. +propagate = 0 [logger_rhodecode] level = DEBUG diff --git a/rhodecode/config/environment.py b/rhodecode/config/environment.py --- a/rhodecode/config/environment.py +++ b/rhodecode/config/environment.py @@ -6,7 +6,7 @@ from rhodecode.config.routing import mak from rhodecode.lib.auth import set_available_permissions, set_base_path from rhodecode.lib.utils import repo2db_mapper, make_ui, set_rhodecode_config from rhodecode.model import init_model -from rhodecode.model.hg_model import _get_repos_cached_initial +from rhodecode.model.scm import ScmModel from sqlalchemy import engine_from_config import logging import os @@ -20,7 +20,7 @@ def load_environment(global_conf, app_co object """ config = PylonsConfig() - + # Pylons paths root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) paths = dict(root=root, @@ -34,11 +34,11 @@ def load_environment(global_conf, app_co config['routes.map'] = make_map(config) config['pylons.app_globals'] = app_globals.Globals(config) config['pylons.h'] = rhodecode.lib.helpers - + # Setup cache object as early as possible import pylons pylons.cache._push_object(config['pylons.app_globals'].cache) - + # Create the Mako TemplateLookup, with the default auto-escaping config['pylons.app_globals'].mako_lookup = TemplateLookup( directories=paths['templates'], @@ -52,9 +52,10 @@ def load_environment(global_conf, app_co test = os.path.split(config['__file__'])[-1] == 'test.ini' if test: from rhodecode.lib.utils import create_test_env, create_test_index - create_test_env('/tmp', config) - create_test_index('/tmp/*', True) - + from rhodecode.tests import TESTS_TMP_PATH + create_test_env(TESTS_TMP_PATH, config) + create_test_index(TESTS_TMP_PATH, True) + #MULTIPLE DB configs # Setup the SQLAlchemy database engine if config['debug'] and not test: @@ -68,12 +69,13 @@ def load_environment(global_conf, app_co init_model(sa_engine_db1) #init baseui config['pylons.app_globals'].baseui = make_ui('db') - - repo2db_mapper(_get_repos_cached_initial(config['pylons.app_globals'], initial)) + + g = config['pylons.app_globals'] + repo2db_mapper(ScmModel().repo_scan(g.paths[0][1], g.baseui)) set_available_permissions(config) set_base_path(config) set_rhodecode_config(config) # CONFIGURATION OPTIONS HERE (note: all config options will override # any Pylons config options) - + return config diff --git a/rhodecode/config/middleware.py b/rhodecode/config/middleware.py --- a/rhodecode/config/middleware.py +++ b/rhodecode/config/middleware.py @@ -8,8 +8,10 @@ from pylons.middleware import ErrorHandl from pylons.wsgiapp import PylonsApp from routes.middleware import RoutesMiddleware from rhodecode.lib.middleware.simplehg import SimpleHg +from rhodecode.lib.middleware.simplegit import SimpleGit from rhodecode.lib.middleware.https_fixup import HttpsFixup from rhodecode.config.environment import load_environment +from paste.gzipper import make_gzip_middleware def make_app(global_conf, full_stack=True, static_files=True, **app_conf): """Create a Pylons WSGI application and return it @@ -35,15 +37,16 @@ def make_app(global_conf, full_stack=Tru # The Pylons WSGI app app = PylonsApp(config=config) - + # Routing/Session/Cache Middleware app = RoutesMiddleware(app, config['routes.map']) app = SessionMiddleware(app, config) - + # CUSTOM MIDDLEWARE HERE (filtered by error handling middlewares) - + app = SimpleHg(app, config) - + app = SimpleGit(app, config) + if asbool(full_stack): # Handle Python exceptions app = ErrorHandler(app, global_conf, **config['pylons.errorware']) @@ -54,10 +57,10 @@ def make_app(global_conf, full_stack=Tru app = StatusCodeRedirect(app) else: app = StatusCodeRedirect(app, [400, 401, 403, 404, 500]) - + #enable https redirets based on HTTP_X_URL_SCHEME set by proxy app = HttpsFixup(app) - + # Establish the Registry for this application app = RegistryManager(app) @@ -65,7 +68,8 @@ def make_app(global_conf, full_stack=Tru # Serve static files static_app = StaticURLParser(config['pylons.paths']['static_files']) app = Cascade([static_app, app]) - + app = make_gzip_middleware(app, global_conf, compress_level=1) + app.config = config return app diff --git a/rhodecode/config/routing.py b/rhodecode/config/routing.py --- a/rhodecode/config/routing.py +++ b/rhodecode/config/routing.py @@ -35,7 +35,7 @@ def make_map(config): #========================================================================== #MAIN PAGE - map.connect('hg_home', '/', controller='hg', action='index') + map.connect('home', '/', controller='home', action='index') map.connect('bugtracker', "http://bitbucket.org/marcinkuzminski/rhodecode/issues", _static=True) map.connect('gpl_license', "http://www.gnu.org/licenses/gpl.html", _static=True) #ADMIN REPOSITORY REST ROUTES @@ -73,13 +73,27 @@ def make_map(config): m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*}", action="delete_perm_user", conditions=dict(method=["DELETE"], function=check_repo)) - + #settings actions + m.connect('repo_stats', "/repos_stats/{repo_name:.*}", + action="repo_stats", conditions=dict(method=["DELETE"], + function=check_repo)) + m.connect('repo_cache', "/repos_cache/{repo_name:.*}", + action="repo_cache", conditions=dict(method=["DELETE"], + function=check_repo)) #ADMIN USER REST ROUTES map.resource('user', 'users', controller='admin/users', path_prefix='/_admin') #ADMIN PERMISSIONS REST ROUTES map.resource('permission', 'permissions', controller='admin/permissions', path_prefix='/_admin') + + ##ADMIN LDAP SETTINGS + map.connect('ldap_settings', '/_admin/ldap', controller='admin/ldap_settings', + action='ldap_settings', conditions=dict(method=["POST"])) + map.connect('ldap_home', '/_admin/ldap', controller='admin/ldap_settings',) + + + #ADMIN SETTINGS REST ROUTES with map.submapper(path_prefix='/_admin', controller='admin/settings') as m: m.connect("admin_settings", "/settings", @@ -116,6 +130,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') diff --git a/rhodecode/controllers/admin/admin.py b/rhodecode/controllers/admin/admin.py --- a/rhodecode/controllers/admin/admin.py +++ b/rhodecode/controllers/admin/admin.py @@ -1,8 +1,15 @@ -#!/usr/bin/env python -# encoding: utf-8 -# admin controller for pylons -# Copyright (C) 2009-2010 Marcin Kuzminski - +# -*- coding: utf-8 -*- +""" + rhodecode.controllers.admin.admin + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Controller for Admin panel of Rhodecode + + :created_on: Apr 7, 2010 + :author: marcink + :copyright: (C) 2009-2010 Marcin Kuzminski + :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; version 2 @@ -17,15 +24,10 @@ # 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 7, 2010 -admin controller for pylons -@author: marcink -""" + import logging -from pylons import request, response, session, tmpl_context as c +from pylons import request, tmpl_context as c from rhodecode.lib.base import BaseController, render -from rhodecode.model import meta from rhodecode.model.db import UserLog from webhelpers.paginate import Page from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator @@ -33,19 +35,19 @@ from rhodecode.lib.auth import LoginRequ log = logging.getLogger(__name__) class AdminController(BaseController): - + @LoginRequired() def __before__(self): super(AdminController, self).__before__() - - @HasPermissionAllDecorator('hg.admin') + + @HasPermissionAllDecorator('hg.admin') def index(self): - + users_log = self.sa.query(UserLog).order_by(UserLog.action_date.desc()) p = int(request.params.get('page', 1)) c.users_log = Page(users_log, page=p, items_per_page=10) c.log_data = render('admin/admin_log.html') if request.params.get('partial'): return c.log_data - return render('admin/admin.html') - + return render('admin/admin.html') + diff --git a/rhodecode/controllers/admin/ldap_settings.py b/rhodecode/controllers/admin/ldap_settings.py new file mode 100644 --- /dev/null +++ b/rhodecode/controllers/admin/ldap_settings.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- +""" + package.rhodecode.controllers.admin.ldap_settings + ~~~~~~~~~~~~~~ + + ldap controller for RhodeCode + :created_on: Nov 26, 2010 + :author: marcink + :copyright: (C) 2009-2010 Marcin Kuzminski + :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; 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. +import logging +import formencode +import traceback + +from formencode import htmlfill + +from pylons import request, response, session, tmpl_context as c, url +from pylons.controllers.util import abort, redirect +from pylons.i18n.translation import _ + +from rhodecode.lib.base import BaseController, render +from rhodecode.lib import helpers as h +from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator +from rhodecode.lib.auth_ldap import LdapImportError +from rhodecode.model.settings import SettingsModel +from rhodecode.model.forms import LdapSettingsForm +from sqlalchemy.exc import DatabaseError + +log = logging.getLogger(__name__) + + + +class LdapSettingsController(BaseController): + + @LoginRequired() + @HasPermissionAllDecorator('hg.admin') + def __before__(self): + c.admin_user = session.get('admin_user') + c.admin_username = session.get('admin_username') + super(LdapSettingsController, self).__before__() + + def index(self): + defaults = SettingsModel().get_ldap_settings() + + return htmlfill.render( + render('admin/ldap/ldap.html'), + defaults=defaults, + encoding="UTF-8", + force_defaults=True,) + + def ldap_settings(self): + """ + POST ldap create and store ldap settings + """ + + settings_model = SettingsModel() + _form = LdapSettingsForm()() + + try: + form_result = _form.to_python(dict(request.POST)) + try: + + for k, v in form_result.items(): + if k.startswith('ldap_'): + setting = settings_model.get(k) + setting.app_settings_value = v + self.sa.add(setting) + + self.sa.commit() + h.flash(_('Ldap settings updated successfully'), + category='success') + except (DatabaseError,): + raise + except LdapImportError: + h.flash(_('Unable to activate ldap. The "python-ldap" library ' + 'is missing.'), category='warning') + + except formencode.Invalid, errors: + + return htmlfill.render( + render('admin/ldap/ldap.html'), + defaults=errors.value, + errors=errors.error_dict or {}, + prefix_error=False, + encoding="UTF-8") + except Exception: + log.error(traceback.format_exc()) + h.flash(_('error occured during update of ldap settings'), + category='error') + + return redirect(url('ldap_home')) diff --git a/rhodecode/controllers/admin/permissions.py b/rhodecode/controllers/admin/permissions.py --- a/rhodecode/controllers/admin/permissions.py +++ b/rhodecode/controllers/admin/permissions.py @@ -1,8 +1,15 @@ -#!/usr/bin/env python -# encoding: utf-8 -# permissions controller for pylons -# Copyright (C) 2009-2010 Marcin Kuzminski -# +# -*- coding: utf-8 -*- +""" + rhodecode.controllers.admin.permissions + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + permissions controller for Rhodecode + + :created_on: Apr 27, 2010 + :author: marcink + :copyright: (C) 2009-2010 Marcin Kuzminski + :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; version 2 @@ -17,11 +24,6 @@ # 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 27, 2010 -permissions controller for pylons -@author: marcink -""" from formencode import htmlfill from pylons import request, session, tmpl_context as c, url @@ -29,11 +31,12 @@ from pylons.controllers.util import abor from pylons.i18n.translation import _ from rhodecode.lib import helpers as h from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator +from rhodecode.lib.auth_ldap import LdapImportError from rhodecode.lib.base import BaseController, render -from rhodecode.model.db import User, UserLog -from rhodecode.model.forms import UserForm, DefaultPermissionsForm -from rhodecode.model.permission_model import PermissionModel -from rhodecode.model.user_model import UserModel +from rhodecode.model.forms import LdapSettingsForm, DefaultPermissionsForm +from rhodecode.model.permission import PermissionModel +from rhodecode.model.settings import SettingsModel +from rhodecode.model.user import UserModel import formencode import logging import traceback @@ -45,29 +48,30 @@ class PermissionsController(BaseControll # To properly map this controller, ensure your config/routing.py # file has a resource setup: # map.resource('permission', 'permissions') - + @LoginRequired() @HasPermissionAllDecorator('hg.admin') def __before__(self): c.admin_user = session.get('admin_user') c.admin_username = session.get('admin_username') super(PermissionsController, self).__before__() - + self.perms_choices = [('repository.none', _('None'),), ('repository.read', _('Read'),), ('repository.write', _('Write'),), ('repository.admin', _('Admin'),)] self.register_choices = [ - ('hg.register.none', 'disabled'), + ('hg.register.none', + _('disabled')), ('hg.register.manual_activate', - _('allowed with manual account activation')), + _('allowed with manual account activation')), ('hg.register.auto_activate', - _('allowed with automatic account activation')), ] - + _('allowed with automatic account activation')), ] + self.create_choices = [('hg.create.none', _('Disabled')), - ('hg.create.repository', _('Enabled'))] + ('hg.create.repository', _('Enabled'))] - + def index(self, format='html'): """GET /permissions: All items in the collection""" # url('permissions') @@ -88,38 +92,39 @@ class PermissionsController(BaseControll # h.form(url('permission', id=ID), # method='put') # url('permission', id=ID) - + permission_model = PermissionModel() - + _form = DefaultPermissionsForm([x[0] for x in self.perms_choices], [x[0] for x in self.register_choices], [x[0] for x in self.create_choices])() - + try: form_result = _form.to_python(dict(request.POST)) form_result.update({'perm_user_name':id}) permission_model.update(form_result) - h.flash(_('Default permissions updated succesfully'), + h.flash(_('Default permissions updated successfully'), category='success') - + except formencode.Invalid, errors: c.perms_choices = self.perms_choices c.register_choices = self.register_choices c.create_choices = self.create_choices - + defaults = errors.value + return htmlfill.render( render('admin/permissions/permissions.html'), - defaults=errors.value, + defaults=defaults, errors=errors.error_dict or {}, prefix_error=False, - encoding="UTF-8") + encoding="UTF-8") except Exception: log.error(traceback.format_exc()) h.flash(_('error occured during update of permissions'), category='error') - + return redirect(url('edit_permission', id=id)) - + def delete(self, id): @@ -141,23 +146,26 @@ class PermissionsController(BaseControll c.perms_choices = self.perms_choices c.register_choices = self.register_choices c.create_choices = self.create_choices - + if id == 'default': - defaults = {'_method':'put'} - for p in UserModel().get_default().user_perms: + default_user = UserModel().get_by_username('default') + defaults = {'_method':'put', + 'anonymous':default_user.active} + + for p in default_user.user_perms: if p.permission.permission_name.startswith('repository.'): - defaults['default_perm'] = p.permission.permission_name - + defaults['default_perm'] = p.permission.permission_name + if p.permission.permission_name.startswith('hg.register.'): defaults['default_register'] = p.permission.permission_name - + if p.permission.permission_name.startswith('hg.create.'): defaults['default_create'] = p.permission.permission_name - + return htmlfill.render( render('admin/permissions/permissions.html'), defaults=defaults, encoding="UTF-8", - force_defaults=True,) + force_defaults=True,) else: return redirect(url('admin_home')) diff --git a/rhodecode/controllers/admin/repos.py b/rhodecode/controllers/admin/repos.py --- a/rhodecode/controllers/admin/repos.py +++ b/rhodecode/controllers/admin/repos.py @@ -1,8 +1,15 @@ -#!/usr/bin/env python -# encoding: utf-8 -# repos controller for pylons -# Copyright (C) 2009-2010 Marcin Kuzminski -# +# -*- coding: utf-8 -*- +""" + rhodecode.controllers.admin.repos + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Admin controller for RhodeCode + + :created_on: Apr 7, 2010 + :author: marcink + :copyright: (C) 2009-2010 Marcin Kuzminski + :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; version 2 @@ -17,17 +24,18 @@ # 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 7, 2010 -admin controller for pylons -@author: marcink -""" + +import logging +import traceback +import formencode +from operator import itemgetter from formencode import htmlfill -from operator import itemgetter + from paste.httpexceptions import HTTPInternalServerError from pylons import request, response, session, tmpl_context as c, url from pylons.controllers.util import abort, redirect from pylons.i18n.translation import _ + from rhodecode.lib import helpers as h from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \ HasPermissionAnyDecorator @@ -35,11 +43,9 @@ from rhodecode.lib.base import BaseContr from rhodecode.lib.utils import invalidate_cache, action_logger from rhodecode.model.db import User from rhodecode.model.forms import RepoForm -from rhodecode.model.hg_model import HgModel -from rhodecode.model.repo_model import RepoModel -import formencode -import logging -import traceback +from rhodecode.model.scm import ScmModel +from rhodecode.model.repo import RepoModel + log = logging.getLogger(__name__) @@ -48,22 +54,22 @@ class ReposController(BaseController): # To properly map this controller, ensure your config/routing.py # file has a resource setup: # map.resource('repo', 'repos') - + @LoginRequired() @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository') def __before__(self): c.admin_user = session.get('admin_user') c.admin_username = session.get('admin_username') super(ReposController, self).__before__() - - @HasPermissionAllDecorator('hg.admin') + + @HasPermissionAllDecorator('hg.admin') def index(self, format='html'): """GET /repos: All items in the collection""" # url('repos') - cached_repo_list = HgModel().get_repos() + cached_repo_list = ScmModel().get_repos() c.repos_list = sorted(cached_repo_list, key=itemgetter('name_sort')) return render('admin/repos/repos.html') - + @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository') def create(self): """POST /repos: Create a new item""" @@ -74,7 +80,6 @@ class ReposController(BaseController): try: form_result = _form.to_python(dict(request.POST)) repo_model.create(form_result, c.rhodecode_user) - invalidate_cache('cached_repo_list') h.flash(_('created repository %s') % form_result['repo_name'], category='success') @@ -83,22 +88,22 @@ class ReposController(BaseController): form_result['repo_name'], '', self.sa) else: action_logger(self.rhodecode_user, 'admin_created_repo', - form_result['repo_name'], '', self.sa) - + form_result['repo_name'], '', self.sa) + except formencode.Invalid, errors: c.new_repo = errors.value['repo_name'] - + if request.POST.get('user_created'): r = render('admin/repos/repo_add_create_repository.html') - else: + else: r = render('admin/repos/repo_add.html') - + return htmlfill.render( r, defaults=errors.value, errors=errors.error_dict or {}, prefix_error=False, - encoding="UTF-8") + encoding="UTF-8") except Exception: log.error(traceback.format_exc()) @@ -106,9 +111,9 @@ class ReposController(BaseController): % form_result.get('repo_name') h.flash(msg, category='error') if request.POST.get('user_created'): - return redirect(url('hg_home')) + return redirect(url('home')) return redirect(url('repos')) - + @HasPermissionAllDecorator('hg.admin') def new(self, format='html'): """GET /repos/new: Form to create a new item""" @@ -116,7 +121,7 @@ class ReposController(BaseController): c.new_repo = h.repo_name_slug(new_repo) return render('admin/repos/repo_add.html') - + @HasPermissionAllDecorator('hg.admin') def update(self, repo_name): """PUT /repos/repo_name: Update an existing item""" @@ -129,16 +134,33 @@ class ReposController(BaseController): repo_model = RepoModel() changed_name = repo_name _form = RepoForm(edit=True, old_data={'repo_name':repo_name})() - + try: form_result = _form.to_python(dict(request.POST)) repo_model.update(repo_name, form_result) - invalidate_cache('cached_repo_list') - h.flash(_('Repository %s updated succesfully' % repo_name), + invalidate_cache('get_repo_cached_%s' % repo_name) + h.flash(_('Repository %s updated successfully' % repo_name), category='success') changed_name = form_result['repo_name'] + action_logger(self.rhodecode_user, 'admin_updated_repo', + changed_name, '', self.sa) + except formencode.Invalid, errors: - c.repo_info = repo_model.get(repo_name) + c.repo_info = repo_model.get_by_repo_name(repo_name) + if c.repo_info.stats: + last_rev = c.repo_info.stats.stat_on_revision + else: + last_rev = 0 + c.stats_revision = last_rev + r = ScmModel().get(repo_name) + c.repo_last_rev = r.revisions[-1] if r.revisions else 0 + + if last_rev == 0: + c.stats_percentage = 0 + else: + c.stats_percentage = '%.2f' % ((float((last_rev)) / + c.repo_last_rev) * 100) + c.users_array = repo_model.get_users_js() errors.value.update({'user':c.repo_info.user.username}) return htmlfill.render( @@ -147,14 +169,14 @@ class ReposController(BaseController): errors=errors.error_dict or {}, prefix_error=False, encoding="UTF-8") - + except Exception: log.error(traceback.format_exc()) - h.flash(_('error occured during update of repository %s') \ + h.flash(_('error occurred during update of repository %s') \ % repo_name, category='error') - + return redirect(url('edit_repo', repo_name=changed_name)) - + @HasPermissionAllDecorator('hg.admin') def delete(self, repo_name): """DELETE /repos/repo_name: Delete an existing item""" @@ -164,82 +186,128 @@ class ReposController(BaseController): # h.form(url('repo', repo_name=ID), # method='delete') # url('repo', repo_name=ID) - + repo_model = RepoModel() - repo = repo_model.get(repo_name) + repo = repo_model.get_by_repo_name(repo_name) if not repo: - h.flash(_('%s repository is not mapped to db perhaps' + h.flash(_('%s repository is not mapped to db perhaps' ' it was moved or renamed from the filesystem' ' please run the application again' ' in order to rescan repositories') % repo_name, category='error') - + return redirect(url('repos')) try: action_logger(self.rhodecode_user, 'admin_deleted_repo', repo_name, '', self.sa) - repo_model.delete(repo) - invalidate_cache('cached_repo_list') + repo_model.delete(repo) + invalidate_cache('get_repo_cached_%s' % repo_name) h.flash(_('deleted repository %s') % repo_name, category='success') - + except Exception, e: log.error(traceback.format_exc()) h.flash(_('An error occured during deletion of %s') % repo_name, category='error') - + return redirect(url('repos')) - - @HasPermissionAllDecorator('hg.admin') + + @HasPermissionAllDecorator('hg.admin') def delete_perm_user(self, repo_name): """ DELETE an existing repository permission user :param repo_name: """ - + try: repo_model = RepoModel() - repo_model.delete_perm_user(request.POST, repo_name) + repo_model.delete_perm_user(request.POST, repo_name) except Exception, e: h.flash(_('An error occured during deletion of repository user'), category='error') raise HTTPInternalServerError() - - @HasPermissionAllDecorator('hg.admin') + + @HasPermissionAllDecorator('hg.admin') + def repo_stats(self, repo_name): + """ + DELETE an existing repository statistics + :param repo_name: + """ + + try: + repo_model = RepoModel() + repo_model.delete_stats(repo_name) + except Exception, e: + h.flash(_('An error occured during deletion of repository stats'), + category='error') + return redirect(url('edit_repo', repo_name=repo_name)) + + @HasPermissionAllDecorator('hg.admin') + def repo_cache(self, repo_name): + """ + INVALIDATE exisitings repository cache + :param repo_name: + """ + + try: + ScmModel().mark_for_invalidation(repo_name) + except Exception, e: + h.flash(_('An error occurred during cache invalidation'), + category='error') + return redirect(url('edit_repo', repo_name=repo_name)) + + @HasPermissionAllDecorator('hg.admin') def show(self, repo_name, format='html'): """GET /repos/repo_name: Show a specific item""" # url('repo', repo_name=ID) - - @HasPermissionAllDecorator('hg.admin') + + @HasPermissionAllDecorator('hg.admin') def edit(self, repo_name, format='html'): """GET /repos/repo_name/edit: Form to edit an existing item""" # url('edit_repo', repo_name=ID) repo_model = RepoModel() - c.repo_info = repo = repo_model.get(repo_name) - if not repo: - h.flash(_('%s repository is not mapped to db perhaps' + r = ScmModel().get(repo_name) + c.repo_info = repo_model.get_by_repo_name(repo_name) + + if c.repo_info is None: + h.flash(_('%s repository is not mapped to db perhaps' ' it was created or renamed from the filesystem' ' please run the application again' ' in order to rescan repositories') % repo_name, category='error') - - return redirect(url('repos')) - defaults = c.repo_info.__dict__ + + return redirect(url('repos')) + + if c.repo_info.stats: + last_rev = c.repo_info.stats.stat_on_revision + else: + last_rev = 0 + c.stats_revision = last_rev + + c.repo_last_rev = r.revisions[-1] if r.revisions else 0 + + if last_rev == 0: + c.stats_percentage = 0 + else: + c.stats_percentage = '%.2f' % ((float((last_rev)) / + c.repo_last_rev) * 100) + + defaults = c.repo_info.get_dict() if c.repo_info.user: defaults.update({'user':c.repo_info.user.username}) else: replacement_user = self.sa.query(User)\ .filter(User.admin == True).first().username defaults.update({'user':replacement_user}) - + c.users_array = repo_model.get_users_js() - + for p in c.repo_info.repo_to_perm: - defaults.update({'perm_%s' % p.user.username: + defaults.update({'perm_%s' % p.user.username: p.permission.permission_name}) - + return htmlfill.render( render('admin/repos/repo_edit.html'), defaults=defaults, encoding="UTF-8", force_defaults=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 @@ -1,8 +1,14 @@ -#!/usr/bin/env python -# encoding: utf-8 -# settings controller for pylons -# Copyright (C) 2009-2010 Marcin Kuzminski -# +# -*- coding: utf-8 -*- +""" + package.rhodecode.controllers.admin.settings + ~~~~~~~~~~~~~~ + settings controller for rhodecode admin + + :created_on: Jul 14, 2010 + :author: marcink + :copyright: (C) 2009-2010 Marcin Kuzminski + :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; version 2 @@ -17,11 +23,7 @@ # 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 July 14, 2010 -settings controller for pylons -@author: marcink -""" + from formencode import htmlfill from pylons import request, session, tmpl_context as c, url, app_globals as g, \ config @@ -29,20 +31,22 @@ from pylons.controllers.util import abor from pylons.i18n.translation import _ from rhodecode.lib import helpers as h from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \ - HasPermissionAnyDecorator + HasPermissionAnyDecorator, NotAnonymous from rhodecode.lib.base import BaseController, render +from rhodecode.lib.celerylib import tasks, run_task from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \ - set_rhodecode_config, get_hg_settings, get_hg_ui_settings, make_ui -from rhodecode.model.db import User, UserLog, RhodeCodeSettings, RhodeCodeUi + set_rhodecode_config +from rhodecode.model.db import RhodeCodeUi, Repository from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \ ApplicationUiSettingsForm -from rhodecode.model.hg_model import HgModel -from rhodecode.model.user_model import UserModel -from rhodecode.lib.celerylib import tasks, run_task +from rhodecode.model.scm import ScmModel +from rhodecode.model.settings import SettingsModel +from rhodecode.model.user import UserModel +from sqlalchemy import func import formencode import logging import traceback - + log = logging.getLogger(__name__) @@ -59,32 +63,32 @@ class SettingsController(BaseController) c.admin_user = session.get('admin_user') c.admin_username = session.get('admin_username') super(SettingsController, self).__before__() - - - @HasPermissionAllDecorator('hg.admin') + + + @HasPermissionAllDecorator('hg.admin') def index(self, format='html'): """GET /admin/settings: All items in the collection""" # url('admin_settings') - defaults = get_hg_settings() - defaults.update(get_hg_ui_settings()) + defaults = SettingsModel().get_app_settings() + defaults.update(self.get_hg_ui_settings()) return htmlfill.render( render('admin/settings/settings.html'), defaults=defaults, encoding="UTF-8", force_defaults=False - ) - + ) + @HasPermissionAllDecorator('hg.admin') def create(self): """POST /admin/settings: Create a new item""" # url('admin_settings') - + @HasPermissionAllDecorator('hg.admin') def new(self, format='html'): """GET /admin/settings/new: Form to create a new item""" # url('admin_new_setting') - + @HasPermissionAllDecorator('hg.admin') def update(self, setting_id): """PUT /admin/settings/setting_id: Update an existing item""" @@ -98,47 +102,48 @@ class SettingsController(BaseController) rm_obsolete = request.POST.get('destroy', False) log.debug('Rescanning directories with destroy=%s', rm_obsolete) - initial = HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui) + initial = ScmModel().repo_scan(g.paths[0][1], g.baseui) + for repo_name in initial.keys(): + invalidate_cache('get_repo_cached_%s' % repo_name) + repo2db_mapper(initial, rm_obsolete) - invalidate_cache('cached_repo_list') - h.flash(_('Repositories successfully rescanned'), category='success') - + + h.flash(_('Repositories successfully rescanned'), category='success') + if setting_id == 'whoosh': - repo_location = get_hg_ui_settings()['paths_root_path'] + repo_location = self.get_hg_ui_settings()['paths_root_path'] full_index = request.POST.get('full_index', False) task = run_task(tasks.whoosh_index, repo_location, full_index) - + h.flash(_('Whoosh reindex task scheduled'), category='success') if setting_id == 'global': - + application_form = ApplicationSettingsForm()() try: form_result = application_form.to_python(dict(request.POST)) - + settings_model = SettingsModel() try: - hgsettings1 = self.sa.query(RhodeCodeSettings)\ - .filter(RhodeCodeSettings.app_settings_name == 'title').one() - hgsettings1.app_settings_value = form_result['rhodecode_title'] - - hgsettings2 = self.sa.query(RhodeCodeSettings)\ - .filter(RhodeCodeSettings.app_settings_name == 'realm').one() - hgsettings2.app_settings_value = form_result['rhodecode_realm'] - - + hgsettings1 = settings_model.get('title') + hgsettings1.app_settings_value = form_result['rhodecode_title'] + + hgsettings2 = settings_model.get('realm') + hgsettings2.app_settings_value = form_result['rhodecode_realm'] + + self.sa.add(hgsettings1) self.sa.add(hgsettings2) self.sa.commit() set_rhodecode_config(config) h.flash(_('Updated application settings'), category='success') - + except: log.error(traceback.format_exc()) h.flash(_('error occurred during updating application settings'), category='error') - + self.sa.rollback() - + except formencode.Invalid, errors: return htmlfill.render( @@ -146,52 +151,60 @@ class SettingsController(BaseController) defaults=errors.value, errors=errors.error_dict or {}, prefix_error=False, - encoding="UTF-8") - + encoding="UTF-8") + if setting_id == 'mercurial': application_form = ApplicationUiSettingsForm()() try: form_result = application_form.to_python(dict(request.POST)) - + try: - + hgsettings1 = self.sa.query(RhodeCodeUi)\ .filter(RhodeCodeUi.ui_key == 'push_ssl').one() hgsettings1.ui_value = form_result['web_push_ssl'] - + hgsettings2 = self.sa.query(RhodeCodeUi)\ .filter(RhodeCodeUi.ui_key == '/').one() - hgsettings2.ui_value = form_result['paths_root_path'] - - + hgsettings2.ui_value = form_result['paths_root_path'] + + #HOOKS hgsettings3 = self.sa.query(RhodeCodeUi)\ .filter(RhodeCodeUi.ui_key == 'changegroup.update').one() - hgsettings3.ui_active = bool(form_result['hooks_changegroup_update']) - + hgsettings3.ui_active = bool(form_result['hooks_changegroup_update']) + hgsettings4 = self.sa.query(RhodeCodeUi)\ .filter(RhodeCodeUi.ui_key == 'changegroup.repo_size').one() - hgsettings4.ui_active = bool(form_result['hooks_changegroup_repo_size']) - - - - + hgsettings4.ui_active = bool(form_result['hooks_changegroup_repo_size']) + + hgsettings5 = self.sa.query(RhodeCodeUi)\ + .filter(RhodeCodeUi.ui_key == 'pretxnchangegroup.push_logger').one() + hgsettings5.ui_active = bool(form_result['hooks_pretxnchangegroup_push_logger']) + + hgsettings6 = self.sa.query(RhodeCodeUi)\ + .filter(RhodeCodeUi.ui_key == 'preoutgoing.pull_logger').one() + hgsettings6.ui_active = bool(form_result['hooks_preoutgoing_pull_logger']) + + self.sa.add(hgsettings1) self.sa.add(hgsettings2) self.sa.add(hgsettings3) self.sa.add(hgsettings4) + self.sa.add(hgsettings5) + self.sa.add(hgsettings6) self.sa.commit() - + h.flash(_('Updated mercurial settings'), category='success') - + except: log.error(traceback.format_exc()) h.flash(_('error occurred during updating application settings'), category='error') - + self.sa.rollback() - + except formencode.Invalid, errors: return htmlfill.render( @@ -199,12 +212,12 @@ class SettingsController(BaseController) defaults=errors.value, errors=errors.error_dict or {}, prefix_error=False, - encoding="UTF-8") - - - + encoding="UTF-8") + + + return redirect(url('admin_settings')) - + @HasPermissionAllDecorator('hg.admin') def delete(self, setting_id): """DELETE /admin/settings/setting_id: Delete an existing item""" @@ -214,41 +227,44 @@ class SettingsController(BaseController) # h.form(url('admin_setting', setting_id=ID), # method='delete') # url('admin_setting', setting_id=ID) - + @HasPermissionAllDecorator('hg.admin') def show(self, setting_id, format='html'): """GET /admin/settings/setting_id: Show a specific item""" # url('admin_setting', setting_id=ID) - - @HasPermissionAllDecorator('hg.admin') + + @HasPermissionAllDecorator('hg.admin') def edit(self, setting_id, format='html'): """GET /admin/settings/setting_id/edit: Form to edit an existing item""" # url('admin_edit_setting', setting_id=ID) - + @NotAnonymous() def my_account(self): """ GET /_admin/my_account Displays info about my account """ # url('admin_settings_my_account') - c.user = self.sa.query(User).get(c.rhodecode_user.user_id) - c.user_repos = [] - for repo in c.cached_repo_list.values(): - if repo.dbrepo.user.username == c.user.username: - c.user_repos.append(repo) - + + c.user = UserModel().get(c.rhodecode_user.user_id, cache=False) + all_repos = self.sa.query(Repository)\ + .filter(Repository.user_id == c.user.user_id)\ + .order_by(func.lower(Repository.repo_name))\ + .all() + + c.user_repos = ScmModel().get_repos(all_repos) + if c.user.username == 'default': - h.flash(_("You can't edit this user since it's" + h.flash(_("You can't edit this user since it's" " crucial for entire application"), category='warning') return redirect(url('users')) - - defaults = c.user.__dict__ + + defaults = c.user.get_dict() return htmlfill.render( render('admin/users/user_edit_my_account.html'), defaults=defaults, encoding="UTF-8", force_defaults=False - ) + ) def my_account_update(self): """PUT /_admin/my_account_update: Update an existing item""" @@ -266,15 +282,18 @@ class SettingsController(BaseController) try: form_result = _form.to_python(dict(request.POST)) user_model.update_my_account(uid, form_result) - h.flash(_('Your account was updated succesfully'), + h.flash(_('Your account was updated successfully'), category='success') - + except formencode.Invalid, errors: - c.user = self.sa.query(User).get(c.rhodecode_user.user_id) - c.user_repos = [] - for repo in c.cached_repo_list.values(): - if repo.dbrepo.user.username == c.user.username: - c.user_repos.append(repo) + c.user = user_model.get(c.rhodecode_user.user_id, cache=False) + c.user = UserModel().get(c.rhodecode_user.user_id, cache=False) + all_repos = self.sa.query(Repository)\ + .filter(Repository.user_id == c.user.user_id)\ + .order_by(func.lower(Repository.repo_name))\ + .all() + c.user_repos = ScmModel().get_repos(all_repos) + return htmlfill.render( render('admin/users/user_edit_my_account.html'), defaults=errors.value, @@ -283,11 +302,12 @@ class SettingsController(BaseController) encoding="UTF-8") except Exception: log.error(traceback.format_exc()) - h.flash(_('error occured during update of user %s') \ + h.flash(_('error occurred during update of user %s') \ % form_result.get('username'), category='error') - + return redirect(url('my_account')) - + + @NotAnonymous() @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository') def create_repository(self): """GET /_admin/create_repository: Form to create a new item""" @@ -295,4 +315,25 @@ class SettingsController(BaseController) c.new_repo = h.repo_name_slug(new_repo) return render('admin/repos/repo_add_create_repository.html') - + + def get_hg_ui_settings(self): + ret = self.sa.query(RhodeCodeUi).all() + + if not ret: + raise Exception('Could not get application ui settings !') + settings = {} + for each in ret: + k = each.ui_key + v = each.ui_value + if k == '/': + k = 'root_path' + + if k.find('.') != -1: + k = k.replace('.', '_') + + if each.ui_section == 'hooks': + v = each.ui_active + + settings[each.ui_section + '_' + k] = v + + return settings diff --git a/rhodecode/controllers/admin/users.py b/rhodecode/controllers/admin/users.py --- a/rhodecode/controllers/admin/users.py +++ b/rhodecode/controllers/admin/users.py @@ -1,8 +1,15 @@ -#!/usr/bin/env python -# encoding: utf-8 -# users controller for pylons -# Copyright (C) 2009-2010 Marcin Kuzminski -# +# -*- coding: utf-8 -*- +""" + rhodecode.controllers.admin.users + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Users crud controller for pylons + + :created_on: Apr 4, 2010 + :author: marcink + :copyright: (C) 2009-2010 Marcin Kuzminski + :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; version 2 @@ -17,26 +24,24 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. -from rhodecode.lib.utils import action_logger -""" -Created on April 4, 2010 -users controller for pylons -@author: marcink -""" + +import logging +import traceback +import formencode from formencode import htmlfill from pylons import request, session, tmpl_context as c, url from pylons.controllers.util import abort, redirect from pylons.i18n.translation import _ + +from rhodecode.lib.exceptions import * from rhodecode.lib import helpers as h from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator from rhodecode.lib.base import BaseController, render -from rhodecode.model.db import User, UserLog + +from rhodecode.model.db import User from rhodecode.model.forms import UserForm -from rhodecode.model.user_model import UserModel, DefaultUserException -import formencode -import logging -import traceback +from rhodecode.model.user import UserModel log = logging.getLogger(__name__) @@ -45,26 +50,26 @@ class UsersController(BaseController): # To properly map this controller, ensure your config/routing.py # file has a resource setup: # map.resource('user', 'users') - + @LoginRequired() @HasPermissionAllDecorator('hg.admin') def __before__(self): c.admin_user = session.get('admin_user') c.admin_username = session.get('admin_username') super(UsersController, self).__before__() - + def index(self, format='html'): """GET /users: All items in the collection""" # url('users') - - c.users_list = self.sa.query(User).all() + + c.users_list = self.sa.query(User).all() return render('admin/users/users.html') - + def create(self): """POST /users: Create a new item""" # url('users') - + user_model = UserModel() login_form = UserForm()() try: @@ -79,13 +84,13 @@ class UsersController(BaseController): defaults=errors.value, errors=errors.error_dict or {}, prefix_error=False, - encoding="UTF-8") + encoding="UTF-8") except Exception: log.error(traceback.format_exc()) h.flash(_('error occured during creation of user %s') \ - % request.POST.get('username'), category='error') + % request.POST.get('username'), category='error') return redirect(url('users')) - + def new(self, format='html'): """GET /users/new: Form to create a new item""" # url('new_user') @@ -100,8 +105,8 @@ class UsersController(BaseController): # method='put') # url('user', id=ID) user_model = UserModel() - c.user = user_model.get_user(id) - + c.user = user_model.get(id) + _form = UserForm(edit=True, old_data={'user_id':id, 'email':c.user.email})() form_result = {} @@ -109,21 +114,21 @@ class UsersController(BaseController): form_result = _form.to_python(dict(request.POST)) user_model.update(id, form_result) h.flash(_('User updated succesfully'), category='success') - + except formencode.Invalid, errors: return htmlfill.render( render('admin/users/user_edit.html'), defaults=errors.value, errors=errors.error_dict or {}, prefix_error=False, - encoding="UTF-8") + encoding="UTF-8") except Exception: log.error(traceback.format_exc()) - h.flash(_('error occured during update of user %s') \ + h.flash(_('error occurred during update of user %s') \ % form_result.get('username'), category='error') - + return redirect(url('users')) - + def delete(self, id): """DELETE /users/id: Delete an existing item""" # Forms posted to this method should contain a hidden field: @@ -136,18 +141,18 @@ class UsersController(BaseController): try: user_model.delete(id) h.flash(_('sucessfully deleted user'), category='success') - except DefaultUserException, e: + except (UserOwnsReposException, DefaultUserException), e: h.flash(str(e), category='warning') except Exception: h.flash(_('An error occured during deletion of user'), - category='error') + category='error') return redirect(url('users')) - + def show(self, id, format='html'): """GET /users/id: Show a specific item""" # url('user', id=ID) - - + + def edit(self, id, format='html'): """GET /users/id/edit: Form to edit an existing item""" # url('edit_user', id=ID) @@ -155,14 +160,13 @@ class UsersController(BaseController): if not c.user: return redirect(url('users')) if c.user.username == 'default': - h.flash(_("You can't edit this user since it's" - " crucial for entire application"), category='warning') + h.flash(_("You can't edit this user"), category='warning') return redirect(url('users')) - - defaults = c.user.__dict__ + + defaults = c.user.get_dict() return htmlfill.render( render('admin/users/user_edit.html'), defaults=defaults, encoding="UTF-8", force_defaults=False - ) + ) diff --git a/rhodecode/controllers/branches.py b/rhodecode/controllers/branches.py --- a/rhodecode/controllers/branches.py +++ b/rhodecode/controllers/branches.py @@ -26,7 +26,7 @@ from pylons import tmpl_context as c from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator from rhodecode.lib.base import BaseController, render from rhodecode.lib.utils import OrderedDict -from rhodecode.model.hg_model import HgModel +from rhodecode.model.scm import ScmModel import logging log = logging.getLogger(__name__) @@ -38,7 +38,7 @@ class BranchesController(BaseController) super(BranchesController, self).__before__() def index(self): - hg_model = HgModel() + hg_model = ScmModel() c.repo_info = hg_model.get_repo(c.repo_name) c.repo_branches = OrderedDict() for name, hash_ in c.repo_info.branches.items(): diff --git a/rhodecode/controllers/changelog.py b/rhodecode/controllers/changelog.py --- a/rhodecode/controllers/changelog.py +++ b/rhodecode/controllers/changelog.py @@ -32,19 +32,19 @@ from mercurial.graphmod import colored, from pylons import request, session, tmpl_context as c from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator from rhodecode.lib.base import BaseController, render -from rhodecode.model.hg_model import HgModel +from rhodecode.model.scm import ScmModel from webhelpers.paginate import Page import logging log = logging.getLogger(__name__) class ChangelogController(BaseController): - + @LoginRequired() @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', - 'repository.admin') + 'repository.admin') def __before__(self): super(ChangelogController, self).__before__() - + def index(self): limit = 100 default = 20 @@ -53,43 +53,47 @@ class ChangelogController(BaseController int_size = int(request.params.get('size')) except ValueError: int_size = default - int_size = int_size if int_size <= limit else limit + int_size = int_size if int_size <= limit else limit c.size = int_size session['changelog_size'] = c.size session.save() else: c.size = int(session.get('changelog_size', default)) - changesets = HgModel().get_repo(c.repo_name) - + changesets = ScmModel().get_repo(c.repo_name) + p = int(request.params.get('page', 1)) c.total_cs = len(changesets) c.pagination = Page(changesets, page=p, item_count=c.total_cs, items_per_page=c.size) - + self._graph(changesets, c.size, p) - + return render('changelog/changelog.html') def _graph(self, repo, size, p): revcount = size - if not repo.revisions:return json.dumps([]), 0 - + if not repo.revisions or repo.alias == 'git': + c.jsdata = json.dumps([]) + return + max_rev = repo.revisions[-1] + offset = 1 if p == 1 else ((p - 1) * revcount + 1) + rev_start = repo.revisions[(-1 * offset)] - + revcount = min(max_rev, revcount) rev_end = max(0, rev_start - revcount) dag = graph_rev(repo.repo, rev_start, rev_end) - + c.dag = tree = list(colored(dag)) data = [] for (id, type, ctx, vtx, edges) in tree: if type != CHANGESET: continue data.append(('', vtx, edges)) - - c.jsdata = json.dumps(data) + c.jsdata = json.dumps(data) + diff --git a/rhodecode/controllers/changeset.py b/rhodecode/controllers/changeset.py --- a/rhodecode/controllers/changeset.py +++ b/rhodecode/controllers/changeset.py @@ -1,7 +1,15 @@ -#!/usr/bin/env python -# encoding: utf-8 -# changeset controller for pylons -# Copyright (C) 2009-2010 Marcin Kuzminski +# -*- coding: utf-8 -*- +""" + rhodecode.controllers.changeset + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + changeset controller for pylons + + :created_on: Apr 25, 2010 + :author: marcink + :copyright: (C) 2009-2010 Marcin Kuzminski + :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; version 2 @@ -16,97 +24,96 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. -from rhodecode.lib.utils import EmptyChangeset -""" -Created on April 25, 2010 -changeset controller for pylons -@author: marcink -""" +import logging +import traceback + from pylons import tmpl_context as c, url, request, response from pylons.i18n.translation import _ from pylons.controllers.util import redirect + +import rhodecode.lib.helpers as h from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator from rhodecode.lib.base import BaseController, render -from rhodecode.model.hg_model import HgModel +from rhodecode.lib.utils import EmptyChangeset +from rhodecode.model.scm import ScmModel + from vcs.exceptions import RepositoryError, ChangesetError from vcs.nodes import FileNode from vcs.utils import diffs as differ -import logging -import traceback log = logging.getLogger(__name__) class ChangesetController(BaseController): - + @LoginRequired() @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', - 'repository.admin') + 'repository.admin') def __before__(self): super(ChangesetController, self).__before__() - + def index(self, revision): - hg_model = HgModel() - cut_off_limit = 1024 * 250 - + hg_model = ScmModel() + def wrap_to_table(str): - + return '''
%s
''' % str - + try: c.changeset = hg_model.get_repo(c.repo_name).get_changeset(revision) - except RepositoryError: + except RepositoryError, e: log.error(traceback.format_exc()) - return redirect(url('hg_home')) + h.flash(str(e), category='warning') + return redirect(url('home')) else: try: c.changeset_old = c.changeset.parents[0] except IndexError: c.changeset_old = None c.changes = [] - + #=================================================================== # ADDED FILES #=================================================================== c.sum_added = 0 for node in c.changeset.added: - + filenode_old = FileNode(node.path, '', EmptyChangeset()) if filenode_old.is_binary or node.is_binary: diff = wrap_to_table(_('binary file')) else: c.sum_added += node.size - if c.sum_added < cut_off_limit: + if c.sum_added < self.cut_off_limit: f_udiff = differ.get_udiff(filenode_old, node) diff = differ.DiffProcessor(f_udiff).as_html() - + else: diff = wrap_to_table(_('Changeset is to big and was cut' ' off, see raw changeset instead')) - + cs1 = None - cs2 = node.last_changeset.short_id + cs2 = node.last_changeset.raw_id c.changes.append(('added', node, diff, cs1, cs2)) - + #=================================================================== # CHANGED FILES #=================================================================== - c.sum_removed = 0 + c.sum_removed = 0 for node in c.changeset.changed: try: filenode_old = c.changeset_old.get_node(node.path) except ChangesetError: filenode_old = FileNode(node.path, '', EmptyChangeset()) - + if filenode_old.is_binary or node.is_binary: diff = wrap_to_table(_('binary file')) else: - - if c.sum_removed < cut_off_limit: + + if c.sum_removed < self.cut_off_limit: f_udiff = differ.get_udiff(filenode_old, node) diff = differ.DiffProcessor(f_udiff).as_html() if diff: @@ -114,68 +121,72 @@ class ChangesetController(BaseController else: diff = wrap_to_table(_('Changeset is to big and was cut' ' off, see raw changeset instead')) - - - cs1 = filenode_old.last_changeset.short_id - cs2 = node.last_changeset.short_id + + + cs1 = filenode_old.last_changeset.raw_id + cs2 = node.last_changeset.raw_id c.changes.append(('changed', node, diff, cs1, cs2)) - + #=================================================================== # REMOVED FILES #=================================================================== for node in c.changeset.removed: - c.changes.append(('removed', node, None, None, None)) - + c.changes.append(('removed', node, None, None, None)) + return render('changeset/changeset.html') def raw_changeset(self, revision): - - hg_model = HgModel() + + hg_model = ScmModel() method = request.GET.get('diff', 'show') try: - c.changeset = hg_model.get_repo(c.repo_name).get_changeset(revision) + r = hg_model.get_repo(c.repo_name) + c.scm_type = r.alias + c.changeset = r.get_changeset(revision) except RepositoryError: log.error(traceback.format_exc()) - return redirect(url('hg_home')) + return redirect(url('home')) else: try: c.changeset_old = c.changeset.parents[0] except IndexError: c.changeset_old = None c.changes = [] - + for node in c.changeset.added: filenode_old = FileNode(node.path, '') if filenode_old.is_binary or node.is_binary: - diff = _('binary file') - else: + diff = _('binary file') + '\n' + else: f_udiff = differ.get_udiff(filenode_old, node) diff = differ.DiffProcessor(f_udiff).raw_diff() cs1 = None - cs2 = node.last_changeset.short_id + cs2 = node.last_changeset.raw_id c.changes.append(('added', node, diff, cs1, cs2)) - + for node in c.changeset.changed: filenode_old = c.changeset_old.get_node(node.path) if filenode_old.is_binary or node.is_binary: diff = _('binary file') - else: + else: f_udiff = differ.get_udiff(filenode_old, node) diff = differ.DiffProcessor(f_udiff).raw_diff() - cs1 = filenode_old.last_changeset.short_id - cs2 = node.last_changeset.short_id - c.changes.append(('changed', node, diff, cs1, cs2)) - + cs1 = filenode_old.last_changeset.raw_id + cs2 = node.last_changeset.raw_id + c.changes.append(('changed', node, diff, cs1, cs2)) + response.content_type = 'text/plain' + if method == 'download': - response.content_disposition = 'attachment; filename=%s.patch' % revision + response.content_disposition = 'attachment; filename=%s.patch' % revision + parent = True if len(c.changeset.parents) > 0 else False c.parent_tmpl = 'Parent %s' % c.changeset.parents[0].raw_id if parent else '' - + c.diffs = '' for x in c.changes: c.diffs += x[2] - + return render('changeset/raw_changeset.html') diff --git a/rhodecode/controllers/error.py b/rhodecode/controllers/error.py --- a/rhodecode/controllers/error.py +++ b/rhodecode/controllers/error.py @@ -1,33 +1,58 @@ -import logging +# -*- coding: utf-8 -*- +""" + package.rhodecode.controllers.error + ~~~~~~~~~~~~~~ + + RhodeCode error controller + + :created_on: Dec 8, 2010 + :author: marcink + :copyright: (C) 2009-2010 Marcin Kuzminski + :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; 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. +import os import cgi -import os +import logging import paste.fileapp -from pylons import tmpl_context as c, app_globals as g, request, config -from pylons.controllers.util import forward + +from pylons import tmpl_context as c, request from pylons.i18n.translation import _ +from pylons.middleware import media_path + from rhodecode.lib.base import BaseController, render -from pylons.middleware import media_path -from rhodecode.lib.utils import check_repo -import rhodecode.lib.helpers as h -from rhodecode import __version__ + log = logging.getLogger(__name__) class ErrorController(BaseController): - """ - Generates error documents as and when they are required. + """Generates error documents as and when they are required. The ErrorDocuments middleware forwards to ErrorController when error related status codes are returned from the application. - This behaviour can be altered by changing the parameters to the + This behavior can be altered by changing the parameters to the ErrorDocuments middleware in your config/middleware.py file. """ + def __before__(self): pass#disable all base actions since we don't need them here - + def document(self): resp = request.environ.get('pylons.original_response') - + log.debug('### %s ###', resp.status) e = request.environ @@ -36,7 +61,7 @@ class ErrorController(BaseController): 'host':e.get('HTTP_HOST'), } - + c.error_message = cgi.escape(request.GET.get('code', str(resp.status))) c.error_explanation = self.get_error_explanation(resp.status_int) @@ -74,7 +99,7 @@ class ErrorController(BaseController): if code == 400: return _('The request could not be understood by the server due to malformed syntax.') if code == 401: - return _('Unathorized access to resource') + return _('Unauthorized access to resource') if code == 403: return _("You don't have permission to view this page") if code == 404: diff --git a/rhodecode/controllers/feed.py b/rhodecode/controllers/feed.py --- a/rhodecode/controllers/feed.py +++ b/rhodecode/controllers/feed.py @@ -24,7 +24,7 @@ feed controller for pylons """ from pylons import tmpl_context as c, url, response from rhodecode.lib.base import BaseController, render -from rhodecode.model.hg_model import HgModel +from rhodecode.model.scm import ScmModel from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed import logging log = logging.getLogger(__name__) @@ -49,12 +49,12 @@ class FeedController(BaseController): language=self.language, ttl=self.ttl) - changesets = HgModel().get_repo(repo_name) + changesets = ScmModel().get_repo(repo_name) for cs in changesets[:self.feed_nr]: feed.add_item(title=cs.message, link=url('changeset_home', repo_name=repo_name, - revision=cs.short_id, qualified=True), + revision=cs.raw_id, qualified=True), description=str(cs.date)) response.content_type = feed.mime_type @@ -69,11 +69,11 @@ class FeedController(BaseController): language=self.language, ttl=self.ttl) - changesets = HgModel().get_repo(repo_name) + changesets = ScmModel().get_repo(repo_name) for cs in changesets[:self.feed_nr]: feed.add_item(title=cs.message, link=url('changeset_home', repo_name=repo_name, - revision=cs.short_id, qualified=True), + revision=cs.raw_id, qualified=True), description=str(cs.date)) response.content_type = feed.mime_type diff --git a/rhodecode/controllers/files.py b/rhodecode/controllers/files.py --- a/rhodecode/controllers/files.py +++ b/rhodecode/controllers/files.py @@ -1,8 +1,15 @@ -#!/usr/bin/env python -# encoding: utf-8 -# files controller for pylons -# Copyright (C) 2009-2010 Marcin Kuzminski +# -*- coding: utf-8 -*- +""" + rhodecode.controllers.files + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Files controller for RhodeCode + + :created_on: Apr 21, 2010 + :author: marcink + :copyright: (C) 2009-2010 Marcin Kuzminski + :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; version 2 @@ -17,107 +24,115 @@ # 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 21, 2010 -files controller for pylons -@author: marcink -""" +import tempfile +import logging +import rhodecode.lib.helpers as h + from mercurial import archival + from pylons import request, response, session, tmpl_context as c, url from pylons.i18n.translation import _ from pylons.controllers.util import redirect + from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator from rhodecode.lib.base import BaseController, render from rhodecode.lib.utils import EmptyChangeset -from rhodecode.model.hg_model import HgModel +from rhodecode.model.scm import ScmModel + from vcs.exceptions import RepositoryError, ChangesetError from vcs.nodes import FileNode from vcs.utils import diffs as differ -import logging -import rhodecode.lib.helpers as h -import tempfile - + log = logging.getLogger(__name__) class FilesController(BaseController): - + @LoginRequired() @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', - 'repository.admin') + 'repository.admin') def __before__(self): super(FilesController, self).__before__() - c.file_size_limit = 250 * 1024 #limit of file size to display + c.cut_off_limit = self.cut_off_limit def index(self, repo_name, revision, f_path): - hg_model = HgModel() - c.repo = repo = hg_model.get_repo(c.repo_name) + hg_model = ScmModel() + c.repo = hg_model.get_repo(c.repo_name) revision = request.POST.get('at_rev', None) or revision - + def get_next_rev(cur): max_rev = len(c.repo.revisions) - 1 r = cur + 1 if r > max_rev: r = max_rev return r - + def get_prev_rev(cur): r = cur - 1 return r c.f_path = f_path - - + + try: - cur_rev = repo.get_changeset(revision).revision - prev_rev = repo.get_changeset(get_prev_rev(cur_rev)).short_id - next_rev = repo.get_changeset(get_next_rev(cur_rev)).short_id - + 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) + 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) - - c.changeset = repo.get_changeset(revision) - - c.cur_rev = c.changeset.short_id - c.rev_nr = c.changeset.revision - c.files_list = c.changeset.get_node(f_path) - c.file_history = self._get_history(repo, c.files_list, f_path) - - except (RepositoryError, ChangesetError): - c.files_list = None - + 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 RepositoryError, e: + h.flash(str(e), category='warning') + redirect(h.url('files_home', repo_name=repo_name, revision='tip')) + + + return render('files/files.html') def rawfile(self, repo_name, revision, f_path): - hg_model = HgModel() + hg_model = ScmModel() c.repo = hg_model.get_repo(c.repo_name) file_node = c.repo.get_changeset(revision).get_node(f_path) response.content_type = file_node.mimetype response.content_disposition = 'attachment; filename=%s' \ - % f_path.split('/')[-1] + % f_path.split('/')[-1] return file_node.content def raw(self, repo_name, revision, f_path): - hg_model = HgModel() + hg_model = ScmModel() c.repo = hg_model.get_repo(c.repo_name) file_node = c.repo.get_changeset(revision).get_node(f_path) response.content_type = 'text/plain' - + return file_node.content - + def annotate(self, repo_name, revision, f_path): - hg_model = HgModel() + hg_model = ScmModel() c.repo = hg_model.get_repo(c.repo_name) - cs = c.repo.get_changeset(revision) - c.file = cs.get_node(f_path) - c.file_msg = cs.get_file_message(f_path) - c.cur_rev = cs.short_id - c.rev_nr = cs.revision + + try: + c.cs = c.repo.get_changeset(revision) + c.file = c.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)) + + c.file_history = self._get_history(c.repo, c.file, f_path) + c.f_path = f_path return render('files/files_annotate.html') - + def archivefile(self, repo_name, revision, fileformat): archive_specs = { '.tar.bz2': ('application/x-tar', 'tbz2'), @@ -126,7 +141,7 @@ class FilesController(BaseController): } if not archive_specs.has_key(fileformat): return 'Unknown archive type %s' % fileformat - + def read_in_chunks(file_object, chunk_size=1024 * 40): """Lazy function (generator) to read a file piece by piece. Default chunk size: 40k.""" @@ -134,10 +149,10 @@ class FilesController(BaseController): data = file_object.read(chunk_size) if not data: break - yield data - + yield data + archive = tempfile.TemporaryFile() - repo = HgModel().get_repo(repo_name).repo + repo = ScmModel().get_repo(repo_name).repo fname = '%s-%s%s' % (repo_name, revision, fileformat) archival.archive(repo, archive, revision, archive_specs[fileformat][1], prefix='%s-%s' % (repo_name, revision)) @@ -145,9 +160,9 @@ class FilesController(BaseController): response.content_disposition = 'attachment; filename=%s' % fname archive.seek(0) return read_in_chunks(archive) - + def diff(self, repo_name, f_path): - hg_model = HgModel() + hg_model = ScmModel() diff1 = request.GET.get('diff1') diff2 = request.GET.get('diff2') c.action = request.GET.get('diff') @@ -162,7 +177,7 @@ class FilesController(BaseController): else: c.changeset_1 = EmptyChangeset() node1 = FileNode('.', '', changeset=c.changeset_1) - + if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]: c.changeset_2 = c.repo.get_changeset(diff2) node2 = c.changeset_2.get_node(f_path) @@ -173,43 +188,66 @@ class FilesController(BaseController): return redirect(url('files_home', repo_name=c.repo_name, f_path=f_path)) - c.diff1 = 'r%s:%s' % (c.changeset_1.revision, c.changeset_1.short_id) - c.diff2 = 'r%s:%s' % (c.changeset_2.revision, c.changeset_2.short_id) - f_udiff = differ.get_udiff(node1, node2) diff = differ.DiffProcessor(f_udiff) - + if c.action == 'download': diff_name = '%s_vs_%s.diff' % (diff1, diff2) response.content_type = 'text/plain' response.content_disposition = 'attachment; filename=%s' \ - % diff_name + % diff_name return diff.raw_diff() - + elif c.action == 'raw': - c.cur_diff = '
%s
' % h.escape(diff.raw_diff()) + response.content_type = 'text/plain' + return diff.raw_diff() + elif c.action == 'diff': - if node1.size > c.file_size_limit or node2.size > c.file_size_limit: + if node1.size > self.cut_off_limit or node2.size > self.cut_off_limit: c.cur_diff = _('Diff is to big to display') else: c.cur_diff = diff.as_html() else: #default option - if node1.size > c.file_size_limit or node2.size > c.file_size_limit: + if node1.size > self.cut_off_limit or node2.size > self.cut_off_limit: c.cur_diff = _('Diff is to big to display') 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): from vcs.nodes import NodeKind if not node.kind is NodeKind.FILE: return [] changesets = node.history hist_l = [] + + changesets_group = ([], _("Changesets")) + branches_group = ([], _("Branches")) + tags_group = ([], _("Tags")) + for chs in changesets: n_desc = 'r%s:%s' % (chs.revision, chs.short_id) - hist_l.append((chs.short_id, n_desc,)) + changesets_group[0].append((chs.raw_id, n_desc,)) + + hist_l.append(changesets_group) + + for name, chs in c.repository_branches.items(): + #chs = chs.split(':')[-1] + branches_group[0].append((chs, name),) + hist_l.append(branches_group) + + for name, chs in c.repository_tags.items(): + #chs = chs.split(':')[-1] + tags_group[0].append((chs, name),) + hist_l.append(tags_group) + return hist_l + +# [ +# ([("u1", "User1"), ("u2", "User2")], "Users"), +# ([("g1", "Group1"), ("g2", "Group2")], "Groups") +# ] + diff --git a/rhodecode/controllers/hg.py b/rhodecode/controllers/home.py rename from rhodecode/controllers/hg.py rename to rhodecode/controllers/home.py --- a/rhodecode/controllers/hg.py +++ b/rhodecode/controllers/home.py @@ -26,33 +26,33 @@ from operator import itemgetter from pylons import tmpl_context as c, request from rhodecode.lib.auth import LoginRequired from rhodecode.lib.base import BaseController, render -from rhodecode.model.hg_model import HgModel +from rhodecode.model.scm import ScmModel import logging log = logging.getLogger(__name__) -class HgController(BaseController): +class HomeController(BaseController): @LoginRequired() def __before__(self): - super(HgController, self).__before__() - + super(HomeController, self).__before__() + def index(self): sortables = ['name', 'description', 'last_change', 'tip', 'contact'] current_sort = request.GET.get('sort', 'name') current_sort_slug = current_sort.replace('-', '') - + if current_sort_slug not in sortables: c.sort_by = 'name' current_sort_slug = c.sort_by else: c.sort_by = current_sort c.sort_slug = current_sort_slug - cached_repo_list = HgModel().get_repos() - + cached_repo_list = ScmModel().get_repos() + sort_key = current_sort_slug + '_sort' if c.sort_by.startswith('-'): c.repos_list = sorted(cached_repo_list, key=itemgetter(sort_key), reverse=True) else: c.repos_list = sorted(cached_repo_list, key=itemgetter(sort_key), reverse=False) - + return render('/index.html') diff --git a/rhodecode/controllers/journal.py b/rhodecode/controllers/journal.py new file mode 100644 --- /dev/null +++ b/rhodecode/controllers/journal.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python +# encoding: utf-8 +# journal controller for pylons +# Copyright (C) 2009-2010 Marcin Kuzminski +# +# 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, NotAnonymous +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 +from sqlalchemy import or_ +import logging +from paste.httpexceptions import HTTPInternalServerError, HTTPNotFound + +log = logging.getLogger(__name__) + +class JournalController(BaseController): + + + @LoginRequired() + @NotAnonymous() + 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() + + repo_ids = [x.follows_repository.repo_id for x in c.following + if x.follows_repository is not None] + user_ids = [x.follows_user.user_id for x in c.following + if x.follows_user is not None] + + c.journal = self.sa.query(UserLog)\ + .filter(or_( + UserLog.repository_id.in_(repo_ids), + UserLog.user_id.in_(user_ids), + ))\ + .order_by(UserLog.action_date.desc())\ + .limit(20)\ + .all() + return render('/journal.html') + + def toggle_following(self): + + 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() diff --git a/rhodecode/controllers/login.py b/rhodecode/controllers/login.py --- a/rhodecode/controllers/login.py +++ b/rhodecode/controllers/login.py @@ -28,10 +28,10 @@ from pylons import request, response, se from pylons.controllers.util import abort, redirect from rhodecode.lib.auth import AuthUser, HasPermissionAnyDecorator from rhodecode.lib.base import BaseController, render -import rhodecode.lib.helpers as h +import rhodecode.lib.helpers as h from pylons.i18n.translation import _ from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm -from rhodecode.model.user_model import UserModel +from rhodecode.model.user import UserModel import formencode import logging @@ -45,17 +45,19 @@ class LoginController(BaseController): def index(self): #redirect if already logged in c.came_from = request.GET.get('came_from', None) - - if c.rhodecode_user.is_authenticated: - return redirect(url('hg_home')) - + + if c.rhodecode_user.is_authenticated \ + and c.rhodecode_user.username != 'default': + + return redirect(url('home')) + if request.POST: #import Login Form validator class login_form = LoginForm() try: c.form_result = login_form.to_python(dict(request.POST)) username = c.form_result['username'] - user = UserModel().get_user_by_name(username) + user = UserModel().get_by_username(username, case_insensitive=True) auth_user = AuthUser() auth_user.username = user.username auth_user.is_authenticated = True @@ -66,14 +68,14 @@ class LoginController(BaseController): session['rhodecode_user'] = auth_user session.save() log.info('user %s is now authenticated', username) - + user.update_lastlogin() - + if c.came_from: return redirect(c.came_from) else: - return redirect(url('hg_home')) - + return redirect(url('home')) + except formencode.Invalid, errors: return htmlfill.render( render('/login.html'), @@ -81,30 +83,30 @@ class LoginController(BaseController): errors=errors.error_dict or {}, prefix_error=False, encoding="UTF-8") - + return render('/login.html') - + @HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate') def register(self): user_model = UserModel() c.auto_active = False - for perm in user_model.get_default().user_perms: + for perm in user_model.get_by_username('default', cache=False).user_perms: if perm.permission.permission_name == 'hg.register.auto_activate': c.auto_active = True break - + if request.POST: - + register_form = RegisterForm()() try: form_result = register_form.to_python(dict(request.POST)) form_result['active'] = c.auto_active user_model.create_registration(form_result) h.flash(_('You have successfully registered into rhodecode'), - category='success') + category='success') return redirect(url('login_home')) - + except formencode.Invalid, errors: return htmlfill.render( render('/register.html'), @@ -112,21 +114,21 @@ class LoginController(BaseController): errors=errors.error_dict or {}, prefix_error=False, encoding="UTF-8") - + return render('/register.html') def password_reset(self): user_model = UserModel() if request.POST: - + password_reset_form = PasswordResetForm()() try: form_result = password_reset_form.to_python(dict(request.POST)) user_model.reset_password(form_result) h.flash(_('Your new password was sent'), - category='success') + category='success') return redirect(url('login_home')) - + except formencode.Invalid, errors: return htmlfill.render( render('/password_reset.html'), @@ -134,11 +136,11 @@ class LoginController(BaseController): errors=errors.error_dict or {}, prefix_error=False, encoding="UTF-8") - + return render('/password_reset.html') - + def logout(self): session['rhodecode_user'] = AuthUser() session.save() log.info('Logging out and setting user as Empty') - redirect(url('hg_home')) + redirect(url('home')) diff --git a/rhodecode/controllers/search.py b/rhodecode/controllers/search.py --- a/rhodecode/controllers/search.py +++ b/rhodecode/controllers/search.py @@ -22,11 +22,11 @@ Created on Aug 7, 2010 search controller for pylons @author: marcink """ -from pylons import request, response, session, tmpl_context as c, url +from pylons import request, response, config, 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.indexers import IDX_LOCATION, SCHEMA, IDX_NAME, ResultWrapper +from rhodecode.lib.indexers import SCHEMA, IDX_NAME, ResultWrapper from webhelpers.paginate import Page from webhelpers.util import update_params from pylons.i18n.translation import _ @@ -42,7 +42,7 @@ class SearchController(BaseController): @LoginRequired() def __before__(self): - super(SearchController, self).__before__() + super(SearchController, self).__before__() def index(self, search_repo=None): c.repo_name = search_repo @@ -56,15 +56,16 @@ class SearchController(BaseController): 'repository':'repository'}\ .get(c.cur_type, 'content') - + if c.cur_query: cur_query = c.cur_query.lower() - + if c.cur_query: p = int(request.params.get('page', 1)) highlight_items = set() try: - idx = open_dir(IDX_LOCATION, indexname=IDX_NAME) + idx = open_dir(config['app_conf']['index_dir'] + , indexname=IDX_NAME) searcher = idx.searcher() qp = QueryParser(search_type, schema=SCHEMA) @@ -72,7 +73,7 @@ class SearchController(BaseController): cur_query = u'repository:%s %s' % (c.repo_name, cur_query) try: query = qp.parse(unicode(cur_query)) - + if isinstance(query, Phrase): highlight_items.update(query.words) else: @@ -81,14 +82,14 @@ class SearchController(BaseController): highlight_items.add(i[1]) matcher = query.matcher(searcher) - + log.debug(query) log.debug(highlight_items) results = searcher.search(query) res_ln = len(results) c.runtime = '%s results (%.3f seconds)' \ % (res_ln, results.runtime) - + def url_generator(**kw): return update_params("?q=%s&type=%s" \ % (c.cur_query, c.cur_search), **kw) @@ -98,8 +99,8 @@ class SearchController(BaseController): highlight_items), page=p, item_count=res_ln, items_per_page=10, url=url_generator) - - + + except QueryParserError: c.runtime = _('Invalid search query. Try quoting it.') searcher.close() @@ -107,6 +108,6 @@ class SearchController(BaseController): log.error(traceback.format_exc()) log.error('Empty Index data') c.runtime = _('There is no index to search in. Please run whoosh indexer') - + # Return a rendered template return render('/search/search.html') diff --git a/rhodecode/controllers/settings.py b/rhodecode/controllers/settings.py --- a/rhodecode/controllers/settings.py +++ b/rhodecode/controllers/settings.py @@ -30,7 +30,7 @@ from rhodecode.lib.auth import LoginRequ from rhodecode.lib.base import BaseController, render from rhodecode.lib.utils import invalidate_cache, action_logger from rhodecode.model.forms import RepoSettingsForm, RepoForkForm -from rhodecode.model.repo_model import RepoModel +from rhodecode.model.repo import RepoModel import formencode import logging import rhodecode.lib.helpers as h @@ -41,35 +41,35 @@ log = logging.getLogger(__name__) class SettingsController(BaseController): @LoginRequired() - @HasRepoPermissionAllDecorator('repository.admin') + @HasRepoPermissionAllDecorator('repository.admin') def __before__(self): super(SettingsController, self).__before__() - + def index(self, repo_name): repo_model = RepoModel() - c.repo_info = repo = repo_model.get(repo_name) + c.repo_info = repo = repo_model.get_by_repo_name(repo_name) if not repo: - h.flash(_('%s repository is not mapped to db perhaps' + h.flash(_('%s repository is not mapped to db perhaps' ' it was created or renamed from the filesystem' ' please run the application again' ' in order to rescan repositories') % repo_name, category='error') - - return redirect(url('hg_home')) - defaults = c.repo_info.__dict__ + + return redirect(url('home')) + defaults = c.repo_info.get_dict() defaults.update({'user':c.repo_info.user.username}) c.users_array = repo_model.get_users_js() - + for p in c.repo_info.repo_to_perm: - defaults.update({'perm_%s' % p.user.username: + defaults.update({'perm_%s' % p.user.username: p.permission.permission_name}) - + return htmlfill.render( render('settings/repo_settings.html'), defaults=defaults, encoding="UTF-8", force_defaults=False - ) + ) def update(self, repo_name): repo_model = RepoModel() @@ -78,12 +78,14 @@ class SettingsController(BaseController) try: form_result = _form.to_python(dict(request.POST)) repo_model.update(repo_name, form_result) - invalidate_cache('cached_repo_list') + invalidate_cache('get_repo_cached_%s' % repo_name) h.flash(_('Repository %s updated successfully' % repo_name), category='success') - changed_name = form_result['repo_name'] + changed_name = form_result['repo_name'] + action_logger(self.rhodecode_user, 'user_updated_repo', + changed_name, '', self.sa) except formencode.Invalid, errors: - c.repo_info = repo_model.get(repo_name) + c.repo_info = repo_model.get_by_repo_name(repo_name) c.users_array = repo_model.get_users_js() errors.value.update({'user':c.repo_info.user.username}) return htmlfill.render( @@ -91,17 +93,17 @@ class SettingsController(BaseController) defaults=errors.value, errors=errors.error_dict or {}, prefix_error=False, - encoding="UTF-8") + encoding="UTF-8") except Exception: log.error(traceback.format_exc()) - h.flash(_('error occured during update of repository %s') \ + h.flash(_('error occurred during update of repository %s') \ % repo_name, category='error') - + return redirect(url('repo_settings_home', repo_name=changed_name)) - def delete(self, repo_name): + def delete(self, repo_name): """DELETE /repos/repo_name: Delete an existing item""" # Forms posted to this method should contain a hidden field: # @@ -109,67 +111,68 @@ class SettingsController(BaseController) # h.form(url('repo_settings_delete', repo_name=ID), # method='delete') # url('repo_settings_delete', repo_name=ID) - + repo_model = RepoModel() - repo = repo_model.get(repo_name) + repo = repo_model.get_by_repo_name(repo_name) if not repo: - h.flash(_('%s repository is not mapped to db perhaps' + h.flash(_('%s repository is not mapped to db perhaps' ' it was moved or renamed from the filesystem' ' please run the application again' ' in order to rescan repositories') % repo_name, category='error') - - return redirect(url('hg_home')) + + return redirect(url('home')) try: action_logger(self.rhodecode_user, 'user_deleted_repo', - repo_name, '', self.sa) - repo_model.delete(repo) - invalidate_cache('cached_repo_list') + repo_name, '', self.sa) + repo_model.delete(repo) + invalidate_cache('get_repo_cached_%s' % repo_name) h.flash(_('deleted repository %s') % repo_name, category='success') except Exception: h.flash(_('An error occurred during deletion of %s') % repo_name, category='error') - - return redirect(url('hg_home')) - + + return redirect(url('home')) + def fork(self, repo_name): repo_model = RepoModel() - c.repo_info = repo = repo_model.get(repo_name) + c.repo_info = repo = repo_model.get_by_repo_name(repo_name) if not repo: - h.flash(_('%s repository is not mapped to db perhaps' + h.flash(_('%s repository is not mapped to db perhaps' ' it was created or renamed from the filesystem' ' please run the application again' ' in order to rescan repositories') % repo_name, category='error') - - return redirect(url('hg_home')) - + + return redirect(url('home')) + return render('settings/repo_fork.html') - - - + + + def fork_create(self, repo_name): repo_model = RepoModel() - c.repo_info = repo_model.get(repo_name) - _form = RepoForkForm()() + c.repo_info = repo_model.get_by_repo_name(repo_name) + _form = RepoForkForm(old_data={'repo_type':c.repo_info.repo_type})() form_result = {} try: form_result = _form.to_python(dict(request.POST)) form_result.update({'repo_name':repo_name}) repo_model.create_fork(form_result, c.rhodecode_user) - h.flash(_('fork %s repository as %s task added') \ + h.flash(_('forked %s repository as %s') \ % (repo_name, form_result['fork_name']), category='success') - action_logger(self.rhodecode_user, 'user_forked_repo', - repo_name, '', self.sa) + action_logger(self.rhodecode_user, + 'user_forked_repo:%s' % form_result['fork_name'], + repo_name, '', self.sa) except formencode.Invalid, errors: c.new_repo = errors.value['fork_name'] r = render('settings/repo_fork.html') - + return htmlfill.render( r, defaults=errors.value, errors=errors.error_dict or {}, prefix_error=False, - encoding="UTF-8") - return redirect(url('hg_home')) + encoding="UTF-8") + return redirect(url('home')) diff --git a/rhodecode/controllers/shortlog.py b/rhodecode/controllers/shortlog.py --- a/rhodecode/controllers/shortlog.py +++ b/rhodecode/controllers/shortlog.py @@ -25,7 +25,7 @@ shortlog controller for pylons from pylons import tmpl_context as c, request from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator from rhodecode.lib.base import BaseController, render -from rhodecode.model.hg_model import HgModel +from rhodecode.model.scm import ScmModel from webhelpers.paginate import Page import logging log = logging.getLogger(__name__) @@ -40,7 +40,7 @@ class ShortlogController(BaseController) def index(self): p = int(request.params.get('page', 1)) - repo = HgModel().get_repo(c.repo_name) + repo = ScmModel().get_repo(c.repo_name) c.repo_changesets = Page(repo, page=p, items_per_page=20) c.shortlog_data = render('shortlog/shortlog_data.html') if request.params.get('partial'): diff --git a/rhodecode/controllers/summary.py b/rhodecode/controllers/summary.py --- a/rhodecode/controllers/summary.py +++ b/rhodecode/controllers/summary.py @@ -1,8 +1,15 @@ -#!/usr/bin/env python -# encoding: utf-8 -# summary controller for pylons -# Copyright (C) 2009-2010 Marcin Kuzminski -# +# -*- coding: utf-8 -*- +""" + package.rhodecode.controllers.summary + ~~~~~~~~~~~~~~ + + Summary controller for Rhodecode + + :created_on: Apr 18, 2010 + :author: marcink + :copyright: (C) 2009-2010 Marcin Kuzminski + :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; version 2 @@ -17,24 +24,29 @@ # 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 18, 2010 -summary controller for pylons -@author: marcink -""" + +import calendar +import logging +from time import mktime +from datetime import datetime, timedelta + +from vcs.exceptions import ChangesetError + from pylons import tmpl_context as c, request, url +from pylons.i18n.translation import _ + +from rhodecode.model.scm import ScmModel +from rhodecode.model.db import Statistics + from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator from rhodecode.lib.base import BaseController, render -from rhodecode.lib.utils import OrderedDict -from rhodecode.model.hg_model import HgModel -from rhodecode.model.db import Statistics -from webhelpers.paginate import Page +from rhodecode.lib.utils import OrderedDict, EmptyChangeset + from rhodecode.lib.celerylib import run_task from rhodecode.lib.celerylib.tasks import get_commits_stats -from datetime import datetime, timedelta -from time import mktime -import calendar -import logging + +from webhelpers.paginate import Page + try: import json except ImportError: @@ -43,66 +55,90 @@ except ImportError: log = logging.getLogger(__name__) class SummaryController(BaseController): - + @LoginRequired() @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', - 'repository.admin') + 'repository.admin') def __before__(self): super(SummaryController, self).__before__() - + def index(self): - hg_model = HgModel() - c.repo_info = hg_model.get_repo(c.repo_name) - c.repo_changesets = Page(list(c.repo_info[:10]), page=1, items_per_page=20) + 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) + + c.repo_changesets = Page(c.repo_info, page=1, items_per_page=10, + url=url_generator) + e = request.environ - - uri = u'%(protocol)s://%(user)s@%(host)s%(prefix)s/%(repo_name)s' % { + + if self.rhodecode_user.username == 'default': + password = ':default' + else: + password = '' + + uri = u'%(protocol)s://%(user)s%(password)s@%(host)s%(prefix)s/%(repo_name)s' % { 'protocol': e.get('wsgi.url_scheme'), 'user':str(c.rhodecode_user.username), + 'password':password, 'host':e.get('HTTP_HOST'), 'prefix':e.get('SCRIPT_NAME'), 'repo_name':c.repo_name, } c.clone_repo_url = uri c.repo_tags = OrderedDict() for name, hash in c.repo_info.tags.items()[:10]: - c.repo_tags[name] = c.repo_info.get_changeset(hash) - + try: + c.repo_tags[name] = c.repo_info.get_changeset(hash) + except ChangesetError: + c.repo_tags[name] = EmptyChangeset(hash) + c.repo_branches = OrderedDict() for name, hash in c.repo_info.branches.items()[:10]: - c.repo_branches[name] = c.repo_info.get_changeset(hash) - - td = datetime.today() + timedelta(days=1) + try: + c.repo_branches[name] = c.repo_info.get_changeset(hash) + except ChangesetError: + c.repo_branches[name] = EmptyChangeset(hash) + + td = datetime.today() + timedelta(days=1) y, m, d = td.year, td.month, td.day - + ts_min_y = mktime((y - 1, (td - timedelta(days=calendar.mdays[m])).month, d, 0, 0, 0, 0, 0, 0,)) ts_min_m = mktime((y, (td - timedelta(days=calendar.mdays[m])).month, d, 0, 0, 0, 0, 0, 0,)) - + ts_max_y = mktime((y, m, d, 0, 0, 0, 0, 0, 0,)) - - run_task(get_commits_stats, c.repo_info.name, ts_min_y, ts_max_y) + if c.repo_info.dbrepo.enable_statistics: + c.no_data_msg = _('No data loaded yet') + run_task(get_commits_stats, c.repo_info.name, ts_min_y, ts_max_y) + else: + c.no_data_msg = _('Statistics update are disabled for this repository') c.ts_min = ts_min_m c.ts_max = ts_max_y - + stats = self.sa.query(Statistics)\ .filter(Statistics.repository == c.repo_info.dbrepo)\ .scalar() - - + + if stats and stats.languages: + c.no_data = False is c.repo_info.dbrepo.enable_statistics lang_stats = json.loads(stats.languages) c.commit_data = stats.commit_activity c.overview_data = stats.commit_activity_combined c.trending_languages = json.dumps(OrderedDict( sorted(lang_stats.items(), reverse=True, - key=lambda k: k[1])[:2] + key=lambda k: k[1])[:10] ) ) else: c.commit_data = json.dumps({}) - c.overview_data = json.dumps([[ts_min_y, 0], [ts_max_y, 0] ]) + c.overview_data = json.dumps([[ts_min_y, 0], [ts_max_y, 10] ]) c.trending_languages = json.dumps({}) - + c.no_data = True + return render('summary/summary.html') diff --git a/rhodecode/controllers/tags.py b/rhodecode/controllers/tags.py --- a/rhodecode/controllers/tags.py +++ b/rhodecode/controllers/tags.py @@ -26,7 +26,7 @@ from pylons import tmpl_context as c from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator from rhodecode.lib.base import BaseController, render from rhodecode.lib.utils import OrderedDict -from rhodecode.model.hg_model import HgModel +from rhodecode.model.scm import ScmModel import logging log = logging.getLogger(__name__) @@ -38,7 +38,7 @@ class TagsController(BaseController): super(TagsController, self).__before__() def index(self): - hg_model = HgModel() + hg_model = ScmModel() c.repo_info = hg_model.get_repo(c.repo_name) c.repo_tags = OrderedDict() for name, hash_ in c.repo_info.tags.items(): diff --git a/rhodecode/lib/app_globals.py b/rhodecode/lib/app_globals.py --- a/rhodecode/lib/app_globals.py +++ b/rhodecode/lib/app_globals.py @@ -19,13 +19,13 @@ class Globals(object): self.cache = CacheManager(**parse_cache_config_options(config)) self.available_permissions = None # propagated after init_model self.baseui = None # propagated after init_model - + @LazyProperty def paths(self): if self.baseui: return self.baseui.configitems('paths') - + @LazyProperty def base_path(self): if self.baseui: - return self.paths[0][1].replace('*', '') + return self.paths[0][1] diff --git a/rhodecode/lib/auth.py b/rhodecode/lib/auth.py --- a/rhodecode/lib/auth.py +++ b/rhodecode/lib/auth.py @@ -22,22 +22,23 @@ Created on April 4, 2010 @author: marcink """ -from beaker.cache import cache_region from pylons import config, session, url, request from pylons.controllers.util import abort, redirect +from rhodecode.lib.exceptions import * 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 -from sqlalchemy.exc import OperationalError -from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound import bcrypt from decorator import decorator import logging import random +import traceback -log = logging.getLogger(__name__) +log = logging.getLogger(__name__) class PasswordGenerator(object): """This is a simple class for generating password from @@ -56,7 +57,7 @@ class PasswordGenerator(object): ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM#[6] ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM#[7] - + def __init__(self, passwd=''): self.passwd = passwd @@ -64,40 +65,93 @@ class PasswordGenerator(object): self.passwd = ''.join([random.choice(type) for _ in xrange(len)]) return self.passwd - + 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)) def check_password(password, hashed): return bcrypt.hashpw(password, hashed) == hashed -@cache_region('super_short_term', 'cached_user') -def get_user_cached(username): - sa = meta.Session - try: - user = sa.query(User).filter(User.username == username).one() - finally: - meta.Session.remove() - return user +def authfunc(environ, username, password): + """ + Dummy authentication function used in Mercurial/Git/ and access control, + + :param environ: needed only for using in Basic auth + """ + return authenticate(username, password) + -def authfunc(environ, username, password): - try: - user = get_user_cached(username) - except (NoResultFound, MultipleResultsFound, OperationalError), e: - log.error(e) - user = None - - if user: +def authenticate(username, password): + """ + Authentication function used for access control, + firstly checks for db authentication then if ldap is enabled for ldap + authentication, also creates ldap user if not in database + + :param username: username + :param password: password + """ + user_model = UserModel() + user = user_model.get_by_username(username, cache=False) + + log.debug('Authenticating user using RhodeCode account') + if user is not None and user.is_ldap is False: if user.active: - if user.username == username and check_password(password, user.password): + + if user.username == 'default' and user.active: + log.info('user %s authenticated correctly as anonymous user', + username) + return True + + elif user.username == username and check_password(password, user.password): log.info('user %s authenticated correctly', username) return True else: - log.error('user %s is disabled', username) - + log.warning('user %s is disabled', username) + + else: + log.debug('Regular authentication failed') + user_obj = user_model.get_by_username(username, cache=False, + case_insensitive=True) + + if user_obj is not None and user_obj.is_ldap is False: + log.debug('this user already exists as non ldap') + return False + + from rhodecode.model.settings import SettingsModel + ldap_settings = SettingsModel().get_ldap_settings() + + #====================================================================== + # FALLBACK TO LDAP AUTH IN ENABLE + #====================================================================== + if ldap_settings.get('ldap_active', False): + log.debug("Authenticating user using ldap") + kwargs = { + 'server':ldap_settings.get('ldap_host', ''), + 'base_dn':ldap_settings.get('ldap_base_dn', ''), + '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'), + 'ldap_version':3, + } + log.debug('Checking for ldap authentication') + try: + aldap = AuthLdap(**kwargs) + res = aldap.authenticate_ldap(username, password) + log.debug('Got ldap response %s', res) + + if user_model.create_ldap(username, password): + log.info('created new ldap user') + + return True + except (LdapUsernameError, LdapPasswordError,): + pass + except (Exception,): + log.error(traceback.format_exc()) + pass return False class AuthUser(object): @@ -114,6 +168,8 @@ class AuthUser(object): self.is_admin = False self.permissions = {} + def __repr__(self): + return "" % (self.user_id, self.username) def set_available_permissions(config): """ @@ -125,82 +181,62 @@ def set_available_permissions(config): """ log.info('getting information about all available permissions') try: - sa = meta.Session + sa = meta.Session() all_perms = sa.query(Permission).all() + except: + pass finally: meta.Session.remove() - + config['available_permissions'] = [x.permission_name for x in all_perms] def set_base_path(config): config['base_path'] = config['pylons.app_globals'].base_path -def fill_data(user): - """ - Fills user data with those from database and log out user if not present - in database - :param user: - """ - sa = meta.Session - dbuser = sa.query(User).options(FromCache('sql_cache_short', - 'getuser_%s' % user.user_id))\ - .get(user.user_id) - if dbuser: - user.username = dbuser.username - user.is_admin = dbuser.admin - user.name = dbuser.name - user.lastname = dbuser.lastname - user.email = dbuser.email - else: - user.is_authenticated = False - meta.Session.remove() - return user - + def fill_perms(user): """ Fills user permission attribute with permissions taken from database :param user: """ - - sa = meta.Session + + sa = meta.Session() user.permissions['repositories'] = {} user.permissions['global'] = set() - + #=========================================================================== # fetch default permissions #=========================================================================== - default_user = sa.query(User)\ - .options(FromCache('sql_cache_short','getuser_%s' % 'default'))\ - .filter(User.username == 'default').scalar() - + default_user = UserModel().get_by_username('default', cache=True) + default_perms = sa.query(RepoToPerm, Repository, Permission)\ .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\ .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\ .filter(RepoToPerm.user == default_user).all() - + if user.is_admin: #======================================================================= # #admin have all default rights set to admin #======================================================================= user.permissions['global'].add('hg.admin') - + for perm in default_perms: p = 'repository.admin' user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p - + else: #======================================================================= # set default permissions #======================================================================= - + #default global default_global_perms = sa.query(UserToPerm)\ - .filter(UserToPerm.user == sa.query(User).filter(User.username == - 'default').one()) - + .filter(UserToPerm.user == sa.query(User)\ + .filter(User.username == 'default').one()) + for perm in default_global_perms: user.permissions['global'].add(perm.permission.permission_name) - + #default repositories for perm in default_perms: if perm.Repository.private and not perm.Repository.user_id == user.user_id: @@ -211,9 +247,9 @@ def fill_perms(user): p = 'repository.admin' else: p = perm.Permission.permission_name - + user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p - + #======================================================================= # #overwrite default with user permissions if any #======================================================================= @@ -221,38 +257,52 @@ def fill_perms(user): .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\ .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\ .filter(RepoToPerm.user_id == user.user_id).all() - + for perm in user_perms: if perm.Repository.user_id == user.user_id:#set admin if owner p = 'repository.admin' else: p = perm.Permission.permission_name user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p - meta.Session.remove() + meta.Session.remove() return user - + def get_user(session): """ Gets user from session, and wraps permissions into user :param session: """ user = session.get('rhodecode_user', AuthUser()) + #if the user is not logged in we check for anonymous access + #if user is logged and it's a default user check if we still have anonymous + #access enabled + if user.user_id is None or user.username == 'default': + anonymous_user = UserModel().get_by_username('default', cache=True) + if anonymous_user.active is True: + #then we set this user is logged in + user.is_authenticated = True + user.user_id = anonymous_user.user_id + else: + user.is_authenticated = False + if user.is_authenticated: - user = fill_data(user) + user = UserModel().fill_data(user) + user = fill_perms(user) session['rhodecode_user'] = user session.save() return user - + #=============================================================================== # CHECK DECORATORS #=============================================================================== class LoginRequired(object): - """Must be logged in to execute this function else redirect to login page""" - + """Must be logged in to execute this function else + redirect to login page""" + def __call__(self, func): return decorator(self.__wrapper, func) - + def __wrapper(self, func, *fargs, **fkwargs): user = session.get('rhodecode_user', AuthUser()) log.debug('Checking login required for user:%s', user.username) @@ -261,21 +311,46 @@ class LoginRequired(object): return func(*fargs, **fkwargs) else: log.warn('user %s not authenticated', user.username) - + p = '' if request.environ.get('SCRIPT_NAME') != '/': p += request.environ.get('SCRIPT_NAME') - + p += request.environ.get('PATH_INFO') if request.environ.get('QUERY_STRING'): p += '?' + request.environ.get('QUERY_STRING') - - log.debug('redirecting to login page with %s', p) + + log.debug('redirecting to login page with %s', p) return redirect(url('login_home', came_from=p)) +class NotAnonymous(object): + """Must be logged in to execute this function else + redirect to login page""" + + def __call__(self, func): + return decorator(self.__wrapper, func) + + def __wrapper(self, func, *fargs, **fkwargs): + user = session.get('rhodecode_user', AuthUser()) + log.debug('Checking if user is not anonymous') + + anonymous = user.username == 'default' + + if anonymous: + p = '' + if request.environ.get('SCRIPT_NAME') != '/': + p += request.environ.get('SCRIPT_NAME') + + p += request.environ.get('PATH_INFO') + if request.environ.get('QUERY_STRING'): + p += '?' + request.environ.get('QUERY_STRING') + return redirect(url('login_home', came_from=p)) + else: + return func(*fargs, **fkwargs) + class PermsDecorator(object): """Base class for decorators""" - + def __init__(self, *required_perms): available_perms = config['available_permissions'] for perm in required_perms: @@ -283,32 +358,33 @@ class PermsDecorator(object): raise Exception("'%s' permission is not defined" % perm) self.required_perms = set(required_perms) self.user_perms = None - + def __call__(self, func): return decorator(self.__wrapper, func) - - + + def __wrapper(self, func, *fargs, **fkwargs): # _wrapper.__name__ = func.__name__ # _wrapper.__dict__.update(func.__dict__) # _wrapper.__doc__ = func.__doc__ + self.user = session.get('rhodecode_user', AuthUser()) + self.user_perms = self.user.permissions + log.debug('checking %s permissions %s for %s %s', + self.__class__.__name__, self.required_perms, func.__name__, + self.user) - self.user_perms = session.get('rhodecode_user', AuthUser()).permissions - log.debug('checking %s permissions %s for %s', - self.__class__.__name__, self.required_perms, func.__name__) - if self.check_permissions(): - log.debug('Permission granted for %s', func.__name__) - + log.debug('Permission granted for %s %s', func.__name__, self.user) + return func(*fargs, **fkwargs) - + else: - log.warning('Permission denied for %s', func.__name__) + log.warning('Permission denied for %s %s', func.__name__, self.user) #redirect with forbidden ret code return abort(403) - - + + def check_permissions(self): """Dummy function for overriding""" raise Exception('You have to write this function in child class') @@ -317,18 +393,18 @@ class HasPermissionAllDecorator(PermsDec """Checks for access permission for all given predicates. All of them have to be meet in order to fulfill the request """ - + def check_permissions(self): if self.required_perms.issubset(self.user_perms.get('global')): return True return False - + class HasPermissionAnyDecorator(PermsDecorator): """Checks for access permission for any of given predicates. In order to fulfill the request any of predicates must be meet """ - + def check_permissions(self): if self.required_perms.intersection(self.user_perms.get('global')): return True @@ -338,7 +414,7 @@ class HasRepoPermissionAllDecorator(Perm """Checks for access permission for all given predicates for specific repository. All of them have to be meet in order to fulfill the request """ - + def check_permissions(self): repo_name = get_repo_slug(request) try: @@ -348,16 +424,16 @@ class HasRepoPermissionAllDecorator(Perm if self.required_perms.issubset(user_perms): return True return False - + class HasRepoPermissionAnyDecorator(PermsDecorator): """Checks for access permission for any of given predicates for specific repository. In order to fulfill the request any of predicates must be meet """ - + def check_permissions(self): repo_name = get_repo_slug(request) - + try: user_perms = set([self.user_perms['repositories'][repo_name]]) except KeyError: @@ -371,10 +447,10 @@ class HasRepoPermissionAnyDecorator(Perm class PermsFunction(object): """Base function for other check functions""" - + def __init__(self, *perms): available_perms = config['available_permissions'] - + for perm in perms: if perm not in available_perms: raise Exception("'%s' permission in not defined" % perm) @@ -382,29 +458,30 @@ class PermsFunction(object): self.user_perms = None self.granted_for = '' self.repo_name = None - + def __call__(self, check_Location=''): user = session.get('rhodecode_user', False) if not user: return False self.user_perms = user.permissions - self.granted_for = user.username - log.debug('checking %s %s', self.__class__.__name__, self.required_perms) - + self.granted_for = user.username + log.debug('checking %s %s %s', self.__class__.__name__, + self.required_perms, user) + if self.check_permissions(): - log.debug('Permission granted for %s @%s', self.granted_for, - check_Location) + log.debug('Permission granted for %s @ %s %s', self.granted_for, + check_Location, user) return True - + else: - log.warning('Permission denied for %s @%s', self.granted_for, - check_Location) - return False - + log.warning('Permission denied for %s @ %s %s', self.granted_for, + check_Location, user) + return False + def check_permissions(self): """Dummy function for overriding""" raise Exception('You have to write this function in child class') - + class HasPermissionAll(PermsFunction): def check_permissions(self): if self.required_perms.issubset(self.user_perms.get('global')): @@ -418,11 +495,11 @@ class HasPermissionAny(PermsFunction): return False class HasRepoPermissionAll(PermsFunction): - + def __call__(self, repo_name=None, check_Location=''): self.repo_name = repo_name return super(HasRepoPermissionAll, self).__call__(check_Location) - + def check_permissions(self): if not self.repo_name: self.repo_name = get_repo_slug(request) @@ -432,17 +509,17 @@ class HasRepoPermissionAll(PermsFunction [self.repo_name]]) except KeyError: return False - self.granted_for = self.repo_name + self.granted_for = self.repo_name if self.required_perms.issubset(self.user_perms): return True return False - + class HasRepoPermissionAny(PermsFunction): - + def __call__(self, repo_name=None, check_Location=''): self.repo_name = repo_name return super(HasRepoPermissionAny, self).__call__(check_Location) - + def check_permissions(self): if not self.repo_name: self.repo_name = get_repo_slug(request) @@ -464,13 +541,13 @@ class HasRepoPermissionAny(PermsFunction class HasPermissionAnyMiddleware(object): def __init__(self, *perms): self.required_perms = set(perms) - + def __call__(self, user, repo_name): usr = AuthUser() usr.user_id = user.user_id usr.username = user.username usr.is_admin = user.admin - + try: self.user_perms = set([fill_perms(usr)\ .permissions['repositories'][repo_name]]) @@ -478,9 +555,9 @@ class HasPermissionAnyMiddleware(object) self.user_perms = set() self.granted_for = '' self.username = user.username - self.repo_name = repo_name + self.repo_name = repo_name return self.check_permissions() - + def check_permissions(self): log.debug('checking mercurial protocol ' 'permissions for user:%s repository:%s', diff --git a/rhodecode/lib/auth_ldap.py b/rhodecode/lib/auth_ldap.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/auth_ldap.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python +# encoding: utf-8 +# ldap authentication lib +# Copyright (C) 2009-2010 Marcin Kuzminski +# +# 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 Nov 17, 2010 + +@author: marcink +""" + +from rhodecode.lib.exceptions import * +import logging + +log = logging.getLogger(__name__) + +try: + import ldap +except ImportError: + pass + +class AuthLdap(object): + + def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='', + use_ldaps=False, ldap_version=3): + self.ldap_version = ldap_version + if use_ldaps: + port = port or 689 + self.LDAP_USE_LDAPS = use_ldaps + self.LDAP_SERVER_ADDRESS = server + self.LDAP_SERVER_PORT = port + + #USE FOR READ ONLY BIND TO LDAP SERVER + self.LDAP_BIND_DN = bind_dn + self.LDAP_BIND_PASS = bind_pass + + ldap_server_type = 'ldap' + if self.LDAP_USE_LDAPS:ldap_server_type = ldap_server_type + 's' + self.LDAP_SERVER = "%s://%s:%s" % (ldap_server_type, + self.LDAP_SERVER_ADDRESS, + self.LDAP_SERVER_PORT) + + self.BASE_DN = base_dn + + def authenticate_ldap(self, username, password): + """Authenticate a user via LDAP and return his/her LDAP properties. + + Raises AuthenticationError if the credentials are rejected, or + EnvironmentError if the LDAP server can't be reached. + + :param username: username + :param password: password + """ + + from rhodecode.lib.helpers import chop_at + + uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS) + + if "," in username: + raise LdapUsernameError("invalid character in username: ,") + try: + ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, '/etc/openldap/cacerts') + ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10) + server = ldap.initialize(self.LDAP_SERVER) + if self.ldap_version == 2: + server.protocol = ldap.VERSION2 + else: + server.protocol = ldap.VERSION3 + + if self.LDAP_BIND_DN and self.LDAP_BIND_PASS: + server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS) + + dn = self.BASE_DN % {'user':uid} + log.debug("Authenticating %r at %s", dn, self.LDAP_SERVER) + server.simple_bind_s(dn, password) + + properties = server.search_s(dn, ldap.SCOPE_SUBTREE) + if not properties: + raise ldap.NO_SUCH_OBJECT() + except ldap.NO_SUCH_OBJECT, e: + log.debug("LDAP says no such user '%s' (%s)", uid, username) + raise LdapUsernameError() + except ldap.INVALID_CREDENTIALS, e: + log.debug("LDAP rejected password for user '%s' (%s)", uid, username) + raise LdapPasswordError() + except ldap.SERVER_DOWN, e: + raise LdapConnectionError("LDAP can't access authentication server") + + return properties[0] + diff --git a/rhodecode/lib/base.py b/rhodecode/lib/base.py --- a/rhodecode/lib/base.py +++ b/rhodecode/lib/base.py @@ -9,30 +9,36 @@ from rhodecode import __version__ from rhodecode.lib import auth from rhodecode.lib.utils import get_repo_slug from rhodecode.model import meta -from rhodecode.model.hg_model import _get_repos_cached, \ - _get_repos_switcher_cached +from rhodecode.model.scm import ScmModel +from rhodecode import BACKENDS class BaseController(WSGIController): - + def __before__(self): c.rhodecode_version = __version__ c.rhodecode_name = config['rhodecode_title'] c.repo_name = get_repo_slug(request) - c.cached_repo_list = _get_repos_cached() - c.repo_switcher_list = _get_repos_switcher_cached(c.cached_repo_list) - + c.cached_repo_list = ScmModel().get_repos() + c.backends = BACKENDS.keys() + self.cut_off_limit = int(config['cut_off_limit']) + self.sa = meta.Session() + scm_model = ScmModel(self.sa) + #c.unread_journal = scm_model.get_unread_journal() + if c.repo_name: - cached_repo = c.cached_repo_list.get(c.repo_name) - + cached_repo = scm_model.get(c.repo_name) if cached_repo: c.repository_tags = cached_repo.tags c.repository_branches = cached_repo.branches + c.repository_followers = scm_model.get_followers(cached_repo.dbrepo.repo_id) + c.repository_forks = scm_model.get_forks(cached_repo.dbrepo.repo_id) else: c.repository_tags = {} c.repository_branches = {} - - self.sa = meta.Session - + c.repository_followers = 0 + c.repository_forks = 0 + + def __call__(self, environ, start_response): """Invoke the Controller""" # WSGIController.__call__ dispatches to the Controller method diff --git a/rhodecode/lib/celerylib/__init__.py b/rhodecode/lib/celerylib/__init__.py --- a/rhodecode/lib/celerylib/__init__.py +++ b/rhodecode/lib/celerylib/__init__.py @@ -1,14 +1,54 @@ -from rhodecode.lib.pidlock import DaemonLock, LockHeld -from vcs.utils.lazy import LazyProperty -from decorator import decorator -import logging +# -*- coding: utf-8 -*- +""" + package.rhodecode.lib.celerylib.__init__ + ~~~~~~~~~~~~~~ + + celery libs for RhodeCode + + :created_on: Nov 27, 2010 + :author: marcink + :copyright: (C) 2009-2010 Marcin Kuzminski + :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; 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. + import os import sys +import socket import traceback +import logging + from hashlib import md5 -import socket +from decorator import decorator +from vcs.utils.lazy import LazyProperty + +from rhodecode.lib.pidlock import DaemonLock, LockHeld + +from pylons import config + log = logging.getLogger(__name__) +def str2bool(v): + return v.lower() in ["yes", "true", "t", "1"] if v else None + +try: + CELERY_ON = str2bool(config['app_conf'].get('use_celery')) +except KeyError: + CELERY_ON = False + class ResultWrapper(object): def __init__(self, task): self.task = task @@ -18,27 +58,22 @@ class ResultWrapper(object): return self.task def run_task(task, *args, **kwargs): - try: - t = task.delay(*args, **kwargs) - log.info('running task %s', t.task_id) - return t - except socket.error, e: - + if CELERY_ON: try: - conn_failed = e.errno == 111 - except AttributeError: - conn_failed = False + t = task.delay(*args, **kwargs) + log.info('running task %s:%s', t.task_id, task) + return t + except socket.error, e: + if e.errno == 111: + log.debug('Unable to connect to celeryd. Sync execution') + else: + log.error(traceback.format_exc()) + except KeyError, e: + log.debug('Unable to connect to celeryd. Sync execution') + except Exception, e: + log.error(traceback.format_exc()) - if conn_failed: - log.debug('Unable to connect to celeryd. Sync execution') - else: - log.debug('Unable to connect to celeryd. Sync execution') - - except KeyError, e: - log.debug('Unable to connect to celeryd. Sync execution') - except Exception, e: - log.error(traceback.format_exc()) - + log.debug('executing task %s in sync mode', task) return ResultWrapper(task(*args, **kwargs)) diff --git a/rhodecode/lib/celerylib/tasks.py b/rhodecode/lib/celerylib/tasks.py --- a/rhodecode/lib/celerylib/tasks.py +++ b/rhodecode/lib/celerylib/tasks.py @@ -1,14 +1,26 @@ from celery.decorators import task +import os +import traceback +from time import mktime from operator import itemgetter + +from pylons import config from pylons.i18n.translation import _ -from rhodecode.lib.celerylib import run_task, locked_task + +from rhodecode.lib.celerylib import run_task, locked_task, str2bool from rhodecode.lib.helpers import person from rhodecode.lib.smtp_mailer import SmtpMailer -from rhodecode.lib.utils import OrderedDict -from time import mktime -from vcs.backends.hg import MercurialRepository -import traceback +from rhodecode.lib.utils import OrderedDict, add_cache +from rhodecode.model import init_model +from rhodecode.model import meta +from rhodecode.model.db import RhodeCodeUi + +from vcs.backends import get_repo + +from sqlalchemy import engine_from_config + +add_cache(config) try: import json @@ -16,96 +28,56 @@ except ImportError: #python 2.5 compatibility import simplejson as json -try: - from celeryconfig import PYLONS_CONFIG as config - celery_on = True -except ImportError: - #if celeryconfig is not present let's just load our pylons - #config instead - from pylons import config - celery_on = False - - __all__ = ['whoosh_index', 'get_commits_stats', 'reset_user_password', 'send_email'] +CELERY_ON = str2bool(config['app_conf'].get('use_celery')) + def get_session(): - if celery_on: - from sqlalchemy import engine_from_config - from sqlalchemy.orm import sessionmaker, scoped_session - engine = engine_from_config(dict(config.items('app:main')), 'sqlalchemy.db1.') - sa = scoped_session(sessionmaker(bind=engine)) - else: - #If we don't use celery reuse our current application Session - from rhodecode.model.meta import Session - sa = Session - + if CELERY_ON: + engine = engine_from_config(config, 'sqlalchemy.db1.') + init_model(engine) + sa = meta.Session() return sa -def get_hg_settings(): - from rhodecode.model.db import RhodeCodeSettings - sa = get_session() - ret = sa.query(RhodeCodeSettings).all() - - if not ret: - raise Exception('Could not get application settings !') - settings = {} - for each in ret: - settings['rhodecode_' + each.app_settings_name] = each.app_settings_value - - return settings - -def get_hg_ui_settings(): - from rhodecode.model.db import RhodeCodeUi +def get_repos_path(): sa = get_session() - ret = sa.query(RhodeCodeUi).all() - - if not ret: - raise Exception('Could not get application ui settings !') - settings = {} - for each in ret: - k = each.ui_key - v = each.ui_value - if k == '/': - k = 'root_path' - - if k.find('.') != -1: - k = k.replace('.', '_') - - if each.ui_section == 'hooks': - v = each.ui_active - - settings[each.ui_section + '_' + k] = v - - return settings + q = sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one() + return q.ui_value @task @locked_task def whoosh_index(repo_location, full_index): log = whoosh_index.get_logger() from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon - WhooshIndexingDaemon(repo_location=repo_location).run(full_index=full_index) + index_location = config['index_dir'] + WhooshIndexingDaemon(index_location=index_location, + repo_location=repo_location, sa=get_session())\ + .run(full_index=full_index) @task @locked_task def get_commits_stats(repo_name, ts_min_y, ts_max_y): from rhodecode.model.db import Statistics, Repository log = get_commits_stats.get_logger() - author_key_cleaner = lambda k: person(k).replace('"', "") #for js data compatibilty - + + #for js data compatibilty + author_key_cleaner = lambda k: person(k).replace('"', "") + commits_by_day_author_aggregate = {} commits_by_day_aggregate = {} - repos_path = get_hg_ui_settings()['paths_root_path'].replace('*', '') - repo = MercurialRepository(repos_path + repo_name) + repos_path = get_repos_path() + p = os.path.join(repos_path, repo_name) + repo = get_repo(p) skip_date_limit = True - parse_limit = 350 #limit for single task changeset parsing optimal for + parse_limit = 250 #limit for single task changeset parsing optimal for last_rev = 0 last_cs = None timegetter = itemgetter('time') - + sa = get_session() - + dbrepo = sa.query(Repository)\ .filter(Repository.repo_name == repo_name).scalar() cur_stats = sa.query(Statistics)\ @@ -114,26 +86,27 @@ def get_commits_stats(repo_name, ts_min_ last_rev = cur_stats.stat_on_revision if not repo.revisions: return True - + if last_rev == repo.revisions[-1] and len(repo.revisions) > 1: - #pass silently without any work if we're not on first revision or current - #state of parsing revision(from db marker) is the last revision + #pass silently without any work if we're not on first revision or + #current state of parsing revision(from db marker) is the last revision return True - + if cur_stats: commits_by_day_aggregate = OrderedDict( json.loads( cur_stats.commit_activity_combined)) commits_by_day_author_aggregate = json.loads(cur_stats.commit_activity) - + log.debug('starting parsing %s', parse_limit) - for cnt, rev in enumerate(repo.revisions[last_rev:]): + lmktime = mktime + + last_rev = last_rev + 1 if last_rev > 0 else last_rev + for rev in repo.revisions[last_rev:last_rev + parse_limit]: last_cs = cs = repo.get_changeset(rev) - k = '%s-%s-%s' % (cs.date.timetuple()[0], cs.date.timetuple()[1], - cs.date.timetuple()[2]) - timetupple = [int(x) for x in k.split('-')] - timetupple.extend([0 for _ in xrange(6)]) - k = mktime(timetupple) + k = lmktime([cs.date.timetuple()[0], cs.date.timetuple()[1], + cs.date.timetuple()[2], 0, 0, 0, 0, 0, 0]) + if commits_by_day_author_aggregate.has_key(author_key_cleaner(cs.author)): try: l = [timegetter(x) for x in commits_by_day_author_aggregate\ @@ -141,20 +114,20 @@ def get_commits_stats(repo_name, ts_min_ time_pos = l.index(k) except ValueError: time_pos = False - + if time_pos >= 0 and time_pos is not False: - + datadict = commits_by_day_author_aggregate\ [author_key_cleaner(cs.author)]['data'][time_pos] - + datadict["commits"] += 1 datadict["added"] += len(cs.added) datadict["changed"] += len(cs.changed) datadict["removed"] += len(cs.removed) - + else: if k >= ts_min_y and k <= ts_max_y or skip_date_limit: - + datadict = {"time":k, "commits":1, "added":len(cs.added), @@ -163,7 +136,7 @@ def get_commits_stats(repo_name, ts_min_ } commits_by_day_author_aggregate\ [author_key_cleaner(cs.author)]['data'].append(datadict) - + else: if k >= ts_min_y and k <= ts_max_y or skip_date_limit: commits_by_day_author_aggregate[author_key_cleaner(cs.author)] = { @@ -175,23 +148,15 @@ def get_commits_stats(repo_name, ts_min_ "removed":len(cs.removed), }], "schema":["commits"], - } - + } + #gather all data by day if commits_by_day_aggregate.has_key(k): commits_by_day_aggregate[k] += 1 else: commits_by_day_aggregate[k] = 1 - - if cnt >= parse_limit: - #don't fetch to much data since we can freeze application - break - overview_data = [] - for k, v in commits_by_day_aggregate.items(): - overview_data.append([k, v]) - overview_data = sorted(overview_data, key=itemgetter(0)) - + overview_data = sorted(commits_by_day_aggregate.items(), key=itemgetter(0)) if not commits_by_day_author_aggregate: commits_by_day_author_aggregate[author_key_cleaner(repo.contact)] = { "label":author_key_cleaner(repo.contact), @@ -206,23 +171,24 @@ def get_commits_stats(repo_name, ts_min_ log.debug('last revison %s', last_rev) leftovers = len(repo.revisions[last_rev:]) log.debug('revisions to parse %s', leftovers) - - if last_rev == 0 or leftovers < parse_limit: + + if last_rev == 0 or leftovers < parse_limit: + log.debug('getting code trending stats') stats.languages = json.dumps(__get_codes_stats(repo_name)) - + stats.repository = dbrepo stats.stat_on_revision = last_cs.revision - + try: sa.add(stats) - sa.commit() + sa.commit() except: log.error(traceback.format_exc()) sa.rollback() return False if len(repo.revisions) > 1: run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y) - + return True @task @@ -230,7 +196,7 @@ def reset_user_password(user_email): log = reset_user_password.get_logger() from rhodecode.lib import auth from rhodecode.model.db import User - + try: try: sa = get_session() @@ -244,38 +210,52 @@ def reset_user_password(user_email): log.info('change password for %s', user_email) if new_passwd is None: raise Exception('unable to generate new password') - + except: log.error(traceback.format_exc()) sa.rollback() - + run_task(send_email, user_email, "Your new rhodecode password", 'Your new rhodecode password:%s' % (new_passwd)) log.info('send new password mail to %s', user_email) - - + + except: log.error('Failed to update user password') log.error(traceback.format_exc()) + return True -@task +@task def send_email(recipients, subject, body): + """ + Sends an email with defined parameters from the .ini files. + + + :param recipients: list of recipients, it this is empty the defined email + address from field 'email_to' is used instead + :param subject: subject of the mail + :param body: body of the mail + """ log = send_email.get_logger() - email_config = dict(config.items('DEFAULT')) + email_config = config + + if not recipients: + recipients = [email_config.get('email_to')] + mail_from = email_config.get('app_email_from') user = email_config.get('smtp_username') passwd = email_config.get('smtp_password') mail_server = email_config.get('smtp_server') mail_port = email_config.get('smtp_port') - tls = email_config.get('smtp_use_tls') - ssl = False - + tls = str2bool(email_config.get('smtp_use_tls')) + ssl = str2bool(email_config.get('smtp_use_ssl')) + try: m = SmtpMailer(mail_from, user, passwd, mail_server, mail_port, ssl, tls) - m.send(recipients, subject, body) + m.send(recipients, subject, body) except: log.error('Mail sending failed') log.error(traceback.format_exc()) @@ -284,45 +264,96 @@ def send_email(recipients, subject, body @task def create_repo_fork(form_data, cur_user): - import os - from rhodecode.model.repo_model import RepoModel - sa = get_session() - rm = RepoModel(sa) - - rm.create(form_data, cur_user, just_db=True, fork=True) - - repos_path = get_hg_ui_settings()['paths_root_path'].replace('*', '') - repo_path = os.path.join(repos_path, form_data['repo_name']) + from rhodecode.model.repo import RepoModel + from vcs import get_backend + log = create_repo_fork.get_logger() + repo_model = RepoModel(get_session()) + repo_model.create(form_data, cur_user, just_db=True, fork=True) + repo_name = form_data['repo_name'] + repos_path = get_repos_path() + repo_path = os.path.join(repos_path, repo_name) repo_fork_path = os.path.join(repos_path, form_data['fork_name']) - - MercurialRepository(str(repo_fork_path), True, clone_url=str(repo_path)) + alias = form_data['repo_type'] - + log.info('creating repo fork %s as %s', repo_name, repo_path) + backend = get_backend(alias) + backend(str(repo_fork_path), create=True, src_url=str(repo_path)) + def __get_codes_stats(repo_name): - LANGUAGES_EXTENSIONS = ['action', 'adp', 'ashx', 'asmx', 'aspx', 'asx', 'axd', 'c', - 'cfg', 'cfm', 'cpp', 'cs', 'diff', 'do', 'el', 'erl', - 'h', 'java', 'js', 'jsp', 'jspx', 'lisp', - 'lua', 'm', 'mako', 'ml', 'pas', 'patch', 'php', 'php3', - 'php4', 'phtml', 'pm', 'py', 'rb', 'rst', 's', 'sh', - 'tpl', 'txt', 'vim', 'wss', 'xhtml', 'xml', 'xsl', 'xslt', - 'yaws'] - repos_path = get_hg_ui_settings()['paths_root_path'].replace('*', '') - repo = MercurialRepository(repos_path + repo_name) + LANGUAGES_EXTENSIONS_MAP = {'scm': 'Scheme', 'asmx': 'VbNetAspx', 'Rout': + 'RConsole', 'rest': 'Rst', 'abap': 'ABAP', 'go': 'Go', 'phtml': 'HtmlPhp', + 'ns2': 'Newspeak', 'xml': 'EvoqueXml', 'sh-session': 'BashSession', 'ads': + 'Ada', 'clj': 'Clojure', 'll': 'Llvm', 'ebuild': 'Bash', 'adb': 'Ada', + 'ada': 'Ada', 'c++-objdump': 'CppObjdump', 'aspx': + 'VbNetAspx', 'ksh': 'Bash', 'coffee': 'CoffeeScript', 'vert': 'GLShader', + 'Makefile.*': 'Makefile', 'di': 'D', 'dpatch': 'DarcsPatch', 'rake': + 'Ruby', 'moo': 'MOOCode', 'erl-sh': 'ErlangShell', 'geo': 'GLShader', + 'pov': 'Povray', 'bas': 'VbNet', 'bat': 'Batch', 'd': 'D', 'lisp': + 'CommonLisp', 'h': 'C', 'rbx': 'Ruby', 'tcl': 'Tcl', 'c++': 'Cpp', 'md': + 'MiniD', '.vimrc': 'Vim', 'xsd': 'Xml', 'ml': 'Ocaml', 'el': 'CommonLisp', + 'befunge': 'Befunge', 'xsl': 'Xslt', 'pyx': 'Cython', 'cfm': + 'ColdfusionHtml', 'evoque': 'Evoque', 'cfg': 'Ini', 'htm': 'Html', + 'Makefile': 'Makefile', 'cfc': 'ColdfusionHtml', 'tex': 'Tex', 'cs': + 'CSharp', 'mxml': 'Mxml', 'patch': 'Diff', 'apache.conf': 'ApacheConf', + 'scala': 'Scala', 'applescript': 'AppleScript', 'GNUmakefile': 'Makefile', + 'c-objdump': 'CObjdump', 'lua': 'Lua', 'apache2.conf': 'ApacheConf', 'rb': + 'Ruby', 'gemspec': 'Ruby', 'rl': 'RagelObjectiveC', 'vala': 'Vala', 'tmpl': + 'Cheetah', 'bf': 'Brainfuck', 'plt': 'Gnuplot', 'G': 'AntlrRuby', 'xslt': + 'Xslt', 'flxh': 'Felix', 'asax': 'VbNetAspx', 'Rakefile': 'Ruby', 'S': 'S', + 'wsdl': 'Xml', 'js': 'Javascript', 'autodelegate': 'Myghty', 'properties': + 'Ini', 'bash': 'Bash', 'c': 'C', 'g': 'AntlrRuby', 'r3': 'Rebol', 's': + 'Gas', 'ashx': 'VbNetAspx', 'cxx': 'Cpp', 'boo': 'Boo', 'prolog': 'Prolog', + 'sqlite3-console': 'SqliteConsole', 'cl': 'CommonLisp', 'cc': 'Cpp', 'pot': + 'Gettext', 'vim': 'Vim', 'pxi': 'Cython', 'yaml': 'Yaml', 'SConstruct': + 'Python', 'diff': 'Diff', 'txt': 'Text', 'cw': 'Redcode', 'pxd': 'Cython', + 'plot': 'Gnuplot', 'java': 'Java', 'hrl': 'Erlang', 'py': 'Python', + 'makefile': 'Makefile', 'squid.conf': 'SquidConf', 'asm': 'Nasm', 'toc': + 'Tex', 'kid': 'Genshi', 'rhtml': 'Rhtml', 'po': 'Gettext', 'pl': 'Prolog', + 'pm': 'Perl', 'hx': 'Haxe', 'ascx': 'VbNetAspx', 'ooc': 'Ooc', 'asy': + 'Asymptote', 'hs': 'Haskell', 'SConscript': 'Python', 'pytb': + 'PythonTraceback', 'myt': 'Myghty', 'hh': 'Cpp', 'R': 'S', 'aux': 'Tex', + 'rst': 'Rst', 'cpp-objdump': 'CppObjdump', 'lgt': 'Logtalk', 'rss': 'Xml', + 'flx': 'Felix', 'b': 'Brainfuck', 'f': 'Fortran', 'rbw': 'Ruby', + '.htaccess': 'ApacheConf', 'cxx-objdump': 'CppObjdump', 'j': 'ObjectiveJ', + 'mll': 'Ocaml', 'yml': 'Yaml', 'mu': 'MuPAD', 'r': 'Rebol', 'ASM': 'Nasm', + 'erl': 'Erlang', 'mly': 'Ocaml', 'mo': 'Modelica', 'def': 'Modula2', 'ini': + 'Ini', 'control': 'DebianControl', 'vb': 'VbNet', 'vapi': 'Vala', 'pro': + 'Prolog', 'spt': 'Cheetah', 'mli': 'Ocaml', 'as': 'ActionScript3', 'cmd': + 'Batch', 'cpp': 'Cpp', 'io': 'Io', 'tac': 'Python', 'haml': 'Haml', 'rkt': + 'Racket', 'st':'Smalltalk', 'inc': 'Povray', 'pas': 'Delphi', 'cmake': + 'CMake', 'csh':'Tcsh', 'hpp': 'Cpp', 'feature': 'Gherkin', 'html': 'Html', + 'php':'Php', 'php3':'Php', 'php4':'Php', 'php5':'Php', 'xhtml': 'Html', + 'hxx': 'Cpp', 'eclass': 'Bash', 'css': 'Css', + 'frag': 'GLShader', 'd-objdump': 'DObjdump', 'weechatlog': 'IrcLogs', + 'tcsh': 'Tcsh', 'objdump': 'Objdump', 'pyw': 'Python', 'h++': 'Cpp', + 'py3tb': 'Python3Traceback', 'jsp': 'Jsp', 'sql': 'Sql', 'mak': 'Makefile', + 'php': 'Php', 'mao': 'Mako', 'man': 'Groff', 'dylan': 'Dylan', 'sass': + 'Sass', 'cfml': 'ColdfusionHtml', 'darcspatch': 'DarcsPatch', 'tpl': + 'Smarty', 'm': 'ObjectiveC', 'f90': 'Fortran', 'mod': 'Modula2', 'sh': + 'Bash', 'lhs': 'LiterateHaskell', 'sources.list': 'SourcesList', 'axd': + 'VbNetAspx', 'sc': 'Python'} + + repos_path = get_repos_path() + p = os.path.join(repos_path, repo_name) + repo = get_repo(p) tip = repo.get_changeset() - code_stats = {} - for topnode, dirs, files in tip.walk('/'): - for f in files: - k = f.mimetype - if f.extension in LANGUAGES_EXTENSIONS: - if code_stats.has_key(k): - code_stats[k] += 1 + + def aggregate(cs): + for f in cs[2]: + ext = f.extension + key = LANGUAGES_EXTENSIONS_MAP.get(ext, ext) + key = key or ext + if ext in LANGUAGES_EXTENSIONS_MAP.keys() and not f.is_binary: + if code_stats.has_key(key): + code_stats[key] += 1 else: - code_stats[k] = 1 - + code_stats[key] = 1 + + map(aggregate, tip.walk('/')) + return code_stats or {} - diff --git a/rhodecode/lib/celerypylons/__init__.py b/rhodecode/lib/celerypylons/__init__.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/celerypylons/__init__.py @@ -0,0 +1,16 @@ +""" +Automatically sets the environment variable `CELERY_LOADER` to +`celerypylons.loader:PylonsLoader`. This ensures the loader is +specified when accessing the rest of this package, and allows celery +to be installed in a webapp just by importing celerypylons:: + + import celerypylons + +""" +import os +import warnings + +CELERYPYLONS_LOADER = 'rhodecode.lib.celerypylons.loader.PylonsLoader' +if os.environ.get('CELERY_LOADER', CELERYPYLONS_LOADER) != CELERYPYLONS_LOADER: + warnings.warn("'CELERY_LOADER' environment variable will be overridden by celery-pylons.") +os.environ['CELERY_LOADER'] = CELERYPYLONS_LOADER diff --git a/rhodecode/lib/celerypylons/commands.py b/rhodecode/lib/celerypylons/commands.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/celerypylons/commands.py @@ -0,0 +1,90 @@ +from rhodecode.lib.utils import BasePasterCommand, Command + + +__all__ = ['CeleryDaemonCommand', 'CeleryBeatCommand', + 'CAMQPAdminCommand', 'CeleryEventCommand'] + + +class CeleryDaemonCommand(BasePasterCommand): + """Start the celery worker + + Starts the celery worker that uses a paste.deploy configuration + file. + """ + usage = 'CONFIG_FILE [celeryd options...]' + summary = __doc__.splitlines()[0] + description = "".join(__doc__.splitlines()[2:]) + + parser = Command.standard_parser(quiet=True) + + def update_parser(self): + from celery.bin import celeryd + for x in celeryd.WorkerCommand().get_options(): + self.parser.add_option(x) + + def command(self): + from celery.bin import celeryd + return celeryd.WorkerCommand().run(**vars(self.options)) + + +class CeleryBeatCommand(BasePasterCommand): + """Start the celery beat server + + Starts the celery beat server using a paste.deploy configuration + file. + """ + usage = 'CONFIG_FILE [celerybeat options...]' + summary = __doc__.splitlines()[0] + description = "".join(__doc__.splitlines()[2:]) + + parser = Command.standard_parser(quiet=True) + + def update_parser(self): + from celery.bin import celerybeat + for x in celerybeat.BeatCommand().get_options(): + self.parser.add_option(x) + + def command(self): + from celery.bin import celerybeat + return celerybeat.BeatCommand(**vars(self.options)) + +class CAMQPAdminCommand(BasePasterCommand): + """CAMQP Admin + + CAMQP celery admin tool. + """ + usage = 'CONFIG_FILE [camqadm options...]' + summary = __doc__.splitlines()[0] + description = "".join(__doc__.splitlines()[2:]) + + parser = Command.standard_parser(quiet=True) + + def update_parser(self): + from celery.bin import camqadm + for x in camqadm.OPTION_LIST: + self.parser.add_option(x) + + def command(self): + from celery.bin import camqadm + return camqadm.camqadm(*self.args, **vars(self.options)) + + +class CeleryEventCommand(BasePasterCommand): + """Celery event commandd. + + Capture celery events. + """ + usage = 'CONFIG_FILE [celeryev options...]' + summary = __doc__.splitlines()[0] + description = "".join(__doc__.splitlines()[2:]) + + parser = Command.standard_parser(quiet=True) + + def update_parser(self): + from celery.bin import celeryev + for x in celeryev.OPTION_LIST: + self.parser.add_option(x) + + def command(self): + from celery.bin import celeryev + return celeryev.run_celeryev(**vars(self.options)) diff --git a/rhodecode/lib/celerypylons/loader.py b/rhodecode/lib/celerypylons/loader.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/celerypylons/loader.py @@ -0,0 +1,55 @@ +from celery.loaders.base import BaseLoader +from pylons import config + +to_pylons = lambda x: x.replace('_', '.').lower() +to_celery = lambda x: x.replace('.', '_').upper() + +LIST_PARAMS = """CELERY_IMPORTS ADMINS ROUTES""".split() + + +class PylonsSettingsProxy(object): + """Pylons Settings Proxy + + Proxies settings from pylons.config + + """ + def __getattr__(self, key): + pylons_key = to_pylons(key) + try: + value = config[pylons_key] + if key in LIST_PARAMS: return value.split() + return self.type_converter(value) + except KeyError: + raise AttributeError(pylons_key) + + def __setattr__(self, key, value): + pylons_key = to_pylons(key) + config[pylons_key] = value + + + def type_converter(self, value): + #cast to int + if value.isdigit(): + return int(value) + + #cast to bool + if value.lower() in ['true', 'false']: + return value.lower() == 'true' + + return value + +class PylonsLoader(BaseLoader): + """Pylons celery loader + + Maps the celery config onto pylons.config + + """ + def read_configuration(self): + self.configured = True + return PylonsSettingsProxy() + + def on_worker_init(self): + """ + Import task modules. + """ + self.import_default_modules() 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 @@ -1,8 +1,16 @@ -#!/usr/bin/env python -# encoding: utf-8 -# database management for RhodeCode -# Copyright (C) 2009-2010 Marcin Kuzminski -# +# -*- coding: utf-8 -*- +""" + rhodecode.lib.db_manage + ~~~~~~~~~~~~~~~~~~~~~~~ + + Database creation, and setup module for RhodeCode. Used for creation + of database as well as for migration operations + + :created_on: Apr 10, 2010 + :author: marcink + :copyright: (C) 2009-2010 Marcin Kuzminski + :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; version 2 @@ -18,51 +26,50 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. -""" -Created on April 10, 2010 -database management and creation for RhodeCode -@author: marcink -""" - -from os.path import dirname as dn, join as jn import os import sys import uuid +import logging +from os.path import dirname as dn, join as jn + +from rhodecode import __dbversion__ +from rhodecode.model import meta from rhodecode.lib.auth import get_crypt_password from rhodecode.lib.utils import ask_ok from rhodecode.model import init_model from rhodecode.model.db import User, Permission, RhodeCodeUi, RhodeCodeSettings, \ - UserToPerm -from rhodecode.model import meta + UserToPerm, DbMigrateVersion + from sqlalchemy.engine import create_engine -import logging log = logging.getLogger(__name__) class DbManage(object): - def __init__(self, log_sql, dbname, root, tests=False): - self.dbname = dbname + def __init__(self, log_sql, dbconf, root, tests=False): + self.dbname = dbconf.split('/')[-1] self.tests = tests self.root = root - dburi = 'sqlite:////%s' % jn(self.root, self.dbname) - engine = create_engine(dburi, echo=log_sql) + self.dburi = dbconf + engine = create_engine(self.dburi, echo=log_sql) init_model(engine) - self.sa = meta.Session + self.sa = meta.Session() self.db_exists = False - + def check_for_db(self, override): db_path = jn(self.root, self.dbname) - 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') + 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') def create_tables(self, override=False): + """Create a auth database """ - Create a auth database - """ + self.check_for_db(override) if self.db_exists: log.info("database exist and it's going to be destroyed") @@ -77,34 +84,163 @@ class DbManage(object): checkfirst = not override meta.Base.metadata.create_all(checkfirst=checkfirst) log.info('Created tables for %s', self.dbname) - + + + + def set_db_version(self): + try: + ver = DbMigrateVersion() + ver.version = __dbversion__ + ver.repository_id = 'rhodecode_db_migrations' + ver.repository_path = 'versions' + self.sa.add(ver) + self.sa.commit() + except: + self.sa.rollback() + raise + log.info('db version set to: %s', __dbversion__) + + + def upgrade(self): + """Upgrades given database schema to given revision following + all needed steps, + + :param revision: revision to upgrade to + """ + + from rhodecode.lib.dbmigrate.migrate.versioning import api + from rhodecode.lib.dbmigrate.migrate.exceptions import \ + DatabaseNotControlledError + + upgrade = ask_ok('You are about to perform database upgrade, make ' + 'sure You backed up your database before. ' + 'Continue ? [y/n]') + if not upgrade: + sys.exit('Nothing done') + + repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))), + 'rhodecode/lib/dbmigrate') + db_uri = self.dburi + + try: + curr_version = api.db_version(db_uri, repository_path) + msg = ('Found current database under version' + ' control with version %s' % curr_version) + + except (RuntimeError, DatabaseNotControlledError), e: + curr_version = 1 + msg = ('Current database is not under version control. Setting' + ' as version %s' % curr_version) + api.version_control(db_uri, repository_path, curr_version) + + print (msg) + + if curr_version == __dbversion__: + sys.exit('This database is already at the newest version') + + #====================================================================== + # UPGRADE STEPS + #====================================================================== + class UpgradeSteps(object): + + def __init__(self, klass): + self.klass = klass + + def step_0(self): + #step 0 is the schema upgrade, and than follow proper upgrades + print ('attempting to do database upgrade to version %s' \ + % __dbversion__) + api.upgrade(db_uri, repository_path, __dbversion__) + print ('Schema upgrade completed') + + def step_1(self): + pass + + def step_2(self): + print ('Patching repo paths for newer version of RhodeCode') + self.klass.fix_repo_paths() + + print ('Patching default user of RhodeCode') + self.klass.fix_default_user() + + log.info('Changing ui settings') + self.klass.create_ui_settings() + + + upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1) + + #CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE + for step in upgrade_steps: + print ('performing upgrade step %s' % step) + callable = getattr(UpgradeSteps(self), 'step_%s' % step)() + + + + def fix_repo_paths(self): + """Fixes a old rhodecode version path into new one without a '*' + """ + + paths = self.sa.query(RhodeCodeUi)\ + .filter(RhodeCodeUi.ui_key == '/')\ + .scalar() + + paths.ui_value = paths.ui_value.replace('*', '') + + try: + self.sa.add(paths) + self.sa.commit() + except: + self.sa.rollback() + raise + + def fix_default_user(self): + """Fixes a old default user with some 'nicer' default values, + used mostly for anonymous access + """ + def_user = self.sa.query(User)\ + .filter(User.username == 'default')\ + .one() + + def_user.name = 'Anonymous' + def_user.lastname = 'User' + def_user.email = 'anonymous@rhodecode.org' + + try: + self.sa.add(def_user) + self.sa.commit() + except: + self.sa.rollback() + raise + + + def admin_prompt(self, second=False): if not self.tests: import getpass - - + + def get_password(): password = getpass.getpass('Specify admin password (min 6 chars):') confirm = getpass.getpass('Confirm password:') - + if password != confirm: log.error('passwords mismatch') return False if len(password) < 6: log.error('password is to short use at least 6 characters') return False - + return password - + username = raw_input('Specify admin username:') - + password = get_password() if not password: #second try password = get_password() if not password: sys.exit() - + email = raw_input('Specify admin email:') self.create_user(username, password, email, True) else: @@ -112,71 +248,121 @@ class DbManage(object): self.create_user('test_admin', 'test12', 'test_admin@mail.com', True) self.create_user('test_regular', 'test12', 'test_regular@mail.com', False) self.create_user('test_regular2', 'test12', 'test_regular2@mail.com', False) - + + def create_ui_settings(self): + """Creates ui settings, fills out hooks + and disables dotencode - + """ + #HOOKS + hooks1_key = 'changegroup.update' + hooks1_ = self.sa.query(RhodeCodeUi)\ + .filter(RhodeCodeUi.ui_key == hooks1_key).scalar() + + hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_ + hooks1.ui_section = 'hooks' + hooks1.ui_key = hooks1_key + hooks1.ui_value = 'hg update >&2' + hooks1.ui_active = False + + hooks2_key = 'changegroup.repo_size' + hooks2_ = self.sa.query(RhodeCodeUi)\ + .filter(RhodeCodeUi.ui_key == hooks2_key).scalar() + + hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_ + hooks2.ui_section = 'hooks' + hooks2.ui_key = hooks2_key + hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size' + + hooks3 = RhodeCodeUi() + hooks3.ui_section = 'hooks' + hooks3.ui_key = 'pretxnchangegroup.push_logger' + hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action' + + hooks4 = RhodeCodeUi() + hooks4.ui_section = 'hooks' + hooks4.ui_key = 'preoutgoing.pull_logger' + hooks4.ui_value = 'python:rhodecode.lib.hooks.log_pull_action' + + #For mercurial 1.7 set backward comapatibility with format + dotencode_disable = RhodeCodeUi() + dotencode_disable.ui_section = 'format' + dotencode_disable.ui_key = 'dotencode' + dotencode_disable.ui_value = 'false' + + try: + self.sa.add(hooks1) + self.sa.add(hooks2) + self.sa.add(hooks3) + self.sa.add(hooks4) + self.sa.add(dotencode_disable) + self.sa.commit() + except: + self.sa.rollback() + raise + + + def create_ldap_options(self): + """Creates ldap settings""" + + try: + for k in ['ldap_active', 'ldap_host', 'ldap_port', 'ldap_ldaps', + 'ldap_dn_user', 'ldap_dn_pass', 'ldap_base_dn']: + + setting = RhodeCodeSettings(k, '') + self.sa.add(setting) + self.sa.commit() + except: + self.sa.rollback() + raise + def config_prompt(self, test_repo_path=''): log.info('Setting up repositories config') - + if not self.tests and not test_repo_path: path = raw_input('Specify valid full path to your repositories' ' you can change this later in application settings:') else: path = test_repo_path - + if not os.path.isdir(path): log.error('You entered wrong path: %s', path) sys.exit() - - hooks1 = RhodeCodeUi() - hooks1.ui_section = 'hooks' - hooks1.ui_key = 'changegroup.update' - hooks1.ui_value = 'hg update >&2' - hooks1.ui_active = False - - hooks2 = RhodeCodeUi() - hooks2.ui_section = 'hooks' - hooks2.ui_key = 'changegroup.repo_size' - hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size' - + + self.create_ui_settings() + + #HG UI OPTIONS web1 = RhodeCodeUi() web1.ui_section = 'web' web1.ui_key = 'push_ssl' web1.ui_value = 'false' - + web2 = RhodeCodeUi() web2.ui_section = 'web' web2.ui_key = 'allow_archive' web2.ui_value = 'gz zip bz2' - + web3 = RhodeCodeUi() web3.ui_section = 'web' web3.ui_key = 'allow_push' web3.ui_value = '*' - + web4 = RhodeCodeUi() web4.ui_section = 'web' web4.ui_key = 'baseurl' - web4.ui_value = '/' - + web4.ui_value = '/' + paths = RhodeCodeUi() paths.ui_section = 'paths' paths.ui_key = '/' - paths.ui_value = os.path.join(path, '*') - - - hgsettings1 = RhodeCodeSettings() - - hgsettings1.app_settings_name = 'realm' - hgsettings1.app_settings_value = 'RhodeCode authentication' - - hgsettings2 = RhodeCodeSettings() - hgsettings2.app_settings_name = 'title' - hgsettings2.app_settings_value = 'RhodeCode' - + paths.ui_value = path + + + hgsettings1 = RhodeCodeSettings('realm', 'RhodeCode authentication') + hgsettings2 = RhodeCodeSettings('title', 'RhodeCode') + + try: - self.sa.add(hooks1) - self.sa.add(hooks2) self.sa.add(web1) self.sa.add(web2) self.sa.add(web3) @@ -184,12 +370,16 @@ class DbManage(object): self.sa.add(paths) self.sa.add(hgsettings1) self.sa.add(hgsettings2) + self.sa.commit() except: self.sa.rollback() - raise + raise + + self.create_ldap_options() + log.info('created ui config') - + def create_user(self, username, password, email='', admin=False): log.info('creating administrator user %s', username) new_user = User() @@ -200,7 +390,7 @@ class DbManage(object): new_user.email = email new_user.admin = admin new_user.active = True - + try: self.sa.add(new_user) self.sa.commit() @@ -214,9 +404,9 @@ class DbManage(object): def_user = User() def_user.username = 'default' def_user.password = get_crypt_password(str(uuid.uuid1())[:8]) - def_user.name = 'default' - def_user.lastname = 'default' - def_user.email = 'default@default.com' + def_user.name = 'Anonymous' + def_user.lastname = 'User' + def_user.email = 'anonymous@rhodecode.org' def_user.admin = False def_user.active = False try: @@ -225,7 +415,7 @@ class DbManage(object): except: self.sa.rollback() raise - + def create_permissions(self): #module.(access|create|change|delete)_[name] #module.(read|write|owner) @@ -240,7 +430,7 @@ class DbManage(object): ('hg.register.manual_activate', 'Register new user with rhodecode without manual activation'), ('hg.register.auto_activate', 'Register new user with rhodecode without auto activation'), ] - + for p in perms: new_perm = Permission() new_perm.permission_name = p[0] @@ -254,28 +444,28 @@ class DbManage(object): def populate_default_permissions(self): log.info('creating default user permissions') - + default_user = self.sa.query(User)\ .filter(User.username == 'default').scalar() - + reg_perm = UserToPerm() reg_perm.user = default_user reg_perm.permission = self.sa.query(Permission)\ .filter(Permission.permission_name == 'hg.register.manual_activate')\ - .scalar() - + .scalar() + create_repo_perm = UserToPerm() create_repo_perm.user = default_user create_repo_perm.permission = self.sa.query(Permission)\ .filter(Permission.permission_name == 'hg.create.repository')\ - .scalar() - + .scalar() + default_repo_perm = UserToPerm() default_repo_perm.user = default_user default_repo_perm.permission = self.sa.query(Permission)\ .filter(Permission.permission_name == 'repository.read')\ - .scalar() - + .scalar() + try: self.sa.add(reg_perm) self.sa.add(create_repo_perm) @@ -283,5 +473,5 @@ class DbManage(object): self.sa.commit() except: self.sa.rollback() - raise - + raise + diff --git a/rhodecode/lib/dbmigrate/__init__.py b/rhodecode/lib/dbmigrate/__init__.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/__init__.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +""" + rhodecode.lib.dbmigrate.__init__ + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Database migration modules + + :created_on: Dec 11, 2010 + :author: marcink + :copyright: (C) 2009-2010 Marcin Kuzminski + :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; 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. + +import logging +from sqlalchemy import engine_from_config + + +from rhodecode.lib.utils import BasePasterCommand, Command, add_cache +from rhodecode.lib.db_manage import DbManage + +log = logging.getLogger(__name__) + +class UpgradeDb(BasePasterCommand): + """Command used for paster to upgrade our database to newer version + """ + + max_args = 1 + min_args = 1 + + usage = "CONFIG_FILE" + summary = "Upgrades current db to newer version given configuration file" + group_name = "RhodeCode" + + parser = Command.standard_parser(verbose=True) + + def command(self): + from pylons import config + + add_cache(config) + + db_uri = config['sqlalchemy.db1.url'] + + dbmanage = DbManage(log_sql=True, dbconf=db_uri, + root=config['here'], tests=False) + + dbmanage.upgrade() + + + + def update_parser(self): + self.parser.add_option('--sql', + action='store_true', + dest='just_sql', + help="Prints upgrade sql for further investigation", + default=False) diff --git a/rhodecode/lib/dbmigrate/migrate.cfg b/rhodecode/lib/dbmigrate/migrate.cfg new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate.cfg @@ -0,0 +1,20 @@ +[db_settings] +# Used to identify which repository this database is versioned under. +# You can use the name of your project. +repository_id=rhodecode_db_migrations + +# The name of the database table used to track the schema version. +# This name shouldn't already be used by your project. +# If this is changed once a database is under version control, you'll need to +# change the table name in each database too. +version_table=db_migrate_version + +# When committing a change script, Migrate will attempt to generate the +# sql for all supported databases; normally, if one of them fails - probably +# because you don't have that database installed - it is ignored and the +# commit continues, perhaps ending successfully. +# Databases in this list MUST compile successfully during a commit, or the +# entire commit will fail. List the databases your application will actually +# be using to ensure your updates to that database work properly. +# This must be a list; example: ['postgres','sqlite'] +required_dbs=['sqlite'] diff --git a/rhodecode/lib/dbmigrate/migrate/__init__.py b/rhodecode/lib/dbmigrate/migrate/__init__.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/__init__.py @@ -0,0 +1,9 @@ +""" + SQLAlchemy migrate provides two APIs :mod:`migrate.versioning` for + database schema version and repository management and + :mod:`migrate.changeset` that allows to define database schema changes + using Python. +""" + +from rhodecode.lib.dbmigrate.migrate.versioning import * +from rhodecode.lib.dbmigrate.migrate.changeset import * diff --git a/rhodecode/lib/dbmigrate/migrate/changeset/__init__.py b/rhodecode/lib/dbmigrate/migrate/changeset/__init__.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/changeset/__init__.py @@ -0,0 +1,28 @@ +""" + This module extends SQLAlchemy and provides additional DDL [#]_ + support. + + .. [#] SQL Data Definition Language +""" +import re +import warnings + +import sqlalchemy +from sqlalchemy import __version__ as _sa_version + +warnings.simplefilter('always', DeprecationWarning) + +_sa_version = tuple(int(re.match("\d+", x).group(0)) for x in _sa_version.split(".")) +SQLA_06 = _sa_version >= (0, 6) + +del re +del _sa_version + +from rhodecode.lib.dbmigrate.migrate.changeset.schema import * +from rhodecode.lib.dbmigrate.migrate.changeset.constraint import * + +sqlalchemy.schema.Table.__bases__ += (ChangesetTable, ) +sqlalchemy.schema.Column.__bases__ += (ChangesetColumn, ) +sqlalchemy.schema.Index.__bases__ += (ChangesetIndex, ) + +sqlalchemy.schema.DefaultClause.__bases__ += (ChangesetDefaultClause, ) diff --git a/rhodecode/lib/dbmigrate/migrate/changeset/ansisql.py b/rhodecode/lib/dbmigrate/migrate/changeset/ansisql.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/changeset/ansisql.py @@ -0,0 +1,358 @@ +""" + Extensions to SQLAlchemy for altering existing tables. + + At the moment, this isn't so much based off of ANSI as much as + things that just happen to work with multiple databases. +""" +import StringIO + +import sqlalchemy as sa +from sqlalchemy.schema import SchemaVisitor +from sqlalchemy.engine.default import DefaultDialect +from sqlalchemy.sql import ClauseElement +from sqlalchemy.schema import (ForeignKeyConstraint, + PrimaryKeyConstraint, + CheckConstraint, + UniqueConstraint, + Index) + +from rhodecode.lib.dbmigrate.migrate import exceptions +from rhodecode.lib.dbmigrate.migrate.changeset import constraint, SQLA_06 + +if not SQLA_06: + from sqlalchemy.sql.compiler import SchemaGenerator, SchemaDropper +else: + from sqlalchemy.schema import AddConstraint, DropConstraint + from sqlalchemy.sql.compiler import DDLCompiler + SchemaGenerator = SchemaDropper = DDLCompiler + + +class AlterTableVisitor(SchemaVisitor): + """Common operations for ``ALTER TABLE`` statements.""" + + if SQLA_06: + # engine.Compiler looks for .statement + # when it spawns off a new compiler + statement = ClauseElement() + + def append(self, s): + """Append content to the SchemaIterator's query buffer.""" + + self.buffer.write(s) + + def execute(self): + """Execute the contents of the SchemaIterator's buffer.""" + try: + return self.connection.execute(self.buffer.getvalue()) + finally: + self.buffer.truncate(0) + + def __init__(self, dialect, connection, **kw): + self.connection = connection + self.buffer = StringIO.StringIO() + self.preparer = dialect.identifier_preparer + self.dialect = dialect + + def traverse_single(self, elem): + ret = super(AlterTableVisitor, self).traverse_single(elem) + if ret: + # adapt to 0.6 which uses a string-returning + # object + self.append(" %s" % ret) + + def _to_table(self, param): + """Returns the table object for the given param object.""" + if isinstance(param, (sa.Column, sa.Index, sa.schema.Constraint)): + ret = param.table + else: + ret = param + return ret + + def start_alter_table(self, param): + """Returns the start of an ``ALTER TABLE`` SQL-Statement. + + Use the param object to determine the table name and use it + for building the SQL statement. + + :param param: object to determine the table from + :type param: :class:`sqlalchemy.Column`, :class:`sqlalchemy.Index`, + :class:`sqlalchemy.schema.Constraint`, :class:`sqlalchemy.Table`, + or string (table name) + """ + table = self._to_table(param) + self.append('\nALTER TABLE %s ' % self.preparer.format_table(table)) + return table + + +class ANSIColumnGenerator(AlterTableVisitor, SchemaGenerator): + """Extends ansisql generator for column creation (alter table add col)""" + + def visit_column(self, column): + """Create a column (table already exists). + + :param column: column object + :type column: :class:`sqlalchemy.Column` instance + """ + if column.default is not None: + self.traverse_single(column.default) + + table = self.start_alter_table(column) + self.append("ADD ") + self.append(self.get_column_specification(column)) + + for cons in column.constraints: + self.traverse_single(cons) + self.execute() + + # ALTER TABLE STATEMENTS + + # add indexes and unique constraints + if column.index_name: + Index(column.index_name,column).create() + elif column.unique_name: + constraint.UniqueConstraint(column, + name=column.unique_name).create() + + # SA bounds FK constraints to table, add manually + for fk in column.foreign_keys: + self.add_foreignkey(fk.constraint) + + # add primary key constraint if needed + if column.primary_key_name: + cons = constraint.PrimaryKeyConstraint(column, + name=column.primary_key_name) + cons.create() + + if SQLA_06: + def add_foreignkey(self, fk): + self.connection.execute(AddConstraint(fk)) + +class ANSIColumnDropper(AlterTableVisitor, SchemaDropper): + """Extends ANSI SQL dropper for column dropping (``ALTER TABLE + DROP COLUMN``). + """ + + def visit_column(self, column): + """Drop a column from its table. + + :param column: the column object + :type column: :class:`sqlalchemy.Column` + """ + table = self.start_alter_table(column) + self.append('DROP COLUMN %s' % self.preparer.format_column(column)) + self.execute() + + +class ANSISchemaChanger(AlterTableVisitor, SchemaGenerator): + """Manages changes to existing schema elements. + + Note that columns are schema elements; ``ALTER TABLE ADD COLUMN`` + is in SchemaGenerator. + + All items may be renamed. Columns can also have many of their properties - + type, for example - changed. + + Each function is passed a tuple, containing (object, name); where + object is a type of object you'd expect for that function + (ie. table for visit_table) and name is the object's new + name. NONE means the name is unchanged. + """ + + def visit_table(self, table): + """Rename a table. Other ops aren't supported.""" + self.start_alter_table(table) + self.append("RENAME TO %s" % self.preparer.quote(table.new_name, + table.quote)) + self.execute() + + def visit_index(self, index): + """Rename an index""" + if hasattr(self, '_validate_identifier'): + # SA <= 0.6.3 + self.append("ALTER INDEX %s RENAME TO %s" % ( + self.preparer.quote( + self._validate_identifier( + index.name, True), index.quote), + self.preparer.quote( + self._validate_identifier( + index.new_name, True), index.quote))) + else: + # SA >= 0.6.5 + self.append("ALTER INDEX %s RENAME TO %s" % ( + self.preparer.quote( + self._index_identifier( + index.name), index.quote), + self.preparer.quote( + self._index_identifier( + index.new_name), index.quote))) + self.execute() + + def visit_column(self, delta): + """Rename/change a column.""" + # ALTER COLUMN is implemented as several ALTER statements + keys = delta.keys() + if 'type' in keys: + self._run_subvisit(delta, self._visit_column_type) + if 'nullable' in keys: + self._run_subvisit(delta, self._visit_column_nullable) + if 'server_default' in keys: + # Skip 'default': only handle server-side defaults, others + # are managed by the app, not the db. + self._run_subvisit(delta, self._visit_column_default) + if 'name' in keys: + self._run_subvisit(delta, self._visit_column_name, start_alter=False) + + def _run_subvisit(self, delta, func, start_alter=True): + """Runs visit method based on what needs to be changed on column""" + table = self._to_table(delta.table) + col_name = delta.current_name + if start_alter: + self.start_alter_column(table, col_name) + ret = func(table, delta.result_column, delta) + self.execute() + + def start_alter_column(self, table, col_name): + """Starts ALTER COLUMN""" + self.start_alter_table(table) + self.append("ALTER COLUMN %s " % self.preparer.quote(col_name, table.quote)) + + def _visit_column_nullable(self, table, column, delta): + nullable = delta['nullable'] + if nullable: + self.append("DROP NOT NULL") + else: + self.append("SET NOT NULL") + + def _visit_column_default(self, table, column, delta): + default_text = self.get_column_default_string(column) + if default_text is not None: + self.append("SET DEFAULT %s" % default_text) + else: + self.append("DROP DEFAULT") + + def _visit_column_type(self, table, column, delta): + type_ = delta['type'] + if SQLA_06: + type_text = str(type_.compile(dialect=self.dialect)) + else: + type_text = type_.dialect_impl(self.dialect).get_col_spec() + self.append("TYPE %s" % type_text) + + def _visit_column_name(self, table, column, delta): + self.start_alter_table(table) + col_name = self.preparer.quote(delta.current_name, table.quote) + new_name = self.preparer.format_column(delta.result_column) + self.append('RENAME COLUMN %s TO %s' % (col_name, new_name)) + + +class ANSIConstraintCommon(AlterTableVisitor): + """ + Migrate's constraints require a separate creation function from + SA's: Migrate's constraints are created independently of a table; + SA's are created at the same time as the table. + """ + + def get_constraint_name(self, cons): + """Gets a name for the given constraint. + + If the name is already set it will be used otherwise the + constraint's :meth:`autoname ` + method is used. + + :param cons: constraint object + """ + if cons.name is not None: + ret = cons.name + else: + ret = cons.name = cons.autoname() + return self.preparer.quote(ret, cons.quote) + + def visit_migrate_primary_key_constraint(self, *p, **k): + self._visit_constraint(*p, **k) + + def visit_migrate_foreign_key_constraint(self, *p, **k): + self._visit_constraint(*p, **k) + + def visit_migrate_check_constraint(self, *p, **k): + self._visit_constraint(*p, **k) + + def visit_migrate_unique_constraint(self, *p, **k): + self._visit_constraint(*p, **k) + +if SQLA_06: + class ANSIConstraintGenerator(ANSIConstraintCommon, SchemaGenerator): + def _visit_constraint(self, constraint): + constraint.name = self.get_constraint_name(constraint) + self.append(self.process(AddConstraint(constraint))) + self.execute() + + class ANSIConstraintDropper(ANSIConstraintCommon, SchemaDropper): + def _visit_constraint(self, constraint): + constraint.name = self.get_constraint_name(constraint) + self.append(self.process(DropConstraint(constraint, cascade=constraint.cascade))) + self.execute() + +else: + class ANSIConstraintGenerator(ANSIConstraintCommon, SchemaGenerator): + + def get_constraint_specification(self, cons, **kwargs): + """Constaint SQL generators. + + We cannot use SA visitors because they append comma. + """ + + if isinstance(cons, PrimaryKeyConstraint): + if cons.name is not None: + self.append("CONSTRAINT %s " % self.preparer.format_constraint(cons)) + self.append("PRIMARY KEY ") + self.append("(%s)" % ', '.join(self.preparer.quote(c.name, c.quote) + for c in cons)) + self.define_constraint_deferrability(cons) + elif isinstance(cons, ForeignKeyConstraint): + self.define_foreign_key(cons) + elif isinstance(cons, CheckConstraint): + if cons.name is not None: + self.append("CONSTRAINT %s " % + self.preparer.format_constraint(cons)) + self.append("CHECK (%s)" % cons.sqltext) + self.define_constraint_deferrability(cons) + elif isinstance(cons, UniqueConstraint): + if cons.name is not None: + self.append("CONSTRAINT %s " % + self.preparer.format_constraint(cons)) + self.append("UNIQUE (%s)" % \ + (', '.join(self.preparer.quote(c.name, c.quote) for c in cons))) + self.define_constraint_deferrability(cons) + else: + raise exceptions.InvalidConstraintError(cons) + + def _visit_constraint(self, constraint): + + table = self.start_alter_table(constraint) + constraint.name = self.get_constraint_name(constraint) + self.append("ADD ") + self.get_constraint_specification(constraint) + self.execute() + + + class ANSIConstraintDropper(ANSIConstraintCommon, SchemaDropper): + + def _visit_constraint(self, constraint): + self.start_alter_table(constraint) + self.append("DROP CONSTRAINT ") + constraint.name = self.get_constraint_name(constraint) + self.append(self.preparer.format_constraint(constraint)) + if constraint.cascade: + self.cascade_constraint(constraint) + self.execute() + + def cascade_constraint(self, constraint): + self.append(" CASCADE") + + +class ANSIDialect(DefaultDialect): + columngenerator = ANSIColumnGenerator + columndropper = ANSIColumnDropper + schemachanger = ANSISchemaChanger + constraintgenerator = ANSIConstraintGenerator + constraintdropper = ANSIConstraintDropper diff --git a/rhodecode/lib/dbmigrate/migrate/changeset/constraint.py b/rhodecode/lib/dbmigrate/migrate/changeset/constraint.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/changeset/constraint.py @@ -0,0 +1,202 @@ +""" + This module defines standalone schema constraint classes. +""" +from sqlalchemy import schema + +from rhodecode.lib.dbmigrate.migrate.exceptions import * +from rhodecode.lib.dbmigrate.migrate.changeset import SQLA_06 + +class ConstraintChangeset(object): + """Base class for Constraint classes.""" + + def _normalize_columns(self, cols, table_name=False): + """Given: column objects or names; return col names and + (maybe) a table""" + colnames = [] + table = None + for col in cols: + if isinstance(col, schema.Column): + if col.table is not None and table is None: + table = col.table + if table_name: + col = '.'.join((col.table.name, col.name)) + else: + col = col.name + colnames.append(col) + return colnames, table + + def __do_imports(self, visitor_name, *a, **kw): + engine = kw.pop('engine', self.table.bind) + from rhodecode.lib.dbmigrate.migrate.changeset.databases.visitor import (get_engine_visitor, + run_single_visitor) + visitorcallable = get_engine_visitor(engine, visitor_name) + run_single_visitor(engine, visitorcallable, self, *a, **kw) + + def create(self, *a, **kw): + """Create the constraint in the database. + + :param engine: the database engine to use. If this is \ + :keyword:`None` the instance's engine will be used + :type engine: :class:`sqlalchemy.engine.base.Engine` + :param connection: reuse connection istead of creating new one. + :type connection: :class:`sqlalchemy.engine.base.Connection` instance + """ + # TODO: set the parent here instead of in __init__ + self.__do_imports('constraintgenerator', *a, **kw) + + def drop(self, *a, **kw): + """Drop the constraint from the database. + + :param engine: the database engine to use. If this is + :keyword:`None` the instance's engine will be used + :param cascade: Issue CASCADE drop if database supports it + :type engine: :class:`sqlalchemy.engine.base.Engine` + :type cascade: bool + :param connection: reuse connection istead of creating new one. + :type connection: :class:`sqlalchemy.engine.base.Connection` instance + :returns: Instance with cleared columns + """ + self.cascade = kw.pop('cascade', False) + self.__do_imports('constraintdropper', *a, **kw) + # the spirit of Constraint objects is that they + # are immutable (just like in a DB. they're only ADDed + # or DROPped). + #self.columns.clear() + return self + + +class PrimaryKeyConstraint(ConstraintChangeset, schema.PrimaryKeyConstraint): + """Construct PrimaryKeyConstraint + + Migrate's additional parameters: + + :param cols: Columns in constraint. + :param table: If columns are passed as strings, this kw is required + :type table: Table instance + :type cols: strings or Column instances + """ + + __migrate_visit_name__ = 'migrate_primary_key_constraint' + + def __init__(self, *cols, **kwargs): + colnames, table = self._normalize_columns(cols) + table = kwargs.pop('table', table) + super(PrimaryKeyConstraint, self).__init__(*colnames, **kwargs) + if table is not None: + self._set_parent(table) + + + def autoname(self): + """Mimic the database's automatic constraint names""" + return "%s_pkey" % self.table.name + + +class ForeignKeyConstraint(ConstraintChangeset, schema.ForeignKeyConstraint): + """Construct ForeignKeyConstraint + + Migrate's additional parameters: + + :param columns: Columns in constraint + :param refcolumns: Columns that this FK reffers to in another table. + :param table: If columns are passed as strings, this kw is required + :type table: Table instance + :type columns: list of strings or Column instances + :type refcolumns: list of strings or Column instances + """ + + __migrate_visit_name__ = 'migrate_foreign_key_constraint' + + def __init__(self, columns, refcolumns, *args, **kwargs): + colnames, table = self._normalize_columns(columns) + table = kwargs.pop('table', table) + refcolnames, reftable = self._normalize_columns(refcolumns, + table_name=True) + super(ForeignKeyConstraint, self).__init__(colnames, refcolnames, *args, + **kwargs) + if table is not None: + self._set_parent(table) + + @property + def referenced(self): + return [e.column for e in self.elements] + + @property + def reftable(self): + return self.referenced[0].table + + def autoname(self): + """Mimic the database's automatic constraint names""" + if hasattr(self.columns, 'keys'): + # SA <= 0.5 + firstcol = self.columns[self.columns.keys()[0]] + ret = "%(table)s_%(firstcolumn)s_fkey" % dict( + table=firstcol.table.name, + firstcolumn=firstcol.name,) + else: + # SA >= 0.6 + ret = "%(table)s_%(firstcolumn)s_fkey" % dict( + table=self.table.name, + firstcolumn=self.columns[0],) + return ret + + +class CheckConstraint(ConstraintChangeset, schema.CheckConstraint): + """Construct CheckConstraint + + Migrate's additional parameters: + + :param sqltext: Plain SQL text to check condition + :param columns: If not name is applied, you must supply this kw\ + to autoname constraint + :param table: If columns are passed as strings, this kw is required + :type table: Table instance + :type columns: list of Columns instances + :type sqltext: string + """ + + __migrate_visit_name__ = 'migrate_check_constraint' + + def __init__(self, sqltext, *args, **kwargs): + cols = kwargs.pop('columns', []) + if not cols and not kwargs.get('name', False): + raise InvalidConstraintError('You must either set "name"' + 'parameter or "columns" to autogenarate it.') + colnames, table = self._normalize_columns(cols) + table = kwargs.pop('table', table) + schema.CheckConstraint.__init__(self, sqltext, *args, **kwargs) + if table is not None: + if not SQLA_06: + self.table = table + self._set_parent(table) + self.colnames = colnames + + def autoname(self): + return "%(table)s_%(cols)s_check" % \ + dict(table=self.table.name, cols="_".join(self.colnames)) + + +class UniqueConstraint(ConstraintChangeset, schema.UniqueConstraint): + """Construct UniqueConstraint + + Migrate's additional parameters: + + :param cols: Columns in constraint. + :param table: If columns are passed as strings, this kw is required + :type table: Table instance + :type cols: strings or Column instances + + .. versionadded:: 0.6.0 + """ + + __migrate_visit_name__ = 'migrate_unique_constraint' + + def __init__(self, *cols, **kwargs): + self.colnames, table = self._normalize_columns(cols) + table = kwargs.pop('table', table) + super(UniqueConstraint, self).__init__(*self.colnames, **kwargs) + if table is not None: + self._set_parent(table) + + def autoname(self): + """Mimic the database's automatic constraint names""" + return "%s_%s_key" % (self.table.name, self.colnames[0]) diff --git a/rhodecode/lib/dbmigrate/migrate/changeset/databases/__init__.py b/rhodecode/lib/dbmigrate/migrate/changeset/databases/__init__.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/changeset/databases/__init__.py @@ -0,0 +1,10 @@ +""" + This module contains database dialect specific changeset + implementations. +""" +__all__ = [ + 'postgres', + 'sqlite', + 'mysql', + 'oracle', +] diff --git a/rhodecode/lib/dbmigrate/migrate/changeset/databases/firebird.py b/rhodecode/lib/dbmigrate/migrate/changeset/databases/firebird.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/changeset/databases/firebird.py @@ -0,0 +1,80 @@ +""" + Firebird database specific implementations of changeset classes. +""" +from sqlalchemy.databases import firebird as sa_base + +from rhodecode.lib.dbmigrate.migrate import exceptions +from rhodecode.lib.dbmigrate.migrate.changeset import ansisql, SQLA_06 + + +if SQLA_06: + FBSchemaGenerator = sa_base.FBDDLCompiler +else: + FBSchemaGenerator = sa_base.FBSchemaGenerator + +class FBColumnGenerator(FBSchemaGenerator, ansisql.ANSIColumnGenerator): + """Firebird column generator implementation.""" + + +class FBColumnDropper(ansisql.ANSIColumnDropper): + """Firebird column dropper implementation.""" + + def visit_column(self, column): + """Firebird supports 'DROP col' instead of 'DROP COLUMN col' syntax + + Drop primary key and unique constraints if dropped column is referencing it.""" + if column.primary_key: + if column.table.primary_key.columns.contains_column(column): + column.table.primary_key.drop() + # TODO: recreate primary key if it references more than this column + if column.unique or getattr(column, 'unique_name', None): + for cons in column.table.constraints: + if cons.contains_column(column): + cons.drop() + # TODO: recreate unique constraint if it refenrences more than this column + + table = self.start_alter_table(column) + self.append('DROP %s' % self.preparer.format_column(column)) + self.execute() + + +class FBSchemaChanger(ansisql.ANSISchemaChanger): + """Firebird schema changer implementation.""" + + def visit_table(self, table): + """Rename table not supported""" + raise exceptions.NotSupportedError( + "Firebird does not support renaming tables.") + + def _visit_column_name(self, table, column, delta): + self.start_alter_table(table) + col_name = self.preparer.quote(delta.current_name, table.quote) + new_name = self.preparer.format_column(delta.result_column) + self.append('ALTER COLUMN %s TO %s' % (col_name, new_name)) + + def _visit_column_nullable(self, table, column, delta): + """Changing NULL is not supported""" + # TODO: http://www.firebirdfaq.org/faq103/ + raise exceptions.NotSupportedError( + "Firebird does not support altering NULL bevahior.") + + +class FBConstraintGenerator(ansisql.ANSIConstraintGenerator): + """Firebird constraint generator implementation.""" + + +class FBConstraintDropper(ansisql.ANSIConstraintDropper): + """Firebird constaint dropper implementation.""" + + def cascade_constraint(self, constraint): + """Cascading constraints is not supported""" + raise exceptions.NotSupportedError( + "Firebird does not support cascading constraints") + + +class FBDialect(ansisql.ANSIDialect): + columngenerator = FBColumnGenerator + columndropper = FBColumnDropper + schemachanger = FBSchemaChanger + constraintgenerator = FBConstraintGenerator + constraintdropper = FBConstraintDropper diff --git a/rhodecode/lib/dbmigrate/migrate/changeset/databases/mysql.py b/rhodecode/lib/dbmigrate/migrate/changeset/databases/mysql.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/changeset/databases/mysql.py @@ -0,0 +1,94 @@ +""" + MySQL database specific implementations of changeset classes. +""" + +from sqlalchemy.databases import mysql as sa_base +from sqlalchemy import types as sqltypes + +from rhodecode.lib.dbmigrate.migrate import exceptions +from rhodecode.lib.dbmigrate.migrate.changeset import ansisql, SQLA_06 + + +if not SQLA_06: + MySQLSchemaGenerator = sa_base.MySQLSchemaGenerator +else: + MySQLSchemaGenerator = sa_base.MySQLDDLCompiler + +class MySQLColumnGenerator(MySQLSchemaGenerator, ansisql.ANSIColumnGenerator): + pass + + +class MySQLColumnDropper(ansisql.ANSIColumnDropper): + pass + + +class MySQLSchemaChanger(MySQLSchemaGenerator, ansisql.ANSISchemaChanger): + + def visit_column(self, delta): + table = delta.table + colspec = self.get_column_specification(delta.result_column) + if delta.result_column.autoincrement: + primary_keys = [c for c in table.primary_key.columns + if (c.autoincrement and + isinstance(c.type, sqltypes.Integer) and + not c.foreign_keys)] + + if primary_keys: + first = primary_keys.pop(0) + if first.name == delta.current_name: + colspec += " AUTO_INCREMENT" + old_col_name = self.preparer.quote(delta.current_name, table.quote) + + self.start_alter_table(table) + + self.append("CHANGE COLUMN %s " % old_col_name) + self.append(colspec) + self.execute() + + def visit_index(self, param): + # If MySQL can do this, I can't find how + raise exceptions.NotSupportedError("MySQL cannot rename indexes") + + +class MySQLConstraintGenerator(ansisql.ANSIConstraintGenerator): + pass + +if SQLA_06: + class MySQLConstraintDropper(MySQLSchemaGenerator, ansisql.ANSIConstraintDropper): + def visit_migrate_check_constraint(self, *p, **k): + raise exceptions.NotSupportedError("MySQL does not support CHECK" + " constraints, use triggers instead.") + +else: + class MySQLConstraintDropper(ansisql.ANSIConstraintDropper): + + def visit_migrate_primary_key_constraint(self, constraint): + self.start_alter_table(constraint) + self.append("DROP PRIMARY KEY") + self.execute() + + def visit_migrate_foreign_key_constraint(self, constraint): + self.start_alter_table(constraint) + self.append("DROP FOREIGN KEY ") + constraint.name = self.get_constraint_name(constraint) + self.append(self.preparer.format_constraint(constraint)) + self.execute() + + def visit_migrate_check_constraint(self, *p, **k): + raise exceptions.NotSupportedError("MySQL does not support CHECK" + " constraints, use triggers instead.") + + def visit_migrate_unique_constraint(self, constraint, *p, **k): + self.start_alter_table(constraint) + self.append('DROP INDEX ') + constraint.name = self.get_constraint_name(constraint) + self.append(self.preparer.format_constraint(constraint)) + self.execute() + + +class MySQLDialect(ansisql.ANSIDialect): + columngenerator = MySQLColumnGenerator + columndropper = MySQLColumnDropper + schemachanger = MySQLSchemaChanger + constraintgenerator = MySQLConstraintGenerator + constraintdropper = MySQLConstraintDropper diff --git a/rhodecode/lib/dbmigrate/migrate/changeset/databases/oracle.py b/rhodecode/lib/dbmigrate/migrate/changeset/databases/oracle.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/changeset/databases/oracle.py @@ -0,0 +1,111 @@ +""" + Oracle database specific implementations of changeset classes. +""" +import sqlalchemy as sa +from sqlalchemy.databases import oracle as sa_base + +from rhodecode.lib.dbmigrate.migrate import exceptions +from rhodecode.lib.dbmigrate.migrate.changeset import ansisql, SQLA_06 + + +if not SQLA_06: + OracleSchemaGenerator = sa_base.OracleSchemaGenerator +else: + OracleSchemaGenerator = sa_base.OracleDDLCompiler + + +class OracleColumnGenerator(OracleSchemaGenerator, ansisql.ANSIColumnGenerator): + pass + + +class OracleColumnDropper(ansisql.ANSIColumnDropper): + pass + + +class OracleSchemaChanger(OracleSchemaGenerator, ansisql.ANSISchemaChanger): + + def get_column_specification(self, column, **kwargs): + # Ignore the NOT NULL generated + override_nullable = kwargs.pop('override_nullable', None) + if override_nullable: + orig = column.nullable + column.nullable = True + ret = super(OracleSchemaChanger, self).get_column_specification( + column, **kwargs) + if override_nullable: + column.nullable = orig + return ret + + def visit_column(self, delta): + keys = delta.keys() + + if 'name' in keys: + self._run_subvisit(delta, + self._visit_column_name, + start_alter=False) + + if len(set(('type', 'nullable', 'server_default')).intersection(keys)): + self._run_subvisit(delta, + self._visit_column_change, + start_alter=False) + + def _visit_column_change(self, table, column, delta): + # Oracle cannot drop a default once created, but it can set it + # to null. We'll do that if default=None + # http://forums.oracle.com/forums/message.jspa?messageID=1273234#1273234 + dropdefault_hack = (column.server_default is None \ + and 'server_default' in delta.keys()) + # Oracle apparently doesn't like it when we say "not null" if + # the column's already not null. Fudge it, so we don't need a + # new function + notnull_hack = ((not column.nullable) \ + and ('nullable' not in delta.keys())) + # We need to specify NULL if we're removing a NOT NULL + # constraint + null_hack = (column.nullable and ('nullable' in delta.keys())) + + if dropdefault_hack: + column.server_default = sa.PassiveDefault(sa.sql.null()) + if notnull_hack: + column.nullable = True + colspec = self.get_column_specification(column, + override_nullable=null_hack) + if null_hack: + colspec += ' NULL' + if notnull_hack: + column.nullable = False + if dropdefault_hack: + column.server_default = None + + self.start_alter_table(table) + self.append("MODIFY (") + self.append(colspec) + self.append(")") + + +class OracleConstraintCommon(object): + + def get_constraint_name(self, cons): + # Oracle constraints can't guess their name like other DBs + if not cons.name: + raise exceptions.NotSupportedError( + "Oracle constraint names must be explicitly stated") + return cons.name + + +class OracleConstraintGenerator(OracleConstraintCommon, + ansisql.ANSIConstraintGenerator): + pass + + +class OracleConstraintDropper(OracleConstraintCommon, + ansisql.ANSIConstraintDropper): + pass + + +class OracleDialect(ansisql.ANSIDialect): + columngenerator = OracleColumnGenerator + columndropper = OracleColumnDropper + schemachanger = OracleSchemaChanger + constraintgenerator = OracleConstraintGenerator + constraintdropper = OracleConstraintDropper diff --git a/rhodecode/lib/dbmigrate/migrate/changeset/databases/postgres.py b/rhodecode/lib/dbmigrate/migrate/changeset/databases/postgres.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/changeset/databases/postgres.py @@ -0,0 +1,46 @@ +""" + `PostgreSQL`_ database specific implementations of changeset classes. + + .. _`PostgreSQL`: http://www.postgresql.org/ +""" +from rhodecode.lib.dbmigrate.migrate.changeset import ansisql, SQLA_06 + +if not SQLA_06: + from sqlalchemy.databases import postgres as sa_base + PGSchemaGenerator = sa_base.PGSchemaGenerator +else: + from sqlalchemy.databases import postgresql as sa_base + PGSchemaGenerator = sa_base.PGDDLCompiler + + +class PGColumnGenerator(PGSchemaGenerator, ansisql.ANSIColumnGenerator): + """PostgreSQL column generator implementation.""" + pass + + +class PGColumnDropper(ansisql.ANSIColumnDropper): + """PostgreSQL column dropper implementation.""" + pass + + +class PGSchemaChanger(ansisql.ANSISchemaChanger): + """PostgreSQL schema changer implementation.""" + pass + + +class PGConstraintGenerator(ansisql.ANSIConstraintGenerator): + """PostgreSQL constraint generator implementation.""" + pass + + +class PGConstraintDropper(ansisql.ANSIConstraintDropper): + """PostgreSQL constaint dropper implementation.""" + pass + + +class PGDialect(ansisql.ANSIDialect): + columngenerator = PGColumnGenerator + columndropper = PGColumnDropper + schemachanger = PGSchemaChanger + constraintgenerator = PGConstraintGenerator + constraintdropper = PGConstraintDropper diff --git a/rhodecode/lib/dbmigrate/migrate/changeset/databases/sqlite.py b/rhodecode/lib/dbmigrate/migrate/changeset/databases/sqlite.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/changeset/databases/sqlite.py @@ -0,0 +1,148 @@ +""" + `SQLite`_ database specific implementations of changeset classes. + + .. _`SQLite`: http://www.sqlite.org/ +""" +from UserDict import DictMixin +from copy import copy + +from sqlalchemy.databases import sqlite as sa_base + +from rhodecode.lib.dbmigrate.migrate import exceptions +from rhodecode.lib.dbmigrate.migrate.changeset import ansisql, SQLA_06 + + +if not SQLA_06: + SQLiteSchemaGenerator = sa_base.SQLiteSchemaGenerator +else: + SQLiteSchemaGenerator = sa_base.SQLiteDDLCompiler + +class SQLiteCommon(object): + + def _not_supported(self, op): + raise exceptions.NotSupportedError("SQLite does not support " + "%s; see http://www.sqlite.org/lang_altertable.html" % op) + + +class SQLiteHelper(SQLiteCommon): + + def recreate_table(self,table,column=None,delta=None): + table_name = self.preparer.format_table(table) + + # we remove all indexes so as not to have + # problems during copy and re-create + for index in table.indexes: + index.drop() + + self.append('ALTER TABLE %s RENAME TO migration_tmp' % table_name) + self.execute() + + insertion_string = self._modify_table(table, column, delta) + + table.create() + self.append(insertion_string % {'table_name': table_name}) + self.execute() + self.append('DROP TABLE migration_tmp') + self.execute() + + def visit_column(self, delta): + if isinstance(delta, DictMixin): + column = delta.result_column + table = self._to_table(delta.table) + else: + column = delta + table = self._to_table(column.table) + self.recreate_table(table,column,delta) + +class SQLiteColumnGenerator(SQLiteSchemaGenerator, + ansisql.ANSIColumnGenerator, + # at the end so we get the normal + # visit_column by default + SQLiteHelper, + SQLiteCommon + ): + """SQLite ColumnGenerator""" + + def _modify_table(self, table, column, delta): + columns = ' ,'.join(map( + self.preparer.format_column, + [c for c in table.columns if c.name!=column.name])) + return ('INSERT INTO %%(table_name)s (%(cols)s) ' + 'SELECT %(cols)s from migration_tmp')%{'cols':columns} + + def visit_column(self,column): + if column.foreign_keys: + SQLiteHelper.visit_column(self,column) + else: + super(SQLiteColumnGenerator,self).visit_column(column) + +class SQLiteColumnDropper(SQLiteHelper, ansisql.ANSIColumnDropper): + """SQLite ColumnDropper""" + + def _modify_table(self, table, column, delta): + columns = ' ,'.join(map(self.preparer.format_column, table.columns)) + return 'INSERT INTO %(table_name)s SELECT ' + columns + \ + ' from migration_tmp' + + +class SQLiteSchemaChanger(SQLiteHelper, ansisql.ANSISchemaChanger): + """SQLite SchemaChanger""" + + def _modify_table(self, table, column, delta): + return 'INSERT INTO %(table_name)s SELECT * from migration_tmp' + + def visit_index(self, index): + """Does not support ALTER INDEX""" + self._not_supported('ALTER INDEX') + + +class SQLiteConstraintGenerator(ansisql.ANSIConstraintGenerator, SQLiteHelper, SQLiteCommon): + + def visit_migrate_primary_key_constraint(self, constraint): + tmpl = "CREATE UNIQUE INDEX %s ON %s ( %s )" + cols = ', '.join(map(self.preparer.format_column, constraint.columns)) + tname = self.preparer.format_table(constraint.table) + name = self.get_constraint_name(constraint) + msg = tmpl % (name, tname, cols) + self.append(msg) + self.execute() + + def _modify_table(self, table, column, delta): + return 'INSERT INTO %(table_name)s SELECT * from migration_tmp' + + def visit_migrate_foreign_key_constraint(self, *p, **k): + self.recreate_table(p[0].table) + + def visit_migrate_unique_constraint(self, *p, **k): + self.recreate_table(p[0].table) + + +class SQLiteConstraintDropper(ansisql.ANSIColumnDropper, + SQLiteCommon, + ansisql.ANSIConstraintCommon): + + def visit_migrate_primary_key_constraint(self, constraint): + tmpl = "DROP INDEX %s " + name = self.get_constraint_name(constraint) + msg = tmpl % (name) + self.append(msg) + self.execute() + + def visit_migrate_foreign_key_constraint(self, *p, **k): + self._not_supported('ALTER TABLE DROP CONSTRAINT') + + def visit_migrate_check_constraint(self, *p, **k): + self._not_supported('ALTER TABLE DROP CONSTRAINT') + + def visit_migrate_unique_constraint(self, *p, **k): + self._not_supported('ALTER TABLE DROP CONSTRAINT') + + +# TODO: technically primary key is a NOT NULL + UNIQUE constraint, should add NOT NULL to index + +class SQLiteDialect(ansisql.ANSIDialect): + columngenerator = SQLiteColumnGenerator + columndropper = SQLiteColumnDropper + schemachanger = SQLiteSchemaChanger + constraintgenerator = SQLiteConstraintGenerator + constraintdropper = SQLiteConstraintDropper diff --git a/rhodecode/lib/dbmigrate/migrate/changeset/databases/visitor.py b/rhodecode/lib/dbmigrate/migrate/changeset/databases/visitor.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/changeset/databases/visitor.py @@ -0,0 +1,78 @@ +""" + Module for visitor class mapping. +""" +import sqlalchemy as sa + +from rhodecode.lib.dbmigrate.migrate.changeset import ansisql +from rhodecode.lib.dbmigrate.migrate.changeset.databases import (sqlite, + postgres, + mysql, + oracle, + firebird) + + +# Map SA dialects to the corresponding Migrate extensions +DIALECTS = { + "default": ansisql.ANSIDialect, + "sqlite": sqlite.SQLiteDialect, + "postgres": postgres.PGDialect, + "postgresql": postgres.PGDialect, + "mysql": mysql.MySQLDialect, + "oracle": oracle.OracleDialect, + "firebird": firebird.FBDialect, +} + + +def get_engine_visitor(engine, name): + """ + Get the visitor implementation for the given database engine. + + :param engine: SQLAlchemy Engine + :param name: Name of the visitor + :type name: string + :type engine: Engine + :returns: visitor + """ + # TODO: link to supported visitors + return get_dialect_visitor(engine.dialect, name) + + +def get_dialect_visitor(sa_dialect, name): + """ + Get the visitor implementation for the given dialect. + + Finds the visitor implementation based on the dialect class and + returns and instance initialized with the given name. + + Binds dialect specific preparer to visitor. + """ + + # map sa dialect to migrate dialect and return visitor + sa_dialect_name = getattr(sa_dialect, 'name', 'default') + migrate_dialect_cls = DIALECTS[sa_dialect_name] + visitor = getattr(migrate_dialect_cls, name) + + # bind preparer + visitor.preparer = sa_dialect.preparer(sa_dialect) + + return visitor + +def run_single_visitor(engine, visitorcallable, element, + connection=None, **kwargs): + """Taken from :meth:`sqlalchemy.engine.base.Engine._run_single_visitor` + with support for migrate visitors. + """ + if connection is None: + conn = engine.contextual_connect(close_with_result=False) + else: + conn = connection + visitor = visitorcallable(engine.dialect, conn) + try: + if hasattr(element, '__migrate_visit_name__'): + fn = getattr(visitor, 'visit_' + element.__migrate_visit_name__) + else: + fn = getattr(visitor, 'visit_' + element.__visit_name__) + fn(element, **kwargs) + finally: + if connection is None: + conn.close() diff --git a/rhodecode/lib/dbmigrate/migrate/changeset/schema.py b/rhodecode/lib/dbmigrate/migrate/changeset/schema.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/changeset/schema.py @@ -0,0 +1,669 @@ +""" + Schema module providing common schema operations. +""" +import warnings + +from UserDict import DictMixin + +import sqlalchemy + +from sqlalchemy.schema import ForeignKeyConstraint +from sqlalchemy.schema import UniqueConstraint + +from rhodecode.lib.dbmigrate.migrate.exceptions import * +from rhodecode.lib.dbmigrate.migrate.changeset import SQLA_06 +from rhodecode.lib.dbmigrate.migrate.changeset.databases.visitor import (get_engine_visitor, + run_single_visitor) + + +__all__ = [ + 'create_column', + 'drop_column', + 'alter_column', + 'rename_table', + 'rename_index', + 'ChangesetTable', + 'ChangesetColumn', + 'ChangesetIndex', + 'ChangesetDefaultClause', + 'ColumnDelta', +] + +DEFAULT_ALTER_METADATA = True + + +def create_column(column, table=None, *p, **kw): + """Create a column, given the table. + + API to :meth:`ChangesetColumn.create`. + """ + if table is not None: + return table.create_column(column, *p, **kw) + return column.create(*p, **kw) + + +def drop_column(column, table=None, *p, **kw): + """Drop a column, given the table. + + API to :meth:`ChangesetColumn.drop`. + """ + if table is not None: + return table.drop_column(column, *p, **kw) + return column.drop(*p, **kw) + + +def rename_table(table, name, engine=None, **kw): + """Rename a table. + + If Table instance is given, engine is not used. + + API to :meth:`ChangesetTable.rename`. + + :param table: Table to be renamed. + :param name: New name for Table. + :param engine: Engine instance. + :type table: string or Table instance + :type name: string + :type engine: obj + """ + table = _to_table(table, engine) + table.rename(name, **kw) + + +def rename_index(index, name, table=None, engine=None, **kw): + """Rename an index. + + If Index instance is given, + table and engine are not used. + + API to :meth:`ChangesetIndex.rename`. + + :param index: Index to be renamed. + :param name: New name for index. + :param table: Table to which Index is reffered. + :param engine: Engine instance. + :type index: string or Index instance + :type name: string + :type table: string or Table instance + :type engine: obj + """ + index = _to_index(index, table, engine) + index.rename(name, **kw) + + +def alter_column(*p, **k): + """Alter a column. + + This is a helper function that creates a :class:`ColumnDelta` and + runs it. + + :argument column: + The name of the column to be altered or a + :class:`ChangesetColumn` column representing it. + + :param table: + A :class:`~sqlalchemy.schema.Table` or table name to + for the table where the column will be changed. + + :param engine: + The :class:`~sqlalchemy.engine.base.Engine` to use for table + reflection and schema alterations. + + :param alter_metadata: + If `True`, which is the default, the + :class:`~sqlalchemy.schema.Column` will also modified. + If `False`, the :class:`~sqlalchemy.schema.Column` will be left + as it was. + + :returns: A :class:`ColumnDelta` instance representing the change. + + + """ + + k.setdefault('alter_metadata', DEFAULT_ALTER_METADATA) + + if 'table' not in k and isinstance(p[0], sqlalchemy.Column): + k['table'] = p[0].table + if 'engine' not in k: + k['engine'] = k['table'].bind + + # deprecation + if len(p) >= 2 and isinstance(p[1], sqlalchemy.Column): + warnings.warn( + "Passing a Column object to alter_column is deprecated." + " Just pass in keyword parameters instead.", + MigrateDeprecationWarning + ) + engine = k['engine'] + delta = ColumnDelta(*p, **k) + + visitorcallable = get_engine_visitor(engine, 'schemachanger') + engine._run_visitor(visitorcallable, delta) + + return delta + + +def _to_table(table, engine=None): + """Return if instance of Table, else construct new with metadata""" + if isinstance(table, sqlalchemy.Table): + return table + + # Given: table name, maybe an engine + meta = sqlalchemy.MetaData() + if engine is not None: + meta.bind = engine + return sqlalchemy.Table(table, meta) + + +def _to_index(index, table=None, engine=None): + """Return if instance of Index, else construct new with metadata""" + if isinstance(index, sqlalchemy.Index): + return index + + # Given: index name; table name required + table = _to_table(table, engine) + ret = sqlalchemy.Index(index) + ret.table = table + return ret + + +class ColumnDelta(DictMixin, sqlalchemy.schema.SchemaItem): + """Extracts the differences between two columns/column-parameters + + May receive parameters arranged in several different ways: + + * **current_column, new_column, \*p, \*\*kw** + Additional parameters can be specified to override column + differences. + + * **current_column, \*p, \*\*kw** + Additional parameters alter current_column. Table name is extracted + from current_column object. + Name is changed to current_column.name from current_name, + if current_name is specified. + + * **current_col_name, \*p, \*\*kw** + Table kw must specified. + + :param table: Table at which current Column should be bound to.\ + If table name is given, reflection will be used. + :type table: string or Table instance + :param alter_metadata: If True, it will apply changes to metadata. + :type alter_metadata: bool + :param metadata: If `alter_metadata` is true, \ + metadata is used to reflect table names into + :type metadata: :class:`MetaData` instance + :param engine: When reflecting tables, either engine or metadata must \ + be specified to acquire engine object. + :type engine: :class:`Engine` instance + :returns: :class:`ColumnDelta` instance provides interface for altered attributes to \ + `result_column` through :func:`dict` alike object. + + * :class:`ColumnDelta`.result_column is altered column with new attributes + + * :class:`ColumnDelta`.current_name is current name of column in db + + + """ + + # Column attributes that can be altered + diff_keys = ('name', 'type', 'primary_key', 'nullable', + 'server_onupdate', 'server_default', 'autoincrement') + diffs = dict() + __visit_name__ = 'column' + + def __init__(self, *p, **kw): + self.alter_metadata = kw.pop("alter_metadata", False) + self.meta = kw.pop("metadata", None) + self.engine = kw.pop("engine", None) + + # Things are initialized differently depending on how many column + # parameters are given. Figure out how many and call the appropriate + # method. + if len(p) >= 1 and isinstance(p[0], sqlalchemy.Column): + # At least one column specified + if len(p) >= 2 and isinstance(p[1], sqlalchemy.Column): + # Two columns specified + diffs = self.compare_2_columns(*p, **kw) + else: + # Exactly one column specified + diffs = self.compare_1_column(*p, **kw) + else: + # Zero columns specified + if not len(p) or not isinstance(p[0], basestring): + raise ValueError("First argument must be column name") + diffs = self.compare_parameters(*p, **kw) + + self.apply_diffs(diffs) + + def __repr__(self): + return '' % (self.alter_metadata, + super(ColumnDelta, self).__repr__()) + + def __getitem__(self, key): + if key not in self.keys(): + raise KeyError("No such diff key, available: %s" % self.diffs) + return getattr(self.result_column, key) + + def __setitem__(self, key, value): + if key not in self.keys(): + raise KeyError("No such diff key, available: %s" % self.diffs) + setattr(self.result_column, key, value) + + def __delitem__(self, key): + raise NotImplementedError + + def keys(self): + return self.diffs.keys() + + def compare_parameters(self, current_name, *p, **k): + """Compares Column objects with reflection""" + self.table = k.pop('table') + self.result_column = self._table.c.get(current_name) + if len(p): + k = self._extract_parameters(p, k, self.result_column) + return k + + def compare_1_column(self, col, *p, **k): + """Compares one Column object""" + self.table = k.pop('table', None) + if self.table is None: + self.table = col.table + self.result_column = col + if len(p): + k = self._extract_parameters(p, k, self.result_column) + return k + + def compare_2_columns(self, old_col, new_col, *p, **k): + """Compares two Column objects""" + self.process_column(new_col) + self.table = k.pop('table', None) + # we cannot use bool() on table in SA06 + if self.table is None: + self.table = old_col.table + if self.table is None: + new_col.table + self.result_column = old_col + + # set differences + # leave out some stuff for later comp + for key in (set(self.diff_keys) - set(('type',))): + val = getattr(new_col, key, None) + if getattr(self.result_column, key, None) != val: + k.setdefault(key, val) + + # inspect types + if not self.are_column_types_eq(self.result_column.type, new_col.type): + k.setdefault('type', new_col.type) + + if len(p): + k = self._extract_parameters(p, k, self.result_column) + return k + + def apply_diffs(self, diffs): + """Populate dict and column object with new values""" + self.diffs = diffs + for key in self.diff_keys: + if key in diffs: + setattr(self.result_column, key, diffs[key]) + + self.process_column(self.result_column) + + # create an instance of class type if not yet + if 'type' in diffs and callable(self.result_column.type): + self.result_column.type = self.result_column.type() + + # add column to the table + if self.table is not None and self.alter_metadata: + self.result_column.add_to_table(self.table) + + def are_column_types_eq(self, old_type, new_type): + """Compares two types to be equal""" + ret = old_type.__class__ == new_type.__class__ + + # String length is a special case + if ret and isinstance(new_type, sqlalchemy.types.String): + ret = (getattr(old_type, 'length', None) == \ + getattr(new_type, 'length', None)) + return ret + + def _extract_parameters(self, p, k, column): + """Extracts data from p and modifies diffs""" + p = list(p) + while len(p): + if isinstance(p[0], basestring): + k.setdefault('name', p.pop(0)) + elif isinstance(p[0], sqlalchemy.types.AbstractType): + k.setdefault('type', p.pop(0)) + elif callable(p[0]): + p[0] = p[0]() + else: + break + + if len(p): + new_col = column.copy_fixed() + new_col._init_items(*p) + k = self.compare_2_columns(column, new_col, **k) + return k + + def process_column(self, column): + """Processes default values for column""" + # XXX: this is a snippet from SA processing of positional parameters + if not SQLA_06 and column.args: + toinit = list(column.args) + else: + toinit = list() + + if column.server_default is not None: + if isinstance(column.server_default, sqlalchemy.FetchedValue): + toinit.append(column.server_default) + else: + toinit.append(sqlalchemy.DefaultClause(column.server_default)) + if column.server_onupdate is not None: + if isinstance(column.server_onupdate, FetchedValue): + toinit.append(column.server_default) + else: + toinit.append(sqlalchemy.DefaultClause(column.server_onupdate, + for_update=True)) + if toinit: + column._init_items(*toinit) + + if not SQLA_06: + column.args = [] + + def _get_table(self): + return getattr(self, '_table', None) + + def _set_table(self, table): + if isinstance(table, basestring): + if self.alter_metadata: + if not self.meta: + raise ValueError("metadata must be specified for table" + " reflection when using alter_metadata") + meta = self.meta + if self.engine: + meta.bind = self.engine + else: + if not self.engine and not self.meta: + raise ValueError("engine or metadata must be specified" + " to reflect tables") + if not self.engine: + self.engine = self.meta.bind + meta = sqlalchemy.MetaData(bind=self.engine) + self._table = sqlalchemy.Table(table, meta, autoload=True) + elif isinstance(table, sqlalchemy.Table): + self._table = table + if not self.alter_metadata: + self._table.meta = sqlalchemy.MetaData(bind=self._table.bind) + + def _get_result_column(self): + return getattr(self, '_result_column', None) + + def _set_result_column(self, column): + """Set Column to Table based on alter_metadata evaluation.""" + self.process_column(column) + if not hasattr(self, 'current_name'): + self.current_name = column.name + if self.alter_metadata: + self._result_column = column + else: + self._result_column = column.copy_fixed() + + table = property(_get_table, _set_table) + result_column = property(_get_result_column, _set_result_column) + + +class ChangesetTable(object): + """Changeset extensions to SQLAlchemy tables.""" + + def create_column(self, column, *p, **kw): + """Creates a column. + + The column parameter may be a column definition or the name of + a column in this table. + + API to :meth:`ChangesetColumn.create` + + :param column: Column to be created + :type column: Column instance or string + """ + if not isinstance(column, sqlalchemy.Column): + # It's a column name + column = getattr(self.c, str(column)) + column.create(table=self, *p, **kw) + + def drop_column(self, column, *p, **kw): + """Drop a column, given its name or definition. + + API to :meth:`ChangesetColumn.drop` + + :param column: Column to be droped + :type column: Column instance or string + """ + if not isinstance(column, sqlalchemy.Column): + # It's a column name + try: + column = getattr(self.c, str(column)) + except AttributeError: + # That column isn't part of the table. We don't need + # its entire definition to drop the column, just its + # name, so create a dummy column with the same name. + column = sqlalchemy.Column(str(column), sqlalchemy.Integer()) + column.drop(table=self, *p, **kw) + + def rename(self, name, connection=None, **kwargs): + """Rename this table. + + :param name: New name of the table. + :type name: string + :param alter_metadata: If True, table will be removed from metadata + :type alter_metadata: bool + :param connection: reuse connection istead of creating new one. + :type connection: :class:`sqlalchemy.engine.base.Connection` instance + """ + self.alter_metadata = kwargs.pop('alter_metadata', DEFAULT_ALTER_METADATA) + engine = self.bind + self.new_name = name + visitorcallable = get_engine_visitor(engine, 'schemachanger') + run_single_visitor(engine, visitorcallable, self, connection, **kwargs) + + # Fix metadata registration + if self.alter_metadata: + self.name = name + self.deregister() + self._set_parent(self.metadata) + + def _meta_key(self): + return sqlalchemy.schema._get_table_key(self.name, self.schema) + + def deregister(self): + """Remove this table from its metadata""" + key = self._meta_key() + meta = self.metadata + if key in meta.tables: + del meta.tables[key] + + +class ChangesetColumn(object): + """Changeset extensions to SQLAlchemy columns.""" + + def alter(self, *p, **k): + """Makes a call to :func:`alter_column` for the column this + method is called on. + """ + if 'table' not in k: + k['table'] = self.table + if 'engine' not in k: + k['engine'] = k['table'].bind + return alter_column(self, *p, **k) + + def create(self, table=None, index_name=None, unique_name=None, + primary_key_name=None, populate_default=True, connection=None, **kwargs): + """Create this column in the database. + + Assumes the given table exists. ``ALTER TABLE ADD COLUMN``, + for most databases. + + :param table: Table instance to create on. + :param index_name: Creates :class:`ChangesetIndex` on this column. + :param unique_name: Creates :class:\ +`~migrate.changeset.constraint.UniqueConstraint` on this column. + :param primary_key_name: Creates :class:\ +`~migrate.changeset.constraint.PrimaryKeyConstraint` on this column. + :param alter_metadata: If True, column will be added to table object. + :param populate_default: If True, created column will be \ +populated with defaults + :param connection: reuse connection istead of creating new one. + :type table: Table instance + :type index_name: string + :type unique_name: string + :type primary_key_name: string + :type alter_metadata: bool + :type populate_default: bool + :type connection: :class:`sqlalchemy.engine.base.Connection` instance + + :returns: self + """ + self.populate_default = populate_default + self.alter_metadata = kwargs.pop('alter_metadata', DEFAULT_ALTER_METADATA) + self.index_name = index_name + self.unique_name = unique_name + self.primary_key_name = primary_key_name + for cons in ('index_name', 'unique_name', 'primary_key_name'): + self._check_sanity_constraints(cons) + + if self.alter_metadata: + self.add_to_table(table) + engine = self.table.bind + visitorcallable = get_engine_visitor(engine, 'columngenerator') + engine._run_visitor(visitorcallable, self, connection, **kwargs) + + # TODO: reuse existing connection + if self.populate_default and self.default is not None: + stmt = table.update().values({self: engine._execute_default(self.default)}) + engine.execute(stmt) + + return self + + def drop(self, table=None, connection=None, **kwargs): + """Drop this column from the database, leaving its table intact. + + ``ALTER TABLE DROP COLUMN``, for most databases. + + :param alter_metadata: If True, column will be removed from table object. + :type alter_metadata: bool + :param connection: reuse connection istead of creating new one. + :type connection: :class:`sqlalchemy.engine.base.Connection` instance + """ + self.alter_metadata = kwargs.pop('alter_metadata', DEFAULT_ALTER_METADATA) + if table is not None: + self.table = table + engine = self.table.bind + if self.alter_metadata: + self.remove_from_table(self.table, unset_table=False) + visitorcallable = get_engine_visitor(engine, 'columndropper') + engine._run_visitor(visitorcallable, self, connection, **kwargs) + if self.alter_metadata: + self.table = None + return self + + def add_to_table(self, table): + if table is not None and self.table is None: + self._set_parent(table) + + def _col_name_in_constraint(self, cons, name): + return False + + def remove_from_table(self, table, unset_table=True): + # TODO: remove primary keys, constraints, etc + if unset_table: + self.table = None + + to_drop = set() + for index in table.indexes: + columns = [] + for col in index.columns: + if col.name != self.name: + columns.append(col) + if columns: + index.columns = columns + else: + to_drop.add(index) + table.indexes = table.indexes - to_drop + + to_drop = set() + for cons in table.constraints: + # TODO: deal with other types of constraint + if isinstance(cons, (ForeignKeyConstraint, + UniqueConstraint)): + for col_name in cons.columns: + if not isinstance(col_name, basestring): + col_name = col_name.name + if self.name == col_name: + to_drop.add(cons) + table.constraints = table.constraints - to_drop + + if table.c.contains_column(self): + table.c.remove(self) + + # TODO: this is fixed in 0.6 + def copy_fixed(self, **kw): + """Create a copy of this ``Column``, with all attributes.""" + return sqlalchemy.Column(self.name, self.type, self.default, + key=self.key, + primary_key=self.primary_key, + nullable=self.nullable, + quote=self.quote, + index=self.index, + unique=self.unique, + onupdate=self.onupdate, + autoincrement=self.autoincrement, + server_default=self.server_default, + server_onupdate=self.server_onupdate, + *[c.copy(**kw) for c in self.constraints]) + + def _check_sanity_constraints(self, name): + """Check if constraints names are correct""" + obj = getattr(self, name) + if (getattr(self, name[:-5]) and not obj): + raise InvalidConstraintError("Column.create() accepts index_name," + " primary_key_name and unique_name to generate constraints") + if not isinstance(obj, basestring) and obj is not None: + raise InvalidConstraintError( + "%s argument for column must be constraint name" % name) + + +class ChangesetIndex(object): + """Changeset extensions to SQLAlchemy Indexes.""" + + __visit_name__ = 'index' + + def rename(self, name, connection=None, **kwargs): + """Change the name of an index. + + :param name: New name of the Index. + :type name: string + :param alter_metadata: If True, Index object will be altered. + :type alter_metadata: bool + :param connection: reuse connection istead of creating new one. + :type connection: :class:`sqlalchemy.engine.base.Connection` instance + """ + self.alter_metadata = kwargs.pop('alter_metadata', DEFAULT_ALTER_METADATA) + engine = self.table.bind + self.new_name = name + visitorcallable = get_engine_visitor(engine, 'schemachanger') + engine._run_visitor(visitorcallable, self, connection, **kwargs) + if self.alter_metadata: + self.name = name + + +class ChangesetDefaultClause(object): + """Implements comparison between :class:`DefaultClause` instances""" + + def __eq__(self, other): + if isinstance(other, self.__class__): + if self.arg == other.arg: + return True + + def __ne__(self, other): + return not self.__eq__(other) diff --git a/rhodecode/lib/dbmigrate/migrate/exceptions.py b/rhodecode/lib/dbmigrate/migrate/exceptions.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/exceptions.py @@ -0,0 +1,87 @@ +""" + Provide exception classes for :mod:`migrate` +""" + + +class Error(Exception): + """Error base class.""" + + +class ApiError(Error): + """Base class for API errors.""" + + +class KnownError(ApiError): + """A known error condition.""" + + +class UsageError(ApiError): + """A known error condition where help should be displayed.""" + + +class ControlledSchemaError(Error): + """Base class for controlled schema errors.""" + + +class InvalidVersionError(ControlledSchemaError): + """Invalid version number.""" + + +class DatabaseNotControlledError(ControlledSchemaError): + """Database should be under version control, but it's not.""" + + +class DatabaseAlreadyControlledError(ControlledSchemaError): + """Database shouldn't be under version control, but it is""" + + +class WrongRepositoryError(ControlledSchemaError): + """This database is under version control by another repository.""" + + +class NoSuchTableError(ControlledSchemaError): + """The table does not exist.""" + + +class PathError(Error): + """Base class for path errors.""" + + +class PathNotFoundError(PathError): + """A path with no file was required; found a file.""" + + +class PathFoundError(PathError): + """A path with a file was required; found no file.""" + + +class RepositoryError(Error): + """Base class for repository errors.""" + + +class InvalidRepositoryError(RepositoryError): + """Invalid repository error.""" + + +class ScriptError(Error): + """Base class for script errors.""" + + +class InvalidScriptError(ScriptError): + """Invalid script error.""" + + +class InvalidVersionError(Error): + """Invalid version error.""" + +# migrate.changeset + +class NotSupportedError(Error): + """Not supported error""" + + +class InvalidConstraintError(Error): + """Invalid constraint error""" + +class MigrateDeprecationWarning(DeprecationWarning): + """Warning for deprecated features in Migrate""" diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/__init__.py b/rhodecode/lib/dbmigrate/migrate/versioning/__init__.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/versioning/__init__.py @@ -0,0 +1,5 @@ +""" + This package provides functionality to create and manage + repositories of database schema changesets and to apply these + changesets to databases. +""" diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/api.py b/rhodecode/lib/dbmigrate/migrate/versioning/api.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/versioning/api.py @@ -0,0 +1,383 @@ +""" + This module provides an external API to the versioning system. + + .. versionchanged:: 0.6.0 + :func:`migrate.versioning.api.test` and schema diff functions + changed order of positional arguments so all accept `url` and `repository` + as first arguments. + + .. versionchanged:: 0.5.4 + ``--preview_sql`` displays source file when using SQL scripts. + If Python script is used, it runs the action with mocked engine and + returns captured SQL statements. + + .. versionchanged:: 0.5.4 + Deprecated ``--echo`` parameter in favour of new + :func:`migrate.versioning.util.construct_engine` behavior. +""" + +# Dear migrate developers, +# +# please do not comment this module using sphinx syntax because its +# docstrings are presented as user help and most users cannot +# interpret sphinx annotated ReStructuredText. +# +# Thanks, +# Jan Dittberner + +import sys +import inspect +import logging + +from rhodecode.lib.dbmigrate.migrate import exceptions +from rhodecode.lib.dbmigrate.migrate.versioning import repository, schema, version, \ + script as script_ # command name conflict +from rhodecode.lib.dbmigrate.migrate.versioning.util import catch_known_errors, with_engine + + +log = logging.getLogger(__name__) +command_desc = { + 'help': 'displays help on a given command', + 'create': 'create an empty repository at the specified path', + 'script': 'create an empty change Python script', + 'script_sql': 'create empty change SQL scripts for given database', + 'version': 'display the latest version available in a repository', + 'db_version': 'show the current version of the repository under version control', + 'source': 'display the Python code for a particular version in this repository', + 'version_control': 'mark a database as under this repository\'s version control', + 'upgrade': 'upgrade a database to a later version', + 'downgrade': 'downgrade a database to an earlier version', + 'drop_version_control': 'removes version control from a database', + 'manage': 'creates a Python script that runs Migrate with a set of default values', + 'test': 'performs the upgrade and downgrade command on the given database', + 'compare_model_to_db': 'compare MetaData against the current database state', + 'create_model': 'dump the current database as a Python model to stdout', + 'make_update_script_for_model': 'create a script changing the old MetaData to the new (current) MetaData', + 'update_db_from_model': 'modify the database to match the structure of the current MetaData', +} +__all__ = command_desc.keys() + +Repository = repository.Repository +ControlledSchema = schema.ControlledSchema +VerNum = version.VerNum +PythonScript = script_.PythonScript +SqlScript = script_.SqlScript + + +# deprecated +def help(cmd=None, **opts): + """%prog help COMMAND + + Displays help on a given command. + """ + if cmd is None: + raise exceptions.UsageError(None) + try: + func = globals()[cmd] + except: + raise exceptions.UsageError( + "'%s' isn't a valid command. Try 'help COMMAND'" % cmd) + ret = func.__doc__ + if sys.argv[0]: + ret = ret.replace('%prog', sys.argv[0]) + return ret + +@catch_known_errors +def create(repository, name, **opts): + """%prog create REPOSITORY_PATH NAME [--table=TABLE] + + Create an empty repository at the specified path. + + You can specify the version_table to be used; by default, it is + 'migrate_version'. This table is created in all version-controlled + databases. + """ + repo_path = Repository.create(repository, name, **opts) + + +@catch_known_errors +def script(description, repository, **opts): + """%prog script DESCRIPTION REPOSITORY_PATH + + Create an empty change script using the next unused version number + appended with the given description. + + For instance, manage.py script "Add initial tables" creates: + repository/versions/001_Add_initial_tables.py + """ + repo = Repository(repository) + repo.create_script(description, **opts) + + +@catch_known_errors +def script_sql(database, repository, **opts): + """%prog script_sql DATABASE REPOSITORY_PATH + + Create empty change SQL scripts for given DATABASE, where DATABASE + is either specific ('postgres', 'mysql', 'oracle', 'sqlite', etc.) + or generic ('default'). + + For instance, manage.py script_sql postgres creates: + repository/versions/001_postgres_upgrade.sql and + repository/versions/001_postgres_postgres.sql + """ + repo = Repository(repository) + repo.create_script_sql(database, **opts) + + +def version(repository, **opts): + """%prog version REPOSITORY_PATH + + Display the latest version available in a repository. + """ + repo = Repository(repository) + return repo.latest + + +@with_engine +def db_version(url, repository, **opts): + """%prog db_version URL REPOSITORY_PATH + + Show the current version of the repository with the given + connection string, under version control of the specified + repository. + + The url should be any valid SQLAlchemy connection string. + """ + engine = opts.pop('engine') + schema = ControlledSchema(engine, repository) + return schema.version + + +def source(version, dest=None, repository=None, **opts): + """%prog source VERSION [DESTINATION] --repository=REPOSITORY_PATH + + Display the Python code for a particular version in this + repository. Save it to the file at DESTINATION or, if omitted, + send to stdout. + """ + if repository is None: + raise exceptions.UsageError("A repository must be specified") + repo = Repository(repository) + ret = repo.version(version).script().source() + if dest is not None: + dest = open(dest, 'w') + dest.write(ret) + dest.close() + ret = None + return ret + + +def upgrade(url, repository, version=None, **opts): + """%prog upgrade URL REPOSITORY_PATH [VERSION] [--preview_py|--preview_sql] + + Upgrade a database to a later version. + + This runs the upgrade() function defined in your change scripts. + + By default, the database is updated to the latest available + version. You may specify a version instead, if you wish. + + You may preview the Python or SQL code to be executed, rather than + actually executing it, using the appropriate 'preview' option. + """ + err = "Cannot upgrade a database of version %s to version %s. "\ + "Try 'downgrade' instead." + return _migrate(url, repository, version, upgrade=True, err=err, **opts) + + +def downgrade(url, repository, version, **opts): + """%prog downgrade URL REPOSITORY_PATH VERSION [--preview_py|--preview_sql] + + Downgrade a database to an earlier version. + + This is the reverse of upgrade; this runs the downgrade() function + defined in your change scripts. + + You may preview the Python or SQL code to be executed, rather than + actually executing it, using the appropriate 'preview' option. + """ + err = "Cannot downgrade a database of version %s to version %s. "\ + "Try 'upgrade' instead." + return _migrate(url, repository, version, upgrade=False, err=err, **opts) + +@with_engine +def test(url, repository, **opts): + """%prog test URL REPOSITORY_PATH [VERSION] + + Performs the upgrade and downgrade option on the given + database. This is not a real test and may leave the database in a + bad state. You should therefore better run the test on a copy of + your database. + """ + engine = opts.pop('engine') + repos = Repository(repository) + script = repos.version(None).script() + + # Upgrade + log.info("Upgrading...") + script.run(engine, 1) + log.info("done") + + log.info("Downgrading...") + script.run(engine, -1) + log.info("done") + log.info("Success") + + +@with_engine +def version_control(url, repository, version=None, **opts): + """%prog version_control URL REPOSITORY_PATH [VERSION] + + Mark a database as under this repository's version control. + + Once a database is under version control, schema changes should + only be done via change scripts in this repository. + + This creates the table version_table in the database. + + The url should be any valid SQLAlchemy connection string. + + By default, the database begins at version 0 and is assumed to be + empty. If the database is not empty, you may specify a version at + which to begin instead. No attempt is made to verify this + version's correctness - the database schema is expected to be + identical to what it would be if the database were created from + scratch. + """ + engine = opts.pop('engine') + ControlledSchema.create(engine, repository, version) + + +@with_engine +def drop_version_control(url, repository, **opts): + """%prog drop_version_control URL REPOSITORY_PATH + + Removes version control from a database. + """ + engine = opts.pop('engine') + schema = ControlledSchema(engine, repository) + schema.drop() + + +def manage(file, **opts): + """%prog manage FILENAME [VARIABLES...] + + Creates a script that runs Migrate with a set of default values. + + For example:: + + %prog manage manage.py --repository=/path/to/repository \ +--url=sqlite:///project.db + + would create the script manage.py. The following two commands + would then have exactly the same results:: + + python manage.py version + %prog version --repository=/path/to/repository + """ + Repository.create_manage_file(file, **opts) + + +@with_engine +def compare_model_to_db(url, repository, model, **opts): + """%prog compare_model_to_db URL REPOSITORY_PATH MODEL + + Compare the current model (assumed to be a module level variable + of type sqlalchemy.MetaData) against the current database. + + NOTE: This is EXPERIMENTAL. + """ # TODO: get rid of EXPERIMENTAL label + engine = opts.pop('engine') + return ControlledSchema.compare_model_to_db(engine, model, repository) + + +@with_engine +def create_model(url, repository, **opts): + """%prog create_model URL REPOSITORY_PATH [DECLERATIVE=True] + + Dump the current database as a Python model to stdout. + + NOTE: This is EXPERIMENTAL. + """ # TODO: get rid of EXPERIMENTAL label + engine = opts.pop('engine') + declarative = opts.get('declarative', False) + return ControlledSchema.create_model(engine, repository, declarative) + + +@catch_known_errors +@with_engine +def make_update_script_for_model(url, repository, oldmodel, model, **opts): + """%prog make_update_script_for_model URL OLDMODEL MODEL REPOSITORY_PATH + + Create a script changing the old Python model to the new (current) + Python model, sending to stdout. + + NOTE: This is EXPERIMENTAL. + """ # TODO: get rid of EXPERIMENTAL label + engine = opts.pop('engine') + return PythonScript.make_update_script_for_model( + engine, oldmodel, model, repository, **opts) + + +@with_engine +def update_db_from_model(url, repository, model, **opts): + """%prog update_db_from_model URL REPOSITORY_PATH MODEL + + Modify the database to match the structure of the current Python + model. This also sets the db_version number to the latest in the + repository. + + NOTE: This is EXPERIMENTAL. + """ # TODO: get rid of EXPERIMENTAL label + engine = opts.pop('engine') + schema = ControlledSchema(engine, repository) + schema.update_db_from_model(model) + +@with_engine +def _migrate(url, repository, version, upgrade, err, **opts): + engine = opts.pop('engine') + url = str(engine.url) + schema = ControlledSchema(engine, repository) + version = _migrate_version(schema, version, upgrade, err) + + changeset = schema.changeset(version) + for ver, change in changeset: + nextver = ver + changeset.step + log.info('%s -> %s... ', ver, nextver) + + if opts.get('preview_sql'): + if isinstance(change, PythonScript): + log.info(change.preview_sql(url, changeset.step, **opts)) + elif isinstance(change, SqlScript): + log.info(change.source()) + + elif opts.get('preview_py'): + if not isinstance(change, PythonScript): + raise exceptions.UsageError("Python source can be only displayed" + " for python migration files") + source_ver = max(ver, nextver) + module = schema.repository.version(source_ver).script().module + funcname = upgrade and "upgrade" or "downgrade" + func = getattr(module, funcname) + log.info(inspect.getsource(func)) + else: + schema.runchange(ver, change, changeset.step) + log.info('done') + + +def _migrate_version(schema, version, upgrade, err): + if version is None: + return version + # Version is specified: ensure we're upgrading in the right direction + # (current version < target version for upgrading; reverse for down) + version = VerNum(version) + cur = schema.version + if upgrade is not None: + if upgrade: + direction = cur <= version + else: + direction = cur >= version + if not direction: + raise exceptions.KnownError(err % (cur, version)) + return version diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/cfgparse.py b/rhodecode/lib/dbmigrate/migrate/versioning/cfgparse.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/versioning/cfgparse.py @@ -0,0 +1,27 @@ +""" + Configuration parser module. +""" + +from ConfigParser import ConfigParser + +from rhodecode.lib.dbmigrate.migrate.versioning.config import * +from rhodecode.lib.dbmigrate.migrate.versioning import pathed + + +class Parser(ConfigParser): + """A project configuration file.""" + + def to_dict(self, sections=None): + """It's easier to access config values like dictionaries""" + return self._sections + + +class Config(pathed.Pathed, Parser): + """Configuration class.""" + + def __init__(self, path, *p, **k): + """Confirm the config file exists; read it.""" + self.require_found(path) + pathed.Pathed.__init__(self, path) + Parser.__init__(self, *p, **k) + self.read(path) diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/config.py b/rhodecode/lib/dbmigrate/migrate/versioning/config.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/versioning/config.py @@ -0,0 +1,14 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from sqlalchemy.util import OrderedDict + + +__all__ = ['databases', 'operations'] + +databases = ('sqlite', 'postgres', 'mysql', 'oracle', 'mssql', 'firebird') + +# Map operation names to function names +operations = OrderedDict() +operations['upgrade'] = 'upgrade' +operations['downgrade'] = 'downgrade' diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/genmodel.py b/rhodecode/lib/dbmigrate/migrate/versioning/genmodel.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/versioning/genmodel.py @@ -0,0 +1,253 @@ +""" + Code to generate a Python model from a database or differences + between a model and database. + + Some of this is borrowed heavily from the AutoCode project at: + http://code.google.com/p/sqlautocode/ +""" + +import sys +import logging + +import sqlalchemy + +from rhodecode.lib.dbmigrate import migrate +from rhodecode.lib.dbmigrate.migrate import changeset + +log = logging.getLogger(__name__) +HEADER = """ +## File autogenerated by genmodel.py + +from sqlalchemy import * +meta = MetaData() +""" + +DECLARATIVE_HEADER = """ +## File autogenerated by genmodel.py + +from sqlalchemy import * +from sqlalchemy.ext import declarative + +Base = declarative.declarative_base() +""" + + +class ModelGenerator(object): + + def __init__(self, diff, engine, declarative=False): + self.diff = diff + self.engine = engine + self.declarative = declarative + + def column_repr(self, col): + kwarg = [] + if col.key != col.name: + kwarg.append('key') + if col.primary_key: + col.primary_key = True # otherwise it dumps it as 1 + kwarg.append('primary_key') + if not col.nullable: + kwarg.append('nullable') + if col.onupdate: + kwarg.append('onupdate') + if col.default: + if col.primary_key: + # I found that PostgreSQL automatically creates a + # default value for the sequence, but let's not show + # that. + pass + else: + kwarg.append('default') + ks = ', '.join('%s=%r' % (k, getattr(col, k)) for k in kwarg) + + # crs: not sure if this is good idea, but it gets rid of extra + # u'' + name = col.name.encode('utf8') + + type_ = col.type + for cls in col.type.__class__.__mro__: + if cls.__module__ == 'sqlalchemy.types' and \ + not cls.__name__.isupper(): + if cls is not type_.__class__: + type_ = cls() + break + + data = { + 'name': name, + 'type': type_, + 'constraints': ', '.join([repr(cn) for cn in col.constraints]), + 'args': ks and ks or ''} + + if data['constraints']: + if data['args']: + data['args'] = ',' + data['args'] + + if data['constraints'] or data['args']: + data['maybeComma'] = ',' + else: + data['maybeComma'] = '' + + commonStuff = """ %(maybeComma)s %(constraints)s %(args)s)""" % data + commonStuff = commonStuff.strip() + data['commonStuff'] = commonStuff + if self.declarative: + return """%(name)s = Column(%(type)r%(commonStuff)s""" % data + else: + return """Column(%(name)r, %(type)r%(commonStuff)s""" % data + + def getTableDefn(self, table): + out = [] + tableName = table.name + if self.declarative: + out.append("class %(table)s(Base):" % {'table': tableName}) + out.append(" __tablename__ = '%(table)s'" % {'table': tableName}) + for col in table.columns: + out.append(" %s" % self.column_repr(col)) + else: + out.append("%(table)s = Table('%(table)s', meta," % \ + {'table': tableName}) + for col in table.columns: + out.append(" %s," % self.column_repr(col)) + out.append(")") + return out + + def _get_tables(self, missingA=False, missingB=False, modified=False): + to_process = [] + for bool_, names, metadata in ( + (missingA, self.diff.tables_missing_from_A, self.diff.metadataB), + (missingB, self.diff.tables_missing_from_B, self.diff.metadataA), + (modified, self.diff.tables_different, self.diff.metadataA), + ): + if bool_: + for name in names: + yield metadata.tables.get(name) + + def toPython(self): + """Assume database is current and model is empty.""" + out = [] + if self.declarative: + out.append(DECLARATIVE_HEADER) + else: + out.append(HEADER) + out.append("") + for table in self._get_tables(missingA=True): + out.extend(self.getTableDefn(table)) + out.append("") + return '\n'.join(out) + + def toUpgradeDowngradePython(self, indent=' '): + ''' Assume model is most current and database is out-of-date. ''' + decls = ['from rhodecode.lib.dbmigrate.migrate.changeset import schema', + 'meta = MetaData()'] + for table in self._get_tables( + missingA=True, missingB=True, modified=True + ): + decls.extend(self.getTableDefn(table)) + + upgradeCommands, downgradeCommands = [], [] + for tableName in self.diff.tables_missing_from_A: + upgradeCommands.append("%(table)s.drop()" % {'table': tableName}) + downgradeCommands.append("%(table)s.create()" % \ + {'table': tableName}) + for tableName in self.diff.tables_missing_from_B: + upgradeCommands.append("%(table)s.create()" % {'table': tableName}) + downgradeCommands.append("%(table)s.drop()" % {'table': tableName}) + + for tableName in self.diff.tables_different: + dbTable = self.diff.metadataB.tables[tableName] + missingInDatabase, missingInModel, diffDecl = \ + self.diff.colDiffs[tableName] + for col in missingInDatabase: + upgradeCommands.append('%s.columns[%r].create()' % ( + modelTable, col.name)) + downgradeCommands.append('%s.columns[%r].drop()' % ( + modelTable, col.name)) + for col in missingInModel: + upgradeCommands.append('%s.columns[%r].drop()' % ( + modelTable, col.name)) + downgradeCommands.append('%s.columns[%r].create()' % ( + modelTable, col.name)) + for modelCol, databaseCol, modelDecl, databaseDecl in diffDecl: + upgradeCommands.append( + 'assert False, "Can\'t alter columns: %s:%s=>%s"', + modelTable, modelCol.name, databaseCol.name) + downgradeCommands.append( + 'assert False, "Can\'t alter columns: %s:%s=>%s"', + modelTable, modelCol.name, databaseCol.name) + pre_command = ' meta.bind = migrate_engine' + + return ( + '\n'.join(decls), + '\n'.join([pre_command] + ['%s%s' % (indent, line) for line in upgradeCommands]), + '\n'.join([pre_command] + ['%s%s' % (indent, line) for line in downgradeCommands])) + + def _db_can_handle_this_change(self, td): + if (td.columns_missing_from_B + and not td.columns_missing_from_A + and not td.columns_different): + # Even sqlite can handle this. + return True + else: + return not self.engine.url.drivername.startswith('sqlite') + + def applyModel(self): + """Apply model to current database.""" + + meta = sqlalchemy.MetaData(self.engine) + + for table in self._get_tables(missingA=True): + table = table.tometadata(meta) + table.drop() + for table in self._get_tables(missingB=True): + table = table.tometadata(meta) + table.create() + for modelTable in self._get_tables(modified=True): + tableName = modelTable.name + modelTable = modelTable.tometadata(meta) + dbTable = self.diff.metadataB.tables[tableName] + + td = self.diff.tables_different[tableName] + + if self._db_can_handle_this_change(td): + + for col in td.columns_missing_from_B: + modelTable.columns[col].create() + for col in td.columns_missing_from_A: + dbTable.columns[col].drop() + # XXX handle column changes here. + else: + # Sqlite doesn't support drop column, so you have to + # do more: create temp table, copy data to it, drop + # old table, create new table, copy data back. + # + # I wonder if this is guaranteed to be unique? + tempName = '_temp_%s' % modelTable.name + + def getCopyStatement(): + preparer = self.engine.dialect.preparer + commonCols = [] + for modelCol in modelTable.columns: + if modelCol.name in dbTable.columns: + commonCols.append(modelCol.name) + commonColsStr = ', '.join(commonCols) + return 'INSERT INTO %s (%s) SELECT %s FROM %s' % \ + (tableName, commonColsStr, commonColsStr, tempName) + + # Move the data in one transaction, so that we don't + # leave the database in a nasty state. + connection = self.engine.connect() + trans = connection.begin() + try: + connection.execute( + 'CREATE TEMPORARY TABLE %s as SELECT * from %s' % \ + (tempName, modelTable.name)) + # make sure the drop takes place inside our + # transaction with the bind parameter + modelTable.drop(bind=connection) + modelTable.create(bind=connection) + connection.execute(getCopyStatement()) + connection.execute('DROP TABLE %s' % tempName) + trans.commit() + except: + trans.rollback() + raise diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/migrate_repository.py b/rhodecode/lib/dbmigrate/migrate/versioning/migrate_repository.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/versioning/migrate_repository.py @@ -0,0 +1,100 @@ +""" + Script to migrate repository from sqlalchemy <= 0.4.4 to the new + repository schema. This shouldn't use any other migrate modules, so + that it can work in any version. +""" + +import os +import sys +import logging + +log = logging.getLogger(__name__) + + +def usage(): + """Gives usage information.""" + print """Usage: %(prog)s repository-to-migrate + + Upgrade your repository to the new flat format. + + NOTE: You should probably make a backup before running this. + """ % {'prog': sys.argv[0]} + + sys.exit(1) + + +def delete_file(filepath): + """Deletes a file and prints a message.""" + log.info('Deleting file: %s' % filepath) + os.remove(filepath) + + +def move_file(src, tgt): + """Moves a file and prints a message.""" + log.info('Moving file %s to %s' % (src, tgt)) + if os.path.exists(tgt): + raise Exception( + 'Cannot move file %s because target %s already exists' % \ + (src, tgt)) + os.rename(src, tgt) + + +def delete_directory(dirpath): + """Delete a directory and print a message.""" + log.info('Deleting directory: %s' % dirpath) + os.rmdir(dirpath) + + +def migrate_repository(repos): + """Does the actual migration to the new repository format.""" + log.info('Migrating repository at: %s to new format' % repos) + versions = '%s/versions' % repos + dirs = os.listdir(versions) + # Only use int's in list. + numdirs = [int(dirname) for dirname in dirs if dirname.isdigit()] + numdirs.sort() # Sort list. + for dirname in numdirs: + origdir = '%s/%s' % (versions, dirname) + log.info('Working on directory: %s' % origdir) + files = os.listdir(origdir) + files.sort() + for filename in files: + # Delete compiled Python files. + if filename.endswith('.pyc') or filename.endswith('.pyo'): + delete_file('%s/%s' % (origdir, filename)) + + # Delete empty __init__.py files. + origfile = '%s/__init__.py' % origdir + if os.path.exists(origfile) and len(open(origfile).read()) == 0: + delete_file(origfile) + + # Move sql upgrade scripts. + if filename.endswith('.sql'): + version, dbms, operation = filename.split('.', 3)[0:3] + origfile = '%s/%s' % (origdir, filename) + # For instance: 2.postgres.upgrade.sql -> + # 002_postgres_upgrade.sql + tgtfile = '%s/%03d_%s_%s.sql' % ( + versions, int(version), dbms, operation) + move_file(origfile, tgtfile) + + # Move Python upgrade script. + pyfile = '%s.py' % dirname + pyfilepath = '%s/%s' % (origdir, pyfile) + if os.path.exists(pyfilepath): + tgtfile = '%s/%03d.py' % (versions, int(dirname)) + move_file(pyfilepath, tgtfile) + + # Try to remove directory. Will fail if it's not empty. + delete_directory(origdir) + + +def main(): + """Main function to be called when using this script.""" + if len(sys.argv) != 2: + usage() + migrate_repository(sys.argv[1]) + + +if __name__ == '__main__': + main() diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/pathed.py b/rhodecode/lib/dbmigrate/migrate/versioning/pathed.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/versioning/pathed.py @@ -0,0 +1,75 @@ +""" + A path/directory class. +""" + +import os +import shutil +import logging + +from rhodecode.lib.dbmigrate.migrate import exceptions +from rhodecode.lib.dbmigrate.migrate.versioning.config import * +from rhodecode.lib.dbmigrate.migrate.versioning.util import KeyedInstance + + +log = logging.getLogger(__name__) + +class Pathed(KeyedInstance): + """ + A class associated with a path/directory tree. + + Only one instance of this class may exist for a particular file; + __new__ will return an existing instance if possible + """ + parent = None + + @classmethod + def _key(cls, path): + return str(path) + + def __init__(self, path): + self.path = path + if self.__class__.parent is not None: + self._init_parent(path) + + def _init_parent(self, path): + """Try to initialize this object's parent, if it has one""" + parent_path = self.__class__._parent_path(path) + self.parent = self.__class__.parent(parent_path) + log.debug("Getting parent %r:%r" % (self.__class__.parent, parent_path)) + self.parent._init_child(path, self) + + def _init_child(self, child, path): + """Run when a child of this object is initialized. + + Parameters: the child object; the path to this object (its + parent) + """ + + @classmethod + def _parent_path(cls, path): + """ + Fetch the path of this object's parent from this object's path. + """ + # os.path.dirname(), but strip directories like files (like + # unix basename) + # + # Treat directories like files... + if path[-1] == '/': + path = path[:-1] + ret = os.path.dirname(path) + return ret + + @classmethod + def require_notfound(cls, path): + """Ensures a given path does not already exist""" + if os.path.exists(path): + raise exceptions.PathFoundError(path) + + @classmethod + def require_found(cls, path): + """Ensures a given path already exists""" + if not os.path.exists(path): + raise exceptions.PathNotFoundError(path) + + def __str__(self): + return self.path diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/repository.py b/rhodecode/lib/dbmigrate/migrate/versioning/repository.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/versioning/repository.py @@ -0,0 +1,231 @@ +""" + SQLAlchemy migrate repository management. +""" +import os +import shutil +import string +import logging + +from pkg_resources import resource_filename +from tempita import Template as TempitaTemplate + +from rhodecode.lib.dbmigrate.migrate import exceptions +from rhodecode.lib.dbmigrate.migrate.versioning import version, pathed, cfgparse +from rhodecode.lib.dbmigrate.migrate.versioning.template import Template +from rhodecode.lib.dbmigrate.migrate.versioning.config import * + + +log = logging.getLogger(__name__) + +class Changeset(dict): + """A collection of changes to be applied to a database. + + Changesets are bound to a repository and manage a set of + scripts from that repository. + + Behaves like a dict, for the most part. Keys are ordered based on step value. + """ + + def __init__(self, start, *changes, **k): + """ + Give a start version; step must be explicitly stated. + """ + self.step = k.pop('step', 1) + self.start = version.VerNum(start) + self.end = self.start + for change in changes: + self.add(change) + + def __iter__(self): + return iter(self.items()) + + def keys(self): + """ + In a series of upgrades x -> y, keys are version x. Sorted. + """ + ret = super(Changeset, self).keys() + # Reverse order if downgrading + ret.sort(reverse=(self.step < 1)) + return ret + + def values(self): + return [self[k] for k in self.keys()] + + def items(self): + return zip(self.keys(), self.values()) + + def add(self, change): + """Add new change to changeset""" + key = self.end + self.end += self.step + self[key] = change + + def run(self, *p, **k): + """Run the changeset scripts""" + for version, script in self: + script.run(*p, **k) + + +class Repository(pathed.Pathed): + """A project's change script repository""" + + _config = 'migrate.cfg' + _versions = 'versions' + + def __init__(self, path): + log.debug('Loading repository %s...' % path) + self.verify(path) + super(Repository, self).__init__(path) + self.config = cfgparse.Config(os.path.join(self.path, self._config)) + self.versions = version.Collection(os.path.join(self.path, + self._versions)) + log.debug('Repository %s loaded successfully' % path) + log.debug('Config: %r' % self.config.to_dict()) + + @classmethod + def verify(cls, path): + """ + Ensure the target path is a valid repository. + + :raises: :exc:`InvalidRepositoryError ` + """ + # Ensure the existence of required files + try: + cls.require_found(path) + cls.require_found(os.path.join(path, cls._config)) + cls.require_found(os.path.join(path, cls._versions)) + except exceptions.PathNotFoundError, e: + raise exceptions.InvalidRepositoryError(path) + + @classmethod + def prepare_config(cls, tmpl_dir, name, options=None): + """ + Prepare a project configuration file for a new project. + + :param tmpl_dir: Path to Repository template + :param config_file: Name of the config file in Repository template + :param name: Repository name + :type tmpl_dir: string + :type config_file: string + :type name: string + :returns: Populated config file + """ + if options is None: + options = {} + options.setdefault('version_table', 'migrate_version') + options.setdefault('repository_id', name) + options.setdefault('required_dbs', []) + + tmpl = open(os.path.join(tmpl_dir, cls._config)).read() + ret = TempitaTemplate(tmpl).substitute(options) + + # cleanup + del options['__template_name__'] + + return ret + + @classmethod + def create(cls, path, name, **opts): + """Create a repository at a specified path""" + cls.require_notfound(path) + theme = opts.pop('templates_theme', None) + t_path = opts.pop('templates_path', None) + + # Create repository + tmpl_dir = Template(t_path).get_repository(theme=theme) + shutil.copytree(tmpl_dir, path) + + # Edit config defaults + config_text = cls.prepare_config(tmpl_dir, name, options=opts) + fd = open(os.path.join(path, cls._config), 'w') + fd.write(config_text) + fd.close() + + opts['repository_name'] = name + + # Create a management script + manager = os.path.join(path, 'manage.py') + Repository.create_manage_file(manager, templates_theme=theme, + templates_path=t_path, **opts) + + return cls(path) + + def create_script(self, description, **k): + """API to :meth:`migrate.versioning.version.Collection.create_new_python_version`""" + self.versions.create_new_python_version(description, **k) + + def create_script_sql(self, database, **k): + """API to :meth:`migrate.versioning.version.Collection.create_new_sql_version`""" + self.versions.create_new_sql_version(database, **k) + + @property + def latest(self): + """API to :attr:`migrate.versioning.version.Collection.latest`""" + return self.versions.latest + + @property + def version_table(self): + """Returns version_table name specified in config""" + return self.config.get('db_settings', 'version_table') + + @property + def id(self): + """Returns repository id specified in config""" + return self.config.get('db_settings', 'repository_id') + + def version(self, *p, **k): + """API to :attr:`migrate.versioning.version.Collection.version`""" + return self.versions.version(*p, **k) + + @classmethod + def clear(cls): + # TODO: deletes repo + super(Repository, cls).clear() + version.Collection.clear() + + def changeset(self, database, start, end=None): + """Create a changeset to migrate this database from ver. start to end/latest. + + :param database: name of database to generate changeset + :param start: version to start at + :param end: version to end at (latest if None given) + :type database: string + :type start: int + :type end: int + :returns: :class:`Changeset instance ` + """ + start = version.VerNum(start) + + if end is None: + end = self.latest + else: + end = version.VerNum(end) + + if start <= end: + step = 1 + range_mod = 1 + op = 'upgrade' + else: + step = -1 + range_mod = 0 + op = 'downgrade' + + versions = range(start + range_mod, end + range_mod, step) + changes = [self.version(v).script(database, op) for v in versions] + ret = Changeset(start, step=step, *changes) + return ret + + @classmethod + def create_manage_file(cls, file_, **opts): + """Create a project management script (manage.py) + + :param file_: Destination file to be written + :param opts: Options that are passed to :func:`migrate.versioning.shell.main` + """ + mng_file = Template(opts.pop('templates_path', None))\ + .get_manage(theme=opts.pop('templates_theme', None)) + + tmpl = open(mng_file).read() + fd = open(file_, 'w') + fd.write(TempitaTemplate(tmpl).substitute(opts)) + fd.close() diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/schema.py b/rhodecode/lib/dbmigrate/migrate/versioning/schema.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/versioning/schema.py @@ -0,0 +1,213 @@ +""" + Database schema version management. +""" +import sys +import logging + +from sqlalchemy import (Table, Column, MetaData, String, Text, Integer, + create_engine) +from sqlalchemy.sql import and_ +from sqlalchemy import exceptions as sa_exceptions +from sqlalchemy.sql import bindparam + +from rhodecode.lib.dbmigrate.migrate import exceptions +from rhodecode.lib.dbmigrate.migrate.versioning import genmodel, schemadiff +from rhodecode.lib.dbmigrate.migrate.versioning.repository import Repository +from rhodecode.lib.dbmigrate.migrate.versioning.util import load_model +from rhodecode.lib.dbmigrate.migrate.versioning.version import VerNum + + +log = logging.getLogger(__name__) + +class ControlledSchema(object): + """A database under version control""" + + def __init__(self, engine, repository): + if isinstance(repository, basestring): + repository = Repository(repository) + self.engine = engine + self.repository = repository + self.meta = MetaData(engine) + self.load() + + def __eq__(self, other): + """Compare two schemas by repositories and versions""" + return (self.repository is other.repository \ + and self.version == other.version) + + def load(self): + """Load controlled schema version info from DB""" + tname = self.repository.version_table + try: + if not hasattr(self, 'table') or self.table is None: + self.table = Table(tname, self.meta, autoload=True) + + result = self.engine.execute(self.table.select( + self.table.c.repository_id == str(self.repository.id))) + + data = list(result)[0] + except: + cls, exc, tb = sys.exc_info() + raise exceptions.DatabaseNotControlledError, exc.__str__(), tb + + self.version = data['version'] + return data + + def drop(self): + """ + Remove version control from a database. + """ + try: + self.table.drop() + except (sa_exceptions.SQLError): + raise exceptions.DatabaseNotControlledError(str(self.table)) + + def changeset(self, version=None): + """API to Changeset creation. + + Uses self.version for start version and engine.name + to get database name. + """ + database = self.engine.name + start_ver = self.version + changeset = self.repository.changeset(database, start_ver, version) + return changeset + + def runchange(self, ver, change, step): + startver = ver + endver = ver + step + # Current database version must be correct! Don't run if corrupt! + if self.version != startver: + raise exceptions.InvalidVersionError("%s is not %s" % \ + (self.version, startver)) + # Run the change + change.run(self.engine, step) + + # Update/refresh database version + self.update_repository_table(startver, endver) + self.load() + + def update_repository_table(self, startver, endver): + """Update version_table with new information""" + update = self.table.update(and_(self.table.c.version == int(startver), + self.table.c.repository_id == str(self.repository.id))) + self.engine.execute(update, version=int(endver)) + + def upgrade(self, version=None): + """ + Upgrade (or downgrade) to a specified version, or latest version. + """ + changeset = self.changeset(version) + for ver, change in changeset: + self.runchange(ver, change, changeset.step) + + def update_db_from_model(self, model): + """ + Modify the database to match the structure of the current Python model. + """ + model = load_model(model) + + diff = schemadiff.getDiffOfModelAgainstDatabase( + model, self.engine, excludeTables=[self.repository.version_table] + ) + genmodel.ModelGenerator(diff,self.engine).applyModel() + + self.update_repository_table(self.version, int(self.repository.latest)) + + self.load() + + @classmethod + def create(cls, engine, repository, version=None): + """ + Declare a database to be under a repository's version control. + + :raises: :exc:`DatabaseAlreadyControlledError` + :returns: :class:`ControlledSchema` + """ + # Confirm that the version # is valid: positive, integer, + # exists in repos + if isinstance(repository, basestring): + repository = Repository(repository) + version = cls._validate_version(repository, version) + table = cls._create_table_version(engine, repository, version) + # TODO: history table + # Load repository information and return + return cls(engine, repository) + + @classmethod + def _validate_version(cls, repository, version): + """ + Ensures this is a valid version number for this repository. + + :raises: :exc:`InvalidVersionError` if invalid + :return: valid version number + """ + if version is None: + version = 0 + try: + version = VerNum(version) # raises valueerror + if version < 0 or version > repository.latest: + raise ValueError() + except ValueError: + raise exceptions.InvalidVersionError(version) + return version + + @classmethod + def _create_table_version(cls, engine, repository, version): + """ + Creates the versioning table in a database. + + :raises: :exc:`DatabaseAlreadyControlledError` + """ + # Create tables + tname = repository.version_table + meta = MetaData(engine) + + table = Table( + tname, meta, + Column('repository_id', String(250), primary_key=True), + Column('repository_path', Text), + Column('version', Integer), ) + + # there can be multiple repositories/schemas in the same db + if not table.exists(): + table.create() + + # test for existing repository_id + s = table.select(table.c.repository_id == bindparam("repository_id")) + result = engine.execute(s, repository_id=repository.id) + if result.fetchone(): + raise exceptions.DatabaseAlreadyControlledError + + # Insert data + engine.execute(table.insert().values( + repository_id=repository.id, + repository_path=repository.path, + version=int(version))) + return table + + @classmethod + def compare_model_to_db(cls, engine, model, repository): + """ + Compare the current model against the current database. + """ + if isinstance(repository, basestring): + repository = Repository(repository) + model = load_model(model) + + diff = schemadiff.getDiffOfModelAgainstDatabase( + model, engine, excludeTables=[repository.version_table]) + return diff + + @classmethod + def create_model(cls, engine, repository, declarative=False): + """ + Dump the current database as a Python model. + """ + if isinstance(repository, basestring): + repository = Repository(repository) + + diff = schemadiff.getDiffOfModelAgainstDatabase( + MetaData(), engine, excludeTables=[repository.version_table] + ) + return genmodel.ModelGenerator(diff, engine, declarative).toPython() diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/schemadiff.py b/rhodecode/lib/dbmigrate/migrate/versioning/schemadiff.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/versioning/schemadiff.py @@ -0,0 +1,285 @@ +""" + Schema differencing support. +""" + +import logging +import sqlalchemy + +from rhodecode.lib.dbmigrate.migrate.changeset import SQLA_06 +from sqlalchemy.types import Float + +log = logging.getLogger(__name__) + +def getDiffOfModelAgainstDatabase(metadata, engine, excludeTables=None): + """ + Return differences of model against database. + + :return: object which will evaluate to :keyword:`True` if there \ + are differences else :keyword:`False`. + """ + return SchemaDiff(metadata, + sqlalchemy.MetaData(engine, reflect=True), + labelA='model', + labelB='database', + excludeTables=excludeTables) + + +def getDiffOfModelAgainstModel(metadataA, metadataB, excludeTables=None): + """ + Return differences of model against another model. + + :return: object which will evaluate to :keyword:`True` if there \ + are differences else :keyword:`False`. + """ + return SchemaDiff(metadataA, metadataB, excludeTables) + + +class ColDiff(object): + """ + Container for differences in one :class:`~sqlalchemy.schema.Column` + between two :class:`~sqlalchemy.schema.Table` instances, ``A`` + and ``B``. + + .. attribute:: col_A + + The :class:`~sqlalchemy.schema.Column` object for A. + + .. attribute:: col_B + + The :class:`~sqlalchemy.schema.Column` object for B. + + .. attribute:: type_A + + The most generic type of the :class:`~sqlalchemy.schema.Column` + object in A. + + .. attribute:: type_B + + The most generic type of the :class:`~sqlalchemy.schema.Column` + object in A. + + """ + + diff = False + + def __init__(self,col_A,col_B): + self.col_A = col_A + self.col_B = col_B + + self.type_A = col_A.type + self.type_B = col_B.type + + self.affinity_A = self.type_A._type_affinity + self.affinity_B = self.type_B._type_affinity + + if self.affinity_A is not self.affinity_B: + self.diff = True + return + + if isinstance(self.type_A,Float) or isinstance(self.type_B,Float): + if not (isinstance(self.type_A,Float) and isinstance(self.type_B,Float)): + self.diff=True + return + + for attr in ('precision','scale','length'): + A = getattr(self.type_A,attr,None) + B = getattr(self.type_B,attr,None) + if not (A is None or B is None) and A!=B: + self.diff=True + return + + def __nonzero__(self): + return self.diff + +class TableDiff(object): + """ + Container for differences in one :class:`~sqlalchemy.schema.Table` + between two :class:`~sqlalchemy.schema.MetaData` instances, ``A`` + and ``B``. + + .. attribute:: columns_missing_from_A + + A sequence of column names that were found in B but weren't in + A. + + .. attribute:: columns_missing_from_B + + A sequence of column names that were found in A but weren't in + B. + + .. attribute:: columns_different + + A dictionary containing information about columns that were + found to be different. + It maps column names to a :class:`ColDiff` objects describing the + differences found. + """ + __slots__ = ( + 'columns_missing_from_A', + 'columns_missing_from_B', + 'columns_different', + ) + + def __nonzero__(self): + return bool( + self.columns_missing_from_A or + self.columns_missing_from_B or + self.columns_different + ) + +class SchemaDiff(object): + """ + Compute the difference between two :class:`~sqlalchemy.schema.MetaData` + objects. + + The string representation of a :class:`SchemaDiff` will summarise + the changes found between the two + :class:`~sqlalchemy.schema.MetaData` objects. + + The length of a :class:`SchemaDiff` will give the number of + changes found, enabling it to be used much like a boolean in + expressions. + + :param metadataA: + First :class:`~sqlalchemy.schema.MetaData` to compare. + + :param metadataB: + Second :class:`~sqlalchemy.schema.MetaData` to compare. + + :param labelA: + The label to use in messages about the first + :class:`~sqlalchemy.schema.MetaData`. + + :param labelB: + The label to use in messages about the second + :class:`~sqlalchemy.schema.MetaData`. + + :param excludeTables: + A sequence of table names to exclude. + + .. attribute:: tables_missing_from_A + + A sequence of table names that were found in B but weren't in + A. + + .. attribute:: tables_missing_from_B + + A sequence of table names that were found in A but weren't in + B. + + .. attribute:: tables_different + + A dictionary containing information about tables that were found + to be different. + It maps table names to a :class:`TableDiff` objects describing the + differences found. + """ + + def __init__(self, + metadataA, metadataB, + labelA='metadataA', + labelB='metadataB', + excludeTables=None): + + self.metadataA, self.metadataB = metadataA, metadataB + self.labelA, self.labelB = labelA, labelB + self.label_width = max(len(labelA),len(labelB)) + excludeTables = set(excludeTables or []) + + A_table_names = set(metadataA.tables.keys()) + B_table_names = set(metadataB.tables.keys()) + + self.tables_missing_from_A = sorted( + B_table_names - A_table_names - excludeTables + ) + self.tables_missing_from_B = sorted( + A_table_names - B_table_names - excludeTables + ) + + self.tables_different = {} + for table_name in A_table_names.intersection(B_table_names): + + td = TableDiff() + + A_table = metadataA.tables[table_name] + B_table = metadataB.tables[table_name] + + A_column_names = set(A_table.columns.keys()) + B_column_names = set(B_table.columns.keys()) + + td.columns_missing_from_A = sorted( + B_column_names - A_column_names + ) + + td.columns_missing_from_B = sorted( + A_column_names - B_column_names + ) + + td.columns_different = {} + + for col_name in A_column_names.intersection(B_column_names): + + cd = ColDiff( + A_table.columns.get(col_name), + B_table.columns.get(col_name) + ) + + if cd: + td.columns_different[col_name]=cd + + # XXX - index and constraint differences should + # be checked for here + + if td: + self.tables_different[table_name]=td + + def __str__(self): + ''' Summarize differences. ''' + out = [] + column_template =' %%%is: %%r' % self.label_width + + for names,label in ( + (self.tables_missing_from_A,self.labelA), + (self.tables_missing_from_B,self.labelB), + ): + if names: + out.append( + ' tables missing from %s: %s' % ( + label,', '.join(sorted(names)) + ) + ) + + for name,td in sorted(self.tables_different.items()): + out.append( + ' table with differences: %s' % name + ) + for names,label in ( + (td.columns_missing_from_A,self.labelA), + (td.columns_missing_from_B,self.labelB), + ): + if names: + out.append( + ' %s missing these columns: %s' % ( + label,', '.join(sorted(names)) + ) + ) + for name,cd in td.columns_different.items(): + out.append(' column with differences: %s' % name) + out.append(column_template % (self.labelA,cd.col_A)) + out.append(column_template % (self.labelB,cd.col_B)) + + if out: + out.insert(0, 'Schema diffs:') + return '\n'.join(out) + else: + return 'No schema diffs' + + def __len__(self): + """ + Used in bool evaluation, return of 0 means no diffs. + """ + return ( + len(self.tables_missing_from_A) + + len(self.tables_missing_from_B) + + len(self.tables_different) + ) diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/script/__init__.py b/rhodecode/lib/dbmigrate/migrate/versioning/script/__init__.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/versioning/script/__init__.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from rhodecode.lib.dbmigrate.migrate.versioning.script.base import BaseScript +from rhodecode.lib.dbmigrate.migrate.versioning.script.py import PythonScript +from rhodecode.lib.dbmigrate.migrate.versioning.script.sql import SqlScript diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/script/base.py b/rhodecode/lib/dbmigrate/migrate/versioning/script/base.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/versioning/script/base.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import logging + +from rhodecode.lib.dbmigrate.migrate import exceptions +from rhodecode.lib.dbmigrate.migrate.versioning.config import operations +from rhodecode.lib.dbmigrate.migrate.versioning import pathed + + +log = logging.getLogger(__name__) + +class BaseScript(pathed.Pathed): + """Base class for other types of scripts. + All scripts have the following properties: + + source (script.source()) + The source code of the script + version (script.version()) + The version number of the script + operations (script.operations()) + The operations defined by the script: upgrade(), downgrade() or both. + Returns a tuple of operations. + Can also check for an operation with ex. script.operation(Script.ops.up) + """ # TODO: sphinxfy this and implement it correctly + + def __init__(self, path): + log.debug('Loading script %s...' % path) + self.verify(path) + super(BaseScript, self).__init__(path) + log.debug('Script %s loaded successfully' % path) + + @classmethod + def verify(cls, path): + """Ensure this is a valid script + This version simply ensures the script file's existence + + :raises: :exc:`InvalidScriptError ` + """ + try: + cls.require_found(path) + except: + raise exceptions.InvalidScriptError(path) + + def source(self): + """:returns: source code of the script. + :rtype: string + """ + fd = open(self.path) + ret = fd.read() + fd.close() + return ret + + def run(self, engine): + """Core of each BaseScript subclass. + This method executes the script. + """ + raise NotImplementedError() diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/script/py.py b/rhodecode/lib/dbmigrate/migrate/versioning/script/py.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/versioning/script/py.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import shutil +import warnings +import logging +from StringIO import StringIO + +from rhodecode.lib.dbmigrate import migrate +from rhodecode.lib.dbmigrate.migrate.versioning import genmodel, schemadiff +from rhodecode.lib.dbmigrate.migrate.versioning.config import operations +from rhodecode.lib.dbmigrate.migrate.versioning.template import Template +from rhodecode.lib.dbmigrate.migrate.versioning.script import base +from rhodecode.lib.dbmigrate.migrate.versioning.util import import_path, load_model, with_engine +from rhodecode.lib.dbmigrate.migrate.exceptions import MigrateDeprecationWarning, InvalidScriptError, ScriptError + +log = logging.getLogger(__name__) +__all__ = ['PythonScript'] + + +class PythonScript(base.BaseScript): + """Base for Python scripts""" + + @classmethod + def create(cls, path, **opts): + """Create an empty migration script at specified path + + :returns: :class:`PythonScript instance `""" + cls.require_notfound(path) + + src = Template(opts.pop('templates_path', None)).get_script(theme=opts.pop('templates_theme', None)) + shutil.copy(src, path) + + return cls(path) + + @classmethod + def make_update_script_for_model(cls, engine, oldmodel, + model, repository, **opts): + """Create a migration script based on difference between two SA models. + + :param repository: path to migrate repository + :param oldmodel: dotted.module.name:SAClass or SAClass object + :param model: dotted.module.name:SAClass or SAClass object + :param engine: SQLAlchemy engine + :type repository: string or :class:`Repository instance ` + :type oldmodel: string or Class + :type model: string or Class + :type engine: Engine instance + :returns: Upgrade / Downgrade script + :rtype: string + """ + + if isinstance(repository, basestring): + # oh dear, an import cycle! + from rhodecode.lib.dbmigrate.migrate.versioning.repository import Repository + repository = Repository(repository) + + oldmodel = load_model(oldmodel) + model = load_model(model) + + # Compute differences. + diff = schemadiff.getDiffOfModelAgainstModel( + oldmodel, + model, + excludeTables=[repository.version_table]) + # TODO: diff can be False (there is no difference?) + decls, upgradeCommands, downgradeCommands = \ + genmodel.ModelGenerator(diff, engine).toUpgradeDowngradePython() + + # Store differences into file. + src = Template(opts.pop('templates_path', None)).get_script(opts.pop('templates_theme', None)) + f = open(src) + contents = f.read() + f.close() + + # generate source + search = 'def upgrade(migrate_engine):' + contents = contents.replace(search, '\n\n'.join((decls, search)), 1) + if upgradeCommands: + contents = contents.replace(' pass', upgradeCommands, 1) + if downgradeCommands: + contents = contents.replace(' pass', downgradeCommands, 1) + return contents + + @classmethod + def verify_module(cls, path): + """Ensure path is a valid script + + :param path: Script location + :type path: string + :raises: :exc:`InvalidScriptError ` + :returns: Python module + """ + # Try to import and get the upgrade() func + module = import_path(path) + try: + assert callable(module.upgrade) + except Exception, e: + raise InvalidScriptError(path + ': %s' % str(e)) + return module + + def preview_sql(self, url, step, **args): + """Mocks SQLAlchemy Engine to store all executed calls in a string + and runs :meth:`PythonScript.run ` + + :returns: SQL file + """ + buf = StringIO() + args['engine_arg_strategy'] = 'mock' + args['engine_arg_executor'] = lambda s, p = '': buf.write(str(s) + p) + + @with_engine + def go(url, step, **kw): + engine = kw.pop('engine') + self.run(engine, step) + return buf.getvalue() + + return go(url, step, **args) + + def run(self, engine, step): + """Core method of Script file. + Exectues :func:`update` or :func:`downgrade` functions + + :param engine: SQLAlchemy Engine + :param step: Operation to run + :type engine: string + :type step: int + """ + if step > 0: + op = 'upgrade' + elif step < 0: + op = 'downgrade' + else: + raise ScriptError("%d is not a valid step" % step) + + funcname = base.operations[op] + script_func = self._func(funcname) + + try: + script_func(engine) + except TypeError: + warnings.warn("upgrade/downgrade functions must accept engine" + " parameter (since version > 0.5.4)", MigrateDeprecationWarning) + raise + + @property + def module(self): + """Calls :meth:`migrate.versioning.script.py.verify_module` + and returns it. + """ + if not hasattr(self, '_module'): + self._module = self.verify_module(self.path) + return self._module + + def _func(self, funcname): + if not hasattr(self.module, funcname): + msg = "Function '%s' is not defined in this script" + raise ScriptError(msg % funcname) + return getattr(self.module, funcname) diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/script/sql.py b/rhodecode/lib/dbmigrate/migrate/versioning/script/sql.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/versioning/script/sql.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import logging +import shutil + +from rhodecode.lib.dbmigrate.migrate.versioning.script import base +from rhodecode.lib.dbmigrate.migrate.versioning.template import Template + + +log = logging.getLogger(__name__) + +class SqlScript(base.BaseScript): + """A file containing plain SQL statements.""" + + @classmethod + def create(cls, path, **opts): + """Create an empty migration script at specified path + + :returns: :class:`SqlScript instance `""" + cls.require_notfound(path) + src = Template(opts.pop('templates_path', None)).get_sql_script(theme=opts.pop('templates_theme', None)) + shutil.copy(src, path) + return cls(path) + + # TODO: why is step parameter even here? + def run(self, engine, step=None, executemany=True): + """Runs SQL script through raw dbapi execute call""" + text = self.source() + # Don't rely on SA's autocommit here + # (SA uses .startswith to check if a commit is needed. What if script + # starts with a comment?) + conn = engine.connect() + try: + trans = conn.begin() + try: + # HACK: SQLite doesn't allow multiple statements through + # its execute() method, but it provides executescript() instead + dbapi = conn.engine.raw_connection() + if executemany and getattr(dbapi, 'executescript', None): + dbapi.executescript(text) + else: + conn.execute(text) + trans.commit() + except: + trans.rollback() + raise + finally: + conn.close() diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/shell.py b/rhodecode/lib/dbmigrate/migrate/versioning/shell.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/versioning/shell.py @@ -0,0 +1,215 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""The migrate command-line tool.""" + +import sys +import inspect +import logging +from optparse import OptionParser, BadOptionError + +from rhodecode.lib.dbmigrate.migrate import exceptions +from rhodecode.lib.dbmigrate.migrate.versioning import api +from rhodecode.lib.dbmigrate.migrate.versioning.config import * +from rhodecode.lib.dbmigrate.migrate.versioning.util import asbool + + +alias = dict( + s=api.script, + vc=api.version_control, + dbv=api.db_version, + v=api.version, +) + +def alias_setup(): + global alias + for key, val in alias.iteritems(): + setattr(api, key, val) +alias_setup() + + +class PassiveOptionParser(OptionParser): + + def _process_args(self, largs, rargs, values): + """little hack to support all --some_option=value parameters""" + + while rargs: + arg = rargs[0] + if arg == "--": + del rargs[0] + return + elif arg[0:2] == "--": + # if parser does not know about the option + # pass it along (make it anonymous) + try: + opt = arg.split('=', 1)[0] + self._match_long_opt(opt) + except BadOptionError: + largs.append(arg) + del rargs[0] + else: + self._process_long_opt(rargs, values) + elif arg[:1] == "-" and len(arg) > 1: + self._process_short_opts(rargs, values) + elif self.allow_interspersed_args: + largs.append(arg) + del rargs[0] + +def main(argv=None, **kwargs): + """Shell interface to :mod:`migrate.versioning.api`. + + kwargs are default options that can be overriden with passing + --some_option as command line option + + :param disable_logging: Let migrate configure logging + :type disable_logging: bool + """ + if argv is not None: + argv = argv + else: + argv = list(sys.argv[1:]) + commands = list(api.__all__) + commands.sort() + + usage = """%%prog COMMAND ... + + Available commands: + %s + + Enter "%%prog help COMMAND" for information on a particular command. + """ % '\n\t'.join(["%s - %s" % (command.ljust(28), + api.command_desc.get(command)) for command in commands]) + + parser = PassiveOptionParser(usage=usage) + parser.add_option("-d", "--debug", + action="store_true", + dest="debug", + default=False, + help="Shortcut to turn on DEBUG mode for logging") + parser.add_option("-q", "--disable_logging", + action="store_true", + dest="disable_logging", + default=False, + help="Use this option to disable logging configuration") + help_commands = ['help', '-h', '--help'] + HELP = False + + try: + command = argv.pop(0) + if command in help_commands: + HELP = True + command = argv.pop(0) + except IndexError: + parser.print_help() + return + + command_func = getattr(api, command, None) + if command_func is None or command.startswith('_'): + parser.error("Invalid command %s" % command) + + parser.set_usage(inspect.getdoc(command_func)) + f_args, f_varargs, f_kwargs, f_defaults = inspect.getargspec(command_func) + for arg in f_args: + parser.add_option( + "--%s" % arg, + dest=arg, + action='store', + type="string") + + # display help of the current command + if HELP: + parser.print_help() + return + + options, args = parser.parse_args(argv) + + # override kwargs with anonymous parameters + override_kwargs = dict() + for arg in list(args): + if arg.startswith('--'): + args.remove(arg) + if '=' in arg: + opt, value = arg[2:].split('=', 1) + else: + opt = arg[2:] + value = True + override_kwargs[opt] = value + + # override kwargs with options if user is overwriting + for key, value in options.__dict__.iteritems(): + if value is not None: + override_kwargs[key] = value + + # arguments that function accepts without passed kwargs + f_required = list(f_args) + candidates = dict(kwargs) + candidates.update(override_kwargs) + for key, value in candidates.iteritems(): + if key in f_args: + f_required.remove(key) + + # map function arguments to parsed arguments + for arg in args: + try: + kw = f_required.pop(0) + except IndexError: + parser.error("Too many arguments for command %s: %s" % (command, + arg)) + kwargs[kw] = arg + + # apply overrides + kwargs.update(override_kwargs) + + # configure options + for key, value in options.__dict__.iteritems(): + kwargs.setdefault(key, value) + + # configure logging + if not asbool(kwargs.pop('disable_logging', False)): + # filter to log =< INFO into stdout and rest to stderr + class SingleLevelFilter(logging.Filter): + def __init__(self, min=None, max=None): + self.min = min or 0 + self.max = max or 100 + + def filter(self, record): + return self.min <= record.levelno <= self.max + + logger = logging.getLogger() + h1 = logging.StreamHandler(sys.stdout) + f1 = SingleLevelFilter(max=logging.INFO) + h1.addFilter(f1) + h2 = logging.StreamHandler(sys.stderr) + f2 = SingleLevelFilter(min=logging.WARN) + h2.addFilter(f2) + logger.addHandler(h1) + logger.addHandler(h2) + + if options.debug: + logger.setLevel(logging.DEBUG) + else: + logger.setLevel(logging.INFO) + + log = logging.getLogger(__name__) + + # check if all args are given + try: + num_defaults = len(f_defaults) + except TypeError: + num_defaults = 0 + f_args_default = f_args[len(f_args) - num_defaults:] + required = list(set(f_required) - set(f_args_default)) + if required: + parser.error("Not enough arguments for command %s: %s not specified" \ + % (command, ', '.join(required))) + + # handle command + try: + ret = command_func(**kwargs) + if ret is not None: + log.info(ret) + except (exceptions.UsageError, exceptions.KnownError), e: + parser.error(e.args[0]) + +if __name__ == "__main__": + main() diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/template.py b/rhodecode/lib/dbmigrate/migrate/versioning/template.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/versioning/template.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import shutil +import sys + +from pkg_resources import resource_filename + +from rhodecode.lib.dbmigrate.migrate.versioning.config import * +from rhodecode.lib.dbmigrate.migrate.versioning import pathed + + +class Collection(pathed.Pathed): + """A collection of templates of a specific type""" + _mask = None + + def get_path(self, file): + return os.path.join(self.path, str(file)) + + +class RepositoryCollection(Collection): + _mask = '%s' + +class ScriptCollection(Collection): + _mask = '%s.py_tmpl' + +class ManageCollection(Collection): + _mask = '%s.py_tmpl' + +class SQLScriptCollection(Collection): + _mask = '%s.py_tmpl' + +class Template(pathed.Pathed): + """Finds the paths/packages of various Migrate templates. + + :param path: Templates are loaded from rhodecode.lib.dbmigrate.migrate package + if `path` is not provided. + """ + pkg = 'rhodecode.lib.dbmigrate.migrate.versioning.templates' + _manage = 'manage.py_tmpl' + + def __new__(cls, path=None): + if path is None: + path = cls._find_path(cls.pkg) + return super(Template, cls).__new__(cls, path) + + def __init__(self, path=None): + if path is None: + path = Template._find_path(self.pkg) + super(Template, self).__init__(path) + self.repository = RepositoryCollection(os.path.join(path, 'repository')) + self.script = ScriptCollection(os.path.join(path, 'script')) + self.manage = ManageCollection(os.path.join(path, 'manage')) + self.sql_script = SQLScriptCollection(os.path.join(path, 'sql_script')) + + @classmethod + def _find_path(cls, pkg): + """Returns absolute path to dotted python package.""" + tmp_pkg = pkg.rsplit('.', 1) + + if len(tmp_pkg) != 1: + return resource_filename(tmp_pkg[0], tmp_pkg[1]) + else: + return resource_filename(tmp_pkg[0], '') + + def _get_item(self, collection, theme=None): + """Locates and returns collection. + + :param collection: name of collection to locate + :param type_: type of subfolder in collection (defaults to "_default") + :returns: (package, source) + :rtype: str, str + """ + item = getattr(self, collection) + theme_mask = getattr(item, '_mask') + theme = theme_mask % (theme or 'default') + return item.get_path(theme) + + def get_repository(self, *a, **kw): + """Calls self._get_item('repository', *a, **kw)""" + return self._get_item('repository', *a, **kw) + + def get_script(self, *a, **kw): + """Calls self._get_item('script', *a, **kw)""" + return self._get_item('script', *a, **kw) + + def get_sql_script(self, *a, **kw): + """Calls self._get_item('sql_script', *a, **kw)""" + return self._get_item('sql_script', *a, **kw) + + def get_manage(self, *a, **kw): + """Calls self._get_item('manage', *a, **kw)""" + return self._get_item('manage', *a, **kw) diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/templates/__init__.py b/rhodecode/lib/dbmigrate/migrate/versioning/templates/__init__.py new file mode 100644 diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/templates/manage.py_tmpl b/rhodecode/lib/dbmigrate/migrate/versioning/templates/manage.py_tmpl new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/versioning/templates/manage.py_tmpl @@ -0,0 +1,5 @@ +#!/usr/bin/env python +from migrate.versioning.shell import main + +if __name__ == '__main__': + main(%(defaults)s) diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/templates/manage/default.py_tmpl b/rhodecode/lib/dbmigrate/migrate/versioning/templates/manage/default.py_tmpl new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/versioning/templates/manage/default.py_tmpl @@ -0,0 +1,10 @@ +#!/usr/bin/env python +from migrate.versioning.shell import main + +{{py: +_vars = locals().copy() +del _vars['__template_name__'] +_vars.pop('repository_name', None) +defaults = ", ".join(["%s='%s'" % var for var in _vars.iteritems()]) +}} +main({{ defaults }}) diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/templates/manage/pylons.py_tmpl b/rhodecode/lib/dbmigrate/migrate/versioning/templates/manage/pylons.py_tmpl new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/versioning/templates/manage/pylons.py_tmpl @@ -0,0 +1,29 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +import sys + +from sqlalchemy import engine_from_config +from paste.deploy.loadwsgi import ConfigLoader + +from migrate.versioning.shell import main +from {{ locals().pop('repository_name') }}.model import migrations + + +if '-c' in sys.argv: + pos = sys.argv.index('-c') + conf_path = sys.argv[pos + 1] + del sys.argv[pos:pos + 2] +else: + conf_path = 'development.ini' + +{{py: +_vars = locals().copy() +del _vars['__template_name__'] +defaults = ", ".join(["%s='%s'" % var for var in _vars.iteritems()]) +}} + +conf_dict = ConfigLoader(conf_path).parser._sections['app:main'] + +# migrate supports passing url as an existing Engine instance (since 0.6.0) +# usage: migrate -c path/to/config.ini COMMANDS +main(url=engine_from_config(conf_dict), repository=migrations.__path__[0],{{ defaults }}) diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/templates/repository/__init__.py b/rhodecode/lib/dbmigrate/migrate/versioning/templates/repository/__init__.py new file mode 100644 diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/templates/repository/default/README b/rhodecode/lib/dbmigrate/migrate/versioning/templates/repository/default/README new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/versioning/templates/repository/default/README @@ -0,0 +1,4 @@ +This is a database migration repository. + +More information at +http://code.google.com/p/sqlalchemy-migrate/ diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/templates/repository/default/__init__.py b/rhodecode/lib/dbmigrate/migrate/versioning/templates/repository/default/__init__.py new file mode 100644 diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/templates/repository/default/migrate.cfg b/rhodecode/lib/dbmigrate/migrate/versioning/templates/repository/default/migrate.cfg new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/versioning/templates/repository/default/migrate.cfg @@ -0,0 +1,20 @@ +[db_settings] +# Used to identify which repository this database is versioned under. +# You can use the name of your project. +repository_id={{ locals().pop('repository_id') }} + +# The name of the database table used to track the schema version. +# This name shouldn't already be used by your project. +# If this is changed once a database is under version control, you'll need to +# change the table name in each database too. +version_table={{ locals().pop('version_table') }} + +# When committing a change script, Migrate will attempt to generate the +# sql for all supported databases; normally, if one of them fails - probably +# because you don't have that database installed - it is ignored and the +# commit continues, perhaps ending successfully. +# Databases in this list MUST compile successfully during a commit, or the +# entire commit will fail. List the databases your application will actually +# be using to ensure your updates to that database work properly. +# This must be a list; example: ['postgres','sqlite'] +required_dbs={{ locals().pop('required_dbs') }} diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/templates/repository/default/versions/__init__.py b/rhodecode/lib/dbmigrate/migrate/versioning/templates/repository/default/versions/__init__.py new file mode 100644 diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/templates/repository/pylons/README b/rhodecode/lib/dbmigrate/migrate/versioning/templates/repository/pylons/README new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/versioning/templates/repository/pylons/README @@ -0,0 +1,4 @@ +This is a database migration repository. + +More information at +http://code.google.com/p/sqlalchemy-migrate/ diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/templates/repository/pylons/__init__.py b/rhodecode/lib/dbmigrate/migrate/versioning/templates/repository/pylons/__init__.py new file mode 100644 diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/templates/repository/pylons/migrate.cfg b/rhodecode/lib/dbmigrate/migrate/versioning/templates/repository/pylons/migrate.cfg new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/versioning/templates/repository/pylons/migrate.cfg @@ -0,0 +1,20 @@ +[db_settings] +# Used to identify which repository this database is versioned under. +# You can use the name of your project. +repository_id={{ locals().pop('repository_id') }} + +# The name of the database table used to track the schema version. +# This name shouldn't already be used by your project. +# If this is changed once a database is under version control, you'll need to +# change the table name in each database too. +version_table={{ locals().pop('version_table') }} + +# When committing a change script, Migrate will attempt to generate the +# sql for all supported databases; normally, if one of them fails - probably +# because you don't have that database installed - it is ignored and the +# commit continues, perhaps ending successfully. +# Databases in this list MUST compile successfully during a commit, or the +# entire commit will fail. List the databases your application will actually +# be using to ensure your updates to that database work properly. +# This must be a list; example: ['postgres','sqlite'] +required_dbs={{ locals().pop('required_dbs') }} diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/templates/repository/pylons/versions/__init__.py b/rhodecode/lib/dbmigrate/migrate/versioning/templates/repository/pylons/versions/__init__.py new file mode 100644 diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/templates/script/__init__.py b/rhodecode/lib/dbmigrate/migrate/versioning/templates/script/__init__.py new file mode 100644 diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/templates/script/default.py_tmpl b/rhodecode/lib/dbmigrate/migrate/versioning/templates/script/default.py_tmpl new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/versioning/templates/script/default.py_tmpl @@ -0,0 +1,11 @@ +from sqlalchemy import * +from migrate import * + +def upgrade(migrate_engine): + # Upgrade operations go here. Don't create your own engine; bind migrate_engine + # to your metadata + pass + +def downgrade(migrate_engine): + # Operations to reverse the above upgrade go here. + pass diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/templates/script/pylons.py_tmpl b/rhodecode/lib/dbmigrate/migrate/versioning/templates/script/pylons.py_tmpl new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/versioning/templates/script/pylons.py_tmpl @@ -0,0 +1,11 @@ +from sqlalchemy import * +from migrate import * + +def upgrade(migrate_engine): + # Upgrade operations go here. Don't create your own engine; bind migrate_engine + # to your metadata + pass + +def downgrade(migrate_engine): + # Operations to reverse the above upgrade go here. + pass diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/templates/sql_script/default.py_tmpl b/rhodecode/lib/dbmigrate/migrate/versioning/templates/sql_script/default.py_tmpl new file mode 100644 diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/templates/sql_script/pylons.py_tmpl b/rhodecode/lib/dbmigrate/migrate/versioning/templates/sql_script/pylons.py_tmpl new file mode 100644 diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/util/__init__.py b/rhodecode/lib/dbmigrate/migrate/versioning/util/__init__.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/versioning/util/__init__.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""".. currentmodule:: migrate.versioning.util""" + +import warnings +import logging +from decorator import decorator +from pkg_resources import EntryPoint + +from sqlalchemy import create_engine +from sqlalchemy.engine import Engine +from sqlalchemy.pool import StaticPool + +from rhodecode.lib.dbmigrate.migrate import exceptions +from rhodecode.lib.dbmigrate.migrate.versioning.util.keyedinstance import KeyedInstance +from rhodecode.lib.dbmigrate.migrate.versioning.util.importpath import import_path + + +log = logging.getLogger(__name__) + +def load_model(dotted_name): + """Import module and use module-level variable". + + :param dotted_name: path to model in form of string: ``some.python.module:Class`` + + .. versionchanged:: 0.5.4 + + """ + if isinstance(dotted_name, basestring): + if ':' not in dotted_name: + # backwards compatibility + warnings.warn('model should be in form of module.model:User ' + 'and not module.model.User', exceptions.MigrateDeprecationWarning) + dotted_name = ':'.join(dotted_name.rsplit('.', 1)) + return EntryPoint.parse('x=%s' % dotted_name).load(False) + else: + # Assume it's already loaded. + return dotted_name + +def asbool(obj): + """Do everything to use object as bool""" + if isinstance(obj, basestring): + obj = obj.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) + if obj in (True, False): + return bool(obj) + else: + raise ValueError("String is not true/false: %r" % obj) + +def guess_obj_type(obj): + """Do everything to guess object type from string + + Tries to convert to `int`, `bool` and finally returns if not succeded. + + .. versionadded: 0.5.4 + """ + + result = None + + try: + result = int(obj) + except: + pass + + if result is None: + try: + result = asbool(obj) + except: + pass + + if result is not None: + return result + else: + return obj + +@decorator +def catch_known_errors(f, *a, **kw): + """Decorator that catches known api errors + + .. versionadded: 0.5.4 + """ + + try: + return f(*a, **kw) + except exceptions.PathFoundError, e: + raise exceptions.KnownError("The path %s already exists" % e.args[0]) + +def construct_engine(engine, **opts): + """.. versionadded:: 0.5.4 + + Constructs and returns SQLAlchemy engine. + + Currently, there are 2 ways to pass create_engine options to :mod:`migrate.versioning.api` functions: + + :param engine: connection string or a existing engine + :param engine_dict: python dictionary of options to pass to `create_engine` + :param engine_arg_*: keyword parameters to pass to `create_engine` (evaluated with :func:`migrate.versioning.util.guess_obj_type`) + :type engine_dict: dict + :type engine: string or Engine instance + :type engine_arg_*: string + :returns: SQLAlchemy Engine + + .. note:: + + keyword parameters override ``engine_dict`` values. + + """ + if isinstance(engine, Engine): + return engine + elif not isinstance(engine, basestring): + raise ValueError("you need to pass either an existing engine or a database uri") + + # get options for create_engine + if opts.get('engine_dict') and isinstance(opts['engine_dict'], dict): + kwargs = opts['engine_dict'] + else: + kwargs = dict() + + # DEPRECATED: handle echo the old way + echo = asbool(opts.get('echo', False)) + if echo: + warnings.warn('echo=True parameter is deprecated, pass ' + 'engine_arg_echo=True or engine_dict={"echo": True}', + exceptions.MigrateDeprecationWarning) + kwargs['echo'] = echo + + # parse keyword arguments + for key, value in opts.iteritems(): + if key.startswith('engine_arg_'): + kwargs[key[11:]] = guess_obj_type(value) + + log.debug('Constructing engine') + # TODO: return create_engine(engine, poolclass=StaticPool, **kwargs) + # seems like 0.5.x branch does not work with engine.dispose and staticpool + return create_engine(engine, **kwargs) + +@decorator +def with_engine(f, *a, **kw): + """Decorator for :mod:`migrate.versioning.api` functions + to safely close resources after function usage. + + Passes engine parameters to :func:`construct_engine` and + resulting parameter is available as kw['engine']. + + Engine is disposed after wrapped function is executed. + + .. versionadded: 0.6.0 + """ + url = a[0] + engine = construct_engine(url, **kw) + + try: + kw['engine'] = engine + return f(*a, **kw) + finally: + if isinstance(engine, Engine): + log.debug('Disposing SQLAlchemy engine %s', engine) + engine.dispose() + + +class Memoize: + """Memoize(fn) - an instance which acts like fn but memoizes its arguments + Will only work on functions with non-mutable arguments + + ActiveState Code 52201 + """ + def __init__(self, fn): + self.fn = fn + self.memo = {} + + def __call__(self, *args): + if not self.memo.has_key(args): + self.memo[args] = self.fn(*args) + return self.memo[args] diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/util/importpath.py b/rhodecode/lib/dbmigrate/migrate/versioning/util/importpath.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/versioning/util/importpath.py @@ -0,0 +1,16 @@ +import os +import sys + +def import_path(fullpath): + """ Import a file with full path specification. Allows one to + import from anywhere, something __import__ does not do. + """ + # http://zephyrfalcon.org/weblog/arch_d7_2002_08_31.html + path, filename = os.path.split(fullpath) + filename, ext = os.path.splitext(filename) + sys.path.append(path) + module = __import__(filename) + reload(module) # Might be out of date during tests + del sys.path[-1] + return module + diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/util/keyedinstance.py b/rhodecode/lib/dbmigrate/migrate/versioning/util/keyedinstance.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/versioning/util/keyedinstance.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +class KeyedInstance(object): + """A class whose instances have a unique identifier of some sort + No two instances with the same unique ID should exist - if we try to create + a second instance, the first should be returned. + """ + + _instances = dict() + + def __new__(cls, *p, **k): + instances = cls._instances + clskey = str(cls) + if clskey not in instances: + instances[clskey] = dict() + instances = instances[clskey] + + key = cls._key(*p, **k) + if key not in instances: + instances[key] = super(KeyedInstance, cls).__new__(cls) + return instances[key] + + @classmethod + def _key(cls, *p, **k): + """Given a unique identifier, return a dictionary key + This should be overridden by child classes, to specify which parameters + should determine an object's uniqueness + """ + raise NotImplementedError() + + @classmethod + def clear(cls): + # Allow cls.clear() as well as uniqueInstance.clear(cls) + if str(cls) in cls._instances: + del cls._instances[str(cls)] diff --git a/rhodecode/lib/dbmigrate/migrate/versioning/version.py b/rhodecode/lib/dbmigrate/migrate/versioning/version.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/migrate/versioning/version.py @@ -0,0 +1,215 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import re +import shutil +import logging + +from rhodecode.lib.dbmigrate.migrate import exceptions +from rhodecode.lib.dbmigrate.migrate.versioning import pathed, script + + +log = logging.getLogger(__name__) + +class VerNum(object): + """A version number that behaves like a string and int at the same time""" + + _instances = dict() + + def __new__(cls, value): + val = str(value) + if val not in cls._instances: + cls._instances[val] = super(VerNum, cls).__new__(cls) + ret = cls._instances[val] + return ret + + def __init__(self,value): + self.value = str(int(value)) + if self < 0: + raise ValueError("Version number cannot be negative") + + def __add__(self, value): + ret = int(self) + int(value) + return VerNum(ret) + + def __sub__(self, value): + return self + (int(value) * -1) + + def __cmp__(self, value): + return int(self) - int(value) + + def __repr__(self): + return "" % self.value + + def __str__(self): + return str(self.value) + + def __int__(self): + return int(self.value) + + +class Collection(pathed.Pathed): + """A collection of versioning scripts in a repository""" + + FILENAME_WITH_VERSION = re.compile(r'^(\d{3,}).*') + + def __init__(self, path): + """Collect current version scripts in repository + and store them in self.versions + """ + super(Collection, self).__init__(path) + + # Create temporary list of files, allowing skipped version numbers. + files = os.listdir(path) + if '1' in files: + # deprecation + raise Exception('It looks like you have a repository in the old ' + 'format (with directories for each version). ' + 'Please convert repository before proceeding.') + + tempVersions = dict() + for filename in files: + match = self.FILENAME_WITH_VERSION.match(filename) + if match: + num = int(match.group(1)) + tempVersions.setdefault(num, []).append(filename) + else: + pass # Must be a helper file or something, let's ignore it. + + # Create the versions member where the keys + # are VerNum's and the values are Version's. + self.versions = dict() + for num, files in tempVersions.items(): + self.versions[VerNum(num)] = Version(num, path, files) + + @property + def latest(self): + """:returns: Latest version in Collection""" + return max([VerNum(0)] + self.versions.keys()) + + def create_new_python_version(self, description, **k): + """Create Python files for new version""" + ver = self.latest + 1 + extra = str_to_filename(description) + + if extra: + if extra == '_': + extra = '' + elif not extra.startswith('_'): + extra = '_%s' % extra + + filename = '%03d%s.py' % (ver, extra) + filepath = self._version_path(filename) + + script.PythonScript.create(filepath, **k) + self.versions[ver] = Version(ver, self.path, [filename]) + + def create_new_sql_version(self, database, **k): + """Create SQL files for new version""" + ver = self.latest + 1 + self.versions[ver] = Version(ver, self.path, []) + + # Create new files. + for op in ('upgrade', 'downgrade'): + filename = '%03d_%s_%s.sql' % (ver, database, op) + filepath = self._version_path(filename) + script.SqlScript.create(filepath, **k) + self.versions[ver].add_script(filepath) + + def version(self, vernum=None): + """Returns latest Version if vernum is not given. + Otherwise, returns wanted version""" + if vernum is None: + vernum = self.latest + return self.versions[VerNum(vernum)] + + @classmethod + def clear(cls): + super(Collection, cls).clear() + + def _version_path(self, ver): + """Returns path of file in versions repository""" + return os.path.join(self.path, str(ver)) + + +class Version(object): + """A single version in a collection + :param vernum: Version Number + :param path: Path to script files + :param filelist: List of scripts + :type vernum: int, VerNum + :type path: string + :type filelist: list + """ + + def __init__(self, vernum, path, filelist): + self.version = VerNum(vernum) + + # Collect scripts in this folder + self.sql = dict() + self.python = None + + for script in filelist: + self.add_script(os.path.join(path, script)) + + def script(self, database=None, operation=None): + """Returns SQL or Python Script""" + for db in (database, 'default'): + # Try to return a .sql script first + try: + return self.sql[db][operation] + except KeyError: + continue # No .sql script exists + + # TODO: maybe add force Python parameter? + ret = self.python + + assert ret is not None, \ + "There is no script for %d version" % self.version + return ret + + def add_script(self, path): + """Add script to Collection/Version""" + if path.endswith(Extensions.py): + self._add_script_py(path) + elif path.endswith(Extensions.sql): + self._add_script_sql(path) + + SQL_FILENAME = re.compile(r'^(\d+)_([^_]+)_([^_]+).sql') + + def _add_script_sql(self, path): + basename = os.path.basename(path) + match = self.SQL_FILENAME.match(basename) + + if match: + version, dbms, op = match.group(1), match.group(2), match.group(3) + else: + raise exceptions.ScriptError( + "Invalid SQL script name %s " % basename + \ + "(needs to be ###_database_operation.sql)") + + # File the script into a dictionary + self.sql.setdefault(dbms, {})[op] = script.SqlScript(path) + + def _add_script_py(self, path): + if self.python is not None: + raise exceptions.ScriptError('You can only have one Python script ' + 'per version, but you have: %s and %s' % (self.python, path)) + self.python = script.PythonScript(path) + + +class Extensions: + """A namespace for file extensions""" + py = 'py' + sql = 'sql' + +def str_to_filename(s): + """Replaces spaces, (double and single) quotes + and double underscores to underscores + """ + + s = s.replace(' ', '_').replace('"', '_').replace("'", '_').replace(".", "_") + while '__' in s: + s = s.replace('__', '_') + return s diff --git a/rhodecode/lib/dbmigrate/versions/001_initial_release.py b/rhodecode/lib/dbmigrate/versions/001_initial_release.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/versions/001_initial_release.py @@ -0,0 +1,237 @@ +#============================================================================== +# DB INITIAL MODEL +#============================================================================== +import logging +import datetime + +from sqlalchemy import * +from sqlalchemy.exc import DatabaseError +from sqlalchemy.orm import relation, backref, class_mapper +from sqlalchemy.orm.session import Session +from rhodecode.model.meta import Base + +from rhodecode.lib.dbmigrate.migrate import * + +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): + __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) + + def __init__(self, k, v): + self.app_settings_name = k + self.app_settings_value = v + + def __repr__(self): + return "" % (self.app_settings_name, + self.app_settings_value) + +class RhodeCodeUi(Base, BaseModel): + __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_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True) + + +class User(Base, BaseModel): + __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) + 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) + 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) + + user_log = relation('UserLog', cascade='all') + 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') + + @property + def full_contact(self): + return '%s %s <%s>' % (self.name, self.lastname, self.email) + + def __repr__(self): + return "" % (self.user_id, self.username) + + def update_lastlogin(self): + """Update user lastlogin""" + + try: + session = Session.object_session(self) + self.last_login = datetime.datetime.now() + session.add(self) + session.commit() + log.debug('updated user %s lastlogin', self.username) + except (DatabaseError,): + session.rollback() + + +class UserLog(Base, BaseModel): + __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(u'users.user_id'), nullable=False, unique=None, default=None) + repository_id = Column("repository_id", Integer(length=None, convert_unicode=False, assert_unicode=None), ForeignKey(u'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) + action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None) + + user = relation('User') + repository = relation('Repository') + +class Repository(Base, BaseModel): + __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=None) + user_id = Column("user_id", Integer(), ForeignKey(u'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) + fork_id = Column("fork_id", Integer(), ForeignKey(u'repositories.repo_id'), nullable=True, unique=False, default=None) + + user = relation('User') + fork = relation('Repository', remote_side=repo_id) + 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 "" % (self.repo_id, self.repo_name) + +class Permission(Base, BaseModel): + __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) + + def __repr__(self): + return "" % (self.permission_id, self.permission_name) + +class RepoToPerm(Base, BaseModel): + __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) + user_id = Column("user_id", Integer(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey(u'permissions.permission_id'), nullable=False, unique=None, default=None) + repository_id = Column("repository_id", Integer(), ForeignKey(u'repositories.repo_id'), nullable=False, unique=None, default=None) + + user = relation('User') + permission = relation('Permission') + repository = relation('Repository') + +class UserToPerm(Base, BaseModel): + __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) + user_id = Column("user_id", Integer(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey(u'permissions.permission_id'), nullable=False, unique=None, default=None) + + user = relation('User') + permission = relation('Permission') + +class Statistics(Base, BaseModel): + __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) + stat_on_revision = Column("stat_on_revision", Integer(), nullable=False) + commit_activity = Column("commit_activity", LargeBinary(), nullable=False)#JSON data + commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data + languages = Column("languages", LargeBinary(), nullable=False)#JSON data + + repository = relation('Repository', single_parent=True) + +class UserFollowing(Base, BaseModel): + __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, BaseModel): + __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_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False) + + + def __init__(self, cache_key, cache_args=''): + self.cache_key = cache_key + self.cache_args = cache_args + self.cache_active = False + + def __repr__(self): + return "" % (self.cache_id, self.cache_key) + + +def upgrade(migrate_engine): + # Upgrade operations go here. Don't create your own engine; bind migrate_engine + # to your metadata + Base.metadata.create_all(bind=migrate_engine, checkfirst=False) + +def downgrade(migrate_engine): + # Operations to reverse the above upgrade go here. + Base.metadata.drop_all(bind=migrate_engine, checkfirst=False) diff --git a/rhodecode/lib/dbmigrate/versions/002_version_1_1_0.py b/rhodecode/lib/dbmigrate/versions/002_version_1_1_0.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/versions/002_version_1_1_0.py @@ -0,0 +1,127 @@ +import logging +import datetime + +from sqlalchemy import * +from sqlalchemy.exc import DatabaseError +from sqlalchemy.orm import relation, backref, class_mapper +from sqlalchemy.orm.session import Session +from rhodecode.model.meta import Base +from rhodecode.model.db import BaseModel + +from rhodecode.lib.dbmigrate.migrate import * + +log = logging.getLogger(__name__) + +def upgrade(migrate_engine): + """ Upgrade operations go here. + Don't create your own engine; bind migrate_engine to your metadata + """ + + #========================================================================== + # Upgrade of `users` table + #========================================================================== + tblname = 'users' + tbl = Table(tblname, MetaData(bind=migrate_engine), autoload=True, + autoload_with=migrate_engine) + + #ADD is_ldap column + is_ldap = Column("is_ldap", Boolean(), nullable=True, + unique=None, default=False) + is_ldap.create(tbl, populate_default=True) + is_ldap.alter(nullable=False) + + #========================================================================== + # Upgrade of `user_logs` table + #========================================================================== + + tblname = 'users' + tbl = Table(tblname, MetaData(bind=migrate_engine), autoload=True, + autoload_with=migrate_engine) + + #ADD revision column + revision = Column('revision', TEXT(length=None, convert_unicode=False, + assert_unicode=None), + nullable=True, unique=None, default=None) + revision.create(tbl) + + + + #========================================================================== + # Upgrade of `repositories` table + #========================================================================== + tblname = 'repositories' + tbl = Table(tblname, MetaData(bind=migrate_engine), autoload=True, + autoload_with=migrate_engine) + + #ADD repo_type column# + repo_type = Column("repo_type", String(length=None, convert_unicode=False, + assert_unicode=None), + nullable=True, unique=False, default='hg') + + repo_type.create(tbl, populate_default=True) + #repo_type.alter(nullable=False) + + #ADD statistics column# + enable_statistics = Column("statistics", Boolean(), nullable=True, + unique=None, default=True) + enable_statistics.create(tbl) + + #========================================================================== + # Add table `user_followings` + #========================================================================== + tblname = 'user_followings' + + class UserFollowing(Base, BaseModel): + __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') + + Base.metadata.tables[tblname].create(migrate_engine) + + #========================================================================== + # Add table `cache_invalidation` + #========================================================================== + tblname = 'cache_invalidation' + + class CacheInvalidation(Base, BaseModel): + __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_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False) + + + def __init__(self, cache_key, cache_args=''): + self.cache_key = cache_key + self.cache_args = cache_args + self.cache_active = False + + def __repr__(self): + return "" % (self.cache_id, self.cache_key) + + Base.metadata.tables[tblname].create(migrate_engine) + + return + + + + + + +def downgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + diff --git a/rhodecode/lib/dbmigrate/versions/__init__.py b/rhodecode/lib/dbmigrate/versions/__init__.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/versions/__init__.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +""" + rhodecode.lib.dbmigrate.versions.__init__ + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Package containing new versions of database models + + :created_on: Dec 11, 2010 + :author: marcink + :copyright: (C) 2009-2010 Marcin Kuzminski + :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; 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. diff --git a/rhodecode/lib/exceptions.py b/rhodecode/lib/exceptions.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/exceptions.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Custom Exceptions modules +# Copyright (C) 2009-2010 Marcin Kuzminski +# +# 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 Nov 17, 2010 +Custom Exceptions modules +@author: marcink +""" + +class LdapUsernameError(Exception):pass +class LdapPasswordError(Exception):pass +class LdapConnectionError(Exception):pass +class LdapImportError(Exception):pass + +class DefaultUserException(Exception):pass +class UserOwnsReposException(Exception):pass diff --git a/rhodecode/lib/helpers.py b/rhodecode/lib/helpers.py --- a/rhodecode/lib/helpers.py +++ b/rhodecode/lib/helpers.py @@ -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 @@ -23,6 +25,36 @@ from webhelpers.pylonslib.secure_form im from webhelpers.text import chop_at, collapse, convert_accented_entities, \ convert_misc_entities, lchop, plural, rchop, remove_formatting, \ replace_whitespace, urlify, truncate, wrap_paragraphs +from webhelpers.date import time_ago_in_words + +from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \ + convert_boolean_attrs, NotGiven + +def _reset(name, value=None, id=NotGiven, type="reset", **attrs): + _set_input_attrs(attrs, type, name, value) + _set_id_attr(attrs, id, name) + convert_boolean_attrs(attrs, ["disabled"]) + return HTML.input(**attrs) + +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): @@ -93,7 +125,7 @@ class _ToolTip(object): var tts = YAHOO.util.Dom.getElementsByClassName('tooltip'); for (var i = 0; i < tts.length; i++) { - //if element doesn not have and id autgenerate one for tooltip + //if element doesn't not have and id autgenerate one for tooltip if (!tts[i].id){ tts[i].id='tt'+i*100; @@ -111,7 +143,7 @@ class _ToolTip(object): showdelay:20, }); - //Mouse Over event disabled for new repositories since they dont + //Mouse Over event disabled for new repositories since they don't //have last commit message myToolTips.contextMouseOverEvent.subscribe( function(type, args) { @@ -270,13 +302,13 @@ def pygmentize_annotation(filenode, **kw tooltip_html = tooltip_html % (changeset.author, changeset.date, tooltip(changeset.message)) - lnk_format = 'r%-5s:%s' % (changeset.revision, - changeset.short_id) + lnk_format = '%5s:%s' % ('r%s' % changeset.revision, + short_id(changeset.raw_id)) uri = link_to( lnk_format, url('changeset_home', repo_name=changeset.repository.name, - revision=changeset.short_id), - style=get_color_string(changeset.short_id), + revision=changeset.raw_id), + style=get_color_string(changeset.raw_id), class_='tooltip', tooltip_title=tooltip_html ) @@ -317,37 +349,168 @@ def get_changeset_safe(repo, rev): flash = _Flash() -#=============================================================================== +#============================================================================== # MERCURIAL FILTERS available via h. -#=============================================================================== +#============================================================================== from mercurial import util -from mercurial.templatefilters import age as _age, person as _person +from mercurial.templatefilters import person as _person + + + +def _age(curdate): + """turns a datetime into an age string.""" + + if not curdate: + return '' + + from datetime import timedelta, datetime + + agescales = [("year", 3600 * 24 * 365), + ("month", 3600 * 24 * 30), + ("day", 3600 * 24), + ("hour", 3600), + ("minute", 60), + ("second", 1), ] + + age = datetime.now() - curdate + age_seconds = (age.days * agescales[2][1]) + age.seconds + pos = 1 + for scale in agescales: + if scale[1] <= age_seconds: + if pos == 6:pos = 5 + return time_ago_in_words(curdate, agescales[pos][0]) + ' ' + _('ago') + pos += 1 + + return _('just now') age = lambda x:_age(x) capitalize = lambda x: x.capitalize() -date = lambda x: util.datestr(x) email = util.email email_or_none = lambda x: util.email(x) if util.email(x) != x else None person = lambda x: _person(x) -hgdate = lambda x: "%d %d" % x -isodate = lambda x: util.datestr(x, '%Y-%m-%d %H:%M %1%2') -isodatesec = lambda x: util.datestr(x, '%Y-%m-%d %H:%M:%S %1%2') -localdate = lambda x: (x[0], util.makedate()[1]) -rfc822date = lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S %1%2") -rfc822date_notz = lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S") -rfc3339date = lambda x: util.datestr(x, "%Y-%m-%dT%H:%M:%S%1:%2") -time_ago = lambda x: util.datestr(_age(x), "%a, %d %b %Y %H:%M:%S %1%2") +short_id = lambda x: x[:12] + + +def bool2icon(value): + """ + Returns True/False values represented as small html image of true/false + icons + :param value: bool value + """ + + if value is True: + return HTML.tag('img', src="/images/icons/accept.png", alt=_('True')) + + if value is False: + return HTML.tag('img', src="/images/icons/cancel.png", alt=_('False')) + + return value -#=============================================================================== +def action_parser(user_log): + """ + This helper will map the specified string action into translated + fancy names with icons and links + + @param action: + """ + action = user_log.action + action_params = ' ' + + x = action.split(':') + + if len(x) > 1: + action, action_params = x + + def get_cs_links(): + if action == 'push': + revs_limit = 5 + revs = action_params.split(',') + cs_links = " " + ', '.join ([link(rev, + url('changeset_home', + repo_name=user_log.repository.repo_name, + revision=rev)) for rev in revs[:revs_limit] ]) + if len(revs) > revs_limit: + uniq_id = revs[0] + html_tmpl = (' %s ' + '
%s ' + '%s') + cs_links += html_tmpl % (_('and'), uniq_id, _('%s more') \ + % (len(revs) - revs_limit), + _('revisions')) + + html_tmpl = '' + cs_links += html_tmpl % (uniq_id, ', '.join([link(rev, + url('changeset_home', + repo_name=user_log.repository.repo_name, + revision=rev)) for rev in revs[:revs_limit] ])) + + return cs_links + return '' + + def get_fork_name(): + if action == 'user_forked_repo': + from rhodecode.model.scm import ScmModel + repo_name = action_params + repo = ScmModel().get(repo_name) + if repo is None: + return repo_name + return link_to(action_params, url('summary_home', + repo_name=repo.name,), + title=repo.dbrepo.description) + return '' + map = {'user_deleted_repo':_('User [deleted] repository'), + 'user_created_repo':_('User [created] repository'), + 'user_forked_repo':_('User [forked] repository as: %s') % get_fork_name(), + 'user_updated_repo':_('User [updated] repository'), + 'admin_deleted_repo':_('Admin [delete] repository'), + 'admin_created_repo':_('Admin [created] repository'), + 'admin_forked_repo':_('Admin [forked] repository'), + 'admin_updated_repo':_('Admin [updated] repository'), + 'push':_('[Pushed] %s') % get_cs_links(), + 'pull':_('[Pulled]'), + 'started_following_repo':_('User [started following] repository'), + 'stopped_following_repo':_('User [stopped following] repository'), + } + + action_str = map.get(action, action) + return literal(action_str.replace('[', '')\ + .replace(']', '')) + +def action_parser_icon(user_log): + action = user_log.action + action_params = None + x = action.split(':') + + if len(x) > 1: + action, action_params = x + + tmpl = """%s""" + map = {'user_deleted_repo':'database_delete.png', + 'user_created_repo':'database_add.png', + 'user_forked_repo':'arrow_divide.png', + 'user_updated_repo':'database_edit.png', + 'admin_deleted_repo':'database_delete.png', + 'admin_created_repo':'database_ddd.png', + 'admin_forked_repo':'arrow_divide.png', + 'admin_updated_repo':'database_edit.png', + 'push':'script_add.png', + 'pull':'down_16.png', + 'started_following_repo':'heart_add.png', + 'stopped_following_repo':'heart_delete.png', + } + return literal(tmpl % (map.get(action, action), action)) + + +#============================================================================== # PERMS -#=============================================================================== +#============================================================================== from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \ HasRepoPermissionAny, HasRepoPermissionAll -#=============================================================================== +#============================================================================== # GRAVATAR URL -#=============================================================================== +#============================================================================== import hashlib import urllib from pylons import request diff --git a/rhodecode/lib/hooks.py b/rhodecode/lib/hooks.py --- a/rhodecode/lib/hooks.py +++ b/rhodecode/lib/hooks.py @@ -22,12 +22,12 @@ Created on Aug 6, 2010 @author: marcink """ - -import sys +from mercurial.cmdutil import revrange +from mercurial.node import nullrev +from rhodecode.lib import helpers as h +from rhodecode.lib.utils import action_logger import os -from rhodecode.lib import helpers as h -from rhodecode.model import meta -from rhodecode.model.db import UserLog, User +import sys def repo_size(ui, repo, hooktype=None, **kwargs): @@ -53,32 +53,53 @@ def repo_size(ui, repo, hooktype=None, * size_total_f = h.format_byte_size(size_root + size_hg) sys.stdout.write('Repository size .hg:%s repo:%s total:%s\n' \ % (size_hg_f, size_root_f, size_total_f)) - - user_action_mapper(ui, repo, hooktype, **kwargs) + +def log_pull_action(ui, repo, **kwargs): + """ + Logs user last pull action + :param ui: + :param repo: + """ -def user_action_mapper(ui, repo, hooktype=None, **kwargs): + extra_params = dict(repo.ui.configitems('rhodecode_extras')) + username = extra_params['username'] + repository = extra_params['repository'] + action = 'pull' + + action_logger(username, action, repository, extra_params['ip']) + + return 0 + +def log_push_action(ui, repo, **kwargs): """ Maps user last push action to new changeset id, from mercurial :param ui: :param repo: - :param hooktype: """ - - try: - sa = meta.Session - username = kwargs['url'].split(':')[-1] - user_log = sa.query(UserLog)\ - .filter(UserLog.user == sa.query(User)\ - .filter(User.username == username).one())\ - .order_by(UserLog.user_log_id.desc()).first() - - if user_log and not user_log.revision: - user_log.revision = str(repo['tip']) - sa.add(user_log) - sa.commit() - - except Exception, e: - sa.rollback() - raise - finally: - meta.Session.remove() + + extra_params = dict(repo.ui.configitems('rhodecode_extras')) + username = extra_params['username'] + repository = extra_params['repository'] + action = 'push:%s' + node = kwargs['node'] + + def get_revs(repo, rev_opt): + if rev_opt: + revs = revrange(repo, rev_opt) + + if len(revs) == 0: + return (nullrev, nullrev) + return (max(revs), min(revs)) + else: + return (len(repo) - 1, 0) + + stop, start = get_revs(repo, [node + ':']) + + revs = (str(repo[r]) for r in xrange(start, stop + 1)) + + action = action % ','.join(revs) + + action_logger(username, action, repository, extra_params['ip']) + + return 0 + diff --git a/rhodecode/lib/indexers/__init__.py b/rhodecode/lib/indexers/__init__.py --- a/rhodecode/lib/indexers/__init__.py +++ b/rhodecode/lib/indexers/__init__.py @@ -1,26 +1,28 @@ +import os +import sys +import traceback from os.path import dirname as dn, join as jn + +#to get the rhodecode import +sys.path.append(dn(dn(dn(os.path.realpath(__file__))))) + +from rhodecode.model import init_model +from rhodecode.model.scm import ScmModel from rhodecode.config.environment import load_environment -from rhodecode.model.hg_model import HgModel +from rhodecode.lib.utils import BasePasterCommand, Command, add_cache + from shutil import rmtree from webhelpers.html.builder import escape from vcs.utils.lazy import LazyProperty +from sqlalchemy import engine_from_config + from whoosh.analysis import RegexTokenizer, LowercaseFilter, StopFilter from whoosh.fields import TEXT, ID, STORED, Schema, FieldType from whoosh.index import create_in, open_dir from whoosh.formats import Characters -from whoosh.highlight import highlight, SimpleFragmenter, HtmlFormatter - -import os -import sys -import traceback +from whoosh.highlight import highlight, SimpleFragmenter, HtmlFormatter -#to get the rhodecode import -sys.path.append(dn(dn(dn(os.path.realpath(__file__))))) - - -#LOCATION WE KEEP THE INDEX -IDX_LOCATION = jn(dn(dn(dn(dn(os.path.abspath(__file__))))), 'data', 'index') #EXTENSIONS WE WANT TO INDEX CONTENT OFF INDEX_EXTENSIONS = ['action', 'adp', 'ashx', 'asmx', 'aspx', 'asx', 'axd', 'c', @@ -45,9 +47,58 @@ SCHEMA = Schema(owner=TEXT(), IDX_NAME = 'HG_INDEX' -FORMATTER = HtmlFormatter('span', between='\n...\n') +FORMATTER = HtmlFormatter('span', between='\n...\n') FRAGMENTER = SimpleFragmenter(200) - + + +class MakeIndex(BasePasterCommand): + + max_args = 1 + min_args = 1 + + usage = "CONFIG_FILE" + summary = "Creates index for full text search given configuration file" + group_name = "RhodeCode" + takes_config_file = -1 + parser = Command.standard_parser(verbose=True) + + def command(self): + + from pylons import config + add_cache(config) + engine = engine_from_config(config, 'sqlalchemy.db1.') + init_model(engine) + + index_location = config['index_dir'] + repo_location = self.options.repo_location + + #====================================================================== + # WHOOSH DAEMON + #====================================================================== + from rhodecode.lib.pidlock import LockHeld, DaemonLock + from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon + try: + l = DaemonLock() + WhooshIndexingDaemon(index_location=index_location, + repo_location=repo_location)\ + .run(full_index=self.options.full_index) + l.release() + except LockHeld: + sys.exit(1) + + def update_parser(self): + self.parser.add_option('--repo-location', + action='store', + dest='repo_location', + help="Specifies repositories location to index REQUIRED", + ) + self.parser.add_option('-f', + action='store_true', + dest='full_index', + help="Specifies that index should be made full i.e" + " destroy old and build from scratch", + default=False) + class ResultWrapper(object): def __init__(self, search_type, searcher, matcher, highlight_items): self.search_type = search_type @@ -55,7 +106,7 @@ class ResultWrapper(object): self.matcher = matcher self.highlight_items = highlight_items self.fragment_size = 200 / 2 - + @LazyProperty def doc_ids(self): docs_id = [] @@ -64,8 +115,8 @@ class ResultWrapper(object): chunks = [offsets for offsets in self.get_chunks()] docs_id.append([docnum, chunks]) self.matcher.next() - return docs_id - + return docs_id + def __str__(self): return '<%s at %s>' % (self.__class__.__name__, len(self.doc_ids)) @@ -91,32 +142,32 @@ class ResultWrapper(object): slice = [] for docid in self.doc_ids[i:j]: slice.append(self.get_full_content(docid)) - return slice - + return slice + def get_full_content(self, docid): res = self.searcher.stored_fields(docid[0]) f_path = res['path'][res['path'].find(res['repository']) \ + len(res['repository']):].lstrip('/') - + content_short = self.get_short_content(res, docid[1]) res.update({'content_short':content_short, 'content_short_hl':self.highlight(content_short), 'f_path':f_path}) - - return res - + + return res + def get_short_content(self, res, chunks): - + return ''.join([res['content'][chunk[0]:chunk[1]] for chunk in chunks]) - + def get_chunks(self): """ Smart function that implements chunking the content but not overlap chunks so it doesn't highlight the same close occurrences twice. - :param matcher: - :param size: + @param matcher: + @param size: """ memory = [(0, 0)] for span in self.matcher.spans(): @@ -124,12 +175,12 @@ class ResultWrapper(object): end = span.endchar or 0 start_offseted = max(0, start - self.fragment_size) end_offseted = end + self.fragment_size - + if start_offseted < memory[-1][1]: start_offseted = memory[-1][1] - memory.append((start_offseted, end_offseted,)) - yield (start_offseted, end_offseted,) - + memory.append((start_offseted, end_offseted,)) + yield (start_offseted, end_offseted,) + def highlight(self, content, top=5): if self.search_type != 'content': return '' @@ -139,4 +190,4 @@ class ResultWrapper(object): fragmenter=FRAGMENTER, formatter=FORMATTER, top=top) - return hl + return hl diff --git a/rhodecode/lib/indexers/daemon.py b/rhodecode/lib/indexers/daemon.py --- a/rhodecode/lib/indexers/daemon.py +++ b/rhodecode/lib/indexers/daemon.py @@ -32,12 +32,12 @@ from os.path import join as jn project_path = dn(dn(dn(dn(os.path.realpath(__file__))))) sys.path.append(project_path) -from rhodecode.lib.pidlock import LockHeld, DaemonLock -from rhodecode.model.hg_model import HgModel + +from rhodecode.model.scm import ScmModel from rhodecode.lib.helpers import safe_unicode from whoosh.index import create_in, open_dir from shutil import rmtree -from rhodecode.lib.indexers import INDEX_EXTENSIONS, IDX_LOCATION, SCHEMA, IDX_NAME +from rhodecode.lib.indexers import INDEX_EXTENSIONS, SCHEMA, IDX_NAME from time import mktime from vcs.exceptions import ChangesetError, RepositoryError @@ -61,55 +61,59 @@ ch.setFormatter(formatter) # add ch to logger log.addHandler(ch) -def scan_paths(root_location): - return HgModel.repo_scan('/', root_location, None, True) - class WhooshIndexingDaemon(object): """ Deamon for atomic jobs """ - def __init__(self, indexname='HG_INDEX', repo_location=None): + def __init__(self, indexname='HG_INDEX', index_location=None, + repo_location=None, sa=None): self.indexname = indexname + + self.index_location = index_location + if not index_location: + raise Exception('You have to provide index location') + self.repo_location = repo_location - self.repo_paths = scan_paths(self.repo_location) + if not repo_location: + raise Exception('You have to provide repositories location') + + self.repo_paths = ScmModel(sa).repo_scan(self.repo_location, None) self.initial = False - if not os.path.isdir(IDX_LOCATION): - os.mkdir(IDX_LOCATION) + if not os.path.isdir(self.index_location): + os.makedirs(self.index_location) log.info('Cannot run incremental index since it does not' ' yet exist running full build') self.initial = True - + def get_paths(self, repo): - """ - recursive walk in root dir and return a set of all path in that dir + """recursive walk in root dir and return a set of all path in that dir based on repository walk function """ index_paths_ = set() try: - tip = repo.get_changeset() - - for topnode, dirs, files in tip.walk('/'): + for topnode, dirs, files in repo.walk('/', 'tip'): for f in files: index_paths_.add(jn(repo.path, f.path)) for dir in dirs: for f in files: index_paths_.add(jn(repo.path, f.path)) - + except RepositoryError: pass - return index_paths_ - + return index_paths_ + def get_node(self, repo, path): n_path = path[len(repo.path) + 1:] node = repo.get_changeset().get_node(n_path) return node - + def get_node_mtime(self, node): return mktime(node.last_changeset.date.timetuple()) - + def add_doc(self, writer, path, repo): - """Adding doc to writer""" + """Adding doc to writer this function itself fetches data from + the instance of vcs backend""" node = self.get_node(repo, path) #we just index the content of chosen files @@ -120,63 +124,63 @@ class WhooshIndexingDaemon(object): log.debug(' >> %s' % path) #just index file name without it's content u_content = u'' - + writer.add_document(owner=unicode(repo.contact), repository=safe_unicode(repo.name), path=safe_unicode(path), content=u_content, modtime=self.get_node_mtime(node), - extension=node.extension) + extension=node.extension) + - def build_index(self): - if os.path.exists(IDX_LOCATION): + if os.path.exists(self.index_location): log.debug('removing previous index') - rmtree(IDX_LOCATION) - - if not os.path.exists(IDX_LOCATION): - os.mkdir(IDX_LOCATION) - - idx = create_in(IDX_LOCATION, SCHEMA, indexname=IDX_NAME) + rmtree(self.index_location) + + if not os.path.exists(self.index_location): + os.mkdir(self.index_location) + + idx = create_in(self.index_location, SCHEMA, indexname=IDX_NAME) writer = idx.writer() - + for cnt, repo in enumerate(self.repo_paths.values()): log.debug('building index @ %s' % repo.path) - + for idx_path in self.get_paths(repo): self.add_doc(writer, idx_path, repo) - + log.debug('>> COMMITING CHANGES <<') writer.commit(merge=True) log.debug('>>> FINISHED BUILDING INDEX <<<') - - + + def update_index(self): log.debug('STARTING INCREMENTAL INDEXING UPDATE') - - idx = open_dir(IDX_LOCATION, indexname=self.indexname) + + idx = open_dir(self.index_location, indexname=self.indexname) # The set of all paths in the index indexed_paths = set() # The set of all paths we need to re-index to_index = set() - + reader = idx.reader() writer = idx.writer() - + # Loop over the stored fields in the index for fields in reader.all_stored_fields(): indexed_path = fields['path'] indexed_paths.add(indexed_path) - + repo = self.repo_paths[fields['repository']] - + try: node = self.get_node(repo, indexed_path) except ChangesetError: # This file was deleted since it was indexed log.debug('removing from index %s' % indexed_path) writer.delete_by_term('path', indexed_path) - + else: # Check if this file was changed since it was indexed indexed_time = fields['modtime'] @@ -187,7 +191,7 @@ class WhooshIndexingDaemon(object): log.debug('adding to reindex list %s' % indexed_path) writer.delete_by_term('path', indexed_path) to_index.add(indexed_path) - + # Loop over the files in the filesystem # Assume we have a function that gathers the filenames of the # documents to be indexed @@ -198,51 +202,14 @@ class WhooshIndexingDaemon(object): # that wasn't indexed before. So index it! self.add_doc(writer, path, repo) log.debug('re indexing %s' % path) - + log.debug('>> COMMITING CHANGES <<') writer.commit(merge=True) log.debug('>>> FINISHED REBUILDING INDEX <<<') - + def run(self, full_index=False): """Run daemon""" if full_index or self.initial: self.build_index() else: self.update_index() - -if __name__ == "__main__": - arg = sys.argv[1:] - if len(arg) != 2: - sys.stderr.write('Please specify indexing type [full|incremental]' - 'and path to repositories as script args \n') - sys.exit() - - - if arg[0] == 'full': - full_index = True - elif arg[0] == 'incremental': - # False means looking just for changes - full_index = False - else: - sys.stdout.write('Please use [full|incremental]' - ' as script first arg \n') - sys.exit() - - if not os.path.isdir(arg[1]): - sys.stderr.write('%s is not a valid path \n' % arg[1]) - sys.exit() - else: - if arg[1].endswith('/'): - repo_location = arg[1] + '*' - else: - repo_location = arg[1] + '/*' - - try: - l = DaemonLock() - WhooshIndexingDaemon(repo_location=repo_location)\ - .run(full_index=full_index) - l.release() - reload(logging) - except LockHeld: - sys.exit(1) - diff --git a/rhodecode/lib/middleware/simplegit.py b/rhodecode/lib/middleware/simplegit.py --- a/rhodecode/lib/middleware/simplegit.py +++ b/rhodecode/lib/middleware/simplegit.py @@ -17,6 +17,14 @@ # 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 2010-04-28 + +@author: marcink +SimpleGit middleware for handling git protocol request (push/clone etc.) +It's implemented with basic auth function +""" + from dulwich import server as dulserver class SimpleGitUploadPackHandler(dulserver.UploadPackHandler): @@ -54,26 +62,28 @@ from dulwich.repo import Repo from dulwich.web import HTTPGitApplication from paste.auth.basic import AuthBasicAuthenticator from paste.httpheaders import REMOTE_USER, AUTH_TYPE -from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware, \ - get_user_cached -from rhodecode.lib.utils import action_logger, is_git, invalidate_cache, \ - check_repo_fast +from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware +from rhodecode.lib.utils import invalidate_cache, check_repo_fast +from rhodecode.model.user import UserModel from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError import logging import os import traceback -""" -Created on 2010-04-28 - -@author: marcink -SimpleGit middleware for handling git protocol request (push/clone etc.) -It's implemented with basic auth function -""" - - log = logging.getLogger(__name__) +def is_git(environ): + """ + Returns True if request's target is git server. ``HTTP_USER_AGENT`` would + then have git client version given. + + :param environ: + """ + http_user_agent = environ.get('HTTP_USER_AGENT') + if http_user_agent and http_user_agent.startswith('git'): + return True + return False + class SimpleGit(object): def __init__(self, application, config): @@ -81,11 +91,19 @@ class SimpleGit(object): self.config = config #authenticate this git request using self.authenticate = AuthBasicAuthenticator('', authfunc) + self.ipaddr = '0.0.0.0' + self.repository = None + self.username = None + self.action = None def __call__(self, environ, start_response): if not is_git(environ): return self.application(environ, start_response) + proxy_key = 'HTTP_X_REAL_IP' + def_key = 'REMOTE_ADDR' + self.ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0')) + #=================================================================== # AUTHENTICATE THIS GIT REQUEST #=================================================================== @@ -99,10 +117,14 @@ class SimpleGit(object): else: return result.wsgi_application(environ, start_response) + #======================================================================= + # GET REPOSITORY + #======================================================================= try: - self.repo_name = environ['PATH_INFO'].split('/')[1] - if self.repo_name.endswith('/'): - self.repo_name = self.repo_name.rstrip('/') + repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:]) + if repo_name.endswith('/'): + repo_name = repo_name.rstrip('/') + self.repository = repo_name except: log.error(traceback.format_exc()) return HTTPInternalServerError()(environ, start_response) @@ -110,20 +132,21 @@ class SimpleGit(object): #=================================================================== # CHECK PERMISSIONS FOR THIS REQUEST #=================================================================== - action = self.__get_action(environ) - if action: + self.action = self.__get_action(environ) + if self.action: username = self.__get_environ_user(environ) try: user = self.__get_user(username) + self.username = user.username except: log.error(traceback.format_exc()) return HTTPInternalServerError()(environ, start_response) #check permissions for this repository - if action == 'push': + if self.action == 'push': if not HasPermissionAnyMiddleware('repository.write', 'repository.admin')\ - (user, self.repo_name): + (user, repo_name): return HTTPForbidden()(environ, start_response) else: @@ -131,15 +154,13 @@ class SimpleGit(object): if not HasPermissionAnyMiddleware('repository.read', 'repository.write', 'repository.admin')\ - (user, self.repo_name): + (user, repo_name): return HTTPForbidden()(environ, start_response) - #log action - if action in ('push', 'pull', 'clone'): - proxy_key = 'HTTP_X_REAL_IP' - def_key = 'REMOTE_ADDR' - ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0')) - self.__log_user_action(user, action, self.repo_name, ipaddr) + self.extras = {'ip':self.ipaddr, + 'username':self.username, + 'action':self.action, + 'repository':self.repository} #=================================================================== # GIT REQUEST HANDLING @@ -151,12 +172,12 @@ class SimpleGit(object): return HTTPNotFound()(environ, start_response) try: app = self.__make_app() - except Exception: + except: log.error(traceback.format_exc()) return HTTPInternalServerError()(environ, start_response) #invalidate cache on push - if action == 'push': + if self.action == 'push': self.__invalidate_cache(self.repo_name) messages = [] messages.append('thank you for using rhodecode') @@ -175,7 +196,7 @@ class SimpleGit(object): return environ.get('REMOTE_USER') def __get_user(self, username): - return get_user_cached(username) + return UserModel().get_by_username(username, cache=True) def __get_action(self, environ): """ @@ -193,12 +214,8 @@ class SimpleGit(object): else: return 'other' - def __log_user_action(self, user, action, repo, ipaddr): - action_logger(user, action, repo, ipaddr) - def __invalidate_cache(self, repo_name): """we know that some change was made to repositories and we should invalidate the cache to see the changes right away but only for push requests""" - invalidate_cache('cached_repo_list') - invalidate_cache('full_changelog', repo_name) + invalidate_cache('get_repo_cached_%s' % repo_name) diff --git a/rhodecode/lib/middleware/simplehg.py b/rhodecode/lib/middleware/simplehg.py --- a/rhodecode/lib/middleware/simplehg.py +++ b/rhodecode/lib/middleware/simplehg.py @@ -24,40 +24,57 @@ Created on 2010-04-28 SimpleHG middleware for handling mercurial protocol request (push/clone etc.) It's implemented with basic auth function """ -from itertools import chain from mercurial.error import RepoError from mercurial.hgweb import hgweb from mercurial.hgweb.request import wsgiapplication from paste.auth.basic import AuthBasicAuthenticator from paste.httpheaders import REMOTE_USER, AUTH_TYPE -from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware, \ - get_user_cached -from rhodecode.lib.utils import is_mercurial, make_ui, invalidate_cache, \ +from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware +from rhodecode.lib.utils import make_ui, invalidate_cache, \ check_repo_fast, ui_sections +from rhodecode.model.user import UserModel from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError -from rhodecode.lib.utils import action_logger import logging import os import traceback log = logging.getLogger(__name__) +def is_mercurial(environ): + """ + Returns True if request's target is mercurial server - header + ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``. + """ + http_accept = environ.get('HTTP_ACCEPT') + if http_accept and http_accept.startswith('application/mercurial'): + return True + return False + class SimpleHg(object): def __init__(self, application, config): self.application = application self.config = config - #authenticate this mercurial request using + #authenticate this mercurial request using authfunc self.authenticate = AuthBasicAuthenticator('', authfunc) + self.ipaddr = '0.0.0.0' + self.repository = None + self.username = None + self.action = None def __call__(self, environ, start_response): if not is_mercurial(environ): return self.application(environ, start_response) + proxy_key = 'HTTP_X_REAL_IP' + def_key = 'REMOTE_ADDR' + self.ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0')) + #=================================================================== # AUTHENTICATE THIS MERCURIAL REQUEST #=================================================================== username = REMOTE_USER(environ) + if not username: self.authenticate.realm = self.config['rhodecode_realm'] result = self.authenticate(environ) @@ -67,10 +84,14 @@ class SimpleHg(object): else: return result.wsgi_application(environ, start_response) + #======================================================================= + # GET REPOSITORY + #======================================================================= try: repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:]) if repo_name.endswith('/'): repo_name = repo_name.rstrip('/') + self.repository = repo_name except: log.error(traceback.format_exc()) return HTTPInternalServerError()(environ, start_response) @@ -78,17 +99,18 @@ class SimpleHg(object): #=================================================================== # CHECK PERMISSIONS FOR THIS REQUEST #=================================================================== - action = self.__get_action(environ) - if action: + self.action = self.__get_action(environ) + if self.action: username = self.__get_environ_user(environ) try: user = self.__get_user(username) + self.username = user.username except: log.error(traceback.format_exc()) return HTTPInternalServerError()(environ, start_response) #check permissions for this repository - if action == 'push': + if self.action == 'push': if not HasPermissionAnyMiddleware('repository.write', 'repository.admin')\ (user, repo_name): @@ -102,12 +124,10 @@ class SimpleHg(object): (user, repo_name): return HTTPForbidden()(environ, start_response) - #log action - if action in ('push', 'pull', 'clone'): - proxy_key = 'HTTP_X_REAL_IP' - def_key = 'REMOTE_ADDR' - ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0')) - self.__log_user_action(user, action, repo_name, ipaddr) + self.extras = {'ip':self.ipaddr, + 'username':self.username, + 'action':self.action, + 'repository':self.repository} #=================================================================== # MERCURIAL REQUEST HANDLING @@ -130,40 +150,21 @@ class SimpleHg(object): return HTTPInternalServerError()(environ, start_response) #invalidate cache on push - if action == 'push': + if self.action == 'push': self.__invalidate_cache(repo_name) - messages = [] - messages.append('thank you for using rhodecode') - - return self.msg_wrapper(app, environ, start_response, messages) - else: - return app(environ, start_response) - - def msg_wrapper(self, app, environ, start_response, messages=[]): - """ - Wrapper for custom messages that come out of mercurial respond messages - is a list of messages that the user will see at the end of response - from merurial protocol actions that involves remote answers - :param app: - :param environ: - :param start_response: - """ - def custom_messages(msg_list): - for msg in msg_list: - yield msg + '\n' - org_response = app(environ, start_response) - return chain(org_response, custom_messages(messages)) + return app(environ, start_response) + def __make_app(self): hgserve = hgweb(str(self.repo_path), baseui=self.baseui) - return self.__load_web_settings(hgserve) + return self.__load_web_settings(hgserve, self.extras) def __get_environ_user(self, environ): return environ.get('REMOTE_USER') def __get_user(self, username): - return get_user_cached(username) + return UserModel().get_by_username(username, cache=True) def __get_action(self, environ): """ @@ -174,7 +175,7 @@ class SimpleHg(object): mapping = {'changegroup': 'pull', 'changegroupsubset': 'pull', 'stream_out': 'pull', - #'listkeys': 'pull', + 'listkeys': 'pull', 'unbundle': 'push', 'pushkey': 'push', } for qry in environ['QUERY_STRING'].split('&'): @@ -185,25 +186,26 @@ class SimpleHg(object): else: return cmd - def __log_user_action(self, user, action, repo, ipaddr): - action_logger(user, action, repo, ipaddr) - def __invalidate_cache(self, repo_name): """we know that some change was made to repositories and we should invalidate the cache to see the changes right away but only for push requests""" - invalidate_cache('cached_repo_list') - invalidate_cache('full_changelog', repo_name) + invalidate_cache('get_repo_cached_%s' % repo_name) - def __load_web_settings(self, hgserve): + def __load_web_settings(self, hgserve, extras={}): #set the global ui for hgserve instance passed hgserve.repo.ui = self.baseui hgrc = os.path.join(self.repo_path, '.hg', 'hgrc') + + #inject some additional parameters that will be available in ui + #for hooks + for k, v in extras.items(): + hgserve.repo.ui.setconfig('rhodecode_extras', k, v) + repoui = make_ui('file', hgrc, False) - if repoui: #overwrite our ui instance with the section from hgrc file for section in ui_sections: diff --git a/rhodecode/lib/smtp_mailer.py b/rhodecode/lib/smtp_mailer.py --- a/rhodecode/lib/smtp_mailer.py +++ b/rhodecode/lib/smtp_mailer.py @@ -22,7 +22,7 @@ class SmtpMailer(object): def __init__(self, mail_from, user, passwd, mail_server, mail_port=None, ssl=False, tls=False): - + self.mail_from = mail_from self.mail_server = mail_server self.mail_port = mail_port @@ -31,7 +31,7 @@ class SmtpMailer(object): self.ssl = ssl self.tls = tls self.debug = False - + def send(self, recipients=[], subject='', body='', attachment_files={}): if isinstance(recipients, basestring): @@ -43,11 +43,11 @@ class SmtpMailer(object): if self.tls: smtp_serv.starttls() - - if self.debug: + + if self.debug: smtp_serv.set_debuglevel(1) - smtp_serv.ehlo("mailer") + smtp_serv.ehlo("rhodecode mailer") #if server requires authorization you must provide login and password smtp_serv.login(self.user, self.passwd) @@ -82,13 +82,13 @@ class SmtpMailer(object): maintype, subtype = ctype.split('/', 1) if maintype == 'text': # Note: we should handle calculating the charset - file_part = MIMEText(self.get_content(msg_file), + file_part = MIMEText(self.get_content(msg_file), _subtype=subtype) elif maintype == 'image': - file_part = MIMEImage(self.get_content(msg_file), + file_part = MIMEImage(self.get_content(msg_file), _subtype=subtype) elif maintype == 'audio': - file_part = MIMEAudio(self.get_content(msg_file), + file_part = MIMEAudio(self.get_content(msg_file), _subtype=subtype) else: file_part = MIMEBase(maintype, subtype) @@ -96,13 +96,13 @@ class SmtpMailer(object): # Encode the payload using Base64 encoders.encode_base64(msg) # Set the filename parameter - file_part.add_header('Content-Disposition', 'attachment', + file_part.add_header('Content-Disposition', 'attachment', filename=f_name) file_part.add_header('Content-Type', ctype, name=f_name) msg.attach(file_part) else: - raise Exception('Attachment files should be' - 'a dict in format {"filename":"filepath"}') + raise Exception('Attachment files should be' + 'a dict in format {"filename":"filepath"}') def get_content(self, msg_file): ''' diff --git a/rhodecode/lib/utils.py b/rhodecode/lib/utils.py --- a/rhodecode/lib/utils.py +++ b/rhodecode/lib/utils.py @@ -1,7 +1,15 @@ -#!/usr/bin/env python -# encoding: utf-8 -# Utilities for RhodeCode -# Copyright (C) 2009-2010 Marcin Kuzminski +# -*- coding: utf-8 -*- +""" + rhodecode.lib.utils + ~~~~~~~~~~~~~~~~~~~ + + Utilities library for RhodeCode + + :created_on: Apr 18, 2010 + :author: marcink + :copyright: (C) 2009-2010 Marcin Kuzminski + :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; version 2 @@ -17,21 +25,28 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. -""" -Created on April 18, 2010 -Utilities for RhodeCode -@author: marcink -""" -from beaker.cache import cache_region +import os +import logging +import datetime +import traceback + +from UserDict import DictMixin + from mercurial import ui, config, hg from mercurial.error import RepoError -from rhodecode.model import meta -from rhodecode.model.db import Repository, User, RhodeCodeUi, RhodeCodeSettings, UserLog + +import paste +import beaker +from paste.script.command import Command, BadCommand + from vcs.backends.base import BaseChangeset from vcs.utils.lazy import LazyProperty -import logging -import datetime -import os + +from rhodecode.model import meta +from rhodecode.model.caching_query import FromCache +from rhodecode.model.db import Repository, User, RhodeCodeUi, UserLog +from rhodecode.model.repo import RepoModel +from rhodecode.model.user import UserModel log = logging.getLogger(__name__) @@ -39,72 +54,95 @@ log = logging.getLogger(__name__) def get_repo_slug(request): return request.environ['pylons.routes_dict'].get('repo_name') -def is_mercurial(environ): - """ - Returns True if request's target is mercurial server - header - ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``. - """ - http_accept = environ.get('HTTP_ACCEPT') - if http_accept and http_accept.startswith('application/mercurial'): - return True - return False - -def is_git(environ): +def action_logger(user, action, repo, ipaddr='', sa=None): """ - Returns True if request's target is git server. ``HTTP_USER_AGENT`` would - then have git client version given. + Action logger for various actions made by users - :param environ: - """ - http_user_agent = environ.get('HTTP_USER_AGENT') - if http_user_agent.startswith('git'): - return True - return False - -def action_logger(user, action, repo, ipaddr, sa=None): - """ - Action logger for various action made by users + :param user: user that made this action, can be a unique username string or + object containing user_id attribute + :param action: action to log, should be on of predefined unique actions for + easy translations + :param repo: string name of repository or object containing repo_id, + that action was made on + :param ipaddr: optional ip address from what the action was made + :param sa: optional sqlalchemy session + """ if not sa: - sa = meta.Session + sa = meta.Session() try: + um = UserModel() if hasattr(user, 'user_id'): - user_id = user.user_id + user_obj = user elif isinstance(user, basestring): - user_id = sa.query(User).filter(User.username == user).one() + user_obj = um.get_by_username(user, cache=False) else: raise Exception('You have to provide user object or username') - repo_name = repo.lstrip('/') + + rm = RepoModel() + if hasattr(repo, 'repo_id'): + repo_obj = rm.get(repo.repo_id, cache=False) + repo_name = repo_obj.repo_name + elif isinstance(repo, basestring): + repo_name = repo.lstrip('/') + repo_obj = rm.get_by_repo_name(repo_name, cache=False) + else: + raise Exception('You have to provide repository to action logger') + + user_log = UserLog() - user_log.user_id = user_id + user_log.user_id = user_obj.user_id user_log.action = action + + user_log.repository_id = repo_obj.repo_id user_log.repository_name = repo_name - user_log.repository = sa.query(Repository)\ - .filter(Repository.repo_name == repo_name).one() + user_log.action_date = datetime.datetime.now() user_log.user_ip = ipaddr sa.add(user_log) sa.commit() - log.info('Adding user %s, action %s on %s', - user.username, action, repo) - except Exception, e: + log.info('Adding user %s, action %s on %s', user_obj, action, repo) + except: + log.error(traceback.format_exc()) sa.rollback() - log.error('could not log user action:%s', str(e)) -def check_repo_dir(paths): - repos_path = paths[0][1].split('/') - if repos_path[-1] in ['*', '**']: - repos_path = repos_path[:-1] - if repos_path[0] != '/': - repos_path[0] = '/' - if not os.path.isdir(os.path.join(*repos_path)): - raise Exception('Not a valid repository in %s' % paths[0][1]) +def get_repos(path, recursive=False, initial=False): + """ + Scans given path for repos and return (name,(type,path)) tuple + :param prefix: + :param path: + :param recursive: + :param initial: + """ + from vcs.utils.helpers import get_scm + from vcs.exceptions import VCSError + + try: + scm = get_scm(path) + except: + pass + else: + raise Exception('The given path %s should not be a repository got %s', + path, scm) + + for dirpath in os.listdir(path): + try: + yield dirpath, get_scm(os.path.join(path, dirpath)) + except VCSError: + pass def check_repo_fast(repo_name, base_path): + """ + Check given path for existance of directory + :param repo_name: + :param base_path: + + :return False: if this directory is present + """ if os.path.isdir(os.path.join(base_path, repo_name)):return False return True @@ -135,57 +173,6 @@ def ask_ok(prompt, retries=4, complaint= if retries < 0: raise IOError print complaint -@cache_region('super_short_term', 'cached_hg_ui') -def get_hg_ui_cached(): - try: - sa = meta.Session - ret = sa.query(RhodeCodeUi).all() - finally: - meta.Session.remove() - return ret - - -def get_hg_settings(): - try: - sa = meta.Session - ret = sa.query(RhodeCodeSettings).all() - finally: - meta.Session.remove() - - if not ret: - raise Exception('Could not get application settings !') - settings = {} - for each in ret: - settings['rhodecode_' + each.app_settings_name] = each.app_settings_value - - return settings - -def get_hg_ui_settings(): - try: - sa = meta.Session - ret = sa.query(RhodeCodeUi).all() - finally: - meta.Session.remove() - - if not ret: - raise Exception('Could not get application ui settings !') - settings = {} - for each in ret: - k = each.ui_key - v = each.ui_value - if k == '/': - k = 'root_path' - - if k.find('.') != -1: - k = k.replace('.', '_') - - if each.ui_section == 'hooks': - v = each.ui_active - - settings[each.ui_section + '_' + k] = v - - return settings - #propagated from mercurial documentation ui_sections = ['alias', 'auth', 'decode/encode', 'defaults', @@ -210,6 +197,11 @@ def make_ui(read_from='file', path=None, baseui = ui.ui() + #clean the baseui object + baseui._ocfg = config.config() + baseui._ucfg = config.config() + baseui._tcfg = config.config() + if read_from == 'file': if not os.path.isfile(path): log.warning('Unable to read config file %s' % path) @@ -219,70 +211,69 @@ def make_ui(read_from='file', path=None, cfg.read(path) for section in ui_sections: for k, v in cfg.items(section): + log.debug('settings ui from file[%s]%s:%s', section, k, v) baseui.setconfig(section, k, v) - log.debug('settings ui from file[%s]%s:%s', section, k, v) - - for k, v in baseui.configitems('extensions'): - baseui.setconfig('extensions', k, '0') - #just enable mq - baseui.setconfig('extensions', 'mq', '1') - if checkpaths:check_repo_dir(cfg.items('paths')) elif read_from == 'db': - hg_ui = get_hg_ui_cached() + sa = meta.Session() + ret = sa.query(RhodeCodeUi)\ + .options(FromCache("sql_cache_short", + "get_hg_ui_settings")).all() + + hg_ui = ret for ui_ in hg_ui: if ui_.ui_active: - log.debug('settings ui from db[%s]%s:%s', ui_.ui_section, ui_.ui_key, ui_.ui_value) + log.debug('settings ui from db[%s]%s:%s', ui_.ui_section, + ui_.ui_key, ui_.ui_value) baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value) - + meta.Session.remove() return baseui def set_rhodecode_config(config): - hgsettings = get_hg_settings() + """ + Updates pylons config with new settings from database + :param config: + """ + from rhodecode.model.settings import SettingsModel + hgsettings = SettingsModel().get_app_settings() for k, v in hgsettings.items(): config[k] = v -def invalidate_cache(name, *args): - """Invalidates given name cache""" - - from beaker.cache import region_invalidate - log.info('INVALIDATING CACHE FOR %s', name) +def invalidate_cache(cache_key, *args): + """ + Puts cache invalidation task into db for + further global cache invalidation + """ + from rhodecode.model.scm import ScmModel - """propagate our arguments to make sure invalidation works. First - argument has to be the name of cached func name give to cache decorator - without that the invalidation would not work""" - tmp = [name] - tmp.extend(args) - args = tuple(tmp) - - if name == 'cached_repo_list': - from rhodecode.model.hg_model import _get_repos_cached - region_invalidate(_get_repos_cached, None, *args) - - if name == 'full_changelog': - from rhodecode.model.hg_model import _full_changelog_cached - region_invalidate(_full_changelog_cached, None, *args) + if cache_key.startswith('get_repo_cached_'): + name = cache_key.split('get_repo_cached_')[-1] + ScmModel().mark_for_invalidation(name) class EmptyChangeset(BaseChangeset): """ - An dummy empty changeset. + An dummy empty changeset. It's possible to pass hash when creating + an EmptyChangeset """ - revision = -1 - message = '' - author = '' - date = '' + def __init__(self, cs='0' * 40): + self._empty_cs = cs + self.revision = -1 + self.message = '' + self.author = '' + self.date = '' + @LazyProperty def raw_id(self): """ - Returns raw string identifing this changeset, useful for web + Returns raw string identifying this changeset, useful for web representation. """ - return '0' * 40 + return self._empty_cs @LazyProperty def short_id(self): @@ -301,26 +292,25 @@ def repo2db_mapper(initial_repo_list, re """ maps all found repositories into db """ - from rhodecode.model.repo_model import RepoModel - sa = meta.Session + sa = meta.Session() + rm = RepoModel() user = sa.query(User).filter(User.admin == True).first() - rm = RepoModel() - for name, repo in initial_repo_list.items(): - if not sa.query(Repository).filter(Repository.repo_name == name).scalar(): + if not rm.get_by_repo_name(name, cache=False): log.info('repository %s not found creating default', name) form_data = { 'repo_name':name, - 'description':repo.description if repo.description != 'unknown' else \ - 'auto description for %s' % name, + 'repo_type':repo.alias, + 'description':repo.description \ + if repo.description != 'unknown' else \ + '%s repository' % name, 'private':False } rm.create(form_data, user, just_db=True) - if remove_obsolete: #remove from database those repositories that are not in the filesystem for repo in sa.query(Repository).all(): @@ -328,11 +318,6 @@ def repo2db_mapper(initial_repo_list, re sa.delete(repo) sa.commit() - - meta.Session.remove() - -from UserDict import DictMixin - class OrderedDict(dict, DictMixin): def __init__(self, *args, **kwds): @@ -433,8 +418,51 @@ class OrderedDict(dict, DictMixin): return not self == other +#set cache regions for beaker so celery can utilise it +def add_cache(settings): + cache_settings = {'regions':None} + for key in settings.keys(): + for prefix in ['beaker.cache.', 'cache.']: + if key.startswith(prefix): + name = key.split(prefix)[1].strip() + cache_settings[name] = settings[key].strip() + if cache_settings['regions']: + for region in cache_settings['regions'].split(','): + region = region.strip() + region_settings = {} + for key, value in cache_settings.items(): + if key.startswith(region): + region_settings[key.split('.')[1]] = value + region_settings['expire'] = int(region_settings.get('expire', + 60)) + region_settings.setdefault('lock_dir', + cache_settings.get('lock_dir')) + if 'type' not in region_settings: + region_settings['type'] = cache_settings.get('type', + 'memory') + beaker.cache.cache_regions[region] = region_settings + +def get_current_revision(): + """ + Returns tuple of (number, id) from repository containing this package + or None if repository could not be found. + """ + try: + from vcs import get_repo + from vcs.utils.helpers import get_scm + from vcs.exceptions import RepositoryError, VCSError + repopath = os.path.join(os.path.dirname(__file__), '..', '..') + scm = get_scm(repopath)[0] + repo = get_repo(path=repopath, alias=scm) + tip = repo.get_changeset() + return (tip.revision, tip.short_id) + except (ImportError, RepositoryError, VCSError), err: + logging.debug("Cannot retrieve rhodecode's revision. Original error " + "was: %s" % err) + return None + #=============================================================================== -# TEST FUNCTIONS +# TEST FUNCTIONS AND CREATORS #=============================================================================== def create_test_index(repo_location, full_index): """Makes default test index @@ -443,15 +471,16 @@ def create_test_index(repo_location, ful """ from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon from rhodecode.lib.pidlock import DaemonLock, LockHeld - from rhodecode.lib.indexers import IDX_LOCATION import shutil - if os.path.exists(IDX_LOCATION): - shutil.rmtree(IDX_LOCATION) + index_location = os.path.join(repo_location, 'index') + if os.path.exists(index_location): + shutil.rmtree(index_location) try: l = DaemonLock() - WhooshIndexingDaemon(repo_location=repo_location)\ + WhooshIndexingDaemon(index_location=index_location, + repo_location=repo_location)\ .run(full_index=full_index) l.release() except LockHeld: @@ -462,10 +491,11 @@ def create_test_env(repos_test_path, con install test repository into tmp dir """ from rhodecode.lib.db_manage import DbManage + from rhodecode.tests import HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, \ + HG_FORK, GIT_FORK, TESTS_TMP_PATH import tarfile import shutil from os.path import dirname as dn, join as jn, abspath - from rhodecode.tests import REPO_PATH, NEW_REPO_PATH, FORK_REPO_PATH log = logging.getLogger('TestEnvCreator') # create logger @@ -485,10 +515,10 @@ def create_test_env(repos_test_path, con log.addHandler(ch) #PART ONE create db - dbname = config['sqlalchemy.db1.url'].split('/')[-1] - log.debug('making test db %s', dbname) + dbconf = config['sqlalchemy.db1.url'] + log.debug('making test db %s', dbconf) - dbmanage = DbManage(log_sql=True, dbname=dbname, root=config['here'], + dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'], tests=True) dbmanage.create_tables(override=True) dbmanage.config_prompt(repos_test_path) @@ -498,18 +528,87 @@ def create_test_env(repos_test_path, con dbmanage.populate_default_permissions() #PART TWO make test repo - log.debug('making test vcs repo') - if os.path.isdir(REPO_PATH): - log.debug('REMOVING %s', REPO_PATH) - shutil.rmtree(REPO_PATH) - if os.path.isdir(NEW_REPO_PATH): - log.debug('REMOVING %s', NEW_REPO_PATH) - shutil.rmtree(NEW_REPO_PATH) - if os.path.isdir(FORK_REPO_PATH): - log.debug('REMOVING %s', FORK_REPO_PATH) - shutil.rmtree(FORK_REPO_PATH) + log.debug('making test vcs repositories') + + #remove old one from previos tests + for r in [HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, HG_FORK, GIT_FORK]: + + if os.path.isdir(jn(TESTS_TMP_PATH, r)): + log.debug('removing %s', r) + shutil.rmtree(jn(TESTS_TMP_PATH, r)) + + #CREATE DEFAULT HG REPOSITORY + cur_dir = dn(dn(abspath(__file__))) + tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz")) + tar.extractall(jn(TESTS_TMP_PATH, HG_REPO)) + tar.close() + + +#============================================================================== +# PASTER COMMANDS +#============================================================================== + +class BasePasterCommand(Command): + """ + Abstract Base Class for paster commands. + + The celery commands are somewhat aggressive about loading + celery.conf, and since our module sets the `CELERY_LOADER` + environment variable to our loader, we have to bootstrap a bit and + make sure we've had a chance to load the pylons config off of the + command line, otherwise everything fails. + """ + min_args = 1 + min_args_error = "Please provide a paster config file as an argument." + takes_config_file = 1 + requires_config_file = True - cur_dir = dn(dn(abspath(__file__))) - tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test.tar.gz")) - tar.extractall('/tmp') - tar.close() + def notify_msg(self, msg, log=False): + """Make a notification to user, additionally if logger is passed + it logs this action using given logger + + :param msg: message that will be printed to user + :param log: logging instance, to use to additionally log this message + + """ + print msg + if log and isinstance(log, logging): + log(msg) + + + def run(self, args): + """ + Overrides Command.run + + Checks for a config file argument and loads it. + """ + if len(args) < self.min_args: + raise BadCommand( + self.min_args_error % {'min_args': self.min_args, + 'actual_args': len(args)}) + + # Decrement because we're going to lob off the first argument. + # @@ This is hacky + self.min_args -= 1 + self.bootstrap_config(args[0]) + self.update_parser() + return super(BasePasterCommand, self).run(args[1:]) + + def update_parser(self): + """ + Abstract method. Allows for the class's parser to be updated + before the superclass's `run` method is called. Necessary to + allow options/arguments to be passed through to the underlying + celery command. + """ + raise NotImplementedError("Abstract Method.") + + def bootstrap_config(self, conf): + """ + Loads the pylons configuration. + """ + from pylons import config as pylonsconfig + + path_to_ini_file = os.path.realpath(conf) + conf = paste.deploy.appconfig('config:' + path_to_ini_file) + pylonsconfig.init_app(conf.global_conf, conf.local_conf) diff --git a/rhodecode/model/__init__.py b/rhodecode/model/__init__.py --- a/rhodecode/model/__init__.py +++ b/rhodecode/model/__init__.py @@ -1,23 +1,71 @@ -"""The application's model objects""" +# -*- coding: utf-8 -*- +""" + rhodecode.model.__init__ + ~~~~~~~~~~~~~~~~~~~~~~~~ + + The application's model objects + + :created_on: Nov 25, 2010 + :author: marcink + :copyright: (C) 2009-2010 Marcin Kuzminski + :license: GPLv3, see COPYING for more details. + + + :example: + + .. code-block:: python + + from paste.deploy import appconfig + from pylons import config + from sqlalchemy import engine_from_config + from rhodecode.config.environment import load_environment + + conf = appconfig('config:development.ini', relative_to = './../../') + load_environment(conf.global_conf, conf.local_conf) + + engine = engine_from_config(config, 'sqlalchemy.') + init_model(engine) + # RUN YOUR CODE HERE + +""" +# 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. + import logging from rhodecode.model import meta log = logging.getLogger(__name__) def init_model(engine): - """Call me before using any of the tables or classes in the model""" - log.info("INITIALIZING DB MODELS") + """Initializes db session, bind the engine with the metadata, + Call this before using any of the tables or classes in the model, preferably + once in application start + + :param engine: engine to bind to + """ + log.info("initializing db models for %s", engine) meta.Base.metadata.bind = engine - #meta.Base2.metadata.bind = engine2 -#THIS IS A TEST FOR EXECUTING SCRIPT AND LOAD PYLONS APPLICATION GLOBALS -#from paste.deploy import appconfig -#from pylons import config -#from sqlalchemy import engine_from_config -#from rhodecode.config.environment import load_environment -# -#conf = appconfig('config:development.ini', relative_to = './../../') -#load_environment(conf.global_conf, conf.local_conf) -# -#engine = engine_from_config(config, 'sqlalchemy.') -#init_model(engine) -# DO SOMETHING +class BaseModel(object): + """Base Model for all RhodeCode models, it adds sql alchemy session + into instance of model + + :param sa: If passed it reuses this session instead of creating a new one + """ + + def __init__(self, sa=None): + if sa is not None: + self.sa = sa + else: + self.sa = meta.Session() diff --git a/rhodecode/model/caching_query.py b/rhodecode/model/caching_query.py --- a/rhodecode/model/caching_query.py +++ b/rhodecode/model/caching_query.py @@ -55,11 +55,11 @@ class CachingQuery(Query): the "public" method of configuring this state upon the CachingQuery. """ - + def __init__(self, manager, *args, **kw): self.cache_manager = manager Query.__init__(self, *args, **kw) - + def __iter__(self): """override __iter__ to pull results from Beaker if particular attributes have been configured. @@ -101,7 +101,7 @@ class CachingQuery(Query): """Set the value in the cache for this query.""" cache, cache_key = _get_cache_parameters(self) - cache.put(cache_key, value) + cache.put(cache_key, value) def query_callable(manager): def query(*arg, **kw): @@ -110,11 +110,11 @@ def query_callable(manager): def get_cache_region(name, region): if region not in beaker.cache.cache_regions: - raise BeakerException('Cache region not configured: %s' + raise BeakerException('Cache region `%s` not configured ' 'Check if proper cache settings are in the .ini files' % region) kw = beaker.cache.cache_regions[region] return beaker.cache.Cache._get_cache(name, kw) - + def _get_cache_parameters(query): """For a query with cache_region and cache_namespace configured, return the correspoinding Cache instance and cache key, based @@ -125,7 +125,7 @@ def _get_cache_parameters(query): raise ValueError("This Query does not have caching parameters configured.") region, namespace, cache_key = query._cache_parameters - + namespace = _namespace_from_query(namespace, query) if cache_key is None: @@ -153,15 +153,15 @@ def _namespace_from_query(namespace, que return namespace def _set_cache_parameters(query, region, namespace, cache_key): - + if hasattr(query, '_cache_parameters'): region, namespace, cache_key = query._cache_parameters raise ValueError("This query is already configured " - "for region %r namespace %r" % + "for region %r namespace %r" % (region, namespace) ) query._cache_parameters = region, namespace, cache_key - + class FromCache(MapperOption): """Specifies that a Query should load results from a cache.""" @@ -187,10 +187,10 @@ class FromCache(MapperOption): self.region = region self.namespace = namespace self.cache_key = cache_key - + def process_query(self, query): """Process a Query during normal loading operation.""" - + _set_cache_parameters(query, self.region, self.namespace, self.cache_key) class RelationshipCache(MapperOption): @@ -263,13 +263,13 @@ def _params_from_query(query): v = [] def visit_bindparam(bind): value = query._params.get(bind.key, bind.value) - + # lazyloader may dig a callable in here, intended # to late-evaluate params after autoflush is called. # convert to a scalar value. if callable(value): value = value() - + v.append(value) if query._criterion is not None: visitors.traverse(query._criterion, {}, {'bindparam':visit_bindparam}) diff --git a/rhodecode/model/db.py b/rhodecode/model/db.py --- a/rhodecode/model/db.py +++ b/rhodecode/model/db.py @@ -1,46 +1,119 @@ -from rhodecode.model.meta import Base +# -*- coding: utf-8 -*- +""" + rhodecode.model.db + ~~~~~~~~~~~~~~~~~~ + + Database Models for RhodeCode + + :created_on: Apr 08, 2010 + :author: marcink + :copyright: (C) 2009-2010 Marcin Kuzminski + :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; 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. +import logging +import datetime + from sqlalchemy import * -from sqlalchemy.orm import relation, backref +from sqlalchemy.exc import DatabaseError +from sqlalchemy.orm import relation, backref, class_mapper from sqlalchemy.orm.session import Session -from vcs.utils.lazy import LazyProperty -import logging + +from rhodecode.model.meta import Base log = logging.getLogger(__name__) -class RhodeCodeSettings(Base): +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): __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", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - app_settings_value = Column("app_settings_value", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + 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) -class RhodeCodeUi(Base): + def __init__(self, k, v): + self.app_settings_name = k + self.app_settings_value = v + + def __repr__(self): + return "" % (self.app_settings_name, + self.app_settings_value) + +class RhodeCodeUi(Base, BaseModel): __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", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_key = Column("ui_key", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_value = Column("ui_value", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_active = Column("ui_active", BOOLEAN(), nullable=True, unique=None, default=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_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True) -class User(Base): +class User(Base, BaseModel): __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", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - password = Column("password", TEXT(length=None, 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", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - lastname = Column("lastname", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - email = Column("email", TEXT(length=None, 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) + 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) + 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) + 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) - user_log = relation('UserLog') - user_perms = relation('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id") + user_log = relation('UserLog', cascade='all') + user_perms = relation('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all') - @LazyProperty + repositories = relation('Repository') + user_followers = relation('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all') + + @property def full_contact(self): return '%s %s <%s>' % (self.name, self.lastname, self.email) @@ -49,7 +122,6 @@ class User(Base): def update_lastlogin(self): """Update user lastlogin""" - import datetime try: session = Session.object_session(self) @@ -57,85 +129,129 @@ class User(Base): session.add(self) session.commit() log.debug('updated user %s lastlogin', self.username) - except Exception: + except (DatabaseError,): session.rollback() -class UserLog(Base): +class UserLog(Base, BaseModel): __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(u'users.user_id'), nullable=False, unique=None, default=None) - repository_id = Column("repository_id", INTEGER(length=None, convert_unicode=False, assert_unicode=None), ForeignKey(u'repositories.repo_id'), nullable=False, unique=None, default=None) - repository_name = Column("repository_name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - user_ip = Column("user_ip", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - action = Column("action", TEXT(length=None, 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) - revision = Column('revision', TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + user_log_id = Column("user_log_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) + repository_id = Column("repository_id", Integer(length=None, convert_unicode=False, assert_unicode=None), ForeignKey(u'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) + action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None) user = relation('User') repository = relation('Repository') -class Repository(Base): +class Repository(Base, BaseModel): __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", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) - user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=False, default=None) - private = Column("private", BOOLEAN(), nullable=True, unique=None, default=None) - description = Column("description", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - fork_id = Column("fork_id", INTEGER(), ForeignKey(u'repositories.repo_id'), nullable=True, unique=False, default=None) + 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') + user_id = Column("user_id", Integer(), ForeignKey(u'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) + fork_id = Column("fork_id", Integer(), ForeignKey(u'repositories.repo_id'), nullable=True, unique=False, default=None) user = relation('User') fork = relation('Repository', remote_side=repo_id) repo_to_perm = relation('RepoToPerm', cascade='all') - stats = relation('Statistics', 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 "" % (self.repo_id, self.repo_name) + return "" % (self.repo_id, self.repo_name) -class Permission(Base): +class Permission(Base, BaseModel): __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", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - permission_longname = Column("permission_longname", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + 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) def __repr__(self): return "" % (self.permission_id, self.permission_name) -class RepoToPerm(Base): +class RepoToPerm(Base, BaseModel): __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) - user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None) - permission_id = Column("permission_id", INTEGER(), ForeignKey(u'permissions.permission_id'), nullable=False, unique=None, default=None) - repository_id = Column("repository_id", INTEGER(), ForeignKey(u'repositories.repo_id'), nullable=False, unique=None, default=None) + repo_to_perm_id = Column("repo_to_perm_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) + permission_id = Column("permission_id", Integer(), ForeignKey(u'permissions.permission_id'), nullable=False, unique=None, default=None) + repository_id = Column("repository_id", Integer(), ForeignKey(u'repositories.repo_id'), nullable=False, unique=None, default=None) user = relation('User') permission = relation('Permission') repository = relation('Repository') -class UserToPerm(Base): +class UserToPerm(Base, BaseModel): __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) - user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None) - permission_id = Column("permission_id", INTEGER(), ForeignKey(u'permissions.permission_id'), nullable=False, unique=None, default=None) + user_to_perm_id = Column("user_to_perm_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) + permission_id = Column("permission_id", Integer(), ForeignKey(u'permissions.permission_id'), nullable=False, unique=None, default=None) user = relation('User') permission = relation('Permission') -class Statistics(Base): +class Statistics(Base, BaseModel): __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) - stat_on_revision = Column("stat_on_revision", INTEGER(), nullable=False) - commit_activity = Column("commit_activity", BLOB(), nullable=False)#JSON data - commit_activity_combined = Column("commit_activity_combined", BLOB(), nullable=False)#JSON data - languages = Column("languages", BLOB(), nullable=False)#JSON data + 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) + stat_on_revision = Column("stat_on_revision", Integer(), nullable=False) + commit_activity = Column("commit_activity", LargeBinary(), nullable=False)#JSON data + commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data + languages = Column("languages", LargeBinary(), nullable=False)#JSON data repository = relation('Repository', single_parent=True) +class UserFollowing(Base, BaseModel): + __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, BaseModel): + __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_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False) + + + def __init__(self, cache_key, cache_args=''): + self.cache_key = cache_key + self.cache_args = cache_args + self.cache_active = False + + def __repr__(self): + return "" % (self.cache_id, self.cache_key) + +class DbMigrateVersion(Base, BaseModel): + __tablename__ = 'db_migrate_version' + __table_args__ = {'useexisting':True} + repository_id = Column('repository_id', String(250), primary_key=True) + repository_path = Column('repository_path', Text) + version = Column('version', Integer) + diff --git a/rhodecode/model/forms.py b/rhodecode/model/forms.py --- a/rhodecode/model/forms.py +++ b/rhodecode/model/forms.py @@ -19,29 +19,34 @@ list=[1,2,3,4,5] for SELECT use formencode.All(OneOf(list), Int()) """ +import os +import re +import logging + +import formencode from formencode import All from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \ Email, Bool, StringBoolean -from pylons import session + from pylons.i18n.translation import _ -from rhodecode.lib.auth import check_password, get_crypt_password + +import rhodecode.lib.helpers as h +from rhodecode.lib.auth import authenticate, get_crypt_password +from rhodecode.lib.exceptions import LdapImportError from rhodecode.model import meta -from rhodecode.model.user_model import UserModel -from rhodecode.model.db import User, Repository -from sqlalchemy.exc import OperationalError -from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound +from rhodecode.model.user import UserModel +from rhodecode.model.repo import RepoModel +from rhodecode.model.db import User +from rhodecode import BACKENDS + from webhelpers.pylonslib.secure_form import authentication_token -import formencode -import logging -import os -import rhodecode.lib.helpers as h + log = logging.getLogger(__name__) - #this is needed to translate the messages using _() in validators class State_obj(object): _ = staticmethod(_) - + #=============================================================================== # VALIDATORS #=============================================================================== @@ -53,75 +58,114 @@ class ValidAuthToken(formencode.validato if value != authentication_token(): raise formencode.Invalid(self.message('invalid_token', state, search_number=value), value, state) - -def ValidUsername(edit, old_data): + +def ValidUsername(edit, old_data): class _ValidUsername(formencode.validators.FancyValidator): - + def validate_python(self, value, state): if value in ['default', 'new_user']: raise formencode.Invalid(_('Invalid username'), value, state) - #check if user is uniq - sa = meta.Session + #check if user is unique old_un = None if edit: - old_un = sa.query(User).get(old_data.get('user_id')).username - - if old_un != value or not edit: - if sa.query(User).filter(User.username == value).scalar(): + old_un = UserModel().get(old_data.get('user_id')).username + + if old_un != value or not edit: + if UserModel().get_by_username(value, cache=False, + case_insensitive=True): raise formencode.Invalid(_('This username already exists') , value, state) - meta.Session.remove() - - return _ValidUsername - + + + if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_]+$', value) is None: + raise formencode.Invalid(_('Username may only contain ' + 'alphanumeric characters underscores ' + 'or dashes and must begin with ' + 'alphanumeric character'), + value, state) + + + + return _ValidUsername + class ValidPassword(formencode.validators.FancyValidator): - + def to_python(self, value, state): + if value: - return get_crypt_password(value) - + + if value.get('password'): + try: + value['password'] = get_crypt_password(value['password']) + except UnicodeEncodeError: + e_dict = {'password':_('Invalid characters in password')} + raise formencode.Invalid('', value, state, error_dict=e_dict) + + if value.get('password_confirmation'): + try: + value['password_confirmation'] = \ + get_crypt_password(value['password_confirmation']) + except UnicodeEncodeError: + e_dict = {'password_confirmation':_('Invalid characters in password')} + raise formencode.Invalid('', value, state, error_dict=e_dict) + + if value.get('new_password'): + try: + value['new_password'] = \ + get_crypt_password(value['new_password']) + except UnicodeEncodeError: + e_dict = {'new_password':_('Invalid characters in password')} + raise formencode.Invalid('', value, state, error_dict=e_dict) + + return value + +class ValidPasswordsMatch(formencode.validators.FancyValidator): + + def validate_python(self, value, state): + + if value['password'] != value['password_confirmation']: + e_dict = {'password_confirmation': + _('Password do not match')} + raise formencode.Invalid('', value, state, error_dict=e_dict) + class ValidAuth(formencode.validators.FancyValidator): messages = { 'invalid_password':_('invalid password'), 'invalid_login':_('invalid user name'), - 'disabled_account':_('Your acccount is disabled') - + 'disabled_account':_('Your account is disabled') + } #error mapping e_dict = {'username':messages['invalid_login'], 'password':messages['invalid_password']} e_dict_disable = {'username':messages['disabled_account']} - + def validate_python(self, value, state): password = value['password'] username = value['username'] - user = UserModel().get_user_by_name(username) - if user is None: - raise formencode.Invalid(self.message('invalid_password', - state=State_obj), value, state, - error_dict=self.e_dict) - if user: - if user.active: - if user.username == username and check_password(password, - user.password): - return value - else: - log.warning('user %s not authenticated', username) - raise formencode.Invalid(self.message('invalid_password', - state=State_obj), value, state, - error_dict=self.e_dict) - else: + user = UserModel().get_by_username(username) + + if authenticate(username, password): + return value + else: + if user and user.active is False: log.warning('user %s is disabled', username) raise formencode.Invalid(self.message('disabled_account', state=State_obj), value, state, error_dict=self.e_dict_disable) - + else: + log.warning('user %s not authenticated', username) + raise formencode.Invalid(self.message('invalid_password', + state=State_obj), value, state, + error_dict=self.e_dict) + class ValidRepoUser(formencode.validators.FancyValidator): - + def to_python(self, value, state): + sa = meta.Session() try: - self.user_db = meta.Session.query(User)\ + self.user_db = sa.query(User)\ .filter(User.active == True)\ .filter(User.username == value).one() except Exception: @@ -129,31 +173,39 @@ class ValidRepoUser(formencode.validator value, state) finally: meta.Session.remove() - + return self.user_db.user_id -def ValidRepoName(edit, old_data): +def ValidRepoName(edit, old_data): class _ValidRepoName(formencode.validators.FancyValidator): - + def to_python(self, value, state): slug = h.repo_name_slug(value) if slug in ['_admin']: raise formencode.Invalid(_('This repository name is disallowed'), value, state) - if old_data.get('repo_name') != value or not edit: - sa = meta.Session - if sa.query(Repository).filter(Repository.repo_name == slug).scalar(): + if old_data.get('repo_name') != value or not edit: + if RepoModel().get_by_repo_name(slug, cache=False): raise formencode.Invalid(_('This repository already exists') , value, state) - meta.Session.remove() - return slug - - + return slug + + return _ValidRepoName +def ValidForkType(old_data): + class _ValidForkType(formencode.validators.FancyValidator): + + def to_python(self, value, state): + if old_data['repo_type'] != value: + raise formencode.Invalid(_('Fork have to be the same type as original'), + value, state) + return value + return _ValidForkType + class ValidPerms(formencode.validators.FancyValidator): messages = {'perm_new_user_name':_('This username is not valid')} - + def to_python(self, value, state): perms_update = [] perms_new = [] @@ -167,7 +219,7 @@ class ValidPerms(formencode.validators.F if (new_user, new_perm) not in perms_new: perms_new.append((new_user, new_perm)) else: - usr = k[5:] + usr = k[5:] if usr == 'default': if value['private']: #set none for default when updating to private repo @@ -184,60 +236,89 @@ class ValidPerms(formencode.validators.F except Exception: msg = self.message('perm_new_user_name', state=State_obj) - raise formencode.Invalid(msg, value, state, error_dict={'perm_new_user_name':msg}) + raise formencode.Invalid(msg, value, state, + error_dict={'perm_new_user_name':msg}) return value - + class ValidSettings(formencode.validators.FancyValidator): - + def to_python(self, value, state): #settings form can't edit user if value.has_key('user'): del['value']['user'] - + return value - + class ValidPath(formencode.validators.FancyValidator): def to_python(self, value, state): - isdir = os.path.isdir(value.replace('*', '')) - if (value.endswith('/*') or value.endswith('/**')) and isdir: - return value - elif not isdir: - msg = _('This is not a valid path') - else: - msg = _('You need to specify * or ** at the end of path (ie. /tmp/*)') - - raise formencode.Invalid(msg, value, state, - error_dict={'paths_root_path':msg}) + + if not os.path.isdir(value): + msg = _('This is not a valid path') + raise formencode.Invalid(msg, value, state, + error_dict={'paths_root_path':msg}) + return value def UniqSystemEmail(old_data): class _UniqSystemEmail(formencode.validators.FancyValidator): def to_python(self, value, state): + value = value.lower() if old_data.get('email') != value: - sa = meta.Session + sa = meta.Session() try: user = sa.query(User).filter(User.email == value).scalar() if user: - raise formencode.Invalid(_("That e-mail address is already taken") , + raise formencode.Invalid(_("This e-mail address is already taken") , value, state) finally: meta.Session.remove() - + return value - + return _UniqSystemEmail - + class ValidSystemEmail(formencode.validators.FancyValidator): def to_python(self, value, state): + value = value.lower() sa = meta.Session try: user = sa.query(User).filter(User.email == value).scalar() if user is None: - raise formencode.Invalid(_("That e-mail address doesn't exist.") , + raise formencode.Invalid(_("This e-mail address doesn't exist.") , value, state) finally: meta.Session.remove() - - return value + + return value + +class LdapLibValidator(formencode.validators.FancyValidator): + + def to_python(self, value, state): + + try: + import ldap + except ImportError: + raise LdapImportError + return value + +class BaseDnValidator(formencode.validators.FancyValidator): + + def to_python(self, value, state): + + try: + value % {'user':'valid'} + + if value.find('%(user)s') == -1: + raise formencode.Invalid(_("You need to specify %(user)s in " + "template for example uid=%(user)s " + ",dc=company...") , + value, state) + + except KeyError: + raise formencode.Invalid(_("Wrong template used, only %(user)s " + "is an valid entry") , + value, state) + + return value #=============================================================================== # FORMS @@ -266,65 +347,87 @@ class LoginForm(formencode.Schema): #chained validators have access to all data chained_validators = [ValidAuth] - + def UserForm(edit=False, old_data={}): class _UserForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = True - username = All(UnicodeString(strip=True, min=1, not_empty=True), ValidUsername(edit, old_data)) + username = All(UnicodeString(strip=True, min=1, not_empty=True), + ValidUsername(edit, old_data)) if edit: - new_password = All(UnicodeString(strip=True, min=6, not_empty=False), ValidPassword) + new_password = All(UnicodeString(strip=True, min=6, not_empty=False)) admin = StringBoolean(if_missing=False) else: - password = All(UnicodeString(strip=True, min=6, not_empty=True), ValidPassword) + password = All(UnicodeString(strip=True, min=6, not_empty=True)) active = StringBoolean(if_missing=False) name = UnicodeString(strip=True, min=1, not_empty=True) lastname = UnicodeString(strip=True, min=1, not_empty=True) email = All(Email(not_empty=True), UniqSystemEmail(old_data)) - + + chained_validators = [ValidPassword] + return _UserForm -RegisterForm = UserForm +def RegisterForm(edit=False, old_data={}): + class _RegisterForm(formencode.Schema): + allow_extra_fields = True + filter_extra_fields = True + username = All(ValidUsername(edit, old_data), + UnicodeString(strip=True, min=1, not_empty=True)) + password = All(UnicodeString(strip=True, min=6, not_empty=True)) + password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True)) + active = StringBoolean(if_missing=False) + name = UnicodeString(strip=True, min=1, not_empty=True) + lastname = UnicodeString(strip=True, min=1, not_empty=True) + email = All(Email(not_empty=True), UniqSystemEmail(old_data)) + + chained_validators = [ValidPasswordsMatch, ValidPassword] + + return _RegisterForm def PasswordResetForm(): class _PasswordResetForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = True - email = All(ValidSystemEmail(), Email(not_empty=True)) + email = All(ValidSystemEmail(), Email(not_empty=True)) return _PasswordResetForm -def RepoForm(edit=False, old_data={}): +def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()): class _RepoForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = False - repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data)) + repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), + ValidRepoName(edit, old_data)) description = UnicodeString(strip=True, min=1, not_empty=True) private = StringBoolean(if_missing=False) - + enable_statistics = StringBoolean(if_missing=False) + repo_type = OneOf(supported_backends) if edit: user = All(Int(not_empty=True), ValidRepoUser) - + chained_validators = [ValidPerms] return _RepoForm -def RepoForkForm(edit=False, old_data={}): +def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()): class _RepoForkForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = False - fork_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data)) + fork_name = All(UnicodeString(strip=True, min=1, not_empty=True), + ValidRepoName(edit, old_data)) description = UnicodeString(strip=True, min=1, not_empty=True) private = StringBoolean(if_missing=False) - + repo_type = All(ValidForkType(old_data), OneOf(supported_backends)) return _RepoForkForm def RepoSettingsForm(edit=False, old_data={}): class _RepoForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = False - repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data)) + repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), + ValidRepoName(edit, old_data)) description = UnicodeString(strip=True, min=1, not_empty=True) private = StringBoolean(if_missing=False) - + chained_validators = [ValidPerms, ValidSettings] return _RepoForm @@ -335,9 +438,9 @@ def ApplicationSettingsForm(): filter_extra_fields = False rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True) rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True) - + return _ApplicationSettingsForm - + def ApplicationUiSettingsForm(): class _ApplicationUiSettingsForm(formencode.Schema): allow_extra_fields = True @@ -346,16 +449,35 @@ def ApplicationUiSettingsForm(): paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True)) hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False) hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False) - + hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False) + hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False) + return _ApplicationUiSettingsForm def DefaultPermissionsForm(perms_choices, register_choices, create_choices): class _DefaultPermissionsForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = True - overwrite_default = OneOf(['true', 'false'], if_missing='false') + overwrite_default = StringBoolean(if_missing=False) + anonymous = OneOf(['True', 'False'], if_missing=False) default_perm = OneOf(perms_choices) default_register = OneOf(register_choices) default_create = OneOf(create_choices) - + return _DefaultPermissionsForm + + +def LdapSettingsForm(): + class _LdapSettingsForm(formencode.Schema): + allow_extra_fields = True + filter_extra_fields = True + pre_validators = [LdapLibValidator] + ldap_active = StringBoolean(if_missing=False) + ldap_host = UnicodeString(strip=True,) + ldap_port = Number(strip=True,) + ldap_ldaps = StringBoolean(if_missing=False) + ldap_dn_user = UnicodeString(strip=True,) + ldap_dn_pass = UnicodeString(strip=True,) + ldap_base_dn = All(BaseDnValidator, UnicodeString(strip=True,)) + + return _LdapSettingsForm diff --git a/rhodecode/model/permission_model.py b/rhodecode/model/permission.py rename from rhodecode/model/permission_model.py rename to rhodecode/model/permission.py --- a/rhodecode/model/permission_model.py +++ b/rhodecode/model/permission.py @@ -1,8 +1,15 @@ -#!/usr/bin/env python -# encoding: utf-8 -# Model for permissions -# Copyright (C) 2009-2010 Marcin Kuzminski - +# -*- coding: utf-8 -*- +""" + rhodecode.model.permission + ~~~~~~~~~~~~~~~~~~~~~~~~~~ + + permissions model for RhodeCode + + :created_on: Aug 20, 2010 + :author: marcink + :copyright: (C) 2009-2010 Marcin Kuzminski + :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; version 2 @@ -17,72 +24,91 @@ # 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 Aug 20, 2010 -Model for permissions -@author: marcink -""" -from pylons.i18n.translation import _ -from rhodecode.model.db import User, Permission, UserToPerm, RepoToPerm -from rhodecode.model.meta import Session import logging import traceback + +from sqlalchemy.exc import DatabaseError + +from rhodecode.model import BaseModel +from rhodecode.model.db import User, Permission, UserToPerm, RepoToPerm +from rhodecode.model.caching_query import FromCache + log = logging.getLogger(__name__) -class PermissionModel(object): +class PermissionModel(BaseModel): + """Permissions model for RhodeCode + """ - def __init__(self): - self.sa = Session() - - def get_default(self): - return self.sa.query(User).filter(User.username == 'default').scalar() - - def get_permission(self, id): - return self.sa.query(Permission).get(id) - - def get_permission_by_name(self, name): - return self.sa.query(Permission)\ - .filter(Permission.permission_name == name).scalar() - - + def get_permission(self, permission_id, cache=False): + """Get's permissions by id + + :param permission_id: id of permission to get from database + :param cache: use Cache for this query + """ + perm = self.sa.query(Permission) + if cache: + perm = perm.options(FromCache("sql_cache_short", + "get_permission_%s" % permission_id)) + return perm.get(permission_id) + + def get_permission_by_name(self, name, cache=False): + """Get's permissions by given name + + :param name: name to fetch + :param cache: Use cache for this query + """ + perm = self.sa.query(Permission)\ + .filter(Permission.permission_name == name) + if cache: + perm = perm.options(FromCache("sql_cache_short", + "get_permission_%s" % name)) + return perm.scalar() + def update(self, form_result): perm_user = self.sa.query(User)\ .filter(User.username == form_result['perm_user_name']).scalar() u2p = self.sa.query(UserToPerm).filter(UserToPerm.user == perm_user).all() if len(u2p) != 3: - raise Exception('There is more than 3 defined \ - permissions for defualt user. This should not happen please verify\ - your database') - + raise Exception('Defined: %s should be 3 permissions for default' + ' user. This should not happen please verify' + ' your database' % len(u2p)) + try: #stage 1 change defaults for p in u2p: if p.permission.permission_name.startswith('repository.'): - p.permission = self.get_permission_by_name(form_result['default_perm']) + p.permission = self.get_permission_by_name( + form_result['default_perm']) self.sa.add(p) - + if p.permission.permission_name.startswith('hg.register.'): - p.permission = self.get_permission_by_name(form_result['default_register']) + p.permission = self.get_permission_by_name( + form_result['default_register']) self.sa.add(p) - + if p.permission.permission_name.startswith('hg.create.'): - p.permission = self.get_permission_by_name(form_result['default_create']) + p.permission = self.get_permission_by_name( + form_result['default_create']) self.sa.add(p) + #stage 2 update all default permissions for repos if checked - if form_result['overwrite_default'] == 'true': - for r2p in self.sa.query(RepoToPerm).filter(RepoToPerm.user == perm_user).all(): - r2p.permission = self.get_permission_by_name(form_result['default_perm']) + if form_result['overwrite_default'] == True: + for r2p in self.sa.query(RepoToPerm)\ + .filter(RepoToPerm.user == perm_user).all(): + r2p.permission = self.get_permission_by_name( + form_result['default_perm']) self.sa.add(r2p) - + + #stage 3 set anonymous access + if perm_user.username == 'default': + perm_user.active = bool(form_result['anonymous']) + self.sa.add(perm_user) + + self.sa.commit() - except: + except (DatabaseError,): log.error(traceback.format_exc()) self.sa.rollback() - raise - - - - - + raise diff --git a/rhodecode/model/repo_model.py b/rhodecode/model/repo.py rename from rhodecode/model/repo_model.py rename to rhodecode/model/repo.py --- a/rhodecode/model/repo_model.py +++ b/rhodecode/model/repo.py @@ -1,7 +1,15 @@ -#!/usr/bin/env python -# encoding: utf-8 -# model for handling repositories actions -# Copyright (C) 2009-2010 Marcin Kuzminski +# -*- coding: utf-8 -*- +""" + rhodecode.model.repo + ~~~~~~~~~~~~~~~~~~~~ + + Repository model for rhodecode + + :created_on: Jun 5, 2010 + :author: marcink + :copyright: (C) 2009-2010 Marcin Kuzminski + :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; version 2 @@ -16,190 +24,240 @@ # 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 Jun 5, 2010 -model for handling repositories actions -@author: marcink -""" -from datetime import datetime -from pylons import app_globals as g -from rhodecode.lib.utils import check_repo -from rhodecode.model.db import Repository, RepoToPerm, User, Permission -from rhodecode.model.meta import Session -from rhodecode.model.user_model import UserModel -from rhodecode.lib.celerylib.tasks import create_repo_fork, run_task -import logging import os import shutil +import logging import traceback +from datetime import datetime + +from pylons import app_globals as g + +from rhodecode.model import BaseModel +from rhodecode.model.caching_query import FromCache +from rhodecode.model.db import Repository, RepoToPerm, User, Permission, \ + Statistics +from rhodecode.model.user import UserModel + +from vcs.backends import get_backend + log = logging.getLogger(__name__) -class RepoModel(object): - - def __init__(self, sa=None): - if not sa: - self.sa = Session() - else: - self.sa = sa - - def get(self, id): - return self.sa.query(Repository)\ - .filter(Repository.repo_name == id).scalar() - +class RepoModel(BaseModel): + + def get(self, repo_id, cache=False): + repo = self.sa.query(Repository)\ + .filter(Repository.repo_id == repo_id) + + if cache: + repo = repo.options(FromCache("sql_cache_short", + "get_repo_%s" % repo_id)) + return repo.scalar() + + + def get_by_repo_name(self, repo_name, cache=False): + repo = self.sa.query(Repository)\ + .filter(Repository.repo_name == repo_name) + + if cache: + repo = repo.options(FromCache("sql_cache_short", + "get_repo_%s" % repo_name)) + return repo.scalar() + def get_users_js(self): - + users = self.sa.query(User).filter(User.active == True).all() u_tmpl = '''{id:%s, fname:"%s", lname:"%s", nname:"%s"},''' users_array = '[%s];' % '\n'.join([u_tmpl % (u.user_id, u.name, - u.lastname, u.username) + u.lastname, u.username) for u in users]) - return users_array - - + return users_array + + def update(self, repo_name, form_data): try: + cur_repo = self.get_by_repo_name(repo_name, cache=False) + user_model = UserModel(self.sa) #update permissions for username, perm in form_data['perms_updates']: r2p = self.sa.query(RepoToPerm)\ - .filter(RepoToPerm.user == self.sa.query(User)\ - .filter(User.username == username).one())\ - .filter(RepoToPerm.repository == self.get(repo_name))\ + .filter(RepoToPerm.user == user_model.get_by_username(username))\ + .filter(RepoToPerm.repository == cur_repo)\ .one() - - r2p.permission_id = self.sa.query(Permission).filter( - Permission.permission_name == - perm).one().permission_id + + r2p.permission = self.sa.query(Permission)\ + .filter(Permission.permission_name == perm)\ + .scalar() self.sa.add(r2p) - + #set new permissions for username, perm in form_data['perms_new']: r2p = RepoToPerm() - r2p.repository = self.get(repo_name) - r2p.user = self.sa.query(User)\ - .filter(User.username == username).one() - - r2p.permission_id = self.sa.query(Permission).filter( - Permission.permission_name == perm)\ - .one().permission_id + r2p.repository = cur_repo + r2p.user = user_model.get_by_username(username, cache=False) + + r2p.permission = self.sa.query(Permission)\ + .filter(Permission.permission_name == perm)\ + .scalar() self.sa.add(r2p) - + #update current repo - cur_repo = self.get(repo_name) - for k, v in form_data.items(): if k == 'user': - cur_repo.user_id = v + cur_repo.user = user_model.get(v) else: setattr(cur_repo, k, v) - + self.sa.add(cur_repo) - + if repo_name != form_data['repo_name']: #rename our data - self.__rename_repo(repo_name, form_data['repo_name']) - + self.__rename_repo(repo_name, form_data['repo_name']) + self.sa.commit() except: log.error(traceback.format_exc()) self.sa.rollback() - raise - + raise + def create(self, form_data, cur_user, just_db=False, fork=False): try: if fork: + #force str since hg doesn't go with unicode repo_name = str(form_data['fork_name']) org_name = str(form_data['repo_name']) - + else: org_name = repo_name = str(form_data['repo_name']) new_repo = Repository() + new_repo.enable_statistics = True for k, v in form_data.items(): if k == 'repo_name': v = repo_name setattr(new_repo, k, v) - + if fork: parent_repo = self.sa.query(Repository)\ .filter(Repository.repo_name == org_name).scalar() new_repo.fork = parent_repo - + new_repo.user_id = cur_user.user_id self.sa.add(new_repo) - + #create default permission repo_to_perm = RepoToPerm() default = 'repository.read' - for p in UserModel(self.sa).get_default().user_perms: + for p in UserModel(self.sa).get_by_username('default', cache=False).user_perms: if p.permission.permission_name.startswith('repository.'): default = p.permission.permission_name break - + default_perm = 'repository.none' if form_data['private'] else default - + repo_to_perm.permission_id = self.sa.query(Permission)\ .filter(Permission.permission_name == default_perm)\ .one().permission_id - + repo_to_perm.repository_id = new_repo.repo_id - repo_to_perm.user_id = self.sa.query(User)\ - .filter(User.username == 'default').one().user_id - + repo_to_perm.user_id = UserModel(self.sa)\ + .get_by_username('default', cache=False).user_id + self.sa.add(repo_to_perm) self.sa.commit() + + + #now automatically start following this repository as owner + from rhodecode.model.scm import ScmModel + ScmModel(self.sa).toggle_following_repo(new_repo.repo_id, + cur_user.user_id) + if not just_db: - self.__create_repo(repo_name) - except: - log.error(traceback.format_exc()) - self.sa.rollback() - raise - - def create_fork(self, form_data, cur_user): - run_task(create_repo_fork, form_data, cur_user) - - def delete(self, repo): - try: - self.sa.delete(repo) - self.sa.commit() - self.__delete_repo(repo.repo_name) + self.__create_repo(repo_name, form_data['repo_type']) except: log.error(traceback.format_exc()) self.sa.rollback() raise - + + def create_fork(self, form_data, cur_user): + from rhodecode.lib.celerylib import tasks, run_task + run_task(tasks.create_repo_fork, form_data, cur_user) + + def delete(self, repo): + try: + self.sa.delete(repo) + self.__delete_repo(repo) + self.sa.commit() + except: + log.error(traceback.format_exc()) + self.sa.rollback() + raise + def delete_perm_user(self, form_data, repo_name): try: self.sa.query(RepoToPerm)\ - .filter(RepoToPerm.repository == self.get(repo_name))\ + .filter(RepoToPerm.repository \ + == self.get_by_repo_name(repo_name))\ .filter(RepoToPerm.user_id == form_data['user_id']).delete() self.sa.commit() except: log.error(traceback.format_exc()) self.sa.rollback() raise - - def __create_repo(self, repo_name): + + def delete_stats(self, repo_name): + try: + self.sa.query(Statistics)\ + .filter(Statistics.repository == \ + self.get_by_repo_name(repo_name)).delete() + self.sa.commit() + except: + log.error(traceback.format_exc()) + self.sa.rollback() + raise + + + def __create_repo(self, repo_name, alias): + """ + makes repository on filesystem + :param repo_name: + :param alias: + """ + from rhodecode.lib.utils import check_repo repo_path = os.path.join(g.base_path, repo_name) if check_repo(repo_name, g.base_path): log.info('creating repo %s in %s', repo_name, repo_path) - from vcs.backends.hg import MercurialRepository - MercurialRepository(repo_path, create=True) + backend = get_backend(alias) + backend(repo_path, create=True) def __rename_repo(self, old, new): + """ + renames repository on filesystem + :param old: old name + :param new: new name + """ log.info('renaming repo from %s to %s', old, new) - + old_path = os.path.join(g.base_path, old) new_path = os.path.join(g.base_path, new) if os.path.isdir(new_path): raise Exception('Was trying to rename to already existing dir %s', - new_path) + new_path) shutil.move(old_path, new_path) - - def __delete_repo(self, name): - rm_path = os.path.join(g.base_path, name) + + def __delete_repo(self, repo): + """ + removes repo from filesystem, the removal is acctually made by + added rm__ prefix into dir, and rename internat .hg/.git dirs so this + repository is no longer valid for rhodecode, can be undeleted later on + by reverting the renames on this repository + :param repo: repo object + """ + rm_path = os.path.join(g.base_path, repo.repo_name) log.info("Removing %s", rm_path) - #disable hg - shutil.move(os.path.join(rm_path, '.hg'), os.path.join(rm_path, 'rm__.hg')) + #disable hg/git + alias = repo.repo_type + shutil.move(os.path.join(rm_path, '.%s' % alias), + os.path.join(rm_path, 'rm__.%s' % alias)) #disable repo shutil.move(rm_path, os.path.join(g.base_path, 'rm__%s__%s' \ - % (datetime.today(), name))) + % (datetime.today(), repo.repo_name))) diff --git a/rhodecode/model/hg_model.py b/rhodecode/model/scm.py rename from rhodecode/model/hg_model.py rename to rhodecode/model/scm.py --- a/rhodecode/model/hg_model.py +++ b/rhodecode/model/scm.py @@ -1,8 +1,15 @@ -#!/usr/bin/env python -# encoding: utf-8 -# Model for RhodeCode -# Copyright (C) 2009-2010 Marcin Kuzminski -# +# -*- coding: utf-8 -*- +""" + rhodecode.model.scm + ~~~~~~~~~~~~~~~~~~~ + + Scm model for RhodeCode + + :created_on: Apr 9, 2010 + :author: marcink + :copyright: (C) 2009-2010 Marcin Kuzminski + :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; version 2 @@ -17,170 +24,354 @@ # 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 9, 2010 -Model for RhodeCode -@author: marcink -""" -from beaker.cache import cache_region +import os +import time +import traceback +import logging + +from vcs import get_backend +from vcs.utils.helpers import get_scm +from vcs.exceptions import RepositoryError, VCSError +from vcs.utils.lazy import LazyProperty + from mercurial import ui -from mercurial.hgweb.hgwebdir_mod import findrepos -from pylons.i18n.translation import _ + +from beaker.cache import cache_region, region_invalidate + +from rhodecode import BACKENDS from rhodecode.lib import helpers as h -from rhodecode.lib.utils import invalidate_cache from rhodecode.lib.auth import HasRepoPermissionAny -from rhodecode.model import meta -from rhodecode.model.db import Repository, User +from rhodecode.lib.utils import get_repos, make_ui, action_logger +from rhodecode.model import BaseModel +from rhodecode.model.user import UserModel + +from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \ + UserFollowing, UserLog +from rhodecode.model.caching_query import FromCache + from sqlalchemy.orm import joinedload -from vcs.exceptions import RepositoryError, VCSError -import logging -import os -import sys +from sqlalchemy.orm.session import make_transient +from sqlalchemy.exc import DatabaseError + log = logging.getLogger(__name__) -try: - from vcs.backends.hg import MercurialRepository -except ImportError: - sys.stderr.write('You have to import vcs module') - raise Exception('Unable to import vcs') - -def _get_repos_cached_initial(app_globals, initial): - """return cached dict with repos - """ - g = app_globals - return HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui, initial) -@cache_region('long_term', 'cached_repo_list') -def _get_repos_cached(): - """return cached dict with repos - """ - log.info('getting all repositories list') - from pylons import app_globals as g - return HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui) +class UserTemp(object): + def __init__(self, user_id): + self.user_id = user_id +class RepoTemp(object): + def __init__(self, repo_id): + self.repo_id = repo_id -@cache_region('super_short_term', 'cached_repos_switcher_list') -def _get_repos_switcher_cached(cached_repo_list): - repos_lst = [] - for repo in [x for x in cached_repo_list.values()]: - if HasRepoPermissionAny('repository.write', 'repository.read', - 'repository.admin')(repo.name, 'main page check'): - repos_lst.append((repo.name, repo.dbrepo.private,)) - - return sorted(repos_lst, key=lambda k:k[0].lower()) - -@cache_region('long_term', 'full_changelog') -def _full_changelog_cached(repo_name): - log.info('getting full changelog for %s', repo_name) - return list(reversed(list(HgModel().get_repo(repo_name)))) - -class HgModel(object): - """Mercurial Model +class ScmModel(BaseModel): + """Generic Scm Model """ - def __init__(self): - pass - - @staticmethod - def repo_scan(repos_prefix, repos_path, baseui, initial=False): - """ - Listing of repositories in given path. This path should not be a - repository itself. Return a dictionary of repository objects - :param repos_path: path to directory it could take syntax with - * or ** for deep recursive displaying repositories + @LazyProperty + def repos_path(self): + """Get's the repositories root path from database """ - sa = meta.Session() - def check_repo_dir(path): - """Checks the repository - :param path: - """ - repos_path = path.split('/') - if repos_path[-1] in ['*', '**']: - repos_path = repos_path[:-1] - if repos_path[0] != '/': - repos_path[0] = '/' - if not os.path.isdir(os.path.join(*repos_path)): - raise RepositoryError('Not a valid repository in %s' % path) - if not repos_path.endswith('*'): - raise VCSError('You need to specify * or ** at the end of path ' - 'for recursive scanning') - - check_repo_dir(repos_path) + + q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one() + + return q.ui_value + + def repo_scan(self, repos_path, baseui): + """Listing of repositories in given path. This path should not be a + repository itself. Return a dictionary of repository objects + + :param repos_path: path to directory containing repositories + :param baseui: baseui instance to instantiate MercurialRepostitory with + """ + log.info('scanning for repositories in %s', repos_path) - repos = findrepos([(repos_prefix, repos_path)]) + if not isinstance(baseui, ui.ui): - baseui = ui.ui() - + baseui = make_ui('db') repos_list = {} - for name, path in repos: + + for name, path in get_repos(repos_path): try: - #name = name.split('/')[-1] if repos_list.has_key(name): - raise RepositoryError('Duplicate repository name %s found in' - ' %s' % (name, path)) + raise RepositoryError('Duplicate repository name %s ' + 'found in %s' % (name, path)) else: - - repos_list[name] = MercurialRepository(path, baseui=baseui) - repos_list[name].name = name - - dbrepo = None - if not initial: - #for initial scann on application first run we don't - #have db repos yet. - dbrepo = sa.query(Repository)\ - .options(joinedload(Repository.fork))\ - .filter(Repository.repo_name == name)\ - .scalar() - - if dbrepo: - log.info('Adding db instance to cached list') - repos_list[name].dbrepo = dbrepo - repos_list[name].description = dbrepo.description - if dbrepo.user: - repos_list[name].contact = dbrepo.user.full_contact - else: - repos_list[name].contact = sa.query(User)\ - .filter(User.admin == True).first().full_contact + + klass = get_backend(path[0]) + + if path[0] == 'hg' and path[0] in BACKENDS.keys(): + repos_list[name] = klass(path[1], baseui=baseui) + + if path[0] == 'git' and path[0] in BACKENDS.keys(): + repos_list[name] = klass(path[1]) except OSError: continue - meta.Session.remove() + return repos_list + + def get_repos(self, all_repos=None): + """Get all repos from db and for each repo create it's backend instance. + and fill that backed with information from database - def get_repos(self): - for name, repo in _get_repos_cached().items(): - if repo._get_hidden(): - #skip hidden web repository - continue - - last_change = repo.last_change - tip = h.get_changeset_safe(repo, 'tip') - - tmp_d = {} - tmp_d['name'] = repo.name - tmp_d['name_sort'] = tmp_d['name'].lower() - tmp_d['description'] = repo.description - tmp_d['description_sort'] = tmp_d['description'] - tmp_d['last_change'] = last_change - tmp_d['last_change_sort'] = last_change[1] - last_change[0] - tmp_d['tip'] = tip.short_id - tmp_d['tip_sort'] = tip.revision - tmp_d['rev'] = tip.revision - tmp_d['contact'] = repo.contact - tmp_d['contact_sort'] = tmp_d['contact'] - tmp_d['repo_archives'] = list(repo._get_archives()) - tmp_d['last_msg'] = tip.message - tmp_d['repo'] = repo - yield tmp_d + :param all_repos: give specific repositories list, good for filtering + """ + + if all_repos is None: + all_repos = self.sa.query(Repository)\ + .order_by(Repository.repo_name).all() + + #get the repositories that should be invalidated + invalidation_list = [str(x.cache_key) for x in \ + self.sa.query(CacheInvalidation.cache_key)\ + .filter(CacheInvalidation.cache_active == False)\ + .all()] + + for r in all_repos: + + repo = self.get(r.repo_name, invalidation_list) + + if repo is not None: + last_change = repo.last_change + tip = h.get_changeset_safe(repo, 'tip') + + tmp_d = {} + tmp_d['name'] = repo.name + tmp_d['name_sort'] = tmp_d['name'].lower() + tmp_d['description'] = repo.dbrepo.description + tmp_d['description_sort'] = tmp_d['description'] + tmp_d['last_change'] = last_change + tmp_d['last_change_sort'] = time.mktime(last_change.timetuple()) + tmp_d['tip'] = tip.raw_id + tmp_d['tip_sort'] = tip.revision + tmp_d['rev'] = tip.revision + tmp_d['contact'] = repo.dbrepo.user.full_contact + tmp_d['contact_sort'] = tmp_d['contact'] + tmp_d['repo_archives'] = list(repo._get_archives()) + tmp_d['last_msg'] = tip.message + tmp_d['repo'] = repo + yield tmp_d def get_repo(self, repo_name): - try: - repo = _get_repos_cached()[repo_name] + return self.get(repo_name) + + def get(self, repo_name, invalidation_list=None): + """Get's repository from given name, creates BackendInstance and + propagates it's data from database with all additional information + + :param repo_name: + :param invalidation_list: if a invalidation list is given the get + method should not manually check if this repository needs + invalidation and just invalidate the repositories in list + + """ + if not HasRepoPermissionAny('repository.read', 'repository.write', + 'repository.admin')(repo_name, 'get repo check'): + return + + #====================================================================== + # CACHE FUNCTION + #====================================================================== + @cache_region('long_term') + def _get_repo(repo_name): + + repo_path = os.path.join(self.repos_path, repo_name) + + try: + alias = get_scm(repo_path)[0] + + log.debug('Creating instance of %s repository', alias) + backend = get_backend(alias) + except VCSError: + log.error(traceback.format_exc()) + return + + if alias == 'hg': + from pylons import app_globals as g + repo = backend(repo_path, create=False, baseui=g.baseui) + #skip hidden web repository + if repo._get_hidden(): + return + else: + repo = backend(repo_path, create=False) + + dbrepo = self.sa.query(Repository)\ + .options(joinedload(Repository.fork))\ + .options(joinedload(Repository.user))\ + .filter(Repository.repo_name == repo_name)\ + .scalar() + + make_transient(dbrepo) + if dbrepo.user: + make_transient(dbrepo.user) + if dbrepo.fork: + make_transient(dbrepo.fork) + + repo.dbrepo = dbrepo return repo - except KeyError: - #i we're here and we got key errors let's try to invalidate the - #cahce and try again - invalidate_cache('cached_repo_list') - repo = _get_repos_cached()[repo_name] - return repo - + + pre_invalidate = True + if invalidation_list is not None: + pre_invalidate = repo_name in invalidation_list + + if pre_invalidate: + invalidate = self._should_invalidate(repo_name) + + if invalidate: + log.info('invalidating cache for repository %s', repo_name) + region_invalidate(_get_repo, None, repo_name) + self._mark_invalidated(invalidate) + + return _get_repo(repo_name) + + + + def mark_for_invalidation(self, repo_name): + """Puts cache invalidation task into db for + further global cache invalidation + :param repo_name: this repo that should invalidation take place + """ + + log.debug('marking %s for invalidation', repo_name) + cache = self.sa.query(CacheInvalidation)\ + .filter(CacheInvalidation.cache_key == repo_name).scalar() + + if cache: + #mark this cache as inactive + cache.cache_active = False + else: + log.debug('cache key not found in invalidation db -> creating one') + cache = CacheInvalidation(repo_name) + + try: + self.sa.add(cache) + self.sa.commit() + except (DatabaseError,): + log.error(traceback.format_exc()) + 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() + action_logger(UserTemp(user_id), + 'stopped_following_repo', + RepoTemp(follow_repo_id)) + 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() + action_logger(UserTemp(user_id), + 'started_following_repo', + RepoTemp(follow_repo_id)) + 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 = UserModel(self.sa).get_by_username(username) + + f = self.sa.query(UserFollowing)\ + .filter(UserFollowing.follows_user == u)\ + .filter(UserFollowing.user_id == user_id).scalar() + + return f is not None + + def get_followers(self, repo_id): + return self.sa.query(UserFollowing)\ + .filter(UserFollowing.follows_repo_id == repo_id).count() + + def get_forks(self, repo_id): + return self.sa.query(Repository)\ + .filter(Repository.fork_id == repo_id).count() + + + def get_unread_journal(self): + return self.sa.query(UserLog).count() + + + def _should_invalidate(self, repo_name): + """Looks up database for invalidation signals for this repo_name + :param repo_name: + """ + + ret = self.sa.query(CacheInvalidation)\ + .options(FromCache('sql_cache_short', + 'get_invalidation_%s' % repo_name))\ + .filter(CacheInvalidation.cache_key == repo_name)\ + .filter(CacheInvalidation.cache_active == False)\ + .scalar() + + return ret + + def _mark_invalidated(self, cache_key): + """ Marks all occurences of cache to invaldation as already invalidated + + :param cache_key: + """ + + if cache_key: + log.debug('marking %s as already invalidated', cache_key) + try: + cache_key.cache_active = True + self.sa.add(cache_key) + self.sa.commit() + except (DatabaseError,): + log.error(traceback.format_exc()) + self.sa.rollback() + diff --git a/rhodecode/model/settings.py b/rhodecode/model/settings.py new file mode 100644 --- /dev/null +++ b/rhodecode/model/settings.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Model for RhodeCode settings +# Copyright (C) 2009-2010 Marcin Kuzminski +# +# 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 Nov 17, 2010 +Model for RhodeCode +:author: marcink +""" + +from rhodecode.lib import helpers as h +from rhodecode.model import BaseModel +from rhodecode.model.caching_query import FromCache +from rhodecode.model.db import RhodeCodeSettings +from sqlalchemy.orm import joinedload +import logging + +log = logging.getLogger(__name__) + +class SettingsModel(BaseModel): + """ + Settings model + """ + + def get(self, settings_key, cache=False): + r = self.sa.query(RhodeCodeSettings)\ + .filter(RhodeCodeSettings.app_settings_name == settings_key).scalar() + if cache: + r = r.options(FromCache("sql_cache_short", + "get_setting_%s" % settings_key)) + return r + + def get_app_settings(self): + ret = self.sa.query(RhodeCodeSettings)\ + .options(FromCache("sql_cache_short", + "get_hg_settings")).all() + + if not ret: + raise Exception('Could not get application settings !') + settings = {} + for each in ret: + settings['rhodecode_' + each.app_settings_name] = each.app_settings_value + + return settings + + def get_ldap_settings(self): + """ + Returns ldap settings from database + :returns: + ldap_active + ldap_host + ldap_port + ldap_ldaps + ldap_dn_user + ldap_dn_pass + ldap_base_dn + """ + + r = self.sa.query(RhodeCodeSettings)\ + .filter(RhodeCodeSettings.app_settings_name\ + .startswith('ldap_'))\ + .all() + + fd = {} + + for row in r: + v = row.app_settings_value + if v in ['0', '1']: + v = v == '1' + fd.update({row.app_settings_name:v}) + + return fd diff --git a/rhodecode/model/user_model.py b/rhodecode/model/user.py rename from rhodecode/model/user_model.py rename to rhodecode/model/user.py --- a/rhodecode/model/user_model.py +++ b/rhodecode/model/user.py @@ -1,8 +1,15 @@ -#!/usr/bin/env python -# encoding: utf-8 -# Model for users -# Copyright (C) 2009-2010 Marcin Kuzminski -# +# -*- coding: utf-8 -*- +""" + package.rhodecode.model.user + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + users model for RhodeCode + + :created_on: Apr 9, 2010 + :author: marcink + :copyright: (C) 2009-2010 Marcin Kuzminski + :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; version 2 @@ -18,92 +25,138 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. -""" -Created on April 9, 2010 -Model for users -@author: marcink -""" -from rhodecode.lib import auth +import logging +import traceback + from pylons.i18n.translation import _ -from rhodecode.lib.celerylib import tasks, run_task + +from rhodecode.model import BaseModel +from rhodecode.model.caching_query import FromCache from rhodecode.model.db import User -from rhodecode.model.meta import Session -import traceback -import logging + +from rhodecode.lib.exceptions import DefaultUserException, UserOwnsReposException + +from sqlalchemy.exc import DatabaseError + log = logging.getLogger(__name__) -class DefaultUserException(Exception):pass - -class UserModel(object): +class UserModel(BaseModel): - def __init__(self, sa=None): - if not sa: - self.sa = Session() + def get(self, user_id, cache=False): + user = self.sa.query(User) + if cache: + user = user.options(FromCache("sql_cache_short", + "get_user_%s" % user_id)) + return user.get(user_id) + + + def get_by_username(self, username, cache=False, case_insensitive=False): + + if case_insensitive: + user = self.sa.query(User).filter(User.username.ilike(username)) else: - self.sa = sa - - def get_default(self): - return self.sa.query(User).filter(User.username == 'default').scalar() - - def get_user(self, id): - return self.sa.query(User).get(id) - - def get_user_by_name(self, name): - return self.sa.query(User).filter(User.username == name).scalar() - + user = self.sa.query(User)\ + .filter(User.username == username) + if cache: + user = user.options(FromCache("sql_cache_short", + "get_user_%s" % username)) + return user.scalar() + def create(self, form_data): try: new_user = User() for k, v in form_data.items(): setattr(new_user, k, v) - - self.sa.add(new_user) - self.sa.commit() - except: - log.error(traceback.format_exc()) - self.sa.rollback() - raise - - def create_registration(self, form_data): - try: - new_user = User() - for k, v in form_data.items(): - if k != 'admin': - setattr(new_user, k, v) - + self.sa.add(new_user) self.sa.commit() except: log.error(traceback.format_exc()) self.sa.rollback() - raise - - def update(self, uid, form_data): + raise + + def create_ldap(self, username, password): + """ + Checks if user is in database, if not creates this user marked + as ldap user + :param username: + :param password: + """ + from rhodecode.lib.auth import get_crypt_password + log.debug('Checking for such ldap account in RhodeCode database') + if self.get_by_username(username, case_insensitive=True) is None: + try: + new_user = User() + new_user.username = username.lower()#add ldap account always lowercase + new_user.password = get_crypt_password(password) + new_user.email = '%s@ldap.server' % username + new_user.active = True + new_user.is_ldap = True + new_user.name = '%s@ldap' % username + new_user.lastname = '' + + + self.sa.add(new_user) + self.sa.commit() + return True + except (DatabaseError,): + log.error(traceback.format_exc()) + self.sa.rollback() + raise + log.debug('this %s user exists skipping creation of ldap account', + username) + return False + + def create_registration(self, form_data): + from rhodecode.lib.celerylib import tasks, run_task try: - new_user = self.sa.query(User).get(uid) + new_user = User() + for k, v in form_data.items(): + if k != 'admin': + setattr(new_user, k, v) + + self.sa.add(new_user) + self.sa.commit() + body = ('New user registration\n' + 'username: %s\n' + 'email: %s\n') + body = body % (form_data['username'], form_data['email']) + + run_task(tasks.send_email, None, + _('[RhodeCode] New User registration'), + body) + except: + log.error(traceback.format_exc()) + self.sa.rollback() + raise + + def update(self, user_id, form_data): + try: + new_user = self.get(user_id, cache=False) if new_user.username == 'default': raise DefaultUserException( - _("You can't Edit this user since it's" + _("You can't Edit this user since it's" " crucial for entire application")) + for k, v in form_data.items(): if k == 'new_password' and v != '': new_user.password = v else: setattr(new_user, k, v) - + self.sa.add(new_user) self.sa.commit() except: log.error(traceback.format_exc()) self.sa.rollback() - raise - - def update_my_account(self, uid, form_data): + raise + + def update_my_account(self, user_id, form_data): try: - new_user = self.sa.query(User).get(uid) + new_user = self.get(user_id, cache=False) if new_user.username == 'default': raise DefaultUserException( - _("You can't Edit this user since it's" + _("You can't Edit this user since it's" " crucial for entire application")) for k, v in form_data.items(): if k == 'new_password' and v != '': @@ -111,28 +164,60 @@ class UserModel(object): else: if k not in ['admin', 'active']: setattr(new_user, k, v) - + self.sa.add(new_user) self.sa.commit() except: log.error(traceback.format_exc()) self.sa.rollback() - raise - - def delete(self, id): + raise + + def delete(self, user_id): try: - - user = self.sa.query(User).get(id) + user = self.get(user_id, cache=False) if user.username == 'default': raise DefaultUserException( - _("You can't remove this user since it's" + _("You can't remove this user since it's" " crucial for entire application")) + if user.repositories: + raise UserOwnsReposException(_('This user still owns %s ' + 'repositories and cannot be ' + 'removed. Switch owners or ' + 'remove those repositories') \ + % user.repositories) self.sa.delete(user) - self.sa.commit() + self.sa.commit() except: log.error(traceback.format_exc()) self.sa.rollback() - raise + raise def reset_password(self, data): + from rhodecode.lib.celerylib import tasks, run_task run_task(tasks.reset_user_password, data['email']) + + + def fill_data(self, user): + """ + Fills user data with those from database and log out user if not + present in database + :param user: + """ + + if not hasattr(user, 'user_id') or user.user_id is None: + raise Exception('passed in user has to have the user_id attribute') + + + log.debug('filling auth user data') + try: + dbuser = self.get(user.user_id) + user.username = dbuser.username + user.is_admin = dbuser.admin + user.name = dbuser.name + user.lastname = dbuser.lastname + user.email = dbuser.email + except: + log.error(traceback.format_exc()) + user.is_authenticated = False + + return user diff --git a/rhodecode/public/css/diff.css b/rhodecode/public/css/diff.css --- a/rhodecode/public/css/diff.css +++ b/rhodecode/public/css/diff.css @@ -62,7 +62,6 @@ table.code-difftable td { .code-difftable .lineno{ background:none repeat scroll 0 0 #EEEEEE !important; - border-right:1px solid #DDDDDD; padding-left:2px; padding-right:2px; text-align:right; @@ -70,6 +69,12 @@ table.code-difftable td { -moz-user-select:none; -webkit-user-select: none; } +.code-difftable .new { + border-right: 1px solid #CCC !important; +} +.code-difftable .old { + border-right: 1px solid #CCC !important; +} .code-difftable .lineno pre{ color:#747474 !important; font:11px "Bitstream Vera Sans Mono",Monaco,"Courier New",Courier,monospace !important; @@ -78,7 +83,8 @@ table.code-difftable td { width:20px; } .code-difftable .lineno a{ - color:#0000CC !important; +font-weight: 700; +cursor: pointer; } .code-difftable .code td{ margin:0; diff --git a/rhodecode/public/css/style.css b/rhodecode/public/css/style.css --- a/rhodecode/public/css/style.css +++ b/rhodecode/public/css/style.css @@ -190,18 +190,32 @@ margin:0; padding:0 30px; } + +#header ul#logged-user{ +margin-bottom:5px !important; +-webkit-border-radius: 0px 0px 8px 8px; +-khtml-border-radius: 0px 0px 8px 8px; +-moz-border-radius: 0px 0px 8px 8px; +border-radius: 0px 0px 8px 8px; +height:37px; +background:url("../images/header_inner.png") repeat-x scroll 0 0 #003367 +} + #header ul#logged-user li { list-style:none; float:left; -border-left:1px solid #bbb; -border-right:1px solid #a5a5a5; -margin:-2px 0 0; -padding:10px 12px; +margin:8px 0 0; +padding:4px 12px; +border-left: 1px solid #316293; } #header ul#logged-user li.first { border-left:none; -margin:-6px; +margin:4px; +} + +#header ul#logged-user li.first div.gravatar { +margin-top:-2px; } #header ul#logged-user li.first div.account { @@ -214,13 +228,12 @@ border-right:none; } #header ul#logged-user li a { -color:#4e4e4e; +color:#fff; font-weight:700; text-decoration:none; } #header ul#logged-user li a:hover { -color:#376ea6; text-decoration:underline; } @@ -229,7 +242,7 @@ color:#fff; } #header ul#logged-user li.highlight a:hover { -color:#376ea6; +color:#FFF; } #header #header-inner { @@ -258,9 +271,8 @@ background-position:0 -40px; #header #header-inner #logo h1 { color:#FFF; -font-size:14px; -text-transform:uppercase; -margin:13px 0 0 13px; +font-size:18px; +margin:10px 0 0 13px; padding:0; } @@ -303,6 +315,10 @@ background:#369 url("../../images/quick_ padding:0; } +#header #header-inner #quick li span.short { +padding:9px 6px 8px 6px; +} + #header #header-inner #quick li span { top:0; right:0; @@ -329,6 +345,15 @@ border-right:1px solid #2e5c89; padding:8px 8px 4px; } +#header #header-inner #quick li span.icon_short { +top:0; +left:0; +border-left:none; +background:url("../../images/quick_l.png") no-repeat top left; +border-right:1px solid #2e5c89; +padding:9px 4px 4px; +} + #header #header-inner #quick li a:hover { background:#4e4e4e url("../../images/quick_l_selected.png") no-repeat top left; } @@ -338,12 +363,13 @@ border-left:1px solid #545454; background:url("../../images/quick_r_selected.png") no-repeat top right; } -#header #header-inner #quick li a:hover span.icon { +#header #header-inner #quick li a:hover span.icon,#header #header-inner #quick li a:hover span.icon_short { border-left:none; border-right:1px solid #464646; background:url("../../images/quick_l_selected.png") no-repeat top left; } + #header #header-inner #quick ul { top:29px; right:0; @@ -364,6 +390,12 @@ overflow-x:hidden; overflow-y:auto; } +#header #header-inner #quick .repo_switcher_type{ +position:absolute; +left:0; +top:9px; + +} #header #header-inner #quick li ul li { border-bottom:1px solid #ddd; } @@ -394,7 +426,7 @@ right:200px; max-height:275px; overflow:auto; overflow-x:hidden; -white-space:nowrap; +white-space:normal; } #header #header-inner #quick li ul li a.journal,#header #header-inner #quick li ul li a.journal:hover { @@ -418,6 +450,20 @@ margin:0; padding:12px 9px 7px 24px; } +#header #header-inner #quick li ul li a.hg,#header #header-inner #quick li ul li a.hg:hover { +background:url("../images/icons/hgicon.png") no-repeat scroll 4px 9px #FFF; +min-width:167px; +margin:0 0 0 14px; +padding:12px 9px 7px 24px; +} + +#header #header-inner #quick li ul li a.git,#header #header-inner #quick li ul li a.git:hover { +background:url("../images/icons/giticon.png") no-repeat scroll 4px 9px #FFF; +min-width:167px; +margin:0 0 0 14px; +padding:12px 9px 7px 24px; +} + #header #header-inner #quick li ul li a.repos,#header #header-inner #quick li ul li a.repos:hover { background:url("../images/icons/database_edit.png") no-repeat scroll 4px 9px #FFF; width:167px; @@ -446,6 +492,13 @@ margin:0; padding:12px 9px 7px 24px; } +#header #header-inner #quick li ul li a.ldap,#header #header-inner #quick li ul li a.ldap:hover { +background:#FFF url("../images/icons/server_key.png") no-repeat 4px 9px; +width:167px; +margin:0; +padding:12px 9px 7px 24px; +} + #header #header-inner #quick li ul li a.fork,#header #header-inner #quick li ul li a.fork:hover { background:#FFF url("../images/icons/arrow_divide.png") no-repeat 4px 9px; width:167px; @@ -787,7 +840,7 @@ padding:0 0 8px; } #content div.box div.form div.fields div.field div.label-select { -padding:2px 0 0 5px; +padding:5px 0 0 5px; } #content div.box-left div.form div.fields div.field div.label-select,#content div.box-right div.form div.fields div.field div.label-select { @@ -806,16 +859,8 @@ font-weight:700; #content div.box div.form div.fields div.field div.input { margin:0 0 0 200px; } - #content div.box-left div.form div.fields div.field div.input,#content div.box-right div.form div.fields div.field div.input { -clear:both; -overflow:hidden; -border-top:1px solid #b3b3b3; -border-left:1px solid #b3b3b3; -border-right:1px solid #eaeaea; -border-bottom:1px solid #eaeaea; -margin:0; -padding:7px 7px 6px; +margin:0 0 0 0px; } #content div.box div.form div.fields div.field div.input input { @@ -831,11 +876,7 @@ margin:0; padding:7px 7px 6px; } -#content div.box-left div.form div.fields div.field div.input input,#content div.box-right div.form div.fields div.field div.input input { -width:100%; -border:none; -padding:0; -} + #content div.box div.form div.fields div.field div.input input.small { width:30%; @@ -1320,7 +1361,6 @@ padding:0 0 2px; } #register div.title { -width:420px; clear:both; overflow:hidden; position:relative; @@ -1330,7 +1370,6 @@ padding:0; } #register div.inner { -width:380px; background:#FFF; border-top:none; border-bottom:none; @@ -1339,7 +1378,7 @@ padding:20px; } #register div.form div.fields div.field div.label { -width:100px; +width:135px; float:left; text-align:right; margin:2px 10px 0 0; @@ -1347,7 +1386,7 @@ padding:5px 0 0 5px; } #register div.form div.fields div.field div.input input { -width:245px; +width:300px; background:#FFF; border-top:1px solid #b3b3b3; border-left:1px solid #b3b3b3; @@ -1366,7 +1405,7 @@ overflow:hidden; border-top:1px solid #DDD; text-align:left; margin:0; -padding:10px 0 0 114px; +padding:10px 0 0 150px; } #register div.form div.fields div.buttons div.highlight input.ui-state-default { @@ -1525,6 +1564,7 @@ display:block; float:right; text-align:center; min-width:15px; +cursor: help; } .right .changes .added { @@ -1618,6 +1658,17 @@ height:16px; padding-left:20px; text-align:left; } +.diffblock .changeset_file{ +background:url("../images/icons/file.png") no-repeat scroll 3px; +height:16px; +padding-left:22px; +text-align:left; +font-size: 14px; +} + +.diffblock .changeset_header{ +margin-left: 6px !important; +} table.code-browser .browser-dir { background:url("../images/icons/folder_16.png") no-repeat scroll 3px; @@ -1771,6 +1822,36 @@ 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; +} + +.currently_following{ +padding-left: 10px; +padding-bottom:5px; +} + +.journal_highlight{ +font-weight: bold; +text-decoration: underline; +} + .add_icon { background:url("../images/icons/add.png") no-repeat scroll 3px; height:16px; @@ -1795,6 +1876,14 @@ padding-top:1px; text-align:left; } +.refresh_icon { +background:url("../images/icons/arrow_refresh.png") no-repeat scroll 3px; +height:16px; +padding-left:20px; +padding-top:1px; +text-align:left; +} + .rss_icon { background:url("../images/icons/rss_16.png") no-repeat scroll 3px; height:16px; @@ -1944,7 +2033,7 @@ padding:2px 2px 0; } #header,#content,#footer { -min-width:1224px; +min-width:1024px; } #content { @@ -2047,10 +2136,6 @@ margin:0; padding:0; } -#content div.box div.form div.fields div.field div.label-checkbox,#content div.box div.form div.fields div.field div.label-radio,#content div.box div.form div.fields div.field div.label-textarea { -padding:0 0 0 5px !important; -} - #content div.box div.form div.fields div.field div.label span,#login div.form div.fields div.field div.label span,#register div.form div.fields div.field div.label span { height:1%; display:block; @@ -2079,11 +2164,17 @@ border-bottom:1px solid #c6d880; margin:0; } +#content div.box-left div.form div.fields div.field div.select,#content div.box-left div.form div.fields div.field div.checkboxes,#content div.box-left div.form div.fields div.field div.radios,#content div.box-right div.form div.fields div.field div.select,#content div.box-right div.form div.fields div.field div.checkboxes,#content div.box-right div.form div.fields div.field div.radios{ +margin:0 0 0 0px !important; +padding:0; +} + #content div.box div.form div.fields div.field div.select,#content div.box div.form div.fields div.field div.checkboxes,#content div.box div.form div.fields div.field div.radios { margin:0 0 0 200px; padding:0; } + #content div.box div.form div.fields div.field div.select a:hover,#content div.box div.form div.fields div.field div.select a.ui-selectmenu:hover,#content div.box div.action a:hover { color:#000; text-decoration:none; @@ -2097,7 +2188,7 @@ border:1px solid #666; clear:both; overflow:hidden; margin:0; -padding:2px 0; +padding:8px 0 2px; } #content div.box div.form div.fields div.field div.checkboxes div.checkbox input,#content div.box div.form div.fields div.field div.radios div.radio input { @@ -2109,7 +2200,7 @@ margin:0; height:1%; display:block; float:left; -margin:3px 0 0 4px; +margin:2px 0 0 4px; } div.form div.fields div.field div.button input,#content div.box div.form div.fields div.buttons input,div.form div.fields div.buttons input,#content div.box div.action div.button input { @@ -2218,7 +2309,7 @@ padding:6px; } #login,#register { -width:420px; +width:520px; margin:10% auto 0; padding:0; } @@ -2259,6 +2350,7 @@ display:block; color:red; margin:8px 0 0; padding:0; +max-width: 320px; } #login div.form div.fields div.field div.label label,#register div.form div.fields div.field div.label label { @@ -2310,4 +2402,13 @@ div#legend_container table td,div#legend border:none !important; height:20px !important; padding:0 !important; -} \ No newline at end of file +} + +#q_filter{ +border:0 none; +color:#AAAAAA; +margin-bottom:-4px; +margin-top:-4px; +padding-left:3px; +} + diff --git a/rhodecode/public/images/hgicon.png b/rhodecode/public/images/hgicon.png deleted file mode 100644 index 60effbc5e613d8c8a6f1418853ded6aa4e599671..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@P)AbwBC0AnPZvAJXqAp_1LIk^K$S0Kp|^ul{2 zTVT%JLv$b>aF0|1QlU81uXfnE{2n0}C?Po=oA+=Vke;5-z@Wefa;`E|p)pcSfDLd! zGm#04Eone}7by^TL-9+lAmAXL*VB(Y9tV3wmhADatcPOd=nb0?HeBPJS=K?R|_ zfbN1lP@8rm`BxC?d}D0s0hp#T5? diff --git a/rhodecode/public/images/icons/lock_open.png b/rhodecode/public/images/icons/lock_open.png index a471765ff1432092e7113e56b42f2798968b443f..f93c6922308568fe16579b81886908acff590e7d GIT binary patch literal 746 zc$@+10u}vPx#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2igk~ z0sS)UmYJGzrPvKl6rhFm&;8Si$ztfR;kr$0QkO7u~<}L7*3W-rAPDg^UG;~_qS*^ zn=cE6g1@k^K&@6A4T3-gL7$+_yi}bv#;~}!xDo_G`EPS# zV&W-qZ*_H5cXxNQr3D~ktwlua=1XH^W5O1pM1<Fr#Nh(Ytb=n%9HjupfD^DWjmjH5ZyWhTNBQ>b zHsW^Cd;BDgPZi>D1;1}6A{Nnc8lXW0v6}qA0lc;@P!1be-qkzkB__XbFTZ|f5wi~B zHUJ)gXpM*kt66zJL!0)?B.dump(I[K],N-1):Q);}else{P.push(I[K]);}P.push(O);}if(P.length>1){P.pop();}P.push("]");}else{P.push("{");for(K in I){if(B.hasOwnProperty(I,K)){P.push(K+L);if(B.isObject(I[K])){P.push((N>0)?B.dump(I[K],N-1):Q);}else{P.push(I[K]);}P.push(O);}}if(P.length>1){P.pop();}P.push("}");}return P.join("");},substitute:function(Y,J,R){var N,M,L,U,V,X,T=[],K,O="dump",S=" ",I="{",W="}",Q,P;for(;;){N=Y.lastIndexOf(I);if(N<0){break;}M=Y.indexOf(W,N);if(N+1>=M){break;}K=Y.substring(N+1,M);U=K;X=null;L=U.indexOf(S);if(L>-1){X=U.substring(L+1);U=U.substring(0,L);}V=J[U];if(R){V=R(U,V,X);}if(B.isObject(V)){if(B.isArray(V)){V=B.dump(V,parseInt(X,10));}else{X=X||"";Q=X.indexOf(O);if(Q>-1){X=X.substring(4);}P=V.toString();if(P===G||Q>-1){V=B.dump(V,parseInt(X,10));}else{V=P;}}}else{if(!B.isString(V)&&!B.isNumber(V)){V="~-"+T.length+"-~";T[T.length]=K;}}Y=Y.substring(0,N)+V+Y.substring(M+1);}for(N=T.length-1;N>=0;N=N-1){Y=Y.replace(new RegExp("~-"+N+"-~"),"{"+T[N]+"}","g");}return Y;},trim:function(I){try{return I.replace(/^\s+|\s+$/g,"");}catch(J){return I;}},merge:function(){var L={},J=arguments,I=J.length,K;for(K=0;K=420){X.addEventListener("load",function(){a(W,U);});}else{var T=M[W];if(T.varName){var V=YAHOO.util.Get.POLL_FREQ;T.maxattempts=YAHOO.util.Get.TIMEOUT/V;T.attempts=0;T._cache=T.varName[0].split(".");T.timer=S.later(V,T,function(j){var f=this._cache,e=f.length,d=this.win,g;for(g=0;gthis.maxattempts){var h="Over retry limit, giving up";T.timer.cancel();Q(W,h);}else{}return;}}T.timer.cancel();a(W,U);},null,true);}else{S.later(YAHOO.util.Get.POLL_FREQ,null,a,[W,U]);}}}}else{X.onload=function(){a(W,U);};}}};return{POLL_FREQ:10,PURGE_THRESH:20,TIMEOUT:2000,_finalize:function(T){S.later(0,null,C,T);},abort:function(U){var V=(S.isString(U))?U:U.tId;var T=M[V];if(T){T.aborted=true;}},script:function(T,U){return H("script",T,U);},css:function(T,U){return H("css",T,U);}};}();YAHOO.register("get",YAHOO.util.Get,{version:"2.8.1",build:"19"});(function(){var Y=YAHOO,util=Y.util,lang=Y.lang,env=Y.env,PROV="_provides",SUPER="_supersedes",REQ="expanded",AFTER="_after";var YUI={dupsAllowed:{"yahoo":true,"get":true},info:{"root":"2.8.1/build/","base":"http://yui.yahooapis.com/2.8.1/build/","comboBase":"http://yui.yahooapis.com/combo?","skin":{"defaultSkin":"sam","base":"assets/skins/","path":"skin.css","after":["reset","fonts","grids","base"],"rollup":3},dupsAllowed:["yahoo","get"],"moduleInfo":{"animation":{"type":"js","path":"animation/animation-min.js","requires":["dom","event"]},"autocomplete":{"type":"js","path":"autocomplete/autocomplete-min.js","requires":["dom","event","datasource"],"optional":["connection","animation"],"skinnable":true},"base":{"type":"css","path":"base/base-min.css","after":["reset","fonts","grids"]},"button":{"type":"js","path":"button/button-min.js","requires":["element"],"optional":["menu"],"skinnable":true},"calendar":{"type":"js","path":"calendar/calendar-min.js","requires":["event","dom"],supersedes:["datemeth"],"skinnable":true},"carousel":{"type":"js","path":"carousel/carousel-min.js","requires":["element"],"optional":["animation"],"skinnable":true},"charts":{"type":"js","path":"charts/charts-min.js","requires":["element","json","datasource","swf"]},"colorpicker":{"type":"js","path":"colorpicker/colorpicker-min.js","requires":["slider","element"],"optional":["animation"],"skinnable":true},"connection":{"type":"js","path":"connection/connection-min.js","requires":["event"],"supersedes":["connectioncore"]},"connectioncore":{"type":"js","path":"connection/connection_core-min.js","requires":["event"],"pkg":"connection"},"container":{"type":"js","path":"container/container-min.js","requires":["dom","event"],"optional":["dragdrop","animation","connection"],"supersedes":["containercore"],"skinnable":true},"containercore":{"type":"js","path":"container/container_core-min.js","requires":["dom","event"],"pkg":"container"},"cookie":{"type":"js","path":"cookie/cookie-min.js","requires":["yahoo"]},"datasource":{"type":"js","path":"datasource/datasource-min.js","requires":["event"],"optional":["connection"]},"datatable":{"type":"js","path":"datatable/datatable-min.js","requires":["element","datasource"],"optional":["calendar","dragdrop","paginator"],"skinnable":true},datemath:{"type":"js","path":"datemath/datemath-min.js","requires":["yahoo"]},"dom":{"type":"js","path":"dom/dom-min.js","requires":["yahoo"]},"dragdrop":{"type":"js","path":"dragdrop/dragdrop-min.js","requires":["dom","event"]},"editor":{"type":"js","path":"editor/editor-min.js","requires":["menu","element","button"],"optional":["animation","dragdrop"],"supersedes":["simpleeditor"],"skinnable":true},"element":{"type":"js","path":"element/element-min.js","requires":["dom","event"],"optional":["event-mouseenter","event-delegate"]},"element-delegate":{"type":"js","path":"element-delegate/element-delegate-min.js","requires":["element"]},"event":{"type":"js","path":"event/event-min.js","requires":["yahoo"]},"event-simulate":{"type":"js","path":"event-simulate/event-simulate-min.js","requires":["event"]},"event-delegate":{"type":"js","path":"event-delegate/event-delegate-min.js","requires":["event"],"optional":["selector"]},"event-mouseenter":{"type":"js","path":"event-mouseenter/event-mouseenter-min.js","requires":["dom","event"]},"fonts":{"type":"css","path":"fonts/fonts-min.css"},"get":{"type":"js","path":"get/get-min.js","requires":["yahoo"]},"grids":{"type":"css","path":"grids/grids-min.css","requires":["fonts"],"optional":["reset"]},"history":{"type":"js","path":"history/history-min.js","requires":["event"]},"imagecropper":{"type":"js","path":"imagecropper/imagecropper-min.js","requires":["dragdrop","element","resize"],"skinnable":true},"imageloader":{"type":"js","path":"imageloader/imageloader-min.js","requires":["event","dom"]},"json":{"type":"js","path":"json/json-min.js","requires":["yahoo"]},"layout":{"type":"js","path":"layout/layout-min.js","requires":["element"],"optional":["animation","dragdrop","resize","selector"],"skinnable":true},"logger":{"type":"js","path":"logger/logger-min.js","requires":["event","dom"],"optional":["dragdrop"],"skinnable":true},"menu":{"type":"js","path":"menu/menu-min.js","requires":["containercore"],"skinnable":true},"paginator":{"type":"js","path":"paginator/paginator-min.js","requires":["element"],"skinnable":true},"profiler":{"type":"js","path":"profiler/profiler-min.js","requires":["yahoo"]},"profilerviewer":{"type":"js","path":"profilerviewer/profilerviewer-min.js","requires":["profiler","yuiloader","element"],"skinnable":true},"progressbar":{"type":"js","path":"progressbar/progressbar-min.js","requires":["element"],"optional":["animation"],"skinnable":true},"reset":{"type":"css","path":"reset/reset-min.css"},"reset-fonts-grids":{"type":"css","path":"reset-fonts-grids/reset-fonts-grids.css","supersedes":["reset","fonts","grids","reset-fonts"],"rollup":4},"reset-fonts":{"type":"css","path":"reset-fonts/reset-fonts.css","supersedes":["reset","fonts"],"rollup":2},"resize":{"type":"js","path":"resize/resize-min.js","requires":["dragdrop","element"],"optional":["animation"],"skinnable":true},"selector":{"type":"js","path":"selector/selector-min.js","requires":["yahoo","dom"]},"simpleeditor":{"type":"js","path":"editor/simpleeditor-min.js","requires":["element"],"optional":["containercore","menu","button","animation","dragdrop"],"skinnable":true,"pkg":"editor"},"slider":{"type":"js","path":"slider/slider-min.js","requires":["dragdrop"],"optional":["animation"],"skinnable":true},"storage":{"type":"js","path":"storage/storage-min.js","requires":["yahoo","event","cookie"],"optional":["swfstore"]},"stylesheet":{"type":"js","path":"stylesheet/stylesheet-min.js","requires":["yahoo"]},"swf":{"type":"js","path":"swf/swf-min.js","requires":["element"],"supersedes":["swfdetect"]},"swfdetect":{"type":"js","path":"swfdetect/swfdetect-min.js","requires":["yahoo"]},"swfstore":{"type":"js","path":"swfstore/swfstore-min.js","requires":["element","cookie","swf"]},"tabview":{"type":"js","path":"tabview/tabview-min.js","requires":["element"],"optional":["connection"],"skinnable":true},"treeview":{"type":"js","path":"treeview/treeview-min.js","requires":["event","dom"],"optional":["json","animation","calendar"],"skinnable":true},"uploader":{"type":"js","path":"uploader/uploader-min.js","requires":["element"]},"utilities":{"type":"js","path":"utilities/utilities.js","supersedes":["yahoo","event","dragdrop","animation","dom","connection","element","yahoo-dom-event","get","yuiloader","yuiloader-dom-event"],"rollup":8},"yahoo":{"type":"js","path":"yahoo/yahoo-min.js"},"yahoo-dom-event":{"type":"js","path":"yahoo-dom-event/yahoo-dom-event.js","supersedes":["yahoo","event","dom"],"rollup":3},"yuiloader":{"type":"js","path":"yuiloader/yuiloader-min.js","supersedes":["yahoo","get"]},"yuiloader-dom-event":{"type":"js","path":"yuiloader-dom-event/yuiloader-dom-event.js","supersedes":["yahoo","dom","event","get","yuiloader","yahoo-dom-event"],"rollup":5},"yuitest":{"type":"js","path":"yuitest/yuitest-min.js","requires":["logger"],"optional":["event-simulate"],"skinnable":true}}},ObjectUtil:{appendArray:function(o,a){if(a){for(var i=0; -i=m.rollup);if(roll){break;}}}}}else{for(j=0;j=m.rollup);if(roll){break;}}}}}if(roll){r[i]=true;rolled=true;this.getRequires(m);}}}if(!rolled){break;}}},_reduce:function(){var i,j,s,m,r=this.required;for(i in r){if(i in this.loaded){delete r[i];}else{var skinDef=this.parseSkin(i);if(skinDef){if(!skinDef.module){var skin_pre=this.SKIN_PREFIX+skinDef.skin;for(j in r){if(lang.hasOwnProperty(r,j)){m=this.moduleInfo[j];var ext=m&&m.ext;if(!ext&&j!==i&&j.indexOf(skin_pre)>-1){delete r[j];}}}}}else{m=this.moduleInfo[i];s=m&&m.supersedes;if(s){for(j=0;j-1){return true;}if(after&&YUI.ArrayUtil.indexOf(after,bb)>-1){return true;}if(checkOptional&&optional&&YUI.ArrayUtil.indexOf(optional,bb)>-1){return true;}var ss=info[bb]&&info[bb].supersedes;if(ss){for(ii=0;iistartLen){YAHOO.util.Get.script(self._filter(js),{data:self._loading,onSuccess:callback,onFailure:self._onFailure,onTimeout:self._onTimeout,insertBefore:self.insertBefore,charset:self.charset,timeout:self.timeout,scope:self});}};if(css.length>startLen){YAHOO.util.Get.css(this._filter(css),{data:this._loading,onSuccess:loadScript,onFailure:this._onFailure,onTimeout:this._onTimeout,insertBefore:this.insertBefore,charset:this.charset,timeout:this.timeout,scope:self});}else{loadScript();}return;}else{this.loadNext(this._loading);}},insert:function(o,type){this.calculate(o);this._loading=true;this.loadType=type;if(this.combine){return this._combine();}if(!type){var self=this;this._internalCallback=function(){self._internalCallback=null;self.insert(null,"js");};this.insert(null,"css");return;}this.loadNext();},sandbox:function(o,type){this._config(o);if(!this.onSuccess){throw new Error("You must supply an onSuccess handler for your sandbox");}this._sandbox=true;var self=this;if(!type||type!=="js"){this._internalCallback=function(){self._internalCallback=null;self.sandbox(null,"js");};this.insert(null,"css");return;}if(!util.Connect){var ld=new YAHOO.util.YUILoader();ld.insert({base:this.base,filter:this.filter,require:"connection",insertBefore:this.insertBefore,charset:this.charset,onSuccess:function(){this.sandbox(null,"js");},scope:this},"js");return;}this._scriptText=[];this._loadCount=0;this._stopCount=this.sorted.length;this._xhr=[];this.calculate();var s=this.sorted,l=s.length,i,m,url;for(i=0;i=this._stopCount){var v=this.varName||"YAHOO";var t="(function() {\n";var b="\nreturn "+v+";\n})();";var ref=eval(t+this._scriptText.join("\n")+b);this._pushEvents(ref);if(ref){this.onSuccess.call(this.scope,{reference:ref,data:this.data});}else{this._onFailure.call(this.varName+" reference failure");}}},failure:function(o){this.onFailure.call(this.scope,{msg:"XHR failure",xhrResponse:o,data:this.data});},scope:this,argument:[i,url,s[i]]};this._xhr.push(util.Connect.asyncRequest("GET",url,xhrData));}},loadNext:function(mname){if(!this._loading){return;}if(mname){if(mname!==this._loading){return;}this.inserted[mname]=true;if(this.onProgress){this.onProgress.call(this.scope,{name:mname,data:this.data});}}var s=this.sorted,len=s.length,i,m;for(i=0;i519)?true:false);while((G=G[u])){z[0]+=G[b];z[1]+=G[P];if(AC){z=E.Dom._calcBorders(G,z);}}if(E.Dom._getStyle(y,p)!==f){G=y;while((G=G[Z])&&G[C]){AA=G[i];AB=G[O];if(H&&(E.Dom._getStyle(G,"overflow")!=="visible")){z=E.Dom._calcBorders(G,z);}if(AA||AB){z[0]-=AB;z[1]-=AA;}}z[0]+=x;z[1]+=Y;}else{if(D){z[0]-=x;z[1]-=Y;}else{if(I||H){z[0]+=x;z[1]+=Y;}}}z[0]=Math.floor(z[0]);z[1]=Math.floor(z[1]);}else{}return z;};}}(),getX:function(G){var Y=function(x){return E.Dom.getXY(x)[0];};return E.Dom.batch(G,Y,E.Dom,true);},getY:function(G){var Y=function(x){return E.Dom.getXY(x)[1];};return E.Dom.batch(G,Y,E.Dom,true);},setXY:function(G,x,Y){E.Dom.batch(G,E.Dom._setXY,{pos:x,noRetry:Y});},_setXY:function(G,z){var AA=E.Dom._getStyle(G,p),y=E.Dom.setStyle,AD=z.pos,Y=z.noRetry,AB=[parseInt(E.Dom.getComputedStyle(G,j),10),parseInt(E.Dom.getComputedStyle(G,o),10)],AC,x;if(AA=="static"){AA=V;y(G,p,AA);}AC=E.Dom._getXY(G);if(!AD||AC===false){return false;}if(isNaN(AB[0])){AB[0]=(AA==V)?0:G[b];}if(isNaN(AB[1])){AB[1]=(AA==V)?0:G[P];}if(AD[0]!==null){y(G,j,AD[0]-AC[0]+AB[0]+"px");}if(AD[1]!==null){y(G,o,AD[1]-AC[1]+AB[1]+"px");}if(!Y){x=E.Dom._getXY(G);if((AD[0]!==null&&x[0]!=AD[0])||(AD[1]!==null&&x[1]!=AD[1])){E.Dom._setXY(G,{pos:AD,noRetry:true});}}},setX:function(Y,G){E.Dom.setXY(Y,[G,null]);},setY:function(G,Y){E.Dom.setXY(G,[null,Y]);},getRegion:function(G){var Y=function(x){var y=false;if(E.Dom._canPosition(x)){y=E.Region.getRegion(x);}else{}return y;};return E.Dom.batch(G,Y,E.Dom,true);},getClientWidth:function(){return E.Dom.getViewportWidth();},getClientHeight:function(){return E.Dom.getViewportHeight();},getElementsByClassName:function(AB,AF,AC,AE,x,AD){AF=AF||"*";AC=(AC)?E.Dom.get(AC):null||K;if(!AC){return[];}var Y=[],G=AC.getElementsByTagName(AF),z=E.Dom.hasClass;for(var y=0,AA=G.length;y-1;}}else{}return G;},addClass:function(Y,G){return E.Dom.batch(Y,E.Dom._addClass,G);},_addClass:function(x,Y){var G=false,y;if(x&&Y){y=E.Dom._getAttribute(x,F)||J;if(!E.Dom._hasClass(x,Y)){E.Dom.setAttribute(x,F,A(y+B+Y));G=true;}}else{}return G;},removeClass:function(Y,G){return E.Dom.batch(Y,E.Dom._removeClass,G);},_removeClass:function(y,x){var Y=false,AA,z,G;if(y&&x){AA=E.Dom._getAttribute(y,F)||J;E.Dom.setAttribute(y,F,AA.replace(E.Dom._getClassRegex(x),J));z=E.Dom._getAttribute(y,F);if(AA!==z){E.Dom.setAttribute(y,F,A(z));Y=true;if(E.Dom._getAttribute(y,F)===""){G=(y.hasAttribute&&y.hasAttribute(g))?g:F; -y.removeAttribute(G);}}}else{}return Y;},replaceClass:function(x,Y,G){return E.Dom.batch(x,E.Dom._replaceClass,{from:Y,to:G});},_replaceClass:function(y,x){var Y,AB,AA,G=false,z;if(y&&x){AB=x.from;AA=x.to;if(!AA){G=false;}else{if(!AB){G=E.Dom._addClass(y,x.to);}else{if(AB!==AA){z=E.Dom._getAttribute(y,F)||J;Y=(B+z.replace(E.Dom._getClassRegex(AB),B+AA)).split(E.Dom._getClassRegex(AA));Y.splice(1,0,B+AA);E.Dom.setAttribute(y,F,A(Y.join(J)));G=true;}}}}else{}return G;},generateId:function(G,x){x=x||"yui-gen";var Y=function(y){if(y&&y.id){return y.id;}var z=x+YAHOO.env._id_counter++;if(y){if(y[e]&&y[e].getElementById(z)){return E.Dom.generateId(y,z+x);}y.id=z;}return z;};return E.Dom.batch(G,Y,E.Dom,true)||Y.apply(E.Dom,arguments);},isAncestor:function(Y,x){Y=E.Dom.get(Y);x=E.Dom.get(x);var G=false;if((Y&&x)&&(Y[l]&&x[l])){if(Y.contains&&Y!==x){G=Y.contains(x);}else{if(Y.compareDocumentPosition){G=!!(Y.compareDocumentPosition(x)&16);}}}else{}return G;},inDocument:function(G,Y){return E.Dom._inDoc(E.Dom.get(G),Y);},_inDoc:function(Y,x){var G=false;if(Y&&Y[C]){x=x||Y[e];G=E.Dom.isAncestor(x[v],Y);}else{}return G;},getElementsBy:function(Y,AF,AB,AD,y,AC,AE){AF=AF||"*";AB=(AB)?E.Dom.get(AB):null||K;if(!AB){return[];}var x=[],G=AB.getElementsByTagName(AF);for(var z=0,AA=G.length;z=8&&K.documentElement.hasAttribute){E.Dom.DOT_ATTRIBUTES.type=true;}})();YAHOO.util.Region=function(C,D,A,B){this.top=C;this.y=C;this[1]=C;this.right=D;this.bottom=A;this.left=B;this.x=B;this[0]=B; -this.width=this.right-this.left;this.height=this.bottom-this.top;};YAHOO.util.Region.prototype.contains=function(A){return(A.left>=this.left&&A.right<=this.right&&A.top>=this.top&&A.bottom<=this.bottom);};YAHOO.util.Region.prototype.getArea=function(){return((this.bottom-this.top)*(this.right-this.left));};YAHOO.util.Region.prototype.intersect=function(E){var C=Math.max(this.top,E.top),D=Math.min(this.right,E.right),A=Math.min(this.bottom,E.bottom),B=Math.max(this.left,E.left);if(A>=C&&D>=B){return new YAHOO.util.Region(C,D,A,B);}else{return null;}};YAHOO.util.Region.prototype.union=function(E){var C=Math.min(this.top,E.top),D=Math.max(this.right,E.right),A=Math.max(this.bottom,E.bottom),B=Math.min(this.left,E.left);return new YAHOO.util.Region(C,D,A,B);};YAHOO.util.Region.prototype.toString=function(){return("Region {"+"top: "+this.top+", right: "+this.right+", bottom: "+this.bottom+", left: "+this.left+", height: "+this.height+", width: "+this.width+"}");};YAHOO.util.Region.getRegion=function(D){var F=YAHOO.util.Dom.getXY(D),C=F[1],E=F[0]+D.offsetWidth,A=F[1]+D.offsetHeight,B=F[0];return new YAHOO.util.Region(C,E,A,B);};YAHOO.util.Point=function(A,B){if(YAHOO.lang.isArray(A)){B=A[1];A=A[0];}YAHOO.util.Point.superclass.constructor.call(this,B,A,B,A);};YAHOO.extend(YAHOO.util.Point,YAHOO.util.Region);(function(){var B=YAHOO.util,A="clientTop",F="clientLeft",J="parentNode",K="right",W="hasLayout",I="px",U="opacity",L="auto",D="borderLeftWidth",G="borderTopWidth",P="borderRightWidth",V="borderBottomWidth",S="visible",Q="transparent",N="height",E="width",H="style",T="currentStyle",R=/^width|height$/,O=/^(\d[.\d]*)+(em|ex|px|gd|rem|vw|vh|vm|ch|mm|cm|in|pt|pc|deg|rad|ms|s|hz|khz|%){1}?/i,M={get:function(X,Z){var Y="",a=X[T][Z];if(Z===U){Y=B.Dom.getStyle(X,U);}else{if(!a||(a.indexOf&&a.indexOf(I)>-1)){Y=a;}else{if(B.Dom.IE_COMPUTED[Z]){Y=B.Dom.IE_COMPUTED[Z](X,Z);}else{if(O.test(a)){Y=B.Dom.IE.ComputedStyle.getPixel(X,Z);}else{Y=a;}}}}return Y;},getOffset:function(Z,e){var b=Z[T][e],X=e.charAt(0).toUpperCase()+e.substr(1),c="offset"+X,Y="pixel"+X,a="",d;if(b==L){d=Z[c];if(d===undefined){a=0;}a=d;if(R.test(e)){Z[H][e]=d;if(Z[c]>d){a=d-(Z[c]-d);}Z[H][e]=L;}}else{if(!Z[H][Y]&&!Z[H][e]){Z[H][e]=b;}a=Z[H][Y];}return a+I;},getBorderWidth:function(X,Z){var Y=null;if(!X[T][W]){X[H].zoom=1;}switch(Z){case G:Y=X[A];break;case V:Y=X.offsetHeight-X.clientHeight-X[A];break;case D:Y=X[F];break;case P:Y=X.offsetWidth-X.clientWidth-X[F];break;}return Y+I;},getPixel:function(Y,X){var a=null,b=Y[T][K],Z=Y[T][X];Y[H][K]=Z;a=Y[H].pixelRight;Y[H][K]=b;return a+I;},getMargin:function(Y,X){var Z;if(Y[T][X]==L){Z=0+I;}else{Z=B.Dom.IE.ComputedStyle.getPixel(Y,X);}return Z;},getVisibility:function(Y,X){var Z;while((Z=Y[T])&&Z[X]=="inherit"){Y=Y[J];}return(Z)?Z[X]:S;},getColor:function(Y,X){return B.Dom.Color.toRGB(Y[T][X])||Q;},getBorderColor:function(Y,X){var Z=Y[T],a=Z[X]||Z.color;return B.Dom.Color.toRGB(B.Dom.Color.toHex(a));}},C={};C.top=C.right=C.bottom=C.left=C[E]=C[N]=M.getOffset;C.color=M.getColor;C[G]=C[P]=C[V]=C[D]=M.getBorderWidth;C.marginTop=C.marginRight=C.marginBottom=C.marginLeft=M.getMargin;C.visibility=M.getVisibility;C.borderColor=C.borderTopColor=C.borderRightColor=C.borderBottomColor=C.borderLeftColor=M.getBorderColor;B.Dom.IE_COMPUTED=C;B.Dom.IE_ComputedStyle=M;})();(function(){var C="toString",A=parseInt,B=RegExp,D=YAHOO.util;D.Dom.Color={KEYWORDS:{black:"000",silver:"c0c0c0",gray:"808080",white:"fff",maroon:"800000",red:"f00",purple:"800080",fuchsia:"f0f",green:"008000",lime:"0f0",olive:"808000",yellow:"ff0",navy:"000080",blue:"00f",teal:"008080",aqua:"0ff"},re_RGB:/^rgb\(([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\)$/i,re_hex:/^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i,re_hex3:/([0-9A-F])/gi,toRGB:function(E){if(!D.Dom.Color.re_RGB.test(E)){E=D.Dom.Color.toHex(E);}if(D.Dom.Color.re_hex.exec(E)){E="rgb("+[A(B.$1,16),A(B.$2,16),A(B.$3,16)].join(", ")+")";}return E;},toHex:function(H){H=D.Dom.Color.KEYWORDS[H]||H;if(D.Dom.Color.re_RGB.exec(H)){var G=(B.$1.length===1)?"0"+B.$1:Number(B.$1),F=(B.$2.length===1)?"0"+B.$2:Number(B.$2),E=(B.$3.length===1)?"0"+B.$3:Number(B.$3);H=[G[C](16),F[C](16),E[C](16)].join("");}if(H.length<6){H=H.replace(D.Dom.Color.re_hex3,"$1$1");}if(H!=="transparent"&&H.indexOf("#")<0){H="#"+H;}return H.toLowerCase();}};}());YAHOO.register("dom",YAHOO.util.Dom,{version:"2.8.1",build:"19"});YAHOO.util.CustomEvent=function(D,C,B,A,E){this.type=D;this.scope=C||window;this.silent=B;this.fireOnce=E;this.fired=false;this.firedWith=null;this.signature=A||YAHOO.util.CustomEvent.LIST;this.subscribers=[];if(!this.silent){}var F="_YUICEOnSubscribe";if(D!==F){this.subscribeEvent=new YAHOO.util.CustomEvent(F,this,true);}this.lastError=null;};YAHOO.util.CustomEvent.LIST=0;YAHOO.util.CustomEvent.FLAT=1;YAHOO.util.CustomEvent.prototype={subscribe:function(B,C,D){if(!B){throw new Error("Invalid callback for subscriber to '"+this.type+"'");}if(this.subscribeEvent){this.subscribeEvent.fire(B,C,D);}var A=new YAHOO.util.Subscriber(B,C,D);if(this.fireOnce&&this.fired){this.notify(A,this.firedWith);}else{this.subscribers.push(A);}},unsubscribe:function(D,F){if(!D){return this.unsubscribeAll();}var E=false;for(var B=0,A=this.subscribers.length;B0){H=C[0];}try{B=F.fn.call(E,H,F.obj);}catch(G){this.lastError=G;if(A){throw G;}}}else{try{B=F.fn.call(E,this.type,C,F.obj);}catch(D){this.lastError=D;if(A){throw D;}}}return B;},unsubscribeAll:function(){var A=this.subscribers.length,B;for(B=A-1;B>-1;B--){this._delete(B);}this.subscribers=[];return A;},_delete:function(A){var B=this.subscribers[A];if(B){delete B.fn;delete B.obj;}this.subscribers.splice(A,1);},toString:function(){return"CustomEvent: "+"'"+this.type+"', "+"context: "+this.scope;}};YAHOO.util.Subscriber=function(A,B,C){this.fn=A;this.obj=YAHOO.lang.isUndefined(B)?null:B;this.overrideContext=C;};YAHOO.util.Subscriber.prototype.getScope=function(A){if(this.overrideContext){if(this.overrideContext===true){return this.obj;}else{return this.overrideContext;}}return A;};YAHOO.util.Subscriber.prototype.contains=function(A,B){if(B){return(this.fn==A&&this.obj==B);}else{return(this.fn==A);}};YAHOO.util.Subscriber.prototype.toString=function(){return"Subscriber { obj: "+this.obj+", overrideContext: "+(this.overrideContext||"no")+" }";};if(!YAHOO.util.Event){YAHOO.util.Event=function(){var G=false,H=[],J=[],A=0,E=[],B=0,C={63232:38,63233:40,63234:37,63235:39,63276:33,63277:34,25:9},D=YAHOO.env.ua.ie,F="focusin",I="focusout";return{POLL_RETRYS:500,POLL_INTERVAL:40,EL:0,TYPE:1,FN:2,WFN:3,UNLOAD_OBJ:3,ADJ_SCOPE:4,OBJ:5,OVERRIDE:6,CAPTURE:7,lastError:null,isSafari:YAHOO.env.ua.webkit,webkit:YAHOO.env.ua.webkit,isIE:D,_interval:null,_dri:null,_specialTypes:{focusin:(D?"focusin":"focus"),focusout:(D?"focusout":"blur")},DOMReady:false,throwErrors:false,startInterval:function(){if(!this._interval){this._interval=YAHOO.lang.later(this.POLL_INTERVAL,this,this._tryPreloadAttach,null,true);}},onAvailable:function(Q,M,O,P,N){var K=(YAHOO.lang.isString(Q))?[Q]:Q;for(var L=0;L-1;M--){S=(this.removeListener(L[M],K,R)&&S);}return S;}}if(!R||!R.call){return this.purgeElement(L,false,K);}if("unload"==K){for(M=J.length-1;M>-1;M--){U=J[M];if(U&&U[0]==L&&U[1]==K&&U[2]==R){J.splice(M,1);return true;}}return false;}var N=null;var O=arguments[3];if("undefined"===typeof O){O=this._getCacheIndex(H,L,K,R);}if(O>=0){N=H[O];}if(!L||!N){return false;}var T=N[this.CAPTURE]===true?true:false;try{this._simpleRemove(L,K,N[this.WFN],T);}catch(Q){this.lastError=Q;return false;}delete H[O][this.WFN];delete H[O][this.FN];H.splice(O,1);return true;},getTarget:function(M,L){var K=M.target||M.srcElement;return this.resolveTextNode(K);},resolveTextNode:function(L){try{if(L&&3==L.nodeType){return L.parentNode;}}catch(K){}return L;},getPageX:function(L){var K=L.pageX;if(!K&&0!==K){K=L.clientX||0;if(this.isIE){K+=this._getScrollLeft();}}return K;},getPageY:function(K){var L=K.pageY;if(!L&&0!==L){L=K.clientY||0;if(this.isIE){L+=this._getScrollTop();}}return L;},getXY:function(K){return[this.getPageX(K),this.getPageY(K)];},getRelatedTarget:function(L){var K=L.relatedTarget;if(!K){if(L.type=="mouseout"){K=L.toElement; -}else{if(L.type=="mouseover"){K=L.fromElement;}}}return this.resolveTextNode(K);},getTime:function(M){if(!M.time){var L=new Date().getTime();try{M.time=L;}catch(K){this.lastError=K;return L;}}return M.time;},stopEvent:function(K){this.stopPropagation(K);this.preventDefault(K);},stopPropagation:function(K){if(K.stopPropagation){K.stopPropagation();}else{K.cancelBubble=true;}},preventDefault:function(K){if(K.preventDefault){K.preventDefault();}else{K.returnValue=false;}},getEvent:function(M,K){var L=M||window.event;if(!L){var N=this.getEvent.caller;while(N){L=N.arguments[0];if(L&&Event==L.constructor){break;}N=N.caller;}}return L;},getCharCode:function(L){var K=L.keyCode||L.charCode||0;if(YAHOO.env.ua.webkit&&(K in C)){K=C[K];}return K;},_getCacheIndex:function(M,P,Q,O){for(var N=0,L=M.length;N0&&E.length>0);}var P=[];var R=function(T,U){var S=T;if(U.overrideContext){if(U.overrideContext===true){S=U.obj;}else{S=U.overrideContext;}}U.fn.call(S,U.obj);};var L,K,O,N,M=[];for(L=0,K=E.length;L-1;L--){O=E[L];if(!O||!O.id){E.splice(L,1);}}this.startInterval();}else{if(this._interval){this._interval.cancel();this._interval=null;}}this.locked=false;},purgeElement:function(O,P,R){var M=(YAHOO.lang.isString(O))?this.getEl(O):O;var Q=this.getListeners(M,R),N,K;if(Q){for(N=Q.length-1;N>-1;N--){var L=Q[N];this.removeListener(M,L.type,L.fn);}}if(P&&M&&M.childNodes){for(N=0,K=M.childNodes.length;N-1;N--){M=H[N];if(M){L.removeListener(M[L.EL],M[L.TYPE],M[L.FN],N);}}M=null;}L._simpleRemove(window,"unload",L._unload);},_getScrollLeft:function(){return this._getScroll()[1];},_getScrollTop:function(){return this._getScroll()[0];},_getScroll:function(){var K=document.documentElement,L=document.body;if(K&&(K.scrollTop||K.scrollLeft)){return[K.scrollTop,K.scrollLeft];}else{if(L){return[L.scrollTop,L.scrollLeft];}else{return[0,0];}}},regCE:function(){},_simpleAdd:function(){if(window.addEventListener){return function(M,N,L,K){M.addEventListener(N,L,(K));};}else{if(window.attachEvent){return function(M,N,L,K){M.attachEvent("on"+N,L);};}else{return function(){};}}}(),_simpleRemove:function(){if(window.removeEventListener){return function(M,N,L,K){M.removeEventListener(N,L,(K));};}else{if(window.detachEvent){return function(L,M,K){L.detachEvent("on"+M,K);};}else{return function(){};}}}()};}();(function(){var EU=YAHOO.util.Event;EU.on=EU.addListener;EU.onFocus=EU.addFocusListener;EU.onBlur=EU.addBlurListener; -/* DOMReady: based on work by: Dean Edwards/John Resig/Matthias Miller/Diego Perini */ -if(EU.isIE){if(self!==self.top){document.onreadystatechange=function(){if(document.readyState=="complete"){document.onreadystatechange=null;EU._ready();}};}else{YAHOO.util.Event.onDOMReady(YAHOO.util.Event._tryPreloadAttach,YAHOO.util.Event,true);var n=document.createElement("p");EU._dri=setInterval(function(){try{n.doScroll("left");clearInterval(EU._dri);EU._dri=null;EU._ready();n=null;}catch(ex){}},EU.POLL_INTERVAL);}}else{if(EU.webkit&&EU.webkit<525){EU._dri=setInterval(function(){var rs=document.readyState;if("loaded"==rs||"complete"==rs){clearInterval(EU._dri);EU._dri=null;EU._ready();}},EU.POLL_INTERVAL);}else{EU._simpleAdd(document,"DOMContentLoaded",EU._ready);}}EU._simpleAdd(window,"load",EU._load);EU._simpleAdd(window,"unload",EU._unload);EU._tryPreloadAttach();})();}YAHOO.util.EventProvider=function(){};YAHOO.util.EventProvider.prototype={__yui_events:null,__yui_subscribers:null,subscribe:function(A,C,F,E){this.__yui_events=this.__yui_events||{};var D=this.__yui_events[A];if(D){D.subscribe(C,F,E);}else{this.__yui_subscribers=this.__yui_subscribers||{};var B=this.__yui_subscribers;if(!B[A]){B[A]=[];}B[A].push({fn:C,obj:F,overrideContext:E});}},unsubscribe:function(C,E,G){this.__yui_events=this.__yui_events||{};var A=this.__yui_events;if(C){var F=A[C];if(F){return F.unsubscribe(E,G);}}else{var B=true;for(var D in A){if(YAHOO.lang.hasOwnProperty(A,D)){B=B&&A[D].unsubscribe(E,G);}}return B;}return false;},unsubscribeAll:function(A){return this.unsubscribe(A); -},createEvent:function(B,G){this.__yui_events=this.__yui_events||{};var E=G||{},D=this.__yui_events,F;if(D[B]){}else{F=new YAHOO.util.CustomEvent(B,E.scope||this,E.silent,YAHOO.util.CustomEvent.FLAT,E.fireOnce);D[B]=F;if(E.onSubscribeCallback){F.subscribeEvent.subscribe(E.onSubscribeCallback);}this.__yui_subscribers=this.__yui_subscribers||{};var A=this.__yui_subscribers[B];if(A){for(var C=0;C=200&&E<300)||E===1223||C){A=B.xdr?B.r:this.createResponseObject(B,G);if(I&&I.success){if(!I.scope){I.success(A);}else{I.success.apply(I.scope,[A]);}}this.successEvent.fire(A);if(B.successEvent){B.successEvent.fire(A);}}else{switch(E){case 12002:case 12029:case 12030:case 12031:case 12152:case 13030:A=this.createExceptionObject(B.tId,G,(D?D:false));if(I&&I.failure){if(!I.scope){I.failure(A);}else{I.failure.apply(I.scope,[A]);}}break;default:A=(B.xdr)?B.response:this.createResponseObject(B,G);if(I&&I.failure){if(!I.scope){I.failure(A);}else{I.failure.apply(I.scope,[A]);}}}this.failureEvent.fire(A);if(B.failureEvent){B.failureEvent.fire(A);}}this.releaseObject(B);A=null;},createResponseObject:function(A,G){var D={},I={},E,C,F,B;try{C=A.conn.getAllResponseHeaders();F=C.split("\n");for(E=0;E'+''+''+"",K=document.createElement("div");document.body.appendChild(K);K.innerHTML=J;}function B(L,I,J,M,K){H[parseInt(L.tId)]={"o":L,"c":M};if(K){M.method=I;M.data=K;}L.conn.send(J,M,L.tId);}function E(I){D(I);G._transport=document.getElementById("YUIConnectionSwf");}function C(){G.xdrReadyEvent.fire();}function A(J,I){if(J){G.startEvent.fire(J,I.argument);if(J.startEvent){J.startEvent.fire(J,I.argument);}}}function F(J){var K=H[J.tId].o,I=H[J.tId].c;if(J.statusText==="xdr:start"){A(K,I);return;}J.responseText=decodeURI(J.responseText);K.r=J;if(I.argument){K.r.argument=I.argument;}this.handleTransactionResponse(K,I,J.statusText==="xdr:abort"?true:false);delete H[J.tId];}G.xdr=B;G.swf=D;G.transport=E;G.xdrReadyEvent=new YAHOO.util.CustomEvent("xdrReady");G.xdrReady=C;G.handleXdrResponse=F;})();(function(){var D=YAHOO.util.Connect,F=YAHOO.util.Event;D._isFormSubmit=false;D._isFileUpload=false;D._formNode=null;D._sFormData=null;D._submitElementValue=null;D.uploadEvent=new YAHOO.util.CustomEvent("upload"),D._hasSubmitListener=function(){if(F){F.addListener(document,"click",function(J){var I=F.getTarget(J),H=I.nodeName.toLowerCase();if((H==="input"||H==="button")&&(I.type&&I.type.toLowerCase()=="submit")){D._submitElementValue=encodeURIComponent(I.name)+"="+encodeURIComponent(I.value);}});return true;}return false;}();function G(T,O,J){var S,I,R,P,W,Q=false,M=[],V=0,L,N,K,U,H;this.resetFormState();if(typeof T=="string"){S=(document.getElementById(T)||document.forms[T]);}else{if(typeof T=="object"){S=T;}else{return;}}if(O){this.createFrame(J?J:null);this._isFormSubmit=true;this._isFileUpload=true;this._formNode=S;return;}for(L=0,N=S.elements.length;L-1){H=I.options[I.selectedIndex];M[V++]=R+encodeURIComponent((H.attributes.value&&H.attributes.value.specified)?H.value:H.text);}break;case"select-multiple":if(I.selectedIndex>-1){for(K=I.selectedIndex,U=I.options.length;K');if(typeof H=="boolean"){J.src="javascript:false";}}else{J=document.createElement("iframe");J.id=I;J.name=I;}J.style.position="absolute";J.style.top="-1000px";J.style.left="-1000px";document.body.appendChild(J);}function E(H){var K=[],I=H.split("&"),J,L;for(J=0;J0){for(P=0;P0)?F:0;}if(C in D&&!("style" in D&&C in D.style)){D[C]=F;}else{B.Dom.setStyle(D,C,F+E);}},getAttribute:function(C){var E=this.getEl();var G=B.Dom.getStyle(E,C);if(G!=="auto"&&!this.patterns.offsetUnit.test(G)){return parseFloat(G);}var D=this.patterns.offsetAttribute.exec(C)||[];var H=!!(D[3]);var F=!!(D[2]);if("style" in E){if(F||(B.Dom.getStyle(E,"position")=="absolute"&&H)){G=E["offset"+D[0].charAt(0).toUpperCase()+D[0].substr(1)];}else{G=0;}}else{if(C in E){G=E[C];}}return G;},getDefaultUnit:function(C){if(this.patterns.defaultUnit.test(C)){return"px";}return"";},setRuntimeAttribute:function(D){var I;var E;var F=this.attributes;this.runtimeAttributes[D]={};var H=function(J){return(typeof J!=="undefined");};if(!H(F[D]["to"])&&!H(F[D]["by"])){return false;}I=(H(F[D]["from"]))?F[D]["from"]:this.getAttribute(D);if(H(F[D]["to"])){E=F[D]["to"];}else{if(H(F[D]["by"])){if(I.constructor==Array){E=[];for(var G=0,C=I.length;G0&&isFinite(K)){if(G.currentFrame+K>=J){K=J-(I+1);}G.currentFrame+=K;}};this._queue=B;this._getIndex=E;};YAHOO.util.Bezier=new function(){this.getPosition=function(E,D){var F=E.length;var C=[];for(var B=0;B0&&!(L[0] instanceof Array)){L=[L];}else{var K=[];for(M=0,O=L.length;M0){this.runtimeAttributes[P]=this.runtimeAttributes[P].concat(L);}this.runtimeAttributes[P][this.runtimeAttributes[P].length]=I;}else{F.setRuntimeAttribute.call(this,P);}};var B=function(G,I){var H=E.Dom.getXY(this.getEl());G=[G[0]-H[0]+I[0],G[1]-H[1]+I[1]];return G;};var D=function(G){return(typeof G!=="undefined");};E.Motion=A;})();(function(){var D=function(F,E,G,H){if(F){D.superclass.constructor.call(this,F,E,G,H);}};D.NAME="Scroll";var B=YAHOO.util;YAHOO.extend(D,B.ColorAnim);var C=D.superclass;var A=D.prototype;A.doMethod=function(E,H,F){var G=null;if(E=="scroll"){G=[this.method(this.currentFrame,H[0],F[0]-H[0],this.totalFrames),this.method(this.currentFrame,H[1],F[1]-H[1],this.totalFrames)];}else{G=C.doMethod.call(this,E,H,F);}return G;};A.getAttribute=function(E){var G=null;var F=this.getEl();if(E=="scroll"){G=[F.scrollLeft,F.scrollTop];}else{G=C.getAttribute.call(this,E);}return G;};A.setAttribute=function(E,H,G){var F=this.getEl();if(E=="scroll"){F.scrollLeft=H[0];F.scrollTop=H[1];}else{C.setAttribute.call(this,E,H,G);}};B.Scroll=D;})();YAHOO.register("animation",YAHOO.util.Anim,{version:"2.8.1",build:"19"});if(!YAHOO.util.DragDropMgr){YAHOO.util.DragDropMgr=function(){var A=YAHOO.util.Event,B=YAHOO.util.Dom;return{useShim:false,_shimActive:false,_shimState:false,_debugShim:false,_createShim:function(){var C=document.createElement("div");C.id="yui-ddm-shim";if(document.body.firstChild){document.body.insertBefore(C,document.body.firstChild);}else{document.body.appendChild(C);}C.style.display="none";C.style.backgroundColor="red";C.style.position="absolute";C.style.zIndex="99999";B.setStyle(C,"opacity","0");this._shim=C;A.on(C,"mouseup",this.handleMouseUp,this,true);A.on(C,"mousemove",this.handleMouseMove,this,true);A.on(window,"scroll",this._sizeShim,this,true);},_sizeShim:function(){if(this._shimActive){var C=this._shim;C.style.height=B.getDocumentHeight()+"px";C.style.width=B.getDocumentWidth()+"px";C.style.top="0";C.style.left="0";}},_activateShim:function(){if(this.useShim){if(!this._shim){this._createShim();}this._shimActive=true;var C=this._shim,D="0";if(this._debugShim){D=".5";}B.setStyle(C,"opacity",D);this._sizeShim();C.style.display="block";}},_deactivateShim:function(){this._shim.style.display="none";this._shimActive=false;},_shim:null,ids:{},handleIds:{},dragCurrent:null,dragOvers:{},deltaX:0,deltaY:0,preventDefault:true,stopPropagation:true,initialized:false,locked:false,interactionInfo:null,init:function(){this.initialized=true;},POINT:0,INTERSECT:1,STRICT_INTERSECT:2,mode:0,_execOnAll:function(E,D){for(var F in this.ids){for(var C in this.ids[F]){var G=this.ids[F][C];if(!this.isTypeOfDD(G)){continue;}G[E].apply(G,D);}}},_onLoad:function(){this.init();A.on(document,"mouseup",this.handleMouseUp,this,true);A.on(document,"mousemove",this.handleMouseMove,this,true);A.on(window,"unload",this._onUnload,this,true);A.on(window,"resize",this._onResize,this,true);},_onResize:function(C){this._execOnAll("resetConstraints",[]);},lock:function(){this.locked=true;},unlock:function(){this.locked=false;},isLocked:function(){return this.locked;},locationCache:{},useCache:true,clickPixelThresh:3,clickTimeThresh:1000,dragThreshMet:false,clickTimeout:null,startX:0,startY:0,fromTimeout:false,regDragDrop:function(D,C){if(!this.initialized){this.init();}if(!this.ids[C]){this.ids[C]={};}this.ids[C][D.id]=D;},removeDDFromGroup:function(E,C){if(!this.ids[C]){this.ids[C]={};}var D=this.ids[C];if(D&&D[E.id]){delete D[E.id];}},_remove:function(E){for(var D in E.groups){if(D){var C=this.ids[D];if(C&&C[E.id]){delete C[E.id];}}}delete this.handleIds[E.id];},regHandle:function(D,C){if(!this.handleIds[D]){this.handleIds[D]={};}this.handleIds[D][C]=C;},isDragDrop:function(C){return(this.getDDById(C))?true:false;},getRelated:function(H,D){var G=[];for(var F in H.groups){for(var E in this.ids[F]){var C=this.ids[F][E];if(!this.isTypeOfDD(C)){continue;}if(!D||C.isTarget){G[G.length]=C;}}}return G;},isLegalTarget:function(G,F){var D=this.getRelated(G,true);for(var E=0,C=D.length;Ethis.clickPixelThresh||D>this.clickPixelThresh){this.startDrag(this.startX,this.startY);}}if(this.dragThreshMet){if(C&&C.events.b4Drag){C.b4Drag(F);C.fireEvent("b4DragEvent",{e:F});}if(C&&C.events.drag){C.onDrag(F);C.fireEvent("dragEvent",{e:F});}if(C){this.fireEvents(F,false);}}this.stopEvent(F);}},fireEvents:function(V,L){var a=this.dragCurrent;if(!a||a.isLocked()||a.dragOnly){return;}var N=YAHOO.util.Event.getPageX(V),M=YAHOO.util.Event.getPageY(V),P=new YAHOO.util.Point(N,M),K=a.getTargetCoord(P.x,P.y),F=a.getDragEl(),E=["out","over","drop","enter"],U=new YAHOO.util.Region(K.y,K.x+F.offsetWidth,K.y+F.offsetHeight,K.x),I=[],D={},Q=[],c={outEvts:[],overEvts:[],dropEvts:[],enterEvts:[]};for(var S in this.dragOvers){var d=this.dragOvers[S];if(!this.isTypeOfDD(d)){continue; -}if(!this.isOverTarget(P,d,this.mode,U)){c.outEvts.push(d);}I[S]=true;delete this.dragOvers[S];}for(var R in a.groups){if("string"!=typeof R){continue;}for(S in this.ids[R]){var G=this.ids[R][S];if(!this.isTypeOfDD(G)){continue;}if(G.isTarget&&!G.isLocked()&&G!=a){if(this.isOverTarget(P,G,this.mode,U)){D[R]=true;if(L){c.dropEvts.push(G);}else{if(!I[G.id]){c.enterEvts.push(G);}else{c.overEvts.push(G);}this.dragOvers[G.id]=G;}}}}}this.interactionInfo={out:c.outEvts,enter:c.enterEvts,over:c.overEvts,drop:c.dropEvts,point:P,draggedRegion:U,sourceRegion:this.locationCache[a.id],validDrop:L};for(var C in D){Q.push(C);}if(L&&!c.dropEvts.length){this.interactionInfo.validDrop=false;if(a.events.invalidDrop){a.onInvalidDrop(V);a.fireEvent("invalidDropEvent",{e:V});}}for(S=0;S2000){}else{setTimeout(C._addListeners,10);if(document&&document.body){C._timeoutCount+=1;}}}},handleWasClicked:function(C,E){if(this.isHandle(E,C.id)){return true;}else{var D=C.parentNode;while(D){if(this.isHandle(E,D.id)){return true;}else{D=D.parentNode;}}}return false;}};}();YAHOO.util.DDM=YAHOO.util.DragDropMgr;YAHOO.util.DDM._addListeners();}(function(){var A=YAHOO.util.Event;var B=YAHOO.util.Dom;YAHOO.util.DragDrop=function(E,C,D){if(E){this.init(E,C,D);}};YAHOO.util.DragDrop.prototype={events:null,on:function(){this.subscribe.apply(this,arguments);},id:null,config:null,dragElId:null,handleElId:null,invalidHandleTypes:null,invalidHandleIds:null,invalidHandleClasses:null,startPageX:0,startPageY:0,groups:null,locked:false,lock:function(){this.locked=true;},unlock:function(){this.locked=false;},isTarget:true,padding:null,dragOnly:false,useShim:false,_domRef:null,__ygDragDrop:true,constrainX:false,constrainY:false,minX:0,maxX:0,minY:0,maxY:0,deltaX:0,deltaY:0,maintainOffset:false,xTicks:null,yTicks:null,primaryButtonOnly:true,available:false,hasOuterHandles:false,cursorIsOver:false,overlap:null,b4StartDrag:function(C,D){},startDrag:function(C,D){},b4Drag:function(C){},onDrag:function(C){},onDragEnter:function(C,D){},b4DragOver:function(C){},onDragOver:function(C,D){},b4DragOut:function(C){},onDragOut:function(C,D){},b4DragDrop:function(C){},onDragDrop:function(C,D){},onInvalidDrop:function(C){},b4EndDrag:function(C){},endDrag:function(C){},b4MouseDown:function(C){},onMouseDown:function(C){},onMouseUp:function(C){},onAvailable:function(){},getEl:function(){if(!this._domRef){this._domRef=B.get(this.id); -}return this._domRef;},getDragEl:function(){return B.get(this.dragElId);},init:function(F,C,D){this.initTarget(F,C,D);A.on(this._domRef||this.id,"mousedown",this.handleMouseDown,this,true);for(var E in this.events){this.createEvent(E+"Event");}},initTarget:function(E,C,D){this.config=D||{};this.events={};this.DDM=YAHOO.util.DDM;this.groups={};if(typeof E!=="string"){this._domRef=E;E=B.generateId(E);}this.id=E;this.addToGroup((C)?C:"default");this.handleElId=E;A.onAvailable(E,this.handleOnAvailable,this,true);this.setDragElId(E);this.invalidHandleTypes={A:"A"};this.invalidHandleIds={};this.invalidHandleClasses=[];this.applyConfig();},applyConfig:function(){this.events={mouseDown:true,b4MouseDown:true,mouseUp:true,b4StartDrag:true,startDrag:true,b4EndDrag:true,endDrag:true,drag:true,b4Drag:true,invalidDrop:true,b4DragOut:true,dragOut:true,dragEnter:true,b4DragOver:true,dragOver:true,b4DragDrop:true,dragDrop:true};if(this.config.events){for(var C in this.config.events){if(this.config.events[C]===false){this.events[C]=false;}}}this.padding=this.config.padding||[0,0,0,0];this.isTarget=(this.config.isTarget!==false);this.maintainOffset=(this.config.maintainOffset);this.primaryButtonOnly=(this.config.primaryButtonOnly!==false);this.dragOnly=((this.config.dragOnly===true)?true:false);this.useShim=((this.config.useShim===true)?true:false);},handleOnAvailable:function(){this.available=true;this.resetConstraints();this.onAvailable();},setPadding:function(E,C,F,D){if(!C&&0!==C){this.padding=[E,E,E,E];}else{if(!F&&0!==F){this.padding=[E,C,E,C];}else{this.padding=[E,C,F,D];}}},setInitPosition:function(F,E){var G=this.getEl();if(!this.DDM.verifyEl(G)){if(G&&G.style&&(G.style.display=="none")){}else{}return;}var D=F||0;var C=E||0;var H=B.getXY(G);this.initPageX=H[0]-D;this.initPageY=H[1]-C;this.lastPageX=H[0];this.lastPageY=H[1];this.setStartPosition(H);},setStartPosition:function(D){var C=D||B.getXY(this.getEl());this.deltaSetXY=null;this.startPageX=C[0];this.startPageY=C[1];},addToGroup:function(C){this.groups[C]=true;this.DDM.regDragDrop(this,C);},removeFromGroup:function(C){if(this.groups[C]){delete this.groups[C];}this.DDM.removeDDFromGroup(this,C);},setDragElId:function(C){this.dragElId=C;},setHandleElId:function(C){if(typeof C!=="string"){C=B.generateId(C);}this.handleElId=C;this.DDM.regHandle(this.id,C);},setOuterHandleElId:function(C){if(typeof C!=="string"){C=B.generateId(C);}A.on(C,"mousedown",this.handleMouseDown,this,true);this.setHandleElId(C);this.hasOuterHandles=true;},unreg:function(){A.removeListener(this.id,"mousedown",this.handleMouseDown);this._domRef=null;this.DDM._remove(this);},isLocked:function(){return(this.DDM.isLocked()||this.locked);},handleMouseDown:function(J,I){var D=J.which||J.button;if(this.primaryButtonOnly&&D>1){return;}if(this.isLocked()){return;}var C=this.b4MouseDown(J),F=true;if(this.events.b4MouseDown){F=this.fireEvent("b4MouseDownEvent",J);}var E=this.onMouseDown(J),H=true;if(this.events.mouseDown){H=this.fireEvent("mouseDownEvent",J);}if((C===false)||(E===false)||(F===false)||(H===false)){return;}this.DDM.refreshCache(this.groups);var G=new YAHOO.util.Point(A.getPageX(J),A.getPageY(J));if(!this.hasOuterHandles&&!this.DDM.isOverTarget(G,this)){}else{if(this.clickValidator(J)){this.setStartPosition();this.DDM.handleMouseDown(J,this);this.DDM.stopEvent(J);}else{}}},clickValidator:function(D){var C=YAHOO.util.Event.getTarget(D);return(this.isValidHandleChild(C)&&(this.id==this.handleElId||this.DDM.handleWasClicked(C,this.id)));},getTargetCoord:function(E,D){var C=E-this.deltaX;var F=D-this.deltaY;if(this.constrainX){if(Cthis.maxX){C=this.maxX;}}if(this.constrainY){if(Fthis.maxY){F=this.maxY;}}C=this.getTick(C,this.xTicks);F=this.getTick(F,this.yTicks);return{x:C,y:F};},addInvalidHandleType:function(C){var D=C.toUpperCase();this.invalidHandleTypes[D]=D;},addInvalidHandleId:function(C){if(typeof C!=="string"){C=B.generateId(C);}this.invalidHandleIds[C]=C;},addInvalidHandleClass:function(C){this.invalidHandleClasses.push(C);},removeInvalidHandleType:function(C){var D=C.toUpperCase();delete this.invalidHandleTypes[D];},removeInvalidHandleId:function(C){if(typeof C!=="string"){C=B.generateId(C);}delete this.invalidHandleIds[C];},removeInvalidHandleClass:function(D){for(var E=0,C=this.invalidHandleClasses.length;E=this.minX;D=D-C){if(!E[D]){this.xTicks[this.xTicks.length]=D;E[D]=true;}}for(D=this.initPageX;D<=this.maxX;D=D+C){if(!E[D]){this.xTicks[this.xTicks.length]=D;E[D]=true;}}this.xTicks.sort(this.DDM.numericSort);},setYTicks:function(F,C){this.yTicks=[];this.yTickSize=C;var E={};for(var D=this.initPageY;D>=this.minY;D=D-C){if(!E[D]){this.yTicks[this.yTicks.length]=D;E[D]=true;}}for(D=this.initPageY;D<=this.maxY;D=D+C){if(!E[D]){this.yTicks[this.yTicks.length]=D;E[D]=true;}}this.yTicks.sort(this.DDM.numericSort);},setXConstraint:function(E,D,C){this.leftConstraint=parseInt(E,10);this.rightConstraint=parseInt(D,10);this.minX=this.initPageX-this.leftConstraint;this.maxX=this.initPageX+this.rightConstraint;if(C){this.setXTicks(this.initPageX,C);}this.constrainX=true;},clearConstraints:function(){this.constrainX=false;this.constrainY=false;this.clearTicks();},clearTicks:function(){this.xTicks=null;this.yTicks=null;this.xTickSize=0;this.yTickSize=0;},setYConstraint:function(C,E,D){this.topConstraint=parseInt(C,10);this.bottomConstraint=parseInt(E,10);this.minY=this.initPageY-this.topConstraint;this.maxY=this.initPageY+this.bottomConstraint;if(D){this.setYTicks(this.initPageY,D); -}this.constrainY=true;},resetConstraints:function(){if(this.initPageX||this.initPageX===0){var D=(this.maintainOffset)?this.lastPageX-this.initPageX:0;var C=(this.maintainOffset)?this.lastPageY-this.initPageY:0;this.setInitPosition(D,C);}else{this.setInitPosition();}if(this.constrainX){this.setXConstraint(this.leftConstraint,this.rightConstraint,this.xTickSize);}if(this.constrainY){this.setYConstraint(this.topConstraint,this.bottomConstraint,this.yTickSize);}},getTick:function(I,F){if(!F){return I;}else{if(F[0]>=I){return F[0];}else{for(var D=0,C=F.length;D=I){var H=I-F[D];var G=F[E]-I;return(G>H)?F[D]:F[E];}}return F[F.length-1];}}},toString:function(){return("DragDrop "+this.id);}};YAHOO.augment(YAHOO.util.DragDrop,YAHOO.util.EventProvider);})();YAHOO.util.DD=function(C,A,B){if(C){this.init(C,A,B);}};YAHOO.extend(YAHOO.util.DD,YAHOO.util.DragDrop,{scroll:true,autoOffset:function(C,B){var A=C-this.startPageX;var D=B-this.startPageY;this.setDelta(A,D);},setDelta:function(B,A){this.deltaX=B;this.deltaY=A;},setDragElPos:function(C,B){var A=this.getDragEl();this.alignElWithMouse(A,C,B);},alignElWithMouse:function(C,G,F){var E=this.getTargetCoord(G,F);if(!this.deltaSetXY){var H=[E.x,E.y];YAHOO.util.Dom.setXY(C,H);var D=parseInt(YAHOO.util.Dom.getStyle(C,"left"),10);var B=parseInt(YAHOO.util.Dom.getStyle(C,"top"),10);this.deltaSetXY=[D-E.x,B-E.y];}else{YAHOO.util.Dom.setStyle(C,"left",(E.x+this.deltaSetXY[0])+"px");YAHOO.util.Dom.setStyle(C,"top",(E.y+this.deltaSetXY[1])+"px");}this.cachePosition(E.x,E.y);var A=this;setTimeout(function(){A.autoScroll.call(A,E.x,E.y,C.offsetHeight,C.offsetWidth);},0);},cachePosition:function(B,A){if(B){this.lastPageX=B;this.lastPageY=A;}else{var C=YAHOO.util.Dom.getXY(this.getEl());this.lastPageX=C[0];this.lastPageY=C[1];}},autoScroll:function(J,I,E,K){if(this.scroll){var L=this.DDM.getClientHeight();var B=this.DDM.getClientWidth();var N=this.DDM.getScrollTop();var D=this.DDM.getScrollLeft();var H=E+I;var M=K+J;var G=(L+N-I-this.deltaY);var F=(B+D-J-this.deltaX);var C=40;var A=(document.all)?80:30;if(H>L&&G0&&I-NB&&F0&&J-D0){if(!aCache){this._aCache=[];}else{var nCacheLength=aCache.length;if(nCacheLength>0){var oResponse=null;this.fireEvent("cacheRequestEvent",{request:oRequest,callback:oCallback,caller:oCaller});for(var i=nCacheLength-1;i>=0;i--){var oCacheElem=aCache[i];if(this.isCacheHit(oRequest,oCacheElem.request)){oResponse=oCacheElem.response;this.fireEvent("cacheResponseEvent",{request:oRequest,response:oResponse,callback:oCallback,caller:oCaller});if(i=this.maxCacheEntries){aCache.shift();}var oCacheElem={request:oRequest,response:oResponse};aCache[aCache.length]=oCacheElem;this.fireEvent("responseCacheEvent",{request:oRequest,response:oResponse});},flushCache:function(){if(this._aCache){this._aCache=[];this.fireEvent("cacheFlushEvent");}},setInterval:function(nMsec,oRequest,oCallback,oCaller){if(lang.isNumber(nMsec)&&(nMsec>=0)){var oSelf=this;var nId=setInterval(function(){oSelf.makeConnection(oRequest,oCallback,oCaller);},nMsec);this._aIntervals.push(nId);return nId;}else{}},clearInterval:function(nId){var tracker=this._aIntervals||[];for(var i=tracker.length-1;i>-1;i--){if(tracker[i]===nId){tracker.splice(i,1);clearInterval(nId);}}},clearAllIntervals:function(){var tracker=this._aIntervals||[];for(var i=tracker.length-1;i>-1;i--){clearInterval(tracker[i]);}tracker=[];},sendRequest:function(oRequest,oCallback,oCaller){var oCachedResponse=this.getCachedResponse(oRequest,oCallback,oCaller);if(oCachedResponse){DS.issueCallback(oCallback,[oRequest,oCachedResponse],false,oCaller);return null;}return this.makeConnection(oRequest,oCallback,oCaller);},makeConnection:function(oRequest,oCallback,oCaller){var tId=DS._nTransactionId++;this.fireEvent("requestEvent",{tId:tId,request:oRequest,callback:oCallback,caller:oCaller});var oRawResponse=this.liveData;this.handleResponse(oRequest,oRawResponse,oCallback,oCaller,tId);return tId;},handleResponse:function(oRequest,oRawResponse,oCallback,oCaller,tId){this.fireEvent("responseEvent",{tId:tId,request:oRequest,response:oRawResponse,callback:oCallback,caller:oCaller});var xhr=(this.dataType==DS.TYPE_XHR)?true:false;var oParsedResponse=null;var oFullResponse=oRawResponse;if(this.responseType===DS.TYPE_UNKNOWN){var ctype=(oRawResponse&&oRawResponse.getResponseHeader)?oRawResponse.getResponseHeader["Content-Type"]:null;if(ctype){if(ctype.indexOf("text/xml")>-1){this.responseType=DS.TYPE_XML;}else{if(ctype.indexOf("application/json")>-1){this.responseType=DS.TYPE_JSON;}else{if(ctype.indexOf("text/plain")>-1){this.responseType=DS.TYPE_TEXT;}}}}else{if(YAHOO.lang.isArray(oRawResponse)){this.responseType=DS.TYPE_JSARRAY; -}else{if(oRawResponse&&oRawResponse.nodeType&&(oRawResponse.nodeType===9||oRawResponse.nodeType===1||oRawResponse.nodeType===11)){this.responseType=DS.TYPE_XML;}else{if(oRawResponse&&oRawResponse.nodeName&&(oRawResponse.nodeName.toLowerCase()=="table")){this.responseType=DS.TYPE_HTMLTABLE;}else{if(YAHOO.lang.isObject(oRawResponse)){this.responseType=DS.TYPE_JSON;}else{if(YAHOO.lang.isString(oRawResponse)){this.responseType=DS.TYPE_TEXT;}}}}}}}switch(this.responseType){case DS.TYPE_JSARRAY:if(xhr&&oRawResponse&&oRawResponse.responseText){oFullResponse=oRawResponse.responseText;}try{if(lang.isString(oFullResponse)){var parseArgs=[oFullResponse].concat(this.parseJSONArgs);if(lang.JSON){oFullResponse=lang.JSON.parse.apply(lang.JSON,parseArgs);}else{if(window.JSON&&JSON.parse){oFullResponse=JSON.parse.apply(JSON,parseArgs);}else{if(oFullResponse.parseJSON){oFullResponse=oFullResponse.parseJSON.apply(oFullResponse,parseArgs.slice(1));}else{while(oFullResponse.length>0&&(oFullResponse.charAt(0)!="{")&&(oFullResponse.charAt(0)!="[")){oFullResponse=oFullResponse.substring(1,oFullResponse.length);}if(oFullResponse.length>0){var arrayEnd=Math.max(oFullResponse.lastIndexOf("]"),oFullResponse.lastIndexOf("}"));oFullResponse=oFullResponse.substring(0,arrayEnd+1);oFullResponse=eval("("+oFullResponse+")");}}}}}}catch(e1){}oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseArrayData(oRequest,oFullResponse);break;case DS.TYPE_JSON:if(xhr&&oRawResponse&&oRawResponse.responseText){oFullResponse=oRawResponse.responseText;}try{if(lang.isString(oFullResponse)){var parseArgs=[oFullResponse].concat(this.parseJSONArgs);if(lang.JSON){oFullResponse=lang.JSON.parse.apply(lang.JSON,parseArgs);}else{if(window.JSON&&JSON.parse){oFullResponse=JSON.parse.apply(JSON,parseArgs);}else{if(oFullResponse.parseJSON){oFullResponse=oFullResponse.parseJSON.apply(oFullResponse,parseArgs.slice(1));}else{while(oFullResponse.length>0&&(oFullResponse.charAt(0)!="{")&&(oFullResponse.charAt(0)!="[")){oFullResponse=oFullResponse.substring(1,oFullResponse.length);}if(oFullResponse.length>0){var objEnd=Math.max(oFullResponse.lastIndexOf("]"),oFullResponse.lastIndexOf("}"));oFullResponse=oFullResponse.substring(0,objEnd+1);oFullResponse=eval("("+oFullResponse+")");}}}}}}catch(e){}oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseJSONData(oRequest,oFullResponse);break;case DS.TYPE_HTMLTABLE:if(xhr&&oRawResponse.responseText){var el=document.createElement("div");el.innerHTML=oRawResponse.responseText;oFullResponse=el.getElementsByTagName("table")[0];}oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseHTMLTableData(oRequest,oFullResponse);break;case DS.TYPE_XML:if(xhr&&oRawResponse.responseXML){oFullResponse=oRawResponse.responseXML;}oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseXMLData(oRequest,oFullResponse);break;case DS.TYPE_TEXT:if(xhr&&lang.isString(oRawResponse.responseText)){oFullResponse=oRawResponse.responseText;}oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseTextData(oRequest,oFullResponse);break;default:oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseData(oRequest,oFullResponse);break;}oParsedResponse=oParsedResponse||{};if(!oParsedResponse.results){oParsedResponse.results=[];}if(!oParsedResponse.meta){oParsedResponse.meta={};}if(!oParsedResponse.error){oParsedResponse=this.doBeforeCallback(oRequest,oFullResponse,oParsedResponse,oCallback);this.fireEvent("responseParseEvent",{request:oRequest,response:oParsedResponse,callback:oCallback,caller:oCaller});this.addToCache(oRequest,oParsedResponse);}else{oParsedResponse.error=true;this.fireEvent("dataErrorEvent",{request:oRequest,response:oRawResponse,callback:oCallback,caller:oCaller,message:DS.ERROR_DATANULL});}oParsedResponse.tId=tId;DS.issueCallback(oCallback,[oRequest,oParsedResponse],oParsedResponse.error,oCaller);},doBeforeParseData:function(oRequest,oFullResponse,oCallback){return oFullResponse;},doBeforeCallback:function(oRequest,oFullResponse,oParsedResponse,oCallback){return oParsedResponse;},parseData:function(oRequest,oFullResponse){if(lang.isValue(oFullResponse)){var oParsedResponse={results:oFullResponse,meta:{}};return oParsedResponse;}return null;},parseArrayData:function(oRequest,oFullResponse){if(lang.isArray(oFullResponse)){var results=[],i,j,rec,field,data;if(lang.isArray(this.responseSchema.fields)){var fields=this.responseSchema.fields;for(i=fields.length-1;i>=0;--i){if(typeof fields[i]!=="object"){fields[i]={key:fields[i]};}}var parsers={},p;for(i=fields.length-1;i>=0;--i){p=(typeof fields[i].parser==="function"?fields[i].parser:DS.Parser[fields[i].parser+""])||fields[i].converter;if(p){parsers[fields[i].key]=p;}}var arrType=lang.isArray(oFullResponse[0]);for(i=oFullResponse.length-1;i>-1;i--){var oResult={};rec=oFullResponse[i];if(typeof rec==="object"){for(j=fields.length-1;j>-1;j--){field=fields[j];data=arrType?rec[j]:rec[field.key];if(parsers[field.key]){data=parsers[field.key].call(this,data);}if(data===undefined){data=null;}oResult[field.key]=data;}}else{if(lang.isString(rec)){for(j=fields.length-1;j>-1;j--){field=fields[j];data=rec;if(parsers[field.key]){data=parsers[field.key].call(this,data);}if(data===undefined){data=null;}oResult[field.key]=data;}}}results[i]=oResult;}}else{results=oFullResponse;}var oParsedResponse={results:results};return oParsedResponse;}return null;},parseTextData:function(oRequest,oFullResponse){if(lang.isString(oFullResponse)){if(lang.isString(this.responseSchema.recordDelim)&&lang.isString(this.responseSchema.fieldDelim)){var oParsedResponse={results:[]};var recDelim=this.responseSchema.recordDelim;var fieldDelim=this.responseSchema.fieldDelim;if(oFullResponse.length>0){var newLength=oFullResponse.length-recDelim.length;if(oFullResponse.substr(newLength)==recDelim){oFullResponse=oFullResponse.substr(0,newLength); -}if(oFullResponse.length>0){var recordsarray=oFullResponse.split(recDelim);for(var i=0,len=recordsarray.length,recIdx=0;i0)){var fielddataarray=recordsarray[i].split(fieldDelim);var oResult={};if(lang.isArray(this.responseSchema.fields)){var fields=this.responseSchema.fields;for(var j=fields.length-1;j>-1;j--){try{var data=fielddataarray[j];if(lang.isString(data)){if(data.charAt(0)=='"'){data=data.substr(1);}if(data.charAt(data.length-1)=='"'){data=data.substr(0,data.length-1);}var field=fields[j];var key=(lang.isValue(field.key))?field.key:field;if(!field.parser&&field.converter){field.parser=field.converter;}var parser=(typeof field.parser==="function")?field.parser:DS.Parser[field.parser+""];if(parser){data=parser.call(this,data);}if(data===undefined){data=null;}oResult[key]=data;}else{bError=true;}}catch(e){bError=true;}}}else{oResult=fielddataarray;}if(!bError){oParsedResponse.results[recIdx++]=oResult;}}}}}return oParsedResponse;}}return null;},parseXMLResult:function(result){var oResult={},schema=this.responseSchema;try{for(var m=schema.fields.length-1;m>=0;m--){var field=schema.fields[m];var key=(lang.isValue(field.key))?field.key:field;var data=null;if(this.useXPath){data=YAHOO.util.DataSource._getLocationValue(field,result);}else{var xmlAttr=result.attributes.getNamedItem(key);if(xmlAttr){data=xmlAttr.value;}else{var xmlNode=result.getElementsByTagName(key);if(xmlNode&&xmlNode.item(0)){var item=xmlNode.item(0);data=(item)?((item.text)?item.text:(item.textContent)?item.textContent:null):null;if(!data){var datapieces=[];for(var j=0,len=item.childNodes.length;j0){data=datapieces.join("");}}}}}if(data===null){data="";}if(!field.parser&&field.converter){field.parser=field.converter;}var parser=(typeof field.parser==="function")?field.parser:DS.Parser[field.parser+""];if(parser){data=parser.call(this,data);}if(data===undefined){data=null;}oResult[key]=data;}}catch(e){}return oResult;},parseXMLData:function(oRequest,oFullResponse){var bError=false,schema=this.responseSchema,oParsedResponse={meta:{}},xmlList=null,metaNode=schema.metaNode,metaLocators=schema.metaFields||{},i,k,loc,v;try{if(this.useXPath){for(k in metaLocators){oParsedResponse.meta[k]=YAHOO.util.DataSource._getLocationValue(metaLocators[k],oFullResponse);}}else{metaNode=metaNode?oFullResponse.getElementsByTagName(metaNode)[0]:oFullResponse;if(metaNode){for(k in metaLocators){if(lang.hasOwnProperty(metaLocators,k)){loc=metaLocators[k];v=metaNode.getElementsByTagName(loc)[0];if(v){v=v.firstChild.nodeValue;}else{v=metaNode.attributes.getNamedItem(loc);if(v){v=v.value;}}if(lang.isValue(v)){oParsedResponse.meta[k]=v;}}}}}xmlList=(schema.resultNode)?oFullResponse.getElementsByTagName(schema.resultNode):null;}catch(e){}if(!xmlList||!lang.isArray(schema.fields)){bError=true;}else{oParsedResponse.results=[];for(i=xmlList.length-1;i>=0;--i){var oResult=this.parseXMLResult(xmlList.item(i));oParsedResponse.results[i]=oResult;}}if(bError){oParsedResponse.error=true;}else{}return oParsedResponse;},parseJSONData:function(oRequest,oFullResponse){var oParsedResponse={results:[],meta:{}};if(lang.isObject(oFullResponse)&&this.responseSchema.resultsList){var schema=this.responseSchema,fields=schema.fields,resultsList=oFullResponse,results=[],metaFields=schema.metaFields||{},fieldParsers=[],fieldPaths=[],simpleFields=[],bError=false,i,len,j,v,key,parser,path;var buildPath=function(needle){var path=null,keys=[],i=0;if(needle){needle=needle.replace(/\[(['"])(.*?)\1\]/g,function(x,$1,$2){keys[i]=$2;return".@"+(i++);}).replace(/\[(\d+)\]/g,function(x,$1){keys[i]=parseInt($1,10)|0;return".@"+(i++);}).replace(/^\./,"");if(!/[^\w\.\$@]/.test(needle)){path=needle.split(".");for(i=path.length-1;i>=0;--i){if(path[i].charAt(0)==="@"){path[i]=keys[parseInt(path[i].substr(1),10)];}}}else{}}return path;};var walkPath=function(path,origin){var v=origin,i=0,len=path.length;for(;i1){fieldPaths[fieldPaths.length]={key:key,path:path};}else{simpleFields[simpleFields.length]={key:key,path:path[0]};}}else{}}for(i=resultsList.length-1;i>=0;--i){var r=resultsList[i],rec={};if(r){for(j=simpleFields.length-1;j>=0;--j){rec[simpleFields[j].key]=(r[simpleFields[j].path]!==undefined)?r[simpleFields[j].path]:r[j];}for(j=fieldPaths.length-1;j>=0;--j){rec[fieldPaths[j].key]=walkPath(fieldPaths[j].path,r);}for(j=fieldParsers.length-1;j>=0;--j){var p=fieldParsers[j].key;rec[p]=fieldParsers[j].parser(rec[p]);if(rec[p]===undefined){rec[p]=null;}}}results[i]=rec;}}else{results=resultsList;}for(key in metaFields){if(lang.hasOwnProperty(metaFields,key)){path=buildPath(metaFields[key]);if(path){v=walkPath(path,oFullResponse);oParsedResponse.meta[key]=v;}}}}else{oParsedResponse.error=true;}oParsedResponse.results=results;}else{oParsedResponse.error=true;}return oParsedResponse;},parseHTMLTableData:function(oRequest,oFullResponse){var bError=false;var elTable=oFullResponse;var fields=this.responseSchema.fields;var oParsedResponse={results:[]};if(lang.isArray(fields)){for(var i=0;i-1;j--){var elRow=elTbody.rows[j];var oResult={};for(var k=fields.length-1;k>-1;k--){var field=fields[k];var key=(lang.isValue(field.key))?field.key:field; -var data=elRow.cells[k].innerHTML;if(!field.parser&&field.converter){field.parser=field.converter;}var parser=(typeof field.parser==="function")?field.parser:DS.Parser[field.parser+""];if(parser){data=parser.call(this,data);}if(data===undefined){data=null;}oResult[key]=data;}oParsedResponse.results[j]=oResult;}}}else{bError=true;}if(bError){oParsedResponse.error=true;}else{}return oParsedResponse;}};lang.augmentProto(DS,util.EventProvider);util.LocalDataSource=function(oLiveData,oConfigs){this.dataType=DS.TYPE_LOCAL;if(oLiveData){if(YAHOO.lang.isArray(oLiveData)){this.responseType=DS.TYPE_JSARRAY;}else{if(oLiveData.nodeType&&oLiveData.nodeType==9){this.responseType=DS.TYPE_XML;}else{if(oLiveData.nodeName&&(oLiveData.nodeName.toLowerCase()=="table")){this.responseType=DS.TYPE_HTMLTABLE;oLiveData=oLiveData.cloneNode(true);}else{if(YAHOO.lang.isString(oLiveData)){this.responseType=DS.TYPE_TEXT;}else{if(YAHOO.lang.isObject(oLiveData)){this.responseType=DS.TYPE_JSON;}}}}}}else{oLiveData=[];this.responseType=DS.TYPE_JSARRAY;}util.LocalDataSource.superclass.constructor.call(this,oLiveData,oConfigs);};lang.extend(util.LocalDataSource,DS);lang.augmentObject(util.LocalDataSource,DS);util.FunctionDataSource=function(oLiveData,oConfigs){this.dataType=DS.TYPE_JSFUNCTION;oLiveData=oLiveData||function(){};util.FunctionDataSource.superclass.constructor.call(this,oLiveData,oConfigs);};lang.extend(util.FunctionDataSource,DS,{scope:null,makeConnection:function(oRequest,oCallback,oCaller){var tId=DS._nTransactionId++;this.fireEvent("requestEvent",{tId:tId,request:oRequest,callback:oCallback,caller:oCaller});var oRawResponse=(this.scope)?this.liveData.call(this.scope,oRequest,this):this.liveData(oRequest);if(this.responseType===DS.TYPE_UNKNOWN){if(YAHOO.lang.isArray(oRawResponse)){this.responseType=DS.TYPE_JSARRAY;}else{if(oRawResponse&&oRawResponse.nodeType&&oRawResponse.nodeType==9){this.responseType=DS.TYPE_XML;}else{if(oRawResponse&&oRawResponse.nodeName&&(oRawResponse.nodeName.toLowerCase()=="table")){this.responseType=DS.TYPE_HTMLTABLE;}else{if(YAHOO.lang.isObject(oRawResponse)){this.responseType=DS.TYPE_JSON;}else{if(YAHOO.lang.isString(oRawResponse)){this.responseType=DS.TYPE_TEXT;}}}}}}this.handleResponse(oRequest,oRawResponse,oCallback,oCaller,tId);return tId;}});lang.augmentObject(util.FunctionDataSource,DS);util.ScriptNodeDataSource=function(oLiveData,oConfigs){this.dataType=DS.TYPE_SCRIPTNODE;oLiveData=oLiveData||"";util.ScriptNodeDataSource.superclass.constructor.call(this,oLiveData,oConfigs);};lang.extend(util.ScriptNodeDataSource,DS,{getUtility:util.Get,asyncMode:"allowAll",scriptCallbackParam:"callback",generateRequestCallback:function(id){return"&"+this.scriptCallbackParam+"=YAHOO.util.ScriptNodeDataSource.callbacks["+id+"]";},doBeforeGetScriptNode:function(sUri){return sUri;},makeConnection:function(oRequest,oCallback,oCaller){var tId=DS._nTransactionId++;this.fireEvent("requestEvent",{tId:tId,request:oRequest,callback:oCallback,caller:oCaller});if(util.ScriptNodeDataSource._nPending===0){util.ScriptNodeDataSource.callbacks=[];util.ScriptNodeDataSource._nId=0;}var id=util.ScriptNodeDataSource._nId;util.ScriptNodeDataSource._nId++;var oSelf=this;util.ScriptNodeDataSource.callbacks[id]=function(oRawResponse){if((oSelf.asyncMode!=="ignoreStaleResponses")||(id===util.ScriptNodeDataSource.callbacks.length-1)){if(oSelf.responseType===DS.TYPE_UNKNOWN){if(YAHOO.lang.isArray(oRawResponse)){oSelf.responseType=DS.TYPE_JSARRAY;}else{if(oRawResponse.nodeType&&oRawResponse.nodeType==9){oSelf.responseType=DS.TYPE_XML;}else{if(oRawResponse.nodeName&&(oRawResponse.nodeName.toLowerCase()=="table")){oSelf.responseType=DS.TYPE_HTMLTABLE;}else{if(YAHOO.lang.isObject(oRawResponse)){oSelf.responseType=DS.TYPE_JSON;}else{if(YAHOO.lang.isString(oRawResponse)){oSelf.responseType=DS.TYPE_TEXT;}}}}}}oSelf.handleResponse(oRequest,oRawResponse,oCallback,oCaller,tId);}else{}delete util.ScriptNodeDataSource.callbacks[id];};util.ScriptNodeDataSource._nPending++;var sUri=this.liveData+oRequest+this.generateRequestCallback(id);sUri=this.doBeforeGetScriptNode(sUri);this.getUtility.script(sUri,{autopurge:true,onsuccess:util.ScriptNodeDataSource._bumpPendingDown,onfail:util.ScriptNodeDataSource._bumpPendingDown});return tId;}});lang.augmentObject(util.ScriptNodeDataSource,DS);lang.augmentObject(util.ScriptNodeDataSource,{_nId:0,_nPending:0,callbacks:[]});util.XHRDataSource=function(oLiveData,oConfigs){this.dataType=DS.TYPE_XHR;this.connMgr=this.connMgr||util.Connect;oLiveData=oLiveData||"";util.XHRDataSource.superclass.constructor.call(this,oLiveData,oConfigs);};lang.extend(util.XHRDataSource,DS,{connMgr:null,connXhrMode:"allowAll",connMethodPost:false,connTimeout:0,makeConnection:function(oRequest,oCallback,oCaller){var oRawResponse=null;var tId=DS._nTransactionId++;this.fireEvent("requestEvent",{tId:tId,request:oRequest,callback:oCallback,caller:oCaller});var oSelf=this;var oConnMgr=this.connMgr;var oQueue=this._oQueue;var _xhrSuccess=function(oResponse){if(oResponse&&(this.connXhrMode=="ignoreStaleResponses")&&(oResponse.tId!=oQueue.conn.tId)){return null;}else{if(!oResponse){this.fireEvent("dataErrorEvent",{request:oRequest,response:null,callback:oCallback,caller:oCaller,message:DS.ERROR_DATANULL});DS.issueCallback(oCallback,[oRequest,{error:true}],true,oCaller);return null;}else{if(this.responseType===DS.TYPE_UNKNOWN){var ctype=(oResponse.getResponseHeader)?oResponse.getResponseHeader["Content-Type"]:null;if(ctype){if(ctype.indexOf("text/xml")>-1){this.responseType=DS.TYPE_XML;}else{if(ctype.indexOf("application/json")>-1){this.responseType=DS.TYPE_JSON;}else{if(ctype.indexOf("text/plain")>-1){this.responseType=DS.TYPE_TEXT;}}}}}this.handleResponse(oRequest,oResponse,oCallback,oCaller,tId);}}};var _xhrFailure=function(oResponse){this.fireEvent("dataErrorEvent",{request:oRequest,response:oResponse,callback:oCallback,caller:oCaller,message:DS.ERROR_DATAINVALID});if(lang.isString(this.liveData)&&lang.isString(oRequest)&&(this.liveData.lastIndexOf("?")!==this.liveData.length-1)&&(oRequest.indexOf("?")!==0)){}oResponse=oResponse||{}; -oResponse.error=true;DS.issueCallback(oCallback,[oRequest,oResponse],true,oCaller);return null;};var _xhrCallback={success:_xhrSuccess,failure:_xhrFailure,scope:this};if(lang.isNumber(this.connTimeout)){_xhrCallback.timeout=this.connTimeout;}if(this.connXhrMode=="cancelStaleRequests"){if(oQueue.conn){if(oConnMgr.abort){oConnMgr.abort(oQueue.conn);oQueue.conn=null;}else{}}}if(oConnMgr&&oConnMgr.asyncRequest){var sLiveData=this.liveData;var isPost=this.connMethodPost;var sMethod=(isPost)?"POST":"GET";var sUri=(isPost||!lang.isValue(oRequest))?sLiveData:sLiveData+oRequest;var sRequest=(isPost)?oRequest:null;if(this.connXhrMode!="queueRequests"){oQueue.conn=oConnMgr.asyncRequest(sMethod,sUri,_xhrCallback,sRequest);}else{if(oQueue.conn){var allRequests=oQueue.requests;allRequests.push({request:oRequest,callback:_xhrCallback});if(!oQueue.interval){oQueue.interval=setInterval(function(){if(oConnMgr.isCallInProgress(oQueue.conn)){return;}else{if(allRequests.length>0){sUri=(isPost||!lang.isValue(allRequests[0].request))?sLiveData:sLiveData+allRequests[0].request;sRequest=(isPost)?allRequests[0].request:null;oQueue.conn=oConnMgr.asyncRequest(sMethod,sUri,allRequests[0].callback,sRequest);allRequests.shift();}else{clearInterval(oQueue.interval);oQueue.interval=null;}}},50);}}else{oQueue.conn=oConnMgr.asyncRequest(sMethod,sUri,_xhrCallback,sRequest);}}}else{DS.issueCallback(oCallback,[oRequest,{error:true}],true,oCaller);}return tId;}});lang.augmentObject(util.XHRDataSource,DS);util.DataSource=function(oLiveData,oConfigs){oConfigs=oConfigs||{};var dataType=oConfigs.dataType;if(dataType){if(dataType==DS.TYPE_LOCAL){lang.augmentObject(util.DataSource,util.LocalDataSource);return new util.LocalDataSource(oLiveData,oConfigs);}else{if(dataType==DS.TYPE_XHR){lang.augmentObject(util.DataSource,util.XHRDataSource);return new util.XHRDataSource(oLiveData,oConfigs);}else{if(dataType==DS.TYPE_SCRIPTNODE){lang.augmentObject(util.DataSource,util.ScriptNodeDataSource);return new util.ScriptNodeDataSource(oLiveData,oConfigs);}else{if(dataType==DS.TYPE_JSFUNCTION){lang.augmentObject(util.DataSource,util.FunctionDataSource);return new util.FunctionDataSource(oLiveData,oConfigs);}}}}}if(YAHOO.lang.isString(oLiveData)){lang.augmentObject(util.DataSource,util.XHRDataSource);return new util.XHRDataSource(oLiveData,oConfigs);}else{if(YAHOO.lang.isFunction(oLiveData)){lang.augmentObject(util.DataSource,util.FunctionDataSource);return new util.FunctionDataSource(oLiveData,oConfigs);}else{lang.augmentObject(util.DataSource,util.LocalDataSource);return new util.LocalDataSource(oLiveData,oConfigs);}}};lang.augmentObject(util.DataSource,DS);})();YAHOO.util.Number={format:function(B,E){if(!isFinite(+B)){return"";}B=!isFinite(+B)?0:+B;E=YAHOO.lang.merge(YAHOO.util.Number.format.defaults,(E||{}));var C=B<0,F=Math.abs(B),A=E.decimalPlaces,I=E.thousandsSeparator,H,G,D;if(A<0){H=F-(F%1)+"";D=H.length+A;if(D>0){H=Number("."+H).toFixed(D).slice(2)+new Array(H.length-D+1).join("0");}else{H="0";}}else{H=F<1&&F>=0.5&&!A?"1":F.toFixed(A);}if(F>1000){G=H.split(/\D/);D=G[0].length%3||3;G[0]=G[0].slice(0,D)+G[0].slice(D).replace(/(\d{3})/g,I+"$1");H=G.join(E.decimalSeparator);}H=E.prefix+H+E.suffix;return C?E.negativeFormat.replace(/#/,H):H;}};YAHOO.util.Number.format.defaults={decimalSeparator:".",decimalPlaces:null,thousandsSeparator:"",prefix:"",suffix:"",negativeFormat:"-#"};(function(){var A=function(C,E,D){if(typeof D==="undefined"){D=10;}for(;parseInt(C,10)1;D/=10){C=E.toString()+C;}return C.toString();};var B={formats:{a:function(D,C){return C.a[D.getDay()];},A:function(D,C){return C.A[D.getDay()];},b:function(D,C){return C.b[D.getMonth()];},B:function(D,C){return C.B[D.getMonth()];},C:function(C){return A(parseInt(C.getFullYear()/100,10),0);},d:["getDate","0"],e:["getDate"," "],g:function(C){return A(parseInt(B.formats.G(C)%100,10),0);},G:function(E){var F=E.getFullYear();var D=parseInt(B.formats.V(E),10);var C=parseInt(B.formats.W(E),10);if(C>D){F++;}else{if(C===0&&D>=52){F--;}}return F;},H:["getHours","0"],I:function(D){var C=D.getHours()%12;return A(C===0?12:C,0);},j:function(G){var F=new Date(""+G.getFullYear()+"/1/1 GMT");var D=new Date(""+G.getFullYear()+"/"+(G.getMonth()+1)+"/"+G.getDate()+" GMT");var C=D-F;var E=parseInt(C/60000/60/24,10)+1;return A(E,0,100);},k:["getHours"," "],l:function(D){var C=D.getHours()%12;return A(C===0?12:C," ");},m:function(C){return A(C.getMonth()+1,0);},M:["getMinutes","0"],p:function(D,C){return C.p[D.getHours()>=12?1:0];},P:function(D,C){return C.P[D.getHours()>=12?1:0];},s:function(D,C){return parseInt(D.getTime()/1000,10);},S:["getSeconds","0"],u:function(C){var D=C.getDay();return D===0?7:D;},U:function(F){var C=parseInt(B.formats.j(F),10);var E=6-F.getDay();var D=parseInt((C+E)/7,10);return A(D,0);},V:function(F){var E=parseInt(B.formats.W(F),10);var C=(new Date(""+F.getFullYear()+"/1/1")).getDay();var D=E+(C>4||C<=1?0:1);if(D===53&&(new Date(""+F.getFullYear()+"/12/31")).getDay()<4){D=1;}else{if(D===0){D=B.formats.V(new Date(""+(F.getFullYear()-1)+"/12/31"));}}return A(D,0);},w:"getDay",W:function(F){var C=parseInt(B.formats.j(F),10);var E=7-B.formats.u(F);var D=parseInt((C+E)/7,10);return A(D,0,10);},y:function(C){return A(C.getFullYear()%100,0);},Y:"getFullYear",z:function(E){var D=E.getTimezoneOffset();var C=A(parseInt(Math.abs(D/60),10),0);var F=A(Math.abs(D%60),0);return(D>0?"-":"+")+C+F;},Z:function(C){var D=C.toString().replace(/^.*:\d\d( GMT[+-]\d+)? \(?([A-Za-z ]+)\)?\d*$/,"$2").replace(/[a-z ]/g,"");if(D.length>4){D=B.formats.z(C);}return D;},"%":function(C){return"%";}},aggregates:{c:"locale",D:"%m/%d/%y",F:"%Y-%m-%d",h:"%b",n:"\n",r:"locale",R:"%H:%M",t:"\t",T:"%H:%M:%S",x:"locale",X:"locale"},format:function(G,F,D){F=F||{};if(!(G instanceof Date)){return YAHOO.lang.isValue(G)?G:"";}var H=F.format||"%m/%d/%Y";if(H==="YYYY/MM/DD"){H="%Y/%m/%d";}else{if(H==="DD/MM/YYYY"){H="%d/%m/%Y";}else{if(H==="MM/DD/YYYY"){H="%m/%d/%Y";}}}D=D||"en";if(!(D in YAHOO.util.DateLocale)){if(D.replace(/-[a-zA-Z]+$/,"") in YAHOO.util.DateLocale){D=D.replace(/-[a-zA-Z]+$/,""); -}else{D="en";}}var J=YAHOO.util.DateLocale[D];var C=function(L,K){var M=B.aggregates[K];return(M==="locale"?J[K]:M);};var E=function(L,K){var M=B.formats[K];if(typeof M==="string"){return G[M]();}else{if(typeof M==="function"){return M.call(G,G,J);}else{if(typeof M==="object"&&typeof M[0]==="string"){return A(G[M[0]](),M[1]);}else{return K;}}}};while(H.match(/%[cDFhnrRtTxX]/)){H=H.replace(/%([cDFhnrRtTxX])/g,C);}var I=H.replace(/%([aAbBCdegGHIjklmMpPsSuUVwWyYzZ%])/g,E);C=E=undefined;return I;}};YAHOO.namespace("YAHOO.util");YAHOO.util.Date=B;YAHOO.util.DateLocale={a:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],A:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],b:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],B:["January","February","March","April","May","June","July","August","September","October","November","December"],c:"%a %d %b %Y %T %Z",p:["AM","PM"],P:["am","pm"],r:"%I:%M:%S %p",x:"%d/%m/%y",X:"%T"};YAHOO.util.DateLocale["en"]=YAHOO.lang.merge(YAHOO.util.DateLocale,{});YAHOO.util.DateLocale["en-US"]=YAHOO.lang.merge(YAHOO.util.DateLocale["en"],{c:"%a %d %b %Y %I:%M:%S %p %Z",x:"%m/%d/%Y",X:"%I:%M:%S %p"});YAHOO.util.DateLocale["en-GB"]=YAHOO.lang.merge(YAHOO.util.DateLocale["en"],{r:"%l:%M:%S %P %Z"});YAHOO.util.DateLocale["en-AU"]=YAHOO.lang.merge(YAHOO.util.DateLocale["en"]);})();YAHOO.register("datasource",YAHOO.util.DataSource,{version:"2.8.1",build:"19"});/* -Copyright (c) 2010, Yahoo! Inc. All rights reserved. -Code licensed under the BSD License: -http://developer.yahoo.com/yui/license.html -version: 2.8.1 -*/ -YAHOO.widget.DS_JSArray=YAHOO.util.LocalDataSource;YAHOO.widget.DS_JSFunction=YAHOO.util.FunctionDataSource;YAHOO.widget.DS_XHR=function(B,A,D){var C=new YAHOO.util.XHRDataSource(B,D);C._aDeprecatedSchema=A;return C;};YAHOO.widget.DS_ScriptNode=function(B,A,D){var C=new YAHOO.util.ScriptNodeDataSource(B,D);C._aDeprecatedSchema=A;return C;};YAHOO.widget.DS_XHR.TYPE_JSON=YAHOO.util.DataSourceBase.TYPE_JSON;YAHOO.widget.DS_XHR.TYPE_XML=YAHOO.util.DataSourceBase.TYPE_XML;YAHOO.widget.DS_XHR.TYPE_FLAT=YAHOO.util.DataSourceBase.TYPE_TEXT;YAHOO.widget.AutoComplete=function(G,B,J,C){if(G&&B&&J){if(J&&YAHOO.lang.isFunction(J.sendRequest)){this.dataSource=J;}else{return;}this.key=0;var D=J.responseSchema;if(J._aDeprecatedSchema){var K=J._aDeprecatedSchema;if(YAHOO.lang.isArray(K)){if((J.responseType===YAHOO.util.DataSourceBase.TYPE_JSON)||(J.responseType===YAHOO.util.DataSourceBase.TYPE_UNKNOWN)){D.resultsList=K[0];this.key=K[1];D.fields=(K.length<3)?null:K.slice(1);}else{if(J.responseType===YAHOO.util.DataSourceBase.TYPE_XML){D.resultNode=K[0];this.key=K[1];D.fields=K.slice(1);}else{if(J.responseType===YAHOO.util.DataSourceBase.TYPE_TEXT){D.recordDelim=K[0];D.fieldDelim=K[1];}}}J.responseSchema=D;}}if(YAHOO.util.Dom.inDocument(G)){if(YAHOO.lang.isString(G)){this._sName="instance"+YAHOO.widget.AutoComplete._nIndex+" "+G;this._elTextbox=document.getElementById(G);}else{this._sName=(G.id)?"instance"+YAHOO.widget.AutoComplete._nIndex+" "+G.id:"instance"+YAHOO.widget.AutoComplete._nIndex;this._elTextbox=G;}YAHOO.util.Dom.addClass(this._elTextbox,"yui-ac-input");}else{return;}if(YAHOO.util.Dom.inDocument(B)){if(YAHOO.lang.isString(B)){this._elContainer=document.getElementById(B);}else{this._elContainer=B;}if(this._elContainer.style.display=="none"){}var E=this._elContainer.parentNode;var A=E.tagName.toLowerCase();if(A=="div"){YAHOO.util.Dom.addClass(E,"yui-ac");}else{}}else{return;}if(this.dataSource.dataType===YAHOO.util.DataSourceBase.TYPE_LOCAL){this.applyLocalFilter=true;}if(C&&(C.constructor==Object)){for(var I in C){if(I){this[I]=C[I];}}}this._initContainerEl();this._initProps();this._initListEl();this._initContainerHelperEls();var H=this;var F=this._elTextbox;YAHOO.util.Event.addListener(F,"keyup",H._onTextboxKeyUp,H);YAHOO.util.Event.addListener(F,"keydown",H._onTextboxKeyDown,H);YAHOO.util.Event.addListener(F,"focus",H._onTextboxFocus,H);YAHOO.util.Event.addListener(F,"blur",H._onTextboxBlur,H);YAHOO.util.Event.addListener(B,"mouseover",H._onContainerMouseover,H);YAHOO.util.Event.addListener(B,"mouseout",H._onContainerMouseout,H);YAHOO.util.Event.addListener(B,"click",H._onContainerClick,H);YAHOO.util.Event.addListener(B,"scroll",H._onContainerScroll,H);YAHOO.util.Event.addListener(B,"resize",H._onContainerResize,H);YAHOO.util.Event.addListener(F,"keypress",H._onTextboxKeyPress,H);YAHOO.util.Event.addListener(window,"unload",H._onWindowUnload,H);this.textboxFocusEvent=new YAHOO.util.CustomEvent("textboxFocus",this);this.textboxKeyEvent=new YAHOO.util.CustomEvent("textboxKey",this);this.dataRequestEvent=new YAHOO.util.CustomEvent("dataRequest",this);this.dataReturnEvent=new YAHOO.util.CustomEvent("dataReturn",this);this.dataErrorEvent=new YAHOO.util.CustomEvent("dataError",this);this.containerPopulateEvent=new YAHOO.util.CustomEvent("containerPopulate",this);this.containerExpandEvent=new YAHOO.util.CustomEvent("containerExpand",this);this.typeAheadEvent=new YAHOO.util.CustomEvent("typeAhead",this);this.itemMouseOverEvent=new YAHOO.util.CustomEvent("itemMouseOver",this);this.itemMouseOutEvent=new YAHOO.util.CustomEvent("itemMouseOut",this);this.itemArrowToEvent=new YAHOO.util.CustomEvent("itemArrowTo",this);this.itemArrowFromEvent=new YAHOO.util.CustomEvent("itemArrowFrom",this);this.itemSelectEvent=new YAHOO.util.CustomEvent("itemSelect",this);this.unmatchedItemSelectEvent=new YAHOO.util.CustomEvent("unmatchedItemSelect",this);this.selectionEnforceEvent=new YAHOO.util.CustomEvent("selectionEnforce",this);this.containerCollapseEvent=new YAHOO.util.CustomEvent("containerCollapse",this);this.textboxBlurEvent=new YAHOO.util.CustomEvent("textboxBlur",this);this.textboxChangeEvent=new YAHOO.util.CustomEvent("textboxChange",this);F.setAttribute("autocomplete","off");YAHOO.widget.AutoComplete._nIndex++;}else{}};YAHOO.widget.AutoComplete.prototype.dataSource=null;YAHOO.widget.AutoComplete.prototype.applyLocalFilter=null;YAHOO.widget.AutoComplete.prototype.queryMatchCase=false;YAHOO.widget.AutoComplete.prototype.queryMatchContains=false;YAHOO.widget.AutoComplete.prototype.queryMatchSubset=false;YAHOO.widget.AutoComplete.prototype.minQueryLength=1;YAHOO.widget.AutoComplete.prototype.maxResultsDisplayed=10;YAHOO.widget.AutoComplete.prototype.queryDelay=0.2;YAHOO.widget.AutoComplete.prototype.typeAheadDelay=0.5;YAHOO.widget.AutoComplete.prototype.queryInterval=500;YAHOO.widget.AutoComplete.prototype.highlightClassName="yui-ac-highlight";YAHOO.widget.AutoComplete.prototype.prehighlightClassName=null;YAHOO.widget.AutoComplete.prototype.delimChar=null;YAHOO.widget.AutoComplete.prototype.autoHighlight=true;YAHOO.widget.AutoComplete.prototype.typeAhead=false;YAHOO.widget.AutoComplete.prototype.animHoriz=false;YAHOO.widget.AutoComplete.prototype.animVert=true;YAHOO.widget.AutoComplete.prototype.animSpeed=0.3;YAHOO.widget.AutoComplete.prototype.forceSelection=false;YAHOO.widget.AutoComplete.prototype.allowBrowserAutocomplete=true;YAHOO.widget.AutoComplete.prototype.alwaysShowContainer=false;YAHOO.widget.AutoComplete.prototype.useIFrame=false;YAHOO.widget.AutoComplete.prototype.useShadow=false;YAHOO.widget.AutoComplete.prototype.suppressInputUpdate=false;YAHOO.widget.AutoComplete.prototype.resultTypeList=true;YAHOO.widget.AutoComplete.prototype.queryQuestionMark=true;YAHOO.widget.AutoComplete.prototype.autoSnapContainer=true;YAHOO.widget.AutoComplete.prototype.toString=function(){return"AutoComplete "+this._sName;};YAHOO.widget.AutoComplete.prototype.getInputEl=function(){return this._elTextbox;};YAHOO.widget.AutoComplete.prototype.getContainerEl=function(){return this._elContainer; -};YAHOO.widget.AutoComplete.prototype.isFocused=function(){return this._bFocused;};YAHOO.widget.AutoComplete.prototype.isContainerOpen=function(){return this._bContainerOpen;};YAHOO.widget.AutoComplete.prototype.getListEl=function(){return this._elList;};YAHOO.widget.AutoComplete.prototype.getListItemMatch=function(A){if(A._sResultMatch){return A._sResultMatch;}else{return null;}};YAHOO.widget.AutoComplete.prototype.getListItemData=function(A){if(A._oResultData){return A._oResultData;}else{return null;}};YAHOO.widget.AutoComplete.prototype.getListItemIndex=function(A){if(YAHOO.lang.isNumber(A._nItemIndex)){return A._nItemIndex;}else{return null;}};YAHOO.widget.AutoComplete.prototype.setHeader=function(B){if(this._elHeader){var A=this._elHeader;if(B){A.innerHTML=B;A.style.display="";}else{A.innerHTML="";A.style.display="none";}}};YAHOO.widget.AutoComplete.prototype.setFooter=function(B){if(this._elFooter){var A=this._elFooter;if(B){A.innerHTML=B;A.style.display="";}else{A.innerHTML="";A.style.display="none";}}};YAHOO.widget.AutoComplete.prototype.setBody=function(A){if(this._elBody){var B=this._elBody;YAHOO.util.Event.purgeElement(B,true);if(A){B.innerHTML=A;B.style.display="";}else{B.innerHTML="";B.style.display="none";}this._elList=null;}};YAHOO.widget.AutoComplete.prototype.generateRequest=function(B){var A=this.dataSource.dataType;if(A===YAHOO.util.DataSourceBase.TYPE_XHR){if(!this.dataSource.connMethodPost){B=(this.queryQuestionMark?"?":"")+(this.dataSource.scriptQueryParam||"query")+"="+B+(this.dataSource.scriptQueryAppend?("&"+this.dataSource.scriptQueryAppend):"");}else{B=(this.dataSource.scriptQueryParam||"query")+"="+B+(this.dataSource.scriptQueryAppend?("&"+this.dataSource.scriptQueryAppend):"");}}else{if(A===YAHOO.util.DataSourceBase.TYPE_SCRIPTNODE){B="&"+(this.dataSource.scriptQueryParam||"query")+"="+B+(this.dataSource.scriptQueryAppend?("&"+this.dataSource.scriptQueryAppend):"");}}return B;};YAHOO.widget.AutoComplete.prototype.sendQuery=function(B){this._bFocused=true;var A=(this.delimChar)?this._elTextbox.value+B:B;this._sendQuery(A);};YAHOO.widget.AutoComplete.prototype.snapContainer=function(){var A=this._elTextbox,B=YAHOO.util.Dom.getXY(A);B[1]+=YAHOO.util.Dom.get(A).offsetHeight+2;YAHOO.util.Dom.setXY(this._elContainer,B);};YAHOO.widget.AutoComplete.prototype.expandContainer=function(){this._toggleContainer(true);};YAHOO.widget.AutoComplete.prototype.collapseContainer=function(){this._toggleContainer(false);};YAHOO.widget.AutoComplete.prototype.clearList=function(){var B=this._elList.childNodes,A=B.length-1;for(;A>-1;A--){B[A].style.display="none";}};YAHOO.widget.AutoComplete.prototype.getSubsetMatches=function(E){var D,C,A;for(var B=E.length;B>=this.minQueryLength;B--){A=this.generateRequest(E.substr(0,B));this.dataRequestEvent.fire(this,D,A);C=this.dataSource.getCachedResponse(A);if(C){return this.filterResults.apply(this.dataSource,[E,C,C,{scope:this}]);}}return null;};YAHOO.widget.AutoComplete.prototype.preparseRawResponse=function(C,B,A){var D=((this.responseStripAfter!=="")&&(B.indexOf))?B.indexOf(this.responseStripAfter):-1;if(D!=-1){B=B.substring(0,D);}return B;};YAHOO.widget.AutoComplete.prototype.filterResults=function(K,M,Q,L){if(L&&L.argument&&L.argument.query){K=L.argument.query;}if(K&&K!==""){Q=YAHOO.widget.AutoComplete._cloneObject(Q);var I=L.scope,P=this,C=Q.results,N=[],B=I.maxResultsDisplayed,J=(P.queryMatchCase||I.queryMatchCase),A=(P.queryMatchContains||I.queryMatchContains);for(var D=0,H=C.length;D-1))){N.push(F);}}if(H>B&&N.length===B){break;}}Q.results=N;}else{}return Q;};YAHOO.widget.AutoComplete.prototype.handleResponse=function(C,A,B){if((this instanceof YAHOO.widget.AutoComplete)&&this._sName){this._populateList(C,A,B);}};YAHOO.widget.AutoComplete.prototype.doBeforeLoadData=function(C,A,B){return true;};YAHOO.widget.AutoComplete.prototype.formatResult=function(B,D,A){var C=(A)?A:"";return C;};YAHOO.widget.AutoComplete.prototype.doBeforeExpandContainer=function(D,A,C,B){return true;};YAHOO.widget.AutoComplete.prototype.destroy=function(){var B=this.toString();var A=this._elTextbox;var D=this._elContainer;this.textboxFocusEvent.unsubscribeAll();this.textboxKeyEvent.unsubscribeAll();this.dataRequestEvent.unsubscribeAll();this.dataReturnEvent.unsubscribeAll();this.dataErrorEvent.unsubscribeAll();this.containerPopulateEvent.unsubscribeAll();this.containerExpandEvent.unsubscribeAll();this.typeAheadEvent.unsubscribeAll();this.itemMouseOverEvent.unsubscribeAll();this.itemMouseOutEvent.unsubscribeAll();this.itemArrowToEvent.unsubscribeAll();this.itemArrowFromEvent.unsubscribeAll();this.itemSelectEvent.unsubscribeAll();this.unmatchedItemSelectEvent.unsubscribeAll();this.selectionEnforceEvent.unsubscribeAll();this.containerCollapseEvent.unsubscribeAll();this.textboxBlurEvent.unsubscribeAll();this.textboxChangeEvent.unsubscribeAll();YAHOO.util.Event.purgeElement(A,true);YAHOO.util.Event.purgeElement(D,true);D.innerHTML="";for(var C in this){if(YAHOO.lang.hasOwnProperty(this,C)){this[C]=null;}}};YAHOO.widget.AutoComplete.prototype.textboxFocusEvent=null;YAHOO.widget.AutoComplete.prototype.textboxKeyEvent=null;YAHOO.widget.AutoComplete.prototype.dataRequestEvent=null;YAHOO.widget.AutoComplete.prototype.dataReturnEvent=null;YAHOO.widget.AutoComplete.prototype.dataErrorEvent=null;YAHOO.widget.AutoComplete.prototype.containerPopulateEvent=null;YAHOO.widget.AutoComplete.prototype.containerExpandEvent=null;YAHOO.widget.AutoComplete.prototype.typeAheadEvent=null;YAHOO.widget.AutoComplete.prototype.itemMouseOverEvent=null;YAHOO.widget.AutoComplete.prototype.itemMouseOutEvent=null; -YAHOO.widget.AutoComplete.prototype.itemArrowToEvent=null;YAHOO.widget.AutoComplete.prototype.itemArrowFromEvent=null;YAHOO.widget.AutoComplete.prototype.itemSelectEvent=null;YAHOO.widget.AutoComplete.prototype.unmatchedItemSelectEvent=null;YAHOO.widget.AutoComplete.prototype.selectionEnforceEvent=null;YAHOO.widget.AutoComplete.prototype.containerCollapseEvent=null;YAHOO.widget.AutoComplete.prototype.textboxBlurEvent=null;YAHOO.widget.AutoComplete.prototype.textboxChangeEvent=null;YAHOO.widget.AutoComplete._nIndex=0;YAHOO.widget.AutoComplete.prototype._sName=null;YAHOO.widget.AutoComplete.prototype._elTextbox=null;YAHOO.widget.AutoComplete.prototype._elContainer=null;YAHOO.widget.AutoComplete.prototype._elContent=null;YAHOO.widget.AutoComplete.prototype._elHeader=null;YAHOO.widget.AutoComplete.prototype._elBody=null;YAHOO.widget.AutoComplete.prototype._elFooter=null;YAHOO.widget.AutoComplete.prototype._elShadow=null;YAHOO.widget.AutoComplete.prototype._elIFrame=null;YAHOO.widget.AutoComplete.prototype._bFocused=false;YAHOO.widget.AutoComplete.prototype._oAnim=null;YAHOO.widget.AutoComplete.prototype._bContainerOpen=false;YAHOO.widget.AutoComplete.prototype._bOverContainer=false;YAHOO.widget.AutoComplete.prototype._elList=null;YAHOO.widget.AutoComplete.prototype._nDisplayedItems=0;YAHOO.widget.AutoComplete.prototype._sCurQuery=null;YAHOO.widget.AutoComplete.prototype._sPastSelections="";YAHOO.widget.AutoComplete.prototype._sInitInputValue=null;YAHOO.widget.AutoComplete.prototype._elCurListItem=null;YAHOO.widget.AutoComplete.prototype._elCurPrehighlightItem=null;YAHOO.widget.AutoComplete.prototype._bItemSelected=false;YAHOO.widget.AutoComplete.prototype._nKeyCode=null;YAHOO.widget.AutoComplete.prototype._nDelayID=-1;YAHOO.widget.AutoComplete.prototype._nTypeAheadDelayID=-1;YAHOO.widget.AutoComplete.prototype._iFrameSrc="javascript:false;";YAHOO.widget.AutoComplete.prototype._queryInterval=null;YAHOO.widget.AutoComplete.prototype._sLastTextboxValue=null;YAHOO.widget.AutoComplete.prototype._initProps=function(){var B=this.minQueryLength;if(!YAHOO.lang.isNumber(B)){this.minQueryLength=1;}var E=this.maxResultsDisplayed;if(!YAHOO.lang.isNumber(E)||(E<1)){this.maxResultsDisplayed=10;}var F=this.queryDelay;if(!YAHOO.lang.isNumber(F)||(F<0)){this.queryDelay=0.2;}var C=this.typeAheadDelay;if(!YAHOO.lang.isNumber(C)||(C<0)){this.typeAheadDelay=0.2;}var A=this.delimChar;if(YAHOO.lang.isString(A)&&(A.length>0)){this.delimChar=[A];}else{if(!YAHOO.lang.isArray(A)){this.delimChar=null;}}var D=this.animSpeed;if((this.animHoriz||this.animVert)&&YAHOO.util.Anim){if(!YAHOO.lang.isNumber(D)||(D<0)){this.animSpeed=0.3;}if(!this._oAnim){this._oAnim=new YAHOO.util.Anim(this._elContent,{},this.animSpeed);}else{this._oAnim.duration=this.animSpeed;}}if(this.forceSelection&&A){}};YAHOO.widget.AutoComplete.prototype._initContainerHelperEls=function(){if(this.useShadow&&!this._elShadow){var A=document.createElement("div");A.className="yui-ac-shadow";A.style.width=0;A.style.height=0;this._elShadow=this._elContainer.appendChild(A);}if(this.useIFrame&&!this._elIFrame){var B=document.createElement("iframe");B.src=this._iFrameSrc;B.frameBorder=0;B.scrolling="no";B.style.position="absolute";B.style.width=0;B.style.height=0;B.style.padding=0;B.tabIndex=-1;B.role="presentation";B.title="Presentational iframe shim";this._elIFrame=this._elContainer.appendChild(B);}};YAHOO.widget.AutoComplete.prototype._initContainerEl=function(){YAHOO.util.Dom.addClass(this._elContainer,"yui-ac-container");if(!this._elContent){var C=document.createElement("div");C.className="yui-ac-content";C.style.display="none";this._elContent=this._elContainer.appendChild(C);var B=document.createElement("div");B.className="yui-ac-hd";B.style.display="none";this._elHeader=this._elContent.appendChild(B);var D=document.createElement("div");D.className="yui-ac-bd";this._elBody=this._elContent.appendChild(D);var A=document.createElement("div");A.className="yui-ac-ft";A.style.display="none";this._elFooter=this._elContent.appendChild(A);}else{}};YAHOO.widget.AutoComplete.prototype._initListEl=function(){var C=this.maxResultsDisplayed,A=this._elList||document.createElement("ul"),B;while(A.childNodes.length=18&&A<=20)||(A==27)||(A>=33&&A<=35)||(A>=36&&A<=40)||(A>=44&&A<=45)||(A==229)){return true;}return false;};YAHOO.widget.AutoComplete.prototype._sendQuery=function(D){if(this.minQueryLength<0){this._toggleContainer(false);return;}if(this.delimChar){var A=this._extractQuery(D);D=A.query;this._sPastSelections=A.previous;}if((D&&(D.length0)){if(this._nDelayID!=-1){clearTimeout(this._nDelayID);}this._toggleContainer(false);return;}D=encodeURIComponent(D);this._nDelayID=-1;if(this.dataSource.queryMatchSubset||this.queryMatchSubset){var C=this.getSubsetMatches(D);if(C){this.handleResponse(D,C,{query:D});return; -}}if(this.dataSource.responseStripAfter){this.dataSource.doBeforeParseData=this.preparseRawResponse;}if(this.applyLocalFilter){this.dataSource.doBeforeCallback=this.filterResults;}var B=this.generateRequest(D);this.dataRequestEvent.fire(this,D,B);this.dataSource.sendRequest(B,{success:this.handleResponse,failure:this.handleResponse,scope:this,argument:{query:D}});};YAHOO.widget.AutoComplete.prototype._populateListItem=function(B,A,C){B.innerHTML=this.formatResult(A,C,B._sResultMatch);};YAHOO.widget.AutoComplete.prototype._populateList=function(K,F,C){if(this._nTypeAheadDelayID!=-1){clearTimeout(this._nTypeAheadDelayID);}K=(C&&C.query)?C.query:K;var H=this.doBeforeLoadData(K,F,C);if(H&&!F.error){this.dataReturnEvent.fire(this,K,F.results);if(this._bFocused){var M=decodeURIComponent(K);this._sCurQuery=M;this._bItemSelected=false;var R=F.results,A=Math.min(R.length,this.maxResultsDisplayed),J=(this.dataSource.responseSchema.fields)?(this.dataSource.responseSchema.fields[0].key||this.dataSource.responseSchema.fields[0]):0;if(A>0){if(!this._elList||(this._elList.childNodes.length=0;Q--){var P=I[Q],E=R[Q];if(this.resultTypeList){var B=[];B[0]=(YAHOO.lang.isString(E))?E:E[J]||E[this.key];var L=this.dataSource.responseSchema.fields;if(YAHOO.lang.isArray(L)&&(L.length>1)){for(var N=1,S=L.length;N=A;O--){G=I[O];G.style.display="none";}}this._nDisplayedItems=A;this.containerPopulateEvent.fire(this,K,R);if(this.autoHighlight){var D=this._elList.firstChild;this._toggleHighlight(D,"to");this.itemArrowToEvent.fire(this,D);this._typeAhead(D,K);}else{this._toggleHighlight(this._elCurListItem,"from");}H=this._doBeforeExpandContainer(this._elTextbox,this._elContainer,K,R);this._toggleContainer(H);}else{this._toggleContainer(false);}return;}}else{this.dataErrorEvent.fire(this,K,F);}};YAHOO.widget.AutoComplete.prototype._doBeforeExpandContainer=function(D,A,C,B){if(this.autoSnapContainer){this.snapContainer();}return this.doBeforeExpandContainer(D,A,C,B);};YAHOO.widget.AutoComplete.prototype._clearSelection=function(){var A=(this.delimChar)?this._extractQuery(this._elTextbox.value):{previous:"",query:this._elTextbox.value};this._elTextbox.value=A.previous;this.selectionEnforceEvent.fire(this,A.query);};YAHOO.widget.AutoComplete.prototype._textMatchesOption=function(){var A=null;for(var B=0;B=0;B--){G=H.lastIndexOf(C[B]);if(G>F){F=G;}}if(C[B]==" "){for(var A=C.length-1;A>=0;A--){if(H[F-1]==C[A]){F--;break;}}}if(F>-1){E=F+1;while(H.charAt(E)==" "){E+=1;}D=H.substring(0,E);H=H.substr(E);}else{D="";}return{previous:D,query:H};};YAHOO.widget.AutoComplete.prototype._toggleContainerHelpers=function(D){var E=this._elContent.offsetWidth+"px";var B=this._elContent.offsetHeight+"px";if(this.useIFrame&&this._elIFrame){var C=this._elIFrame;if(D){C.style.width=E;C.style.height=B;C.style.padding="";}else{C.style.width=0;C.style.height=0;C.style.padding=0;}}if(this.useShadow&&this._elShadow){var A=this._elShadow;if(D){A.style.width=E;A.style.height=B;}else{A.style.width=0;A.style.height=0;}}};YAHOO.widget.AutoComplete.prototype._toggleContainer=function(I){var D=this._elContainer;if(this.alwaysShowContainer&&this._bContainerOpen){return;}if(!I){this._toggleHighlight(this._elCurListItem,"from");this._nDisplayedItems=0;this._sCurQuery=null;if(this._elContent.style.display=="none"){return;}}var A=this._oAnim;if(A&&A.getEl()&&(this.animHoriz||this.animVert)){if(A.isAnimated()){A.stop(true);}var G=this._elContent.cloneNode(true);D.appendChild(G);G.style.top="-9000px";G.style.width="";G.style.height="";G.style.display="";var F=G.offsetWidth;var C=G.offsetHeight;var B=(this.animHoriz)?0:F;var E=(this.animVert)?0:C;A.attributes=(I)?{width:{to:F},height:{to:C}}:{width:{to:B},height:{to:E}};if(I&&!this._bContainerOpen){this._elContent.style.width=B+"px";this._elContent.style.height=E+"px";}else{this._elContent.style.width=F+"px";this._elContent.style.height=C+"px";}D.removeChild(G);G=null;var H=this;var J=function(){A.onComplete.unsubscribeAll();if(I){H._toggleContainerHelpers(true);H._bContainerOpen=I;H.containerExpandEvent.fire(H);}else{H._elContent.style.display="none";H._bContainerOpen=I;H.containerCollapseEvent.fire(H);}};this._toggleContainerHelpers(false);this._elContent.style.display="";A.onComplete.subscribe(J);A.animate();}else{if(I){this._elContent.style.display="";this._toggleContainerHelpers(true);this._bContainerOpen=I;this.containerExpandEvent.fire(this);}else{this._toggleContainerHelpers(false);this._elContent.style.display="none";this._bContainerOpen=I;this.containerCollapseEvent.fire(this);}}};YAHOO.widget.AutoComplete.prototype._toggleHighlight=function(A,C){if(A){var B=this.highlightClassName; -if(this._elCurListItem){YAHOO.util.Dom.removeClass(this._elCurListItem,B);this._elCurListItem=null;}if((C=="to")&&B){YAHOO.util.Dom.addClass(A,B);this._elCurListItem=A;}}};YAHOO.widget.AutoComplete.prototype._togglePrehighlight=function(B,C){var A=this.prehighlightClassName;if(this._elCurPrehighlightItem){YAHOO.util.Dom.removeClass(this._elCurPrehighlightItem,A);}if(B==this._elCurListItem){return;}if((C=="mouseover")&&A){YAHOO.util.Dom.addClass(B,A);this._elCurPrehighlightItem=B;}else{YAHOO.util.Dom.removeClass(B,A);}};YAHOO.widget.AutoComplete.prototype._updateValue=function(C){if(!this.suppressInputUpdate){var F=this._elTextbox;var E=(this.delimChar)?(this.delimChar[0]||this.delimChar):null;var B=C._sResultMatch;var D="";if(E){D=this._sPastSelections;D+=B+E;if(E!=" "){D+=" ";}}else{D=B;}F.value=D;if(F.type=="textarea"){F.scrollTop=F.scrollHeight;}var A=F.value.length;this._selectText(F,A,A);this._elCurListItem=C;}};YAHOO.widget.AutoComplete.prototype._selectItem=function(A){this._bItemSelected=true;this._updateValue(A);this._sPastSelections=this._elTextbox.value;this._clearInterval();this.itemSelectEvent.fire(this,A,A._oResultData);this._toggleContainer(false);};YAHOO.widget.AutoComplete.prototype._jumpSelection=function(){if(this._elCurListItem){this._selectItem(this._elCurListItem);}else{this._toggleContainer(false);}};YAHOO.widget.AutoComplete.prototype._moveSelection=function(G){if(this._bContainerOpen){var H=this._elCurListItem,D=-1;if(H){D=H._nItemIndex;}var E=(G==40)?(D+1):(D-1);if(E<-2||E>=this._nDisplayedItems){return;}if(H){this._toggleHighlight(H,"from");this.itemArrowFromEvent.fire(this,H);}if(E==-1){if(this.delimChar){this._elTextbox.value=this._sPastSelections+this._sCurQuery;}else{this._elTextbox.value=this._sCurQuery;}return;}if(E==-2){this._toggleContainer(false);return;}var F=this._elList.childNodes[E],B=this._elContent,C=YAHOO.util.Dom.getStyle(B,"overflow"),I=YAHOO.util.Dom.getStyle(B,"overflowY"),A=((C=="auto")||(C=="scroll")||(I=="auto")||(I=="scroll"));if(A&&(E>-1)&&(E(B.scrollTop+B.offsetHeight)){B.scrollTop=(F.offsetTop+F.offsetHeight)-B.offsetHeight;}else{if((F.offsetTop+F.offsetHeight)(B.scrollTop+B.offsetHeight)){this._elContent.scrollTop=(F.offsetTop+F.offsetHeight)-B.offsetHeight;}}}}this._toggleHighlight(F,"to");this.itemArrowToEvent.fire(this,F);if(this.typeAhead){this._updateValue(F);}}};YAHOO.widget.AutoComplete.prototype._onContainerMouseover=function(A,C){var D=YAHOO.util.Event.getTarget(A);var B=D.nodeName.toLowerCase();while(D&&(B!="table")){switch(B){case"body":return;case"li":if(C.prehighlightClassName){C._togglePrehighlight(D,"mouseover");}else{C._toggleHighlight(D,"to");}C.itemMouseOverEvent.fire(C,D);break;case"div":if(YAHOO.util.Dom.hasClass(D,"yui-ac-container")){C._bOverContainer=true;return;}break;default:break;}D=D.parentNode;if(D){B=D.nodeName.toLowerCase();}}};YAHOO.widget.AutoComplete.prototype._onContainerMouseout=function(A,C){var D=YAHOO.util.Event.getTarget(A);var B=D.nodeName.toLowerCase();while(D&&(B!="table")){switch(B){case"body":return;case"li":if(C.prehighlightClassName){C._togglePrehighlight(D,"mouseout");}else{C._toggleHighlight(D,"from");}C.itemMouseOutEvent.fire(C,D);break;case"ul":C._toggleHighlight(C._elCurListItem,"to");break;case"div":if(YAHOO.util.Dom.hasClass(D,"yui-ac-container")){C._bOverContainer=false;return;}break;default:break;}D=D.parentNode;if(D){B=D.nodeName.toLowerCase();}}};YAHOO.widget.AutoComplete.prototype._onContainerClick=function(A,C){var D=YAHOO.util.Event.getTarget(A);var B=D.nodeName.toLowerCase();while(D&&(B!="table")){switch(B){case"body":return;case"li":C._toggleHighlight(D,"to");C._selectItem(D);return;default:break;}D=D.parentNode;if(D){B=D.nodeName.toLowerCase();}}};YAHOO.widget.AutoComplete.prototype._onContainerScroll=function(A,B){B._focus();};YAHOO.widget.AutoComplete.prototype._onContainerResize=function(A,B){B._toggleContainerHelpers(B._bContainerOpen);};YAHOO.widget.AutoComplete.prototype._onTextboxKeyDown=function(A,B){var C=A.keyCode;if(B._nTypeAheadDelayID!=-1){clearTimeout(B._nTypeAheadDelayID);}switch(C){case 9:if(!YAHOO.env.ua.opera&&(navigator.userAgent.toLowerCase().indexOf("mac")==-1)||(YAHOO.env.ua.webkit>420)){if(B._elCurListItem){if(B.delimChar&&(B._nKeyCode!=C)){if(B._bContainerOpen){YAHOO.util.Event.stopEvent(A);}}B._selectItem(B._elCurListItem);}else{B._toggleContainer(false);}}break;case 13:if(!YAHOO.env.ua.opera&&(navigator.userAgent.toLowerCase().indexOf("mac")==-1)||(YAHOO.env.ua.webkit>420)){if(B._elCurListItem){if(B._nKeyCode!=C){if(B._bContainerOpen){YAHOO.util.Event.stopEvent(A);}}B._selectItem(B._elCurListItem);}else{B._toggleContainer(false);}}break;case 27:B._toggleContainer(false);return;case 39:B._jumpSelection();break;case 38:if(B._bContainerOpen){YAHOO.util.Event.stopEvent(A);B._moveSelection(C);}break;case 40:if(B._bContainerOpen){YAHOO.util.Event.stopEvent(A);B._moveSelection(C);}break;default:B._bItemSelected=false;B._toggleHighlight(B._elCurListItem,"from");B.textboxKeyEvent.fire(B,C);break;}if(C===18){B._enableIntervalDetection();}B._nKeyCode=C;};YAHOO.widget.AutoComplete.prototype._onTextboxKeyPress=function(A,B){var C=A.keyCode;if(YAHOO.env.ua.opera||(navigator.userAgent.toLowerCase().indexOf("mac")!=-1)&&(YAHOO.env.ua.webkit<420)){switch(C){case 9:if(B._bContainerOpen){if(B.delimChar){YAHOO.util.Event.stopEvent(A);}if(B._elCurListItem){B._selectItem(B._elCurListItem);}else{B._toggleContainer(false);}}break;case 13:if(B._bContainerOpen){YAHOO.util.Event.stopEvent(A);if(B._elCurListItem){B._selectItem(B._elCurListItem);}else{B._toggleContainer(false);}}break;default:break;}}else{if(C==229){B._enableIntervalDetection();}}};YAHOO.widget.AutoComplete.prototype._onTextboxKeyUp=function(A,C){var B=this.value;C._initProps();var D=A.keyCode;if(C._isIgnoreKey(D)){return; -}if(C._nDelayID!=-1){clearTimeout(C._nDelayID);}C._nDelayID=setTimeout(function(){C._sendQuery(B);},(C.queryDelay*1000));};YAHOO.widget.AutoComplete.prototype._onTextboxFocus=function(A,B){if(!B._bFocused){B._elTextbox.setAttribute("autocomplete","off");B._bFocused=true;B._sInitInputValue=B._elTextbox.value;B.textboxFocusEvent.fire(B);}};YAHOO.widget.AutoComplete.prototype._onTextboxBlur=function(A,C){if(!C._bOverContainer||(C._nKeyCode==9)){if(!C._bItemSelected){var B=C._textMatchesOption();if(!C._bContainerOpen||(C._bContainerOpen&&(B===null))){if(C.forceSelection){C._clearSelection();}else{C.unmatchedItemSelectEvent.fire(C,C._sCurQuery);}}else{if(C.forceSelection){C._selectItem(B);}}}C._clearInterval();C._bFocused=false;if(C._sInitInputValue!==C._elTextbox.value){C.textboxChangeEvent.fire(C);}C.textboxBlurEvent.fire(C);C._toggleContainer(false);}else{C._focus();}};YAHOO.widget.AutoComplete.prototype._onWindowUnload=function(A,B){if(B&&B._elTextbox&&B.allowBrowserAutocomplete){B._elTextbox.setAttribute("autocomplete","on");}};YAHOO.widget.AutoComplete.prototype.doBeforeSendQuery=function(A){return this.generateRequest(A);};YAHOO.widget.AutoComplete.prototype.getListItems=function(){var C=[],B=this._elList.childNodes;for(var A=B.length-1;A>=0;A--){C[A]=B[A];}return C;};YAHOO.widget.AutoComplete._cloneObject=function(D){if(!YAHOO.lang.isValue(D)){return D;}var F={};if(YAHOO.lang.isFunction(D)){F=D;}else{if(YAHOO.lang.isArray(D)){var E=[];for(var C=0,B=D.length;C0){G=F-1;do{D=E.subscribers[G];if(D&&D.obj==I&&D.fn==H){return true;}}while(G--);}return false;};YAHOO.lang.augmentProto(A,YAHOO.util.EventProvider);}());(function(){YAHOO.widget.Module=function(R,Q){if(R){this.init(R,Q);}else{}};var F=YAHOO.util.Dom,D=YAHOO.util.Config,N=YAHOO.util.Event,M=YAHOO.util.CustomEvent,G=YAHOO.widget.Module,I=YAHOO.env.ua,H,P,O,E,A={"BEFORE_INIT":"beforeInit","INIT":"init","APPEND":"append","BEFORE_RENDER":"beforeRender","RENDER":"render","CHANGE_HEADER":"changeHeader","CHANGE_BODY":"changeBody","CHANGE_FOOTER":"changeFooter","CHANGE_CONTENT":"changeContent","DESTROY":"destroy","BEFORE_SHOW":"beforeShow","SHOW":"show","BEFORE_HIDE":"beforeHide","HIDE":"hide"},J={"VISIBLE":{key:"visible",value:true,validator:YAHOO.lang.isBoolean},"EFFECT":{key:"effect",suppressEvent:true,supercedes:["visible"]},"MONITOR_RESIZE":{key:"monitorresize",value:true},"APPEND_TO_DOCUMENT_BODY":{key:"appendtodocumentbody",value:false}};G.IMG_ROOT=null;G.IMG_ROOT_SSL=null;G.CSS_MODULE="yui-module";G.CSS_HEADER="hd";G.CSS_BODY="bd";G.CSS_FOOTER="ft";G.RESIZE_MONITOR_SECURE_URL="javascript:false;";G.RESIZE_MONITOR_BUFFER=1;G.textResizeEvent=new M("textResize");G.forceDocumentRedraw=function(){var Q=document.documentElement;if(Q){Q.className+=" ";Q.className=YAHOO.lang.trim(Q.className);}};function L(){if(!H){H=document.createElement("div");H.innerHTML=('
'+'
');P=H.firstChild;O=P.nextSibling;E=O.nextSibling;}return H;}function K(){if(!P){L();}return(P.cloneNode(false));}function B(){if(!O){L();}return(O.cloneNode(false));}function C(){if(!E){L();}return(E.cloneNode(false));}G.prototype={constructor:G,element:null,header:null,body:null,footer:null,id:null,imageRoot:G.IMG_ROOT,initEvents:function(){var Q=M.LIST; -this.beforeInitEvent=this.createEvent(A.BEFORE_INIT);this.beforeInitEvent.signature=Q;this.initEvent=this.createEvent(A.INIT);this.initEvent.signature=Q;this.appendEvent=this.createEvent(A.APPEND);this.appendEvent.signature=Q;this.beforeRenderEvent=this.createEvent(A.BEFORE_RENDER);this.beforeRenderEvent.signature=Q;this.renderEvent=this.createEvent(A.RENDER);this.renderEvent.signature=Q;this.changeHeaderEvent=this.createEvent(A.CHANGE_HEADER);this.changeHeaderEvent.signature=Q;this.changeBodyEvent=this.createEvent(A.CHANGE_BODY);this.changeBodyEvent.signature=Q;this.changeFooterEvent=this.createEvent(A.CHANGE_FOOTER);this.changeFooterEvent.signature=Q;this.changeContentEvent=this.createEvent(A.CHANGE_CONTENT);this.changeContentEvent.signature=Q;this.destroyEvent=this.createEvent(A.DESTROY);this.destroyEvent.signature=Q;this.beforeShowEvent=this.createEvent(A.BEFORE_SHOW);this.beforeShowEvent.signature=Q;this.showEvent=this.createEvent(A.SHOW);this.showEvent.signature=Q;this.beforeHideEvent=this.createEvent(A.BEFORE_HIDE);this.beforeHideEvent.signature=Q;this.hideEvent=this.createEvent(A.HIDE);this.hideEvent.signature=Q;},platform:function(){var Q=navigator.userAgent.toLowerCase();if(Q.indexOf("windows")!=-1||Q.indexOf("win32")!=-1){return"windows";}else{if(Q.indexOf("macintosh")!=-1){return"mac";}else{return false;}}}(),browser:function(){var Q=navigator.userAgent.toLowerCase();if(Q.indexOf("opera")!=-1){return"opera";}else{if(Q.indexOf("msie 7")!=-1){return"ie7";}else{if(Q.indexOf("msie")!=-1){return"ie";}else{if(Q.indexOf("safari")!=-1){return"safari";}else{if(Q.indexOf("gecko")!=-1){return"gecko";}else{return false;}}}}}}(),isSecure:function(){if(window.location.href.toLowerCase().indexOf("https")===0){return true;}else{return false;}}(),initDefaultConfig:function(){this.cfg.addProperty(J.VISIBLE.key,{handler:this.configVisible,value:J.VISIBLE.value,validator:J.VISIBLE.validator});this.cfg.addProperty(J.EFFECT.key,{suppressEvent:J.EFFECT.suppressEvent,supercedes:J.EFFECT.supercedes});this.cfg.addProperty(J.MONITOR_RESIZE.key,{handler:this.configMonitorResize,value:J.MONITOR_RESIZE.value});this.cfg.addProperty(J.APPEND_TO_DOCUMENT_BODY.key,{value:J.APPEND_TO_DOCUMENT_BODY.value});},init:function(V,U){var S,W;this.initEvents();this.beforeInitEvent.fire(G);this.cfg=new D(this);if(this.isSecure){this.imageRoot=G.IMG_ROOT_SSL;}if(typeof V=="string"){S=V;V=document.getElementById(V);if(!V){V=(L()).cloneNode(false);V.id=S;}}this.id=F.generateId(V);this.element=V;W=this.element.firstChild;if(W){var R=false,Q=false,T=false;do{if(1==W.nodeType){if(!R&&F.hasClass(W,G.CSS_HEADER)){this.header=W;R=true;}else{if(!Q&&F.hasClass(W,G.CSS_BODY)){this.body=W;Q=true;}else{if(!T&&F.hasClass(W,G.CSS_FOOTER)){this.footer=W;T=true;}}}}}while((W=W.nextSibling));}this.initDefaultConfig();F.addClass(this.element,G.CSS_MODULE);if(U){this.cfg.applyConfig(U,true);}if(!D.alreadySubscribed(this.renderEvent,this.cfg.fireQueue,this.cfg)){this.renderEvent.subscribe(this.cfg.fireQueue,this.cfg,true);}this.initEvent.fire(G);},initResizeMonitor:function(){var R=(I.gecko&&this.platform=="windows");if(R){var Q=this;setTimeout(function(){Q._initResizeMonitor();},0);}else{this._initResizeMonitor();}},_initResizeMonitor:function(){var Q,S,U;function W(){G.textResizeEvent.fire();}if(!I.opera){S=F.get("_yuiResizeMonitor");var V=this._supportsCWResize();if(!S){S=document.createElement("iframe");if(this.isSecure&&G.RESIZE_MONITOR_SECURE_URL&&I.ie){S.src=G.RESIZE_MONITOR_SECURE_URL;}if(!V){U=["
${c.users_log.pager('$link_previous ~2~ $link_next', onclick="""YAHOO.util.Connect.asyncRequest('GET','$partial_url',{ -success:function(o){YAHOO.util.Dom.get(data_div).innerHTML=o.responseText; -YAHOO.util.Event.addListener(YAHOO.util.Dom.getElementsByClassName('pager_link'),"click",function(){ - YAHOO.util.Dom.setStyle(data_div,'opacity','0.3');}); -YAHOO.util.Dom.setStyle(data_div,'opacity','1');}},null); return false;""")} +success:function(o){ + YUD.get(data_div).innerHTML=o.responseText; + YUE.on(YUD.getElementsByClassName('pager_link'),"click",function(){ + YUD.setStyle(data_div,'opacity','0.3'); + }); + YUE.on(YUD.getElementsByClassName('show_more'),'click',function(e){ + var el = e.target; + YUD.setStyle(YUD.get(el.id.substring(1)),'display',''); + YUD.setStyle(el.parentNode,'display','none'); + }); + YUD.setStyle(data_div,'opacity','1');} + +},null); return false;""")}
%else: ${_('No actions yet')} diff --git a/rhodecode/templates/admin/ldap/ldap.html b/rhodecode/templates/admin/ldap/ldap.html new file mode 100644 --- /dev/null +++ b/rhodecode/templates/admin/ldap/ldap.html @@ -0,0 +1,73 @@ +## -*- coding: utf-8 -*- +<%inherit file="/base/base.html"/> + +<%def name="title()"> + ${_('LDAP administration')} - ${c.rhodecode_name} + + +<%def name="breadcrumbs_links()"> + ${h.link_to(_('Admin'),h.url('admin_home'))} + » + ${_('Ldap')} + + +<%def name="page_nav()"> + ${self.menu('admin')} + + +<%def name="main()"> +
+ +
+ ${self.breadcrumbs()} +
+

${_('LDAP administration')}

+ ${h.form(url('ldap_settings'))} +
+
+ +
+
+
${h.checkbox('ldap_active',True,class_='small')}
+
+
+
+
${h.text('ldap_host',class_='small')}
+
+
+
+
${h.text('ldap_port',class_='small')}
+
+
+
+
${h.checkbox('ldap_ldaps',True,class_='small')}
+
+
+
+
${h.text('ldap_dn_user',class_='small')}
+
+
+
+
${h.password('ldap_dn_pass',class_='small')}
+
+
+
+
${h.text('ldap_base_dn',class_='small')}
+
+ +
+ ${h.submit('save','Save',class_="ui-button ui-widget ui-state-default ui-corner-all")} +
+
+
+ ${h.end_form()} +
+ + + + + + + + + diff --git a/rhodecode/templates/admin/permissions/permissions.html b/rhodecode/templates/admin/permissions/permissions.html --- a/rhodecode/templates/admin/permissions/permissions.html +++ b/rhodecode/templates/admin/permissions/permissions.html @@ -26,9 +26,18 @@
- +
+
+ +
+
+
+ ${h.checkbox('anonymous',True)} +
+
+
-
+
@@ -66,3 +75,10 @@ ${h.end_form()}
+ + + + + + + diff --git a/rhodecode/templates/admin/repos/repo_add.html b/rhodecode/templates/admin/repos/repo_add.html --- a/rhodecode/templates/admin/repos/repo_add.html +++ b/rhodecode/templates/admin/repos/repo_add.html @@ -35,6 +35,14 @@
+
+ +
+
+ ${h.select('repo_type','hg',c.backends,class_="small")} +
+
+
diff --git a/rhodecode/templates/admin/repos/repo_add_create_repository.html b/rhodecode/templates/admin/repos/repo_add_create_repository.html --- a/rhodecode/templates/admin/repos/repo_add_create_repository.html +++ b/rhodecode/templates/admin/repos/repo_add_create_repository.html @@ -32,6 +32,14 @@
+
+ +
+
+ ${h.select('repo_type','hg',c.backends,class_="small")} +
+
+
diff --git a/rhodecode/templates/admin/repos/repo_edit.html b/rhodecode/templates/admin/repos/repo_edit.html --- a/rhodecode/templates/admin/repos/repo_edit.html +++ b/rhodecode/templates/admin/repos/repo_edit.html @@ -18,7 +18,7 @@ <%def name="main()"> -
+
${self.breadcrumbs()} @@ -31,11 +31,18 @@
-
- ${h.text('repo_name',class_="small")} +
+ ${h.text('repo_name',class_="medium")}
- +
+
+ +
+
+ ${h.select('repo_type','hg',c.backends,class_="medium")} +
+
@@ -53,7 +60,14 @@ ${h.checkbox('private',value="True")}
- +
+
+ +
+
+ ${h.checkbox('enable_statistics',value="True")} +
+
@@ -142,7 +156,8 @@
- ${h.submit('save','save',class_="ui-button ui-widget ui-state-default ui-corner-all")} + ${h.submit('save','Save',class_="ui-button ui-widget ui-state-default ui-corner-all")} + ${h.reset('reset','Reset',class_="ui-button ui-widget ui-state-default ui-corner-all")}
@@ -272,4 +287,50 @@
+ +
+
+
${_('Administration')}
+
+ +

${_('Statistics')}

+ + ${h.form(url('repo_stats', repo_name=c.repo_info.repo_name),method='delete')} +
+
+ ${h.submit('reset_stats_%s' % c.repo_info.repo_name,_('Reset current statistics'),class_="refresh_icon action_button",onclick="return confirm('Confirm to remove current statistics');")} + +
+
    +
  • ${_('Fetched to rev')}: ${c.stats_revision}/${c.repo_last_rev}
  • +
  • ${_('Percentage of stats gathered')}: ${c.stats_percentage} %
  • +
+
+ +
+
+ ${h.end_form()} + +

${_('Cache')}

+ ${h.form(url('repo_cache', repo_name=c.repo_info.repo_name),method='delete')} +
+
+ ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate repository cache'),class_="refresh_icon action_button",onclick="return confirm('Confirm to invalidate repository cache');")} +
+
+ ${h.end_form()} + + +

${_('Delete')}

+ ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='delete')} +
+
+ ${h.submit('remove_%s' % c.repo_info.repo_name,_('Remove this repository'),class_="delete_icon action_button",onclick="return confirm('Confirm to delete this repository');")} +
+
+ ${h.end_form()} + +
+ + \ No newline at end of file diff --git a/rhodecode/templates/admin/repos/repos.html b/rhodecode/templates/admin/repos/repos.html --- a/rhodecode/templates/admin/repos/repos.html +++ b/rhodecode/templates/admin/repos/repos.html @@ -37,6 +37,16 @@ %for cnt,repo in enumerate(c.repos_list): + ## TYPE OF REPO + %if repo['repo'].dbrepo.repo_type =='hg': + ${_('Mercurial repository')} + %elif repo['repo'].dbrepo.repo_type =='git': + ${_('Git repository')} + %else: + + %endif + + ## PRIVATE/PUBLIC REPO %if repo['repo'].dbrepo.private: ${_('private')} %else: @@ -55,7 +65,7 @@ ${h.age(repo['last_change'])} %if repo['rev']>=0: - ${h.link_to('r%s:%s' % (repo['rev'],repo['tip']), + ${h.link_to('r%s:%s' % (repo['rev'],h.short_id(repo['tip'])), h.url('changeset_home',repo_name=repo['name'],revision=repo['tip']), class_="tooltip", tooltip_title=h.tooltip(repo['last_msg']))} diff --git a/rhodecode/templates/admin/settings/settings.html b/rhodecode/templates/admin/settings/settings.html --- a/rhodecode/templates/admin/settings/settings.html +++ b/rhodecode/templates/admin/settings/settings.html @@ -42,7 +42,7 @@
- ${h.submit('rescan','rescan repositories',class_="ui-button ui-widget ui-state-default ui-corner-all")} + ${h.submit('rescan','Rescan repositories',class_="ui-button ui-widget ui-state-default ui-corner-all")}
@@ -67,7 +67,7 @@
- ${h.submit('reindex','reindex',class_="ui-button ui-widget ui-state-default ui-corner-all")} + ${h.submit('reindex','Reindex',class_="ui-button ui-widget ui-state-default ui-corner-all")}
@@ -99,7 +99,8 @@
- ${h.submit('save','save settings',class_="ui-button ui-widget ui-state-default ui-corner-all")} + ${h.submit('save','Save settings',class_="ui-button ui-widget ui-state-default ui-corner-all")} + ${h.reset('reset','Reset',class_="ui-button ui-widget ui-state-default ui-corner-all")}
@@ -136,7 +137,15 @@
${h.checkbox('hooks_changegroup_repo_size','True')} -
+ +
+ ${h.checkbox('hooks_pretxnchangegroup_push_logger','True')} + +
+
+ ${h.checkbox('hooks_preoutgoing_pull_logger','True')} + +
@@ -153,7 +162,8 @@
- ${h.submit('save','save settings',class_="ui-button ui-widget ui-state-default ui-corner-all")} + ${h.submit('save','Save settings',class_="ui-button ui-widget ui-state-default ui-corner-all")} + ${h.reset('reset','Reset',class_="ui-button ui-widget ui-state-default ui-corner-all")}
diff --git a/rhodecode/templates/admin/users/user_add.html b/rhodecode/templates/admin/users/user_add.html --- a/rhodecode/templates/admin/users/user_add.html +++ b/rhodecode/templates/admin/users/user_add.html @@ -47,7 +47,7 @@
- +
${h.text('name',class_='small')} @@ -56,7 +56,7 @@
- +
${h.text('lastname',class_='small')} diff --git a/rhodecode/templates/admin/users/user_edit.html b/rhodecode/templates/admin/users/user_edit.html --- a/rhodecode/templates/admin/users/user_edit.html +++ b/rhodecode/templates/admin/users/user_edit.html @@ -58,7 +58,7 @@
- +
${h.text('name',class_='small')} @@ -67,7 +67,7 @@
- +
${h.text('lastname',class_='small')} @@ -101,7 +101,8 @@
- ${h.submit('save','save',class_="ui-button ui-widget ui-state-default ui-corner-all")} + ${h.submit('save','Save',class_="ui-button ui-widget ui-state-default ui-corner-all")} + ${h.reset('reset','Reset',class_="ui-button ui-widget ui-state-default ui-corner-all")}
diff --git a/rhodecode/templates/admin/users/user_edit_my_account.html b/rhodecode/templates/admin/users/user_edit_my_account.html --- a/rhodecode/templates/admin/users/user_edit_my_account.html +++ b/rhodecode/templates/admin/users/user_edit_my_account.html @@ -24,13 +24,24 @@
${h.form(url('admin_settings_my_account_update'),method='put')}
+ +
+
+
gravatar
+

+ Change your avatar at gravatar.com
+ ${_('Using')} ${c.user.email} +

+
+
+
- ${h.text('username')} + ${h.text('username',class_="medium")}
@@ -39,25 +50,25 @@
- ${h.password('new_password')} + ${h.password('new_password',class_="medium")}
- +
- ${h.text('name')} + ${h.text('name',class_="medium")}
- +
- ${h.text('lastname')} + ${h.text('lastname',class_="medium")}
@@ -66,12 +77,16 @@
- ${h.text('email')} + ${h.text('email',class_="medium")}
- ${h.submit('save','save',class_="ui-button ui-widget ui-state-default ui-corner-all")} + ${h.submit('save','Save',class_="ui-button ui-widget ui-state-default ui-corner-all")} + ${h.reset('reset','Reset',class_="ui-button ui-widget ui-state-default ui-corner-all")} + + +
@@ -82,36 +97,57 @@
-
${_('My repositories')}
+
${_('My repositories')} + +
+ %if h.HasPermissionAny('hg.admin','hg.create.repository')(): + + %endif
+ + + + + + %if c.user_repos: %for repo in c.user_repos: - - - + + @@ -127,4 +163,48 @@ + \ No newline at end of file diff --git a/rhodecode/templates/admin/users/users.html b/rhodecode/templates/admin/users/users.html --- a/rhodecode/templates/admin/users/users.html +++ b/rhodecode/templates/admin/users/users.html @@ -35,20 +35,23 @@ + %for cnt,user in enumerate(c.users_list): %if user.name !='default': - + - - + + + diff --git a/rhodecode/templates/base/base.html b/rhodecode/templates/base/base.html --- a/rhodecode/templates/base/base.html +++ b/rhodecode/templates/base/base.html @@ -3,7 +3,7 @@ ${next.title()} - + @@ -16,22 +16,43 @@
${_('Name')}${_('revision')}${_('action')}
- %if repo.dbrepo.private: + %if repo['repo'].dbrepo.repo_type =='hg': + ${_('Mercurial repository')} + %elif repo['repo'].dbrepo.repo_type =='git': + ${_('Git repository')} + %else: + + %endif + %if repo['repo'].dbrepo.private: ${_('private')} %else: ${_('public')} %endif - ${h.link_to(repo.name, h.url('summary_home',repo_name=repo.name))} - %if repo.dbrepo.fork: - + ${h.link_to(repo['repo'].name, h.url('summary_home',repo_name=repo['repo'].name),class_="repo_name")} + %if repo['repo'].dbrepo.fork: + ${_('public')} %endif ${_('revision')}: ${h.get_changeset_safe(repo,'tip').revision}${_('last changed')}: ${h.age(repo.last_change)}${_('private')} ${h.link_to(_('edit'),h.url('repo_settings_home',repo_name=repo.name))}${("r%s:%s") % (h.get_changeset_safe(repo['repo'],'tip').revision,h.short_id(h.get_changeset_safe(repo['repo'],'tip').raw_id))}${_('private')} - ${h.form(url('repo_settings_delete', repo_name=repo.name),method='delete')} - ${h.submit('remove_%s' % repo.name,'delete',class_="delete_icon action_button",onclick="return confirm('Confirm to delete this repository');")} + ${h.form(url('repo_settings_delete', repo_name=repo['repo'].name),method='delete')} + ${h.submit('remove_%s' % repo['repo'].name,'',class_="delete_icon action_button",onclick="return confirm('Confirm to delete this repository');")} ${h.end_form()}
${_('lastname')} ${_('active')} ${_('admin')}${_('ldap')} ${_('action')}
gravatar
gravatar
${h.link_to(user.username,h.url('edit_user', id=user.user_id))} ${user.name} ${user.lastname}${user.active}${user.admin}${h.bool2icon(user.active)}${h.bool2icon(user.admin)}${h.bool2icon(user.is_ldap)} ${h.form(url('user', id=user.user_id),method='delete')} - ${h.submit('remove','delete',class_="delete_icon action_button")} + ${h.submit('remove_','delete',id="remove_user_%s" % user.user_id, + class_="delete_icon action_button",onclick="return confirm('Confirm to delete this user');")} ${h.end_form()}
+ + - %for cnt,branch in enumerate(c.repo_branches.items()): - - - + + + + %endfor diff --git a/rhodecode/templates/changelog/changelog.html b/rhodecode/templates/changelog/changelog.html --- a/rhodecode/templates/changelog/changelog.html +++ b/rhodecode/templates/changelog/changelog.html @@ -46,7 +46,7 @@ ${c.repo_name} ${_('Changelog')} - ${c.r %for cnt,cs in enumerate(c.pagination):
-
${_('commit')} ${cs.revision}: ${cs.short_id}@${cs.date}
+
${_('commit')} ${cs.revision}: ${h.short_id(cs.raw_id)}@${cs.date}
gravatar @@ -54,13 +54,26 @@ ${c.repo_name} ${_('Changelog')} - ${c.r ${h.person(cs.author)}
${h.email_or_none(cs.author)}
-
${h.link_to(h.wrap_paragraphs(cs.message),h.url('changeset_home',repo_name=c.repo_name,revision=cs.short_id))}
+
${h.link_to(h.wrap_paragraphs(cs.message),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}
- ${len(cs.removed)} - ${len(cs.changed)} - ${len(cs.added)} + + <% + def changed_tooltip(cs): + if cs: + pref = ': ' + suf = '' + if len(cs) > 30: + suf='
'+_(' and %s more') % (len(cs) - 30) + return pref+'
'.join([x.path for x in cs[:30]]) + suf + else: + return ': '+_('No Files') + %> + + ${len(cs.removed)} + ${len(cs.changed)} + ${len(cs.added)}
%if len(cs.parents)>1:
@@ -69,8 +82,8 @@ ${c.repo_name} ${_('Changelog')} - ${c.r %endif %if cs.parents: %for p_cs in reversed(cs.parents): -
${_('Parent')} ${p_cs.revision}: ${h.link_to(p_cs.short_id, - h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.short_id),title=p_cs.message)} +
${_('Parent')} ${p_cs.revision}: ${h.link_to(h.short_id(p_cs.raw_id), + h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}
%endfor %else: @@ -78,11 +91,13 @@ ${c.repo_name} ${_('Changelog')} - ${c.r %endif + %if cs.branch: - ${h.link_to(cs.branch,h.url('files_home',repo_name=c.repo_name,revision=cs.short_id))} + ${h.link_to(cs.branch,h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))} + %endif %for tag in cs.tags: - ${h.link_to(tag,h.url('files_home',repo_name=c.repo_name,revision=cs.short_id))} + ${h.link_to(tag,h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))} %endfor
diff --git a/rhodecode/templates/changeset/changeset.html b/rhodecode/templates/changeset/changeset.html --- a/rhodecode/templates/changeset/changeset.html +++ b/rhodecode/templates/changeset/changeset.html @@ -1,7 +1,7 @@ <%inherit file="/base/base.html"/> <%def name="title()"> - ${c.repo_name} ${_('Changeset')} - r${c.changeset.revision}:${c.changeset.short_id} - ${c.rhodecode_name} + ${c.repo_name} ${_('Changeset')} - r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)} - ${c.rhodecode_name} <%def name="breadcrumbs_links()"> @@ -9,7 +9,7 @@ » ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))} » - ${_('Changeset')} - r${c.changeset.revision}:${c.changeset.short_id} + ${_('Changeset')} - r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)} <%def name="page_nav()"> @@ -26,18 +26,18 @@
- ${_('Changeset')} - r${c.changeset.revision}:${c.changeset.short_id} + ${_('Changeset')} - r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)} » ${h.link_to(_('raw diff'), - h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.short_id,diff='show'))} + h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='show'))} » ${h.link_to(_('download diff'), - h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.short_id,diff='download'))} + h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='download'))}
-
${_('commit')} ${c.changeset.revision}: ${c.changeset.short_id}@${c.changeset.date}
+
${_('commit')} ${c.changeset.revision}: ${h.short_id(c.changeset.raw_id)}@${c.changeset.date}
gravatar @@ -45,7 +45,7 @@ ${h.person(c.changeset.author)}
${h.email_or_none(c.changeset.author)}
-
${h.link_to(h.wrap_paragraphs(c.changeset.message),h.url('changeset_home',repo_name=c.repo_name,revision=c.changeset.short_id))}
+
${h.link_to(h.wrap_paragraphs(c.changeset.message),h.url('changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}
@@ -61,8 +61,8 @@ %if c.changeset.parents: %for p_cs in reversed(c.changeset.parents): -
${_('Parent')} ${p_cs.revision}: ${h.link_to(p_cs.short_id, - h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.short_id),title=p_cs.message)} +
${_('Parent')} ${p_cs.revision}: ${h.link_to(h.short_id(p_cs.raw_id), + h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}
%endfor %else: @@ -70,10 +70,10 @@ %endif - ${h.link_to(c.changeset.branch,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.short_id))} + ${h.link_to(c.changeset.branch,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))} %for tag in c.changeset.tags: - ${h.link_to(tag,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.short_id))} + ${h.link_to(tag,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))} %endfor
@@ -93,10 +93,10 @@
-
- +
+ ${h.link_to_if(change!='removed',filenode.path,h.url('files_home',repo_name=c.repo_name, - revision=filenode.changeset.short_id,f_path=filenode.path))} + revision=filenode.changeset.raw_id,f_path=filenode.path))} %if 1: » ${h.link_to(_('diff'), diff --git a/rhodecode/templates/changeset/raw_changeset.html b/rhodecode/templates/changeset/raw_changeset.html --- a/rhodecode/templates/changeset/raw_changeset.html +++ b/rhodecode/templates/changeset/raw_changeset.html @@ -1,6 +1,6 @@ -# HG changeset patch +# ${c.scm_type} changeset patch # User ${c.changeset.author|n} -# Date ${"%d %d" % c.changeset._ctx.date()} +# Date ${c.changeset.date} # Node ID ${c.changeset.raw_id} # ${c.parent_tmpl} ${c.changeset.message} diff --git a/rhodecode/templates/errors/error_404.html b/rhodecode/templates/errors/error_404.html --- a/rhodecode/templates/errors/error_404.html +++ b/rhodecode/templates/errors/error_404.html @@ -6,7 +6,7 @@ <%def name="breadcrumbs()"> - ${h.link_to(u'Home',h.url('hg_home'))} + ${h.link_to(u'Home',h.url('home'))} / ${h.link_to(u'Admin',h.url('admin_home'))} @@ -26,7 +26,7 @@ ${_('Create "%s" repository as %s' % (c.repo_name,c.repo_name_cleaned))}

-

${h.link_to(_('Go back to the main repository list page'),h.url('hg_home'))}

+

${h.link_to(_('Go back to the main repository list page'),h.url('home'))}

\ No newline at end of file diff --git a/rhodecode/templates/files/file_diff.html b/rhodecode/templates/files/file_diff.html --- a/rhodecode/templates/files/file_diff.html +++ b/rhodecode/templates/files/file_diff.html @@ -9,7 +9,7 @@ » ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))} » - ${'%s: %s %s %s' % (_('File diff'),c.diff2,'→',c.diff1)|n} + ${_('File diff')} r${c.changeset_1.revision}:${h.short_id(c.changeset_1.raw_id)} → r${c.changeset_2.revision}:${h.short_id(c.changeset_2.raw_id)} <%def name="page_nav()"> @@ -24,15 +24,15 @@
-
- ${h.link_to(c.f_path,h.url('files_home',repo_name=c.repo_name, - revision=c.diff2.split(':')[1],f_path=c.f_path))} +
+ ${h.link_to(c.f_path,h.url('files_home',repo_name=c.repo_name, + revision=c.changeset_2.raw_id,f_path=c.f_path))} » ${h.link_to(_('diff'), - h.url.current(diff2=c.diff2.split(':')[-1],diff1=c.diff1.split(':')[-1],diff='diff'))} + h.url.current(diff2=c.changeset_2.raw_id,diff1=c.changeset_1.raw_id,diff='diff'))} » ${h.link_to(_('raw diff'), - h.url.current(diff2=c.diff2.split(':')[-1],diff1=c.diff1.split(':')[-1],diff='raw'))} + h.url.current(diff2=c.changeset_2.raw_id,diff1=c.changeset_1.raw_id,diff='raw'))} » ${h.link_to(_('download diff'), - h.url.current(diff2=c.diff2.split(':')[-1],diff1=c.diff1.split(':')[-1],diff='download'))} + h.url.current(diff2=c.changeset_2.raw_id,diff1=c.changeset_1.raw_id,diff='download'))}
diff --git a/rhodecode/templates/files/files.html b/rhodecode/templates/files/files.html --- a/rhodecode/templates/files/files.html +++ b/rhodecode/templates/files/files.html @@ -11,7 +11,7 @@ » ${_('files')} %if c.files_list: - @ R${c.rev_nr}:${c.cur_rev} + @ r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)} %endif @@ -23,12 +23,19 @@
- ${self.breadcrumbs()} + ${self.breadcrumbs()} +
%if c.files_list: -

${_('Location')}: ${h.files_breadcrumbs(c.repo_name,c.cur_rev,c.files_list.path)}

+

+ ${_('Location')}: ${h.files_breadcrumbs(c.repo_name,c.changeset.raw_id,c.files_list.path)} +

%if c.files_list.is_dir(): <%include file='files_browser.html'/> %else: diff --git a/rhodecode/templates/files/files_annotate.html b/rhodecode/templates/files/files_annotate.html --- a/rhodecode/templates/files/files_annotate.html +++ b/rhodecode/templates/files/files_annotate.html @@ -9,7 +9,7 @@ » ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))} » - ${_('annotate')} @ R${c.rev_nr}:${c.cur_rev} + ${_('annotate')} @ R${c.cs.revision}:${h.short_id(c.cs.raw_id)} <%def name="page_nav()"> @@ -20,41 +20,67 @@
${self.breadcrumbs()} +
-

${_('Location')}: ${h.files_breadcrumbs(c.repo_name,c.cur_rev,c.file.path)}

+

${_('Location')}: ${h.files_breadcrumbs(c.repo_name,c.cs.revision,c.file.path)}

-
${_('Last revision')}
-
${h.link_to("r%s:%s" % (c.file.last_changeset.revision,c.file.last_changeset.short_id), - h.url('files_annotate_home',repo_name=c.repo_name,revision=c.file.last_changeset.short_id,f_path=c.f_path))}
+
${_('Revision')}
+
${h.link_to("r%s:%s" % (c.file.last_changeset.revision,h.short_id(c.file.last_changeset.raw_id)), + h.url('changeset_home',repo_name=c.repo_name,revision=c.file.last_changeset.raw_id))}
${_('Size')}
${h.format_byte_size(c.file.size,binary=True)}
${_('Mimetype')}
${c.file.mimetype}
${_('Options')}
${h.link_to(_('show source'), - h.url('files_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))} + h.url('files_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path))} / ${h.link_to(_('show as raw'), - h.url('files_raw_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))} + h.url('files_raw_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path))} / ${h.link_to(_('download as raw'), - h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))} -
+ h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path))} + +
${_('History')}
+
+
+ ${h.form(h.url('files_diff_home',repo_name=c.repo_name,f_path=c.f_path),method='get')} + ${h.hidden('diff2',c.file.last_changeset.raw_id)} + ${h.select('diff1',c.file.last_changeset.raw_id,c.file_history)} + ${h.submit('diff','diff to revision',class_="ui-button ui-widget ui-state-default ui-corner-all")} + ${h.submit('show_rev','show at revision',class_="ui-button ui-widget ui-state-default ui-corner-all")} + ${h.end_form()} +
+
-
${c.file.name}@r${c.file.last_changeset.revision}:${c.file.last_changeset.short_id}
-
"${c.file_msg}"
+
${c.file.name}@r${c.file.last_changeset.revision}:${h.short_id(c.file.last_changeset.raw_id)}
+
"${c.file.message}"
- % if c.file.size < c.file_size_limit: + % if c.file.size < c.cut_off_limit: ${h.pygmentize_annotation(c.file,linenos=True,anchorlinenos=True,lineanchors='S',cssclass="code-highlight")} %else: ${_('File is to big to display')} ${h.link_to(_('show as raw'), - h.url('files_raw_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))} + h.url('files_raw_home',repo_name=c.repo_name,revision=c.cs.revision,f_path=c.f_path))} %endif
+
diff --git a/rhodecode/templates/files/files_browser.html b/rhodecode/templates/files/files_browser.html --- a/rhodecode/templates/files/files_browser.html +++ b/rhodecode/templates/files/files_browser.html @@ -10,9 +10,9 @@ ${h.form(h.url.current())}
${_('view')}@rev - « - ${h.text('at_rev',value=c.rev_nr,size=3)} - » + « + ${h.text('at_rev',value=c.changeset.revision,size=3)} + » ${h.submit('view','view')}
${h.end_form()} @@ -30,23 +30,23 @@ - % if c.files_list.parent: + %if c.files_list.parent:
- + %endif %for cnt,node in enumerate(c.files_list): - + %endfor diff --git a/rhodecode/templates/files/files_source.html b/rhodecode/templates/files/files_source.html --- a/rhodecode/templates/files/files_source.html +++ b/rhodecode/templates/files/files_source.html @@ -1,8 +1,8 @@
-
${_('Last revision')}
+
${_('Revision')}
- ${h.link_to("r%s:%s" % (c.files_list.last_changeset.revision,c.files_list.last_changeset.short_id), - h.url('files_home',repo_name=c.repo_name,revision=c.files_list.last_changeset.short_id,f_path=c.f_path))} + ${h.link_to("r%s:%s" % (c.files_list.last_changeset.revision,h.short_id(c.files_list.last_changeset.raw_id)), + h.url('changeset_home',repo_name=c.repo_name,revision=c.files_list.last_changeset.raw_id))}
${_('Size')}
${h.format_byte_size(c.files_list.size,binary=True)}
@@ -10,18 +10,18 @@
${c.files_list.mimetype}
${_('Options')}
${h.link_to(_('show annotation'), - h.url('files_annotate_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))} + h.url('files_annotate_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path))} / ${h.link_to(_('show as raw'), - h.url('files_raw_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))} + h.url('files_raw_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path))} / ${h.link_to(_('download as raw'), - h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))} + h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path))}
${_('History')}
${h.form(h.url('files_diff_home',repo_name=c.repo_name,f_path=c.f_path),method='get')} - ${h.hidden('diff2',c.files_list.last_changeset.short_id)} - ${h.select('diff1',c.files_list.last_changeset.short_id,c.file_history)} + ${h.hidden('diff2',c.files_list.last_changeset.raw_id)} + ${h.select('diff1',c.files_list.last_changeset.raw_id,c.file_history)} ${h.submit('diff','diff to revision',class_="ui-button ui-widget ui-state-default ui-corner-all")} ${h.submit('show_rev','show at revision',class_="ui-button ui-widget ui-state-default ui-corner-all")} ${h.end_form()} @@ -32,15 +32,15 @@
-
${c.files_list.name}@r${c.files_list.last_changeset.revision}:${c.files_list.last_changeset.short_id}
+
${c.files_list.name}@r${c.files_list.last_changeset.revision}:${h.short_id(c.files_list.last_changeset.raw_id)}
"${c.files_list.last_changeset.message}"
- % if c.files_list.size < c.file_size_limit: + % if c.files_list.size < c.cut_off_limit: ${h.pygmentize(c.files_list,linenos=True,anchorlinenos=True,lineanchors='S',cssclass="code-highlight")} %else: ${_('File is to big to display')} ${h.link_to(_('show as raw'), - h.url('files_raw_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.f_path))} + h.url('files_raw_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path))} %endif
diff --git a/rhodecode/templates/index.html b/rhodecode/templates/index.html --- a/rhodecode/templates/index.html +++ b/rhodecode/templates/index.html @@ -27,72 +27,142 @@
-
${_('Dashboard')}
- %if h.HasPermissionAny('hg.admin','hg.create.repository')(): - - %endif +
${_('Dashboard')} + +
+ %if c.rhodecode_user.username != 'default': + %if h.HasPermissionAny('hg.admin','hg.create.repository')(): + + %endif + %endif
-
${_('date')}${_('name')}${_('author')} ${_('revision')}${_('name')} ${_('links')}
${h.age(branch[1]._ctx.date())}r${branch[1].revision}:${branch[1].short_id} - - ${h.link_to(branch[0], - h.url('changeset_home',repo_name=c.repo_name,revision=branch[1].short_id))} - - + ${branch[1].date} + + + ${h.link_to(branch[0], + h.url('changeset_home',repo_name=c.repo_name,revision=branch[1].raw_id))} + + ${h.person(branch[1].author)}r${branch[1].revision}:${h.short_id(branch[1].raw_id)} - ${h.link_to(_('changeset'),h.url('changeset_home',repo_name=c.repo_name,revision=branch[1].short_id))} + ${h.link_to(_('changeset'),h.url('changeset_home',repo_name=c.repo_name,revision=branch[1].raw_id))} | - ${h.link_to(_('files'),h.url('files_home',repo_name=c.repo_name,revision=branch[1].short_id))} + ${h.link_to(_('files'),h.url('files_home',repo_name=c.repo_name,revision=branch[1].raw_id))}
- ${h.link_to('..',h.url('files_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=c.files_list.parent.path),class_="browser-dir")} + ${h.link_to('..',h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.files_list.parent.path),class_="browser-dir")}
- ${h.link_to(node.name,h.url('files_home',repo_name=c.repo_name,revision=c.cur_rev,f_path=node.path),class_=file_class(node))} + ${h.link_to(node.name,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=node.path),class_=file_class(node))} %if node.is_file(): @@ -59,19 +59,19 @@ %endif - %if node.is_file(): - ${node.last_changeset.revision} - %endif + %if node.is_file(): + ${node.last_changeset.revision} + %endif - %if node.is_file(): - ${h.age(node.last_changeset._ctx.date())} - ${node.last_changeset.date} - %endif + %if node.is_file(): + ${node.last_changeset.date} - ${h.age(node.last_changeset.date)} + %endif - %if node.is_file(): - ${node.last_changeset.author} - %endif + %if node.is_file(): + ${node.last_changeset.author} + %endif
+
- + - - %for cnt,repo in enumerate(c.repos_list): - %if h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(repo['name'],'main page check'): - - - - - - - - - - %endif - %endfor - -
${get_sort(_('Name'))} ${get_sort(_('Description'))} ${get_sort(_('Last change'))} ${get_sort(_('Tip'))}${get_sort(_('Contact'))}${get_sort(_('Owner'))} ${_('RSS')} ${_('Atom')}
- %if repo['repo'].dbrepo.private: - ${_('private')} - %else: - ${_('public')} - %endif - ${h.link_to(repo['name'], - h.url('summary_home',repo_name=repo['name']))} - %if repo['repo'].dbrepo.fork: - - ${_('public')} - %endif - ${h.truncate(repo['description'],60)}${h.age(repo['last_change'])} - %if repo['rev']>=0: - ${h.link_to('r%s:%s' % (repo['rev'],repo['tip']), - h.url('changeset_home',repo_name=repo['name'],revision=repo['tip']), - class_="tooltip", - tooltip_title=h.tooltip(repo['last_msg']))} - %else: - ${_('No changesets yet')} - %endif - ${h.person(repo['contact'])} - - - -
+ + %for cnt,repo in enumerate(c.repos_list): + + +
+ ## TYPE OF REPO + %if repo['repo'].dbrepo.repo_type =='hg': + ${_('Mercurial repository')} + %elif repo['repo'].dbrepo.repo_type =='git': + ${_('Git repository')} + %else: + + %endif + + ##PRIVATE/PUBLIC + %if repo['repo'].dbrepo.private: + ${_('private repository')} + %else: + ${_('public repository')} + %endif + + ##NAME + ${h.link_to(repo['name'], + h.url('summary_home',repo_name=repo['name']),class_="repo_name")} + %if repo['repo'].dbrepo.fork: + + ${_('fork')} + %endif +
+ + ##DESCRIPTION + + ${h.truncate(repo['description'],60)} + + ##LAST CHANGE + + + ${h.age(repo['last_change'])} + + + %if repo['rev']>=0: + ${h.link_to('r%s:%s' % (repo['rev'],h.short_id(repo['tip'])), + h.url('changeset_home',repo_name=repo['name'],revision=repo['tip']), + class_="tooltip", + tooltip_title=h.tooltip(repo['last_msg']))} + %else: + ${_('No changesets yet')} + %endif + + ${h.person(repo['contact'])} + + + + + + + + %endfor + +
-
+
+ + + + diff --git a/rhodecode/templates/journal.html b/rhodecode/templates/journal.html new file mode 100644 --- /dev/null +++ b/rhodecode/templates/journal.html @@ -0,0 +1,92 @@ +## -*- coding: utf-8 -*- +<%inherit file="base/base.html"/> +<%def name="title()"> + ${_('Journal')} - ${c.rhodecode_name} + +<%def name="breadcrumbs()"> + ${c.rhodecode_name} + +<%def name="page_nav()"> + ${self.menu('home')} + +<%def name="main()"> + +
+ +
+
${_('Journal')}
+
+
+ %if c.journal: + %for entry in c.journal: +
+
+ gravatar +
+
${entry.user.name} ${entry.user.lastname}
+
${h.action_parser(entry)}
+
+ ${h.action_parser_icon(entry)} +
+
+ + %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 + - ${h.age(entry.action_date)} +
+
+
+ %endfor + %else: + ${_('No entries yet')} + %endif +
+
+ +
+ +
+
${_('Following')}
+
+
+ %if c.following: + %for entry in c.following: +
+ %if entry.follows_user_id: + ${_('user')} + ${entry.follows_user.full_contact} + %endif + + %if entry.follows_repo_id: + + %if entry.follows_repository.private: + ${_('private repository')} + %else: + ${_('public repository')} + %endif + + ${h.link_to(entry.follows_repository.repo_name,h.url('summary_home', + repo_name=entry.follows_repository.repo_name))} + + %endif +
+ %endfor + %else: + ${_('You are not following any users or repositories')} + %endif +
+
+ + + + diff --git a/rhodecode/templates/login.html b/rhodecode/templates/login.html --- a/rhodecode/templates/login.html +++ b/rhodecode/templates/login.html @@ -4,7 +4,7 @@ ${_('Sign In')} - ${c.rhodecode_name} - + diff --git a/rhodecode/templates/register.html b/rhodecode/templates/register.html --- a/rhodecode/templates/register.html +++ b/rhodecode/templates/register.html @@ -15,7 +15,7 @@
-
${_('Sign Up to rhodecode')}
+
${_('Sign Up to RhodeCode')}
${h.form(url('register'))} @@ -27,25 +27,34 @@
- ${h.text('username')} + ${h.text('username',class_="medium")}
- +
- ${h.password('password')} + ${h.password('password',class_="medium")}
- + +
+
+ +
+
+ ${h.password('password_confirmation',class_="medium")} +
+
+
- ${h.text('name')} + ${h.text('name',class_="medium")}
@@ -54,7 +63,7 @@
- ${h.text('lastname')} + ${h.text('lastname',class_="medium")}
@@ -63,7 +72,7 @@
- ${h.text('email')} + ${h.text('email',class_="medium")}
diff --git a/rhodecode/templates/settings/repo_fork.html b/rhodecode/templates/settings/repo_fork.html --- a/rhodecode/templates/settings/repo_fork.html +++ b/rhodecode/templates/settings/repo_fork.html @@ -30,6 +30,7 @@
${h.text('fork_name',class_="small")} + ${h.hidden('repo_type',c.repo_info.repo_type)}
diff --git a/rhodecode/templates/settings/repo_settings.html b/rhodecode/templates/settings/repo_settings.html --- a/rhodecode/templates/settings/repo_settings.html +++ b/rhodecode/templates/settings/repo_settings.html @@ -128,7 +128,8 @@
- ${h.submit('update','update',class_="ui-button ui-widget ui-state-default ui-corner-all")} + ${h.submit('update','Update',class_="ui-button ui-widget ui-state-default ui-corner-all")} + ${h.reset('reset','Reset',class_="ui-button ui-widget ui-state-default ui-corner-all")}
diff --git a/rhodecode/templates/shortlog/shortlog_data.html b/rhodecode/templates/shortlog/shortlog_data.html --- a/rhodecode/templates/shortlog/shortlog_data.html +++ b/rhodecode/templates/shortlog/shortlog_data.html @@ -2,10 +2,10 @@ % if c.repo_changesets: - + + - @@ -13,14 +13,16 @@ %for cnt,cs in enumerate(c.repo_changesets): - + + - - + %endfor diff --git a/rhodecode/templates/summary/summary.html b/rhodecode/templates/summary/summary.html --- a/rhodecode/templates/summary/summary.html +++ b/rhodecode/templates/summary/summary.html @@ -17,17 +17,6 @@ <%def name="main()"> -
@@ -42,12 +31,30 @@ E.onDOMReady(function(e){
+ %if c.repo_info.dbrepo.repo_type =='hg': + ${_('Mercurial repository')} + %endif + %if c.repo_info.dbrepo.repo_type =='git': + ${_('Git repository')} + %endif + %if c.repo_info.dbrepo.private: - ${_('private')} + ${_('private repository')} %else: - ${_('public')} + ${_('public repository')} %endif ${c.repo_info.name} + %if c.rhodecode_user.username != 'default': + %if c.following: + + + %else: + + %endif + %endif:
%if c.repo_info.dbrepo.fork: @@ -68,7 +75,7 @@ E.onDOMReady(function(e){
- ${c.repo_info.description} + ${c.repo_info.dbrepo.description}
@@ -92,7 +99,7 @@ E.onDOMReady(function(e){
- ${h.age(c.repo_info.last_change)} - ${h.rfc822date_notz(c.repo_info.last_change)} + ${h.age(c.repo_info.last_change)} - ${c.repo_info.last_change} ${_('by')} ${h.get_changeset_safe(c.repo_info,'tip').author}
@@ -109,13 +116,19 @@ E.onDOMReady(function(e){
- +
@@ -199,9 +248,13 @@ E.onDOMReady(function(e){
-
+ + %if c.no_data: +
${c.no_data_msg}
+ %endif: +
-
+
@@ -228,10 +281,10 @@ E.onDOMReady(function(e){ }; var dataset = dataset; var overview_dataset = [overview_dataset]; - var choiceContainer = YAHOO.util.Dom.get("legend_choices"); - var choiceContainerTable = YAHOO.util.Dom.get("legend_choices_tables"); - var plotContainer = YAHOO.util.Dom.get('commit_history'); - var overviewContainer = YAHOO.util.Dom.get('overview'); + var choiceContainer = YUD.get("legend_choices"); + var choiceContainerTable = YUD.get("legend_choices_tables"); + var plotContainer = YUD.get('commit_history'); + var overviewContainer = YUD.get('overview'); var plot_options = { bars: {show:true,align:'center',lineWidth:4}, @@ -257,7 +310,7 @@ E.onDOMReady(function(e){ bars: {show:true,barWidth: 2,}, shadowSize: 0, xaxis: {mode: "time", timeformat: "%d/%m/%y",}, - yaxis: {ticks: 3, min: 0,}, + yaxis: {ticks: 3, min: 0,tickDecimals:0,}, grid: {color: "#999",}, selection: {mode: "x"} }; @@ -314,7 +367,7 @@ E.onDOMReady(function(e){ div.style.backgroundColor='#fee'; document.body.appendChild(div); } - YAHOO.util.Dom.setStyle(div, 'opacity', 0); + YUD.setStyle(div, 'opacity', 0); div.innerHTML = contents; div.style.top=(y + 5) + "px"; div.style.left=(x + 5) + "px"; @@ -324,9 +377,9 @@ E.onDOMReady(function(e){ } /** - * This function will detect if selected period has some changesets for this user - if it does this data is then pushed for displaying - Additionally it will only display users that are selected by the checkbox + * This function will detect if selected period has some changesets + for this user if it does this data is then pushed for displaying + Additionally it will only display users that are selected by the checkbox */ function getDataAccordingToRanges(ranges) { @@ -334,31 +387,28 @@ E.onDOMReady(function(e){ var keys = []; for(var key in dataset){ var push = false; + //method1 slow !! - ///* + //* for(var ds in dataset[key].data){ commit_data = dataset[key].data[ds]; - //console.log(key); - //console.log(new Date(commit_data.time*1000)); - //console.log(new Date(ranges.xaxis.from*1000)); - //console.log(new Date(ranges.xaxis.to*1000)); if (commit_data.time >= ranges.xaxis.from && commit_data.time <= ranges.xaxis.to){ push = true; break; } } - //*/ + //*/ + /*//method2 sorted commit data !!! + var first_commit = dataset[key].data[0].time; var last_commit = dataset[key].data[dataset[key].data.length-1].time; - console.log(first_commit); - console.log(last_commit); - if (first_commit >= ranges.xaxis.from && last_commit <= ranges.xaxis.to){ push = true; } - */ + //*/ + if(push){ data.push(dataset[key]); } @@ -399,14 +449,14 @@ E.onDOMReady(function(e){ new_data.push(getDummyData(checkbox_key)); } } - + var new_options = YAHOO.lang.merge(plot_options, { xaxis: { min: cur_ranges.xaxis.from, max: cur_ranges.xaxis.to, mode:"time", timeformat: "%d/%m", - } + }, }); if (!new_data){ new_data = [[0,1]]; @@ -440,7 +490,12 @@ E.onDOMReady(function(e){ max: ranges.xaxis.to, mode:"time", timeformat: "%d/%m", - } + }, + yaxis: { + min: ranges.yaxis.from, + max: ranges.yaxis.to, + }, + }); // do the zooming plot = YAHOO.widget.Flot(plotContainer, data, new_options); @@ -454,7 +509,7 @@ E.onDOMReady(function(e){ overview.setSelection(ranges, true); //resubscribe choiced - YAHOO.util.Event.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, ranges]); + YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, ranges]); } var previousPoint = null; @@ -463,13 +518,13 @@ E.onDOMReady(function(e){ var pos = o.pos; var item = o.item; - //YAHOO.util.Dom.get("x").innerHTML = pos.x.toFixed(2); - //YAHOO.util.Dom.get("y").innerHTML = pos.y.toFixed(2); + //YUD.get("x").innerHTML = pos.x.toFixed(2); + //YUD.get("y").innerHTML = pos.y.toFixed(2); if (item) { if (previousPoint != item.datapoint) { previousPoint = item.datapoint; - var tooltip = YAHOO.util.Dom.get("tooltip"); + var tooltip = YUD.get("tooltip"); if(tooltip) { tooltip.parentNode.removeChild(tooltip); } @@ -508,7 +563,7 @@ E.onDOMReady(function(e){ } } else { - var tooltip = YAHOO.util.Dom.get("tooltip"); + var tooltip = YUD.get("tooltip"); if(tooltip) { tooltip.parentNode.removeChild(tooltip); @@ -541,7 +596,7 @@ E.onDOMReady(function(e){ plot.subscribe("plothover", plothover); - YAHOO.util.Event.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, initial_ranges]); + YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, initial_ranges]); } SummaryPlot(${c.ts_min},${c.ts_max},${c.commit_data|n},${c.overview_data|n}); @@ -551,18 +606,20 @@ E.onDOMReady(function(e){
- +
- <%include file='../shortlog/shortlog_data.html'/> - %if c.repo_changesets: - ${h.link_to(_('show more'),h.url('changelog_home',repo_name=c.repo_name))} - %endif +
+ <%include file='../shortlog/shortlog_data.html'/> +
+ ##%if c.repo_changesets: + ## ${h.link_to(_('show more'),h.url('changelog_home',repo_name=c.repo_name))} + ##%endif
- +
<%include file='../tags/tags_data.html'/> @@ -573,7 +630,7 @@ E.onDOMReady(function(e){
- +
<%include file='../branches/branches_data.html'/> diff --git a/rhodecode/templates/tags/tags_data.html b/rhodecode/templates/tags/tags_data.html --- a/rhodecode/templates/tags/tags_data.html +++ b/rhodecode/templates/tags/tags_data.html @@ -1,25 +1,29 @@ %if c.repo_tags:
${_('date')}${_('commit message')}${_('age')} ${_('author')} ${_('revision')}${_('commit message')} ${_('branch')} ${_('tags')} ${_('links')}
${h.age(cs._ctx.date())} - ${h.rfc822date_notz(cs._ctx.date())} + ${h.link_to(h.truncate(cs.message,50), + h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id), + title=cs.message)} + + ${h.age(cs.date)} + ${h.person(cs.author)}r${cs.revision}:${cs.short_id} - ${h.link_to(h.truncate(cs.message,60), - h.url('changeset_home',repo_name=c.repo_name,revision=cs.short_id), - title=cs.message)} - r${cs.revision}:${h.short_id(cs.raw_id)} ${cs.branch} @@ -34,9 +36,9 @@ - ${h.link_to(_('changeset'),h.url('changeset_home',repo_name=c.repo_name,revision=cs.short_id))} + ${h.link_to(_('changeset'),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))} | - ${h.link_to(_('files'),h.url('files_home',repo_name=c.repo_name,revision=cs.short_id))} + ${h.link_to(_('files'),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
- - - + + + + %for cnt,tag in enumerate(c.repo_tags.items()): - - - - + + + + + %endfor diff --git a/rhodecode/tests/__init__.py b/rhodecode/tests/__init__.py --- a/rhodecode/tests/__init__.py +++ b/rhodecode/tests/__init__.py @@ -18,33 +18,44 @@ import os from rhodecode.model import meta import logging + log = logging.getLogger(__name__) import pylons.test -__all__ = ['environ', 'url', 'TestController'] +__all__ = ['environ', 'url', 'TestController', 'TESTS_TMP_PATH', 'HG_REPO', + 'GIT_REPO', 'NEW_HG_REPO', 'NEW_GIT_REPO', 'HG_FORK', 'GIT_FORK', ] # Invoke websetup with the current config file #SetupCommand('setup-app').run([config_file]) ##RUNNING DESIRED TESTS -#nosetests rhodecode.tests.functional.test_admin_settings:TestSettingsController.test_my_account +#nosetests -x rhodecode.tests.functional.test_admin_settings:TestSettingsController.test_my_account environ = {} -TEST_DIR = '/tmp' -REPO_PATH = os.path.join(TEST_DIR, 'vcs_test') -NEW_REPO_PATH = os.path.join(TEST_DIR, 'vcs_test_new') -FORK_REPO_PATH = os.path.join(TEST_DIR, 'vcs_test_fork') + +#SOME GLOBALS FOR TESTS +TESTS_TMP_PATH = '/tmp' + +HG_REPO = 'vcs_test_hg' +GIT_REPO = 'vcs_test_git' + +NEW_HG_REPO = 'vcs_test_hg_new' +NEW_GIT_REPO = 'vcs_test_git_new' + +HG_FORK = 'vcs_test_hg_fork' +GIT_FORK = 'vcs_test_git_fork' class TestController(TestCase): def __init__(self, *args, **kwargs): wsgiapp = pylons.test.pylonsapp config = wsgiapp.config + self.app = TestApp(wsgiapp) url._push_object(URLGenerator(config['routes.map'], environ)) self.sa = meta.Session - + self.index_location = config['app_conf']['index_dir'] TestCase.__init__(self, *args, **kwargs) def log_user(self, username='test_admin', password='test12'): diff --git a/rhodecode/tests/functional/test_admin_ldap_settings.py b/rhodecode/tests/functional/test_admin_ldap_settings.py new file mode 100644 --- /dev/null +++ b/rhodecode/tests/functional/test_admin_ldap_settings.py @@ -0,0 +1,7 @@ +from rhodecode.tests import * + +class TestLdapSettingsController(TestController): + + def test_index(self): + response = self.app.get(url(controller='admin/ldap_settings', action='index')) + # Test response... diff --git a/rhodecode/tests/functional/test_permissions.py b/rhodecode/tests/functional/test_admin_permissions.py rename from rhodecode/tests/functional/test_permissions.py rename to rhodecode/tests/functional/test_admin_permissions.py --- a/rhodecode/tests/functional/test_permissions.py +++ b/rhodecode/tests/functional/test_admin_permissions.py @@ -1,6 +1,6 @@ from rhodecode.tests import * -class TestPermissionsController(TestController): +class TestAdminPermissionsController(TestController): def test_index(self): response = self.app.get(url('permissions')) diff --git a/rhodecode/tests/functional/test_repos.py b/rhodecode/tests/functional/test_admin_repos.py rename from rhodecode/tests/functional/test_repos.py rename to rhodecode/tests/functional/test_admin_repos.py --- a/rhodecode/tests/functional/test_repos.py +++ b/rhodecode/tests/functional/test_admin_repos.py @@ -1,7 +1,7 @@ from rhodecode.model.db import Repository from rhodecode.tests import * -class TestReposController(TestController): +class TestAdminReposController(TestController): def test_index(self): self.log_user() @@ -11,34 +11,56 @@ class TestReposController(TestController def test_index_as_xml(self): response = self.app.get(url('formatted_repos', format='xml')) - def test_create(self): + def test_create_hg(self): self.log_user() - repo_name = 'vcs_test_new' + repo_name = NEW_HG_REPO description = 'description for newly created repo' private = False response = self.app.post(url('repos'), {'repo_name':repo_name, - 'description':description, - 'private':private}) + 'repo_type':'hg', + 'description':description, + 'private':private}) - print response - + #test if we have a message for that repository - print '-' * 100 - print response.session assert '''created repository %s''' % (repo_name) in response.session['flash'][0], 'No flash message about new repo' - + #test if the fork was created in the database new_repo = self.sa.query(Repository).filter(Repository.repo_name == repo_name).one() - + assert new_repo.repo_name == repo_name, 'wrong name of repo name in db' assert new_repo.description == description, 'wrong description' - + #test if repository is visible in the list ? response = response.follow() - + assert repo_name in response.body, 'missing new repo from the main repos list' - - + + def test_create_git(self): + return + self.log_user() + repo_name = NEW_GIT_REPO + description = 'description for newly created repo' + private = False + response = self.app.post(url('repos'), {'repo_name':repo_name, + 'repo_type':'git', + 'description':description, + 'private':private}) + + + #test if we have a message for that repository + assert '''created repository %s''' % (repo_name) in response.session['flash'][0], 'No flash message about new repo' + + #test if the fork was created in the database + new_repo = self.sa.query(Repository).filter(Repository.repo_name == repo_name).one() + + assert new_repo.repo_name == repo_name, 'wrong name of repo name in db' + assert new_repo.description == description, 'wrong description' + + #test if repository is visible in the list ? + response = response.follow() + + assert repo_name in response.body, 'missing new repo from the main repos list' def test_new(self): @@ -49,10 +71,10 @@ class TestReposController(TestController response = self.app.get(url('formatted_new_repo', format='xml')) def test_update(self): - response = self.app.put(url('repo', repo_name='vcs_test')) + response = self.app.put(url('repo', repo_name=HG_REPO)) def test_update_browser_fakeout(self): - response = self.app.post(url('repo', repo_name='vcs_test'), params=dict(_method='put')) + response = self.app.post(url('repo', repo_name=HG_REPO), params=dict(_method='put')) def test_delete(self): self.log_user() @@ -60,54 +82,50 @@ class TestReposController(TestController description = 'description for newly created repo' private = False response = self.app.post(url('repos'), {'repo_name':repo_name, + 'repo_type':'hg', 'description':description, 'private':private}) - print response - + #test if we have a message for that repository - print '-' * 100 - print response.session assert '''created repository %s''' % (repo_name) in response.session['flash'][0], 'No flash message about new repo' - + #test if the repo was created in the database new_repo = self.sa.query(Repository).filter(Repository.repo_name == repo_name).one() - + assert new_repo.repo_name == repo_name, 'wrong name of repo name in db' assert new_repo.description == description, 'wrong description' - + #test if repository is visible in the list ? response = response.follow() - + assert repo_name in response.body, 'missing new repo from the main repos list' - - + + response = self.app.delete(url('repo', repo_name=repo_name)) - - print '-' * 100 - print response.session + assert '''deleted repository %s''' % (repo_name) in response.session['flash'][0], 'No flash message about delete repo' - + response.follow() - + #check if repo was deleted from db deleted_repo = self.sa.query(Repository).filter(Repository.repo_name == repo_name).scalar() - + assert deleted_repo is None, 'Deleted repository was found in db' - + def test_delete_browser_fakeout(self): - response = self.app.post(url('repo', repo_name='vcs_test'), params=dict(_method='delete')) + response = self.app.post(url('repo', repo_name=HG_REPO), params=dict(_method='delete')) def test_show(self): self.log_user() - response = self.app.get(url('repo', repo_name='vcs_test')) + response = self.app.get(url('repo', repo_name=HG_REPO)) def test_show_as_xml(self): - response = self.app.get(url('formatted_repo', repo_name='vcs_test', format='xml')) + response = self.app.get(url('formatted_repo', repo_name=HG_REPO, format='xml')) def test_edit(self): - response = self.app.get(url('edit_repo', repo_name='vcs_test')) + response = self.app.get(url('edit_repo', repo_name=HG_REPO)) def test_edit_as_xml(self): - response = self.app.get(url('formatted_edit_repo', repo_name='vcs_test', format='xml')) + response = self.app.get(url('formatted_edit_repo', repo_name=HG_REPO, format='xml')) diff --git a/rhodecode/tests/functional/test_admin_settings.py b/rhodecode/tests/functional/test_admin_settings.py --- a/rhodecode/tests/functional/test_admin_settings.py +++ b/rhodecode/tests/functional/test_admin_settings.py @@ -1,7 +1,8 @@ -from rhodecode.tests import * +from rhodecode.lib.auth import get_crypt_password, check_password from rhodecode.model.db import User +from rhodecode.tests import * -class TestSettingsController(TestController): +class TestAdminSettingsController(TestController): def test_index(self): response = self.app.get(url('admin_settings')) @@ -48,45 +49,64 @@ class TestSettingsController(TestControl response = self.app.get(url('admin_settings_my_account')) print response assert 'value="test_admin' in response.body - - - + + + def test_my_account_update(self): self.log_user() + new_email = 'new@mail.pl' + new_name = 'NewName' + new_lastname = 'NewLastname' + new_password = 'test123' + + response = self.app.post(url('admin_settings_my_account_update'), params=dict( _method='put', username='test_admin', - new_password='test12', + new_password=new_password, password='', - name='NewName', - lastname='NewLastname', + name=new_name, + lastname=new_lastname, email=new_email,)) response.follow() - print response - - print 'x' * 100 - print response.session - assert 'Your account was updated succesfully' in response.session['flash'][0][1], 'no flash message about success of change' + + assert 'Your account was updated successfully' in response.session['flash'][0][1], 'no flash message about success of change' user = self.sa.query(User).filter(User.username == 'test_admin').one() assert user.email == new_email , 'incorrect user email after update got %s vs %s' % (user.email, new_email) - - def test_my_account_update_own_email_ok(self): - self.log_user() - - new_email = 'new@mail.pl' + assert user.name == new_name, 'updated field mismatch %s vs %s' % (user.name, new_name) + assert user.lastname == new_lastname, 'updated field mismatch %s vs %s' % (user.lastname, new_lastname) + assert check_password(new_password, user.password) is True, 'password field mismatch %s vs %s' % (user.password, new_password) + + #bring back the admin settings + old_email = 'test_admin@mail.com' + old_name = 'RhodeCode' + old_lastname = 'Admin' + old_password = 'test12' + response = self.app.post(url('admin_settings_my_account_update'), params=dict( _method='put', username='test_admin', - new_password='test12', - name='NewName', - lastname='NewLastname', - email=new_email,)) - print response - + new_password=old_password, + password='', + name=old_name, + lastname=old_lastname, + email=old_email,)) + + response.follow() + assert 'Your account was updated successfully' in response.session['flash'][0][1], 'no flash message about success of change' + user = self.sa.query(User).filter(User.username == 'test_admin').one() + assert user.email == old_email , 'incorrect user email after update got %s vs %s' % (user.email, old_email) + + assert user.email == old_email , 'incorrect user email after update got %s vs %s' % (user.email, old_email) + assert user.name == old_name, 'updated field mismatch %s vs %s' % (user.name, old_name) + assert user.lastname == old_lastname, 'updated field mismatch %s vs %s' % (user.lastname, old_lastname) + assert check_password(old_password, user.password) is True , 'password updated field mismatch %s vs %s' % (user.password, old_password) + + def test_my_account_update_err_email_exists(self): self.log_user() - + new_email = 'test_regular@mail.com'#already exisitn email response = self.app.post(url('admin_settings_my_account_update'), params=dict( _method='put', @@ -96,13 +116,13 @@ class TestSettingsController(TestControl lastname='NewLastname', email=new_email,)) print response - - assert 'That e-mail address is already taken' in response.body, 'Missing error message about existing email' - - + + assert 'This e-mail address is already taken' in response.body, 'Missing error message about existing email' + + def test_my_account_update_err(self): self.log_user('test_regular2', 'test12') - + new_email = 'newmail.pl' response = self.app.post(url('admin_settings_my_account_update'), params=dict( _method='put', diff --git a/rhodecode/tests/functional/test_users.py b/rhodecode/tests/functional/test_admin_users.py rename from rhodecode/tests/functional/test_users.py rename to rhodecode/tests/functional/test_admin_users.py --- a/rhodecode/tests/functional/test_users.py +++ b/rhodecode/tests/functional/test_admin_users.py @@ -1,6 +1,9 @@ from rhodecode.tests import * +from rhodecode.model.db import User +from rhodecode.lib.auth import check_password +from sqlalchemy.orm.exc import NoResultFound -class TestUsersController(TestController): +class TestAdminUsersController(TestController): def test_index(self): response = self.app.get(url('users')) @@ -10,7 +13,60 @@ class TestUsersController(TestController response = self.app.get(url('formatted_users', format='xml')) def test_create(self): - response = self.app.post(url('users')) + self.log_user() + username = 'newtestuser' + password = 'test12' + name = 'name' + lastname = 'lastname' + email = 'mail@mail.com' + + response = self.app.post(url('users'), {'username':username, + 'password':password, + 'name':name, + 'active':True, + 'lastname':lastname, + 'email':email}) + + + assert '''created user %s''' % (username) in response.session['flash'][0], 'No flash message about new user' + + new_user = self.sa.query(User).filter(User.username == username).one() + + + assert new_user.username == username, 'wrong info about username' + assert check_password(password, new_user.password) == True , 'wrong info about password' + assert new_user.name == name, 'wrong info about name' + assert new_user.lastname == lastname, 'wrong info about lastname' + assert new_user.email == email, 'wrong info about email' + + + response.follow() + response = response.follow() + assert """edit">newtestuser""" in response.body + + def test_create_err(self): + self.log_user() + username = 'new_user' + password = '' + name = 'name' + lastname = 'lastname' + email = 'errmail.com' + + response = self.app.post(url('users'), {'username':username, + 'password':password, + 'name':name, + 'active':False, + 'lastname':lastname, + 'email':email}) + + assert """Invalid username""" in response.body + assert """Please enter a value""" in response.body + assert """An email address must contain a single @""" in response.body + + def get_user(): + self.sa.query(User).filter(User.username == username).one() + + self.assertRaises(NoResultFound, get_user), 'found user in database' def test_new(self): response = self.app.get(url('new_user')) @@ -25,7 +81,27 @@ class TestUsersController(TestController response = self.app.post(url('user', id=1), params=dict(_method='put')) def test_delete(self): - response = self.app.delete(url('user', id=1)) + self.log_user() + username = 'newtestuserdeleteme' + password = 'test12' + name = 'name' + lastname = 'lastname' + email = 'todeletemail@mail.com' + + response = self.app.post(url('users'), {'username':username, + 'password':password, + 'name':name, + 'active':True, + 'lastname':lastname, + 'email':email}) + + response = response.follow() + + new_user = self.sa.query(User).filter(User.username == username).one() + response = self.app.delete(url('user', id=new_user.user_id)) + + assert """sucessfully deleted user""" in response.session['flash'][0], 'No info about user deletion' + def test_delete_browser_fakeout(self): response = self.app.post(url('user', id=1), params=dict(_method='delete')) diff --git a/rhodecode/tests/functional/test_branches.py b/rhodecode/tests/functional/test_branches.py --- a/rhodecode/tests/functional/test_branches.py +++ b/rhodecode/tests/functional/test_branches.py @@ -4,5 +4,15 @@ class TestBranchesController(TestControl def test_index(self): self.log_user() - response = self.app.get(url(controller='branches', action='index',repo_name='vcs_test')) + response = self.app.get(url(controller='branches', action='index', repo_name=HG_REPO)) + + assert """default""" % HG_REPO in response.body, 'wrong info about default branch' + assert """git""" % HG_REPO in response.body, 'wrong info about default git' + assert """web""" % HG_REPO in response.body, 'wrong info about default web' + + + + + + # Test response... diff --git a/rhodecode/tests/functional/test_changelog.py b/rhodecode/tests/functional/test_changelog.py --- a/rhodecode/tests/functional/test_changelog.py +++ b/rhodecode/tests/functional/test_changelog.py @@ -2,29 +2,37 @@ from rhodecode.tests import * class TestChangelogController(TestController): - def test_index(self): + def test_index_hg(self): self.log_user() - response = self.app.get(url(controller='changelog', action='index', repo_name='vcs_test')) + response = self.app.get(url(controller='changelog', action='index', repo_name=HG_REPO)) + print response.body assert """
""" in response.body, 'wrong info about number of changes' + assert """
commit 154: 5e204e7583b9@2010-08-10 01:18:46
""" in response.body , 'no info on this commit' assert """Small update at simplevcs app""" in response.body, 'missing info about commit message' - assert """0""" in response.body, 'wrong info about removed nodes' - assert """2""" in response.body, 'wrong info about changed nodes' - assert """1""" in response.body, 'wrong info about added nodes' + assert """0""" in response.body, 'wrong info about removed nodes' + assert """2""" in response.body, 'wrong info about changed nodes' + assert """1""" in response.body, 'wrong info about added nodes' #pagination - response = self.app.get(url(controller='changelog', action='index', repo_name='vcs_test'), {'page':1}) - response = self.app.get(url(controller='changelog', action='index', repo_name='vcs_test'), {'page':2}) - response = self.app.get(url(controller='changelog', action='index', repo_name='vcs_test'), {'page':3}) - response = self.app.get(url(controller='changelog', action='index', repo_name='vcs_test'), {'page':4}) - response = self.app.get(url(controller='changelog', action='index', repo_name='vcs_test'), {'page':5}) - response = self.app.get(url(controller='changelog', action='index', repo_name='vcs_test'), {'page':6}) + response = self.app.get(url(controller='changelog', action='index', repo_name=HG_REPO), {'page':1}) + response = self.app.get(url(controller='changelog', action='index', repo_name=HG_REPO), {'page':2}) + response = self.app.get(url(controller='changelog', action='index', repo_name=HG_REPO), {'page':3}) + response = self.app.get(url(controller='changelog', action='index', repo_name=HG_REPO), {'page':4}) + response = self.app.get(url(controller='changelog', action='index', repo_name=HG_REPO), {'page':5}) + response = self.app.get(url(controller='changelog', action='index', repo_name=HG_REPO), {'page':6}) + # Test response after pagination... + print response.body + assert """
commit 64: 46ad32a4f974@2010-04-20 00:33:21
"""in response.body, 'wrong info about commit 64' + assert """1"""in response.body, 'wrong info about number of removed' + assert """13"""in response.body, 'wrong info about number of changes' + assert """20"""in response.body, 'wrong info about number of added' + assert """""" % HG_REPO in response.body, 'wrong info about commit 64 is a merge' - assert """20"""in response.body, 'wrong info about number of removed' - assert """1"""in response.body, 'wrong info about number of changes' - assert """0"""in response.body, 'wrong info about number of added' - assert """
commit 64: 46ad32a4f974@2010-04-20 00:33:21
"""in response.body, 'wrong info about commit 64' + - assert """"""in response.body, 'wrong info about commit 64 is a merge' + #def test_index_git(self): + # self.log_user() + # response = self.app.get(url(controller='changelog', action='index', repo_name=GIT_REPO)) diff --git a/rhodecode/tests/functional/test_changeset.py b/rhodecode/tests/functional/test_changeset.py --- a/rhodecode/tests/functional/test_changeset.py +++ b/rhodecode/tests/functional/test_changeset.py @@ -4,5 +4,5 @@ class TestChangesetController(TestContro def test_index(self): response = self.app.get(url(controller='changeset', action='index', - repo_name='vcs_test',revision='tip')) + repo_name=HG_REPO,revision='tip')) # Test response... diff --git a/rhodecode/tests/functional/test_feed.py b/rhodecode/tests/functional/test_feed.py --- a/rhodecode/tests/functional/test_feed.py +++ b/rhodecode/tests/functional/test_feed.py @@ -5,11 +5,11 @@ class TestFeedController(TestController) def test_rss(self): self.log_user() response = self.app.get(url(controller='feed', action='rss', - repo_name='vcs_test')) + repo_name=HG_REPO)) # Test response... def test_atom(self): self.log_user() response = self.app.get(url(controller='feed', action='atom', - repo_name='vcs_test')) + repo_name=HG_REPO)) # Test response... \ No newline at end of file diff --git a/rhodecode/tests/functional/test_files.py b/rhodecode/tests/functional/test_files.py --- a/rhodecode/tests/functional/test_files.py +++ b/rhodecode/tests/functional/test_files.py @@ -5,7 +5,186 @@ class TestFilesController(TestController def test_index(self): self.log_user() response = self.app.get(url(controller='files', action='index', - repo_name='vcs_test', + repo_name=HG_REPO, revision='tip', f_path='/')) # Test response... + assert 'docs' in response.body, 'missing dir' + assert 'tests' in response.body, 'missing dir' + assert 'vcs' in response.body, 'missing dir' + assert '.hgignore' in response.body, 'missing file' + assert 'MANIFEST.in' in response.body, 'missing file' + + + def test_index_revision(self): + self.log_user() + + response = self.app.get(url(controller='files', action='index', + repo_name=HG_REPO, + revision='7ba66bec8d6dbba14a2155be32408c435c5f4492', + f_path='/')) + + + + #Test response... + + assert 'docs' in response.body, 'missing dir' + assert 'tests' in response.body, 'missing dir' + assert 'README.rst' in response.body, 'missing file' + assert '1.1 KiB' in response.body, 'missing size of setup.py' + assert 'text/x-python' in response.body, 'missing mimetype of setup.py' + + + + def test_index_different_branch(self): + self.log_user() + + response = self.app.get(url(controller='files', action='index', + repo_name=HG_REPO, + revision='97e8b885c04894463c51898e14387d80c30ed1ee', + f_path='/')) + + + + assert """branch: git""" in response.body, 'missing or wrong branch info' + + + + def test_index_paging(self): + self.log_user() + + for r in [(73, 'a066b25d5df7016b45a41b7e2a78c33b57adc235'), + (92, 'cc66b61b8455b264a7a8a2d8ddc80fcfc58c221e'), + (109, '75feb4c33e81186c87eac740cee2447330288412'), + (1, '3d8f361e72ab303da48d799ff1ac40d5ac37c67e'), + (0, 'b986218ba1c9b0d6a259fac9b050b1724ed8e545')]: + + response = self.app.get(url(controller='files', action='index', + repo_name=HG_REPO, + revision=r[1], + f_path='/')) + + assert """@ r%s:%s""" % (r[0], r[1][:12]) in response.body, 'missing info about current revision' + + def test_file_source(self): + self.log_user() + response = self.app.get(url(controller='files', action='index', + repo_name=HG_REPO, + revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc', + f_path='vcs/nodes.py')) + + #test or history + assert """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""" in response.body + + + assert """
"Partially implemented #16. filecontent/commit message/author/node name are safe_unicode now. +In addition some other __str__ are unicode as well +Added test for unicode +Improved test to clone into uniq repository. +removed extra unicode conversion in diff."
""" in response.body + + assert """branch: default""" in response.body, 'missing or wrong branch info' + + def test_file_annotation(self): + self.log_user() + response = self.app.get(url(controller='files', action='annotate', + repo_name=HG_REPO, + revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc', + f_path='vcs/nodes.py')) + + print response.body + assert """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""" in response.body, 'missing or wrong history in annotation' + + assert """branch: default""" in response.body, 'missing or wrong branch info' diff --git a/rhodecode/tests/functional/test_hg.py b/rhodecode/tests/functional/test_home.py rename from rhodecode/tests/functional/test_hg.py rename to rhodecode/tests/functional/test_home.py --- a/rhodecode/tests/functional/test_hg.py +++ b/rhodecode/tests/functional/test_home.py @@ -1,11 +1,11 @@ from rhodecode.tests import * -class TestAdminController(TestController): +class TestHomeController(TestController): def test_index(self): self.log_user() - response = self.app.get(url(controller='hg', action='index')) + response = self.app.get(url(controller='home', action='index')) #if global permission is set assert 'ADD NEW REPOSITORY' in response.body, 'Wrong main page' - assert 'href="/vcs_test/summary"' in response.body, ' mising repository in list' + assert 'href="/%s/summary"' % HG_REPO in response.body, ' mising repository in list' # Test response... diff --git a/rhodecode/tests/functional/test_journal.py b/rhodecode/tests/functional/test_journal.py new file mode 100644 --- /dev/null +++ b/rhodecode/tests/functional/test_journal.py @@ -0,0 +1,7 @@ +from rhodecode.tests import * + +class TestJournalController(TestController): + + def test_index(self): + response = self.app.get(url(controller='journal', action='index')) + # Test response... diff --git a/rhodecode/tests/functional/test_login.py b/rhodecode/tests/functional/test_login.py --- a/rhodecode/tests/functional/test_login.py +++ b/rhodecode/tests/functional/test_login.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from rhodecode.tests import * from rhodecode.model.db import User from rhodecode.lib.auth import check_password @@ -17,8 +18,8 @@ class TestLoginController(TestController assert response.status == '302 Found', 'Wrong response code from login got %s' % response.status assert response.session['rhodecode_user'].username == 'test_admin', 'wrong logged in user' response = response.follow() - assert 'auto description for vcs_test' in response.body - + assert '%s repository' % HG_REPO in response.body + def test_login_regular_ok(self): response = self.app.post(url(controller='login', action='index'), {'username':'test_regular', @@ -27,9 +28,9 @@ class TestLoginController(TestController assert response.status == '302 Found', 'Wrong response code from login got %s' % response.status assert response.session['rhodecode_user'].username == 'test_regular', 'wrong logged in user' response = response.follow() - assert 'auto description for vcs_test' in response.body + assert '%s repository' % HG_REPO in response.body assert '' not in response.body - + def test_login_ok_came_from(self): test_came_from = '/_admin/users' response = self.app.post(url(controller='login', action='index', came_from=test_came_from), @@ -37,11 +38,11 @@ class TestLoginController(TestController 'password':'test12'}) assert response.status == '302 Found', 'Wrong response code from came from redirection' response = response.follow() - + assert response.status == '200 OK', 'Wrong response from login page got %s' % response.status assert 'Users administration' in response.body, 'No proper title in response' - - + + def test_login_short_password(self): response = self.app.post(url(controller='login', action='index'), {'username':'error', @@ -55,71 +56,152 @@ class TestLoginController(TestController {'username':'error', 'password':'test12'}) assert response.status == '200 OK', 'Wrong response from login page' - + assert 'invalid user name' in response.body, 'No error username message in response' assert 'invalid password' in response.body, 'No error password message in response' - - + + #========================================================================== + # REGISTRATIONS + #========================================================================== def test_register(self): response = self.app.get(url(controller='login', action='register')) - assert 'Sign Up to rhodecode' in response.body, 'wrong page for user registration' - + assert 'Sign Up to RhodeCode' in response.body, 'wrong page for user registration' + def test_register_err_same_username(self): response = self.app.post(url(controller='login', action='register'), {'username':'test_admin', - 'password':'test', + 'password':'test12', + 'password_confirmation':'test12', 'email':'goodmail@domain.com', 'name':'test', 'lastname':'test'}) - + + assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status + assert 'This username already exists' in response.body + + def test_register_err_same_email(self): + response = self.app.post(url(controller='login', action='register'), + {'username':'test_admin_0', + 'password':'test12', + 'password_confirmation':'test12', + 'email':'test_admin@mail.com', + 'name':'test', + 'lastname':'test'}) + assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status - assert 'This username already exists' in response.body - + assert 'This e-mail address is already taken' in response.body + + def test_register_err_same_email_case_sensitive(self): + response = self.app.post(url(controller='login', action='register'), + {'username':'test_admin_1', + 'password':'test12', + 'password_confirmation':'test12', + 'email':'TesT_Admin@mail.COM', + 'name':'test', + 'lastname':'test'}) + assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status + assert 'This e-mail address is already taken' in response.body + def test_register_err_wrong_data(self): response = self.app.post(url(controller='login', action='register'), {'username':'xs', - 'password':'', + 'password':'test', + 'password_confirmation':'test', + 'email':'goodmailm', + 'name':'test', + 'lastname':'test'}) + assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status + assert 'An email address must contain a single @' in response.body + assert 'Enter a value 6 characters long or more' in response.body + + + def test_register_err_username(self): + response = self.app.post(url(controller='login', action='register'), + {'username':'error user', + 'password':'test12', + 'password_confirmation':'test12', + 'email':'goodmailm', + 'name':'test', + 'lastname':'test'}) + + print response.body + assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status + assert 'An email address must contain a single @' in response.body + assert 'Username may only contain alphanumeric characters underscores or dashes and must begin with alphanumeric character' in response.body + + def test_register_err_case_sensitive(self): + response = self.app.post(url(controller='login', action='register'), + {'username':'Test_Admin', + 'password':'test12', + 'password_confirmation':'test12', 'email':'goodmailm', 'name':'test', 'lastname':'test'}) - + assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status assert 'An email address must contain a single @' in response.body - assert 'Please enter a value' in response.body - - - + assert 'This username already exists' in response.body + + + + def test_register_special_chars(self): + response = self.app.post(url(controller='login', action='register'), + {'username':'xxxaxn', + 'password':'ąćźżąśśśś', + 'password_confirmation':'ąćźżąśśśś', + 'email':'goodmailm@test.plx', + 'name':'test', + 'lastname':'test'}) + + print response.body + assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status + assert 'Invalid characters in password' in response.body + + + def test_register_password_mismatch(self): + response = self.app.post(url(controller='login', action='register'), + {'username':'xs', + 'password':'123qwe', + 'password_confirmation':'qwe123', + 'email':'goodmailm@test.plxa', + 'name':'test', + 'lastname':'test'}) + + assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status + print response.body + assert 'Password do not match' in response.body + def test_register_ok(self): username = 'test_regular4' password = 'qweqwe' email = 'marcin@test.com' name = 'testname' lastname = 'testlastname' - + response = self.app.post(url(controller='login', action='register'), {'username':username, 'password':password, + 'password_confirmation':password, 'email':email, 'name':name, 'lastname':lastname}) - print response.body - assert response.status == '302 Found', 'Wrong response from register page got %s' % response.status + assert response.status == '302 Found', 'Wrong response from register page got %s' % response.status assert 'You have successfully registered into rhodecode' in response.session['flash'][0], 'No flash message about user registration' - + ret = self.sa.query(User).filter(User.username == 'test_regular4').one() assert ret.username == username , 'field mismatch %s %s' % (ret.username, username) assert check_password(password, ret.password) == True , 'password mismatch' assert ret.email == email , 'field mismatch %s %s' % (ret.email, email) assert ret.name == name , 'field mismatch %s %s' % (ret.name, name) assert ret.lastname == lastname , 'field mismatch %s %s' % (ret.lastname, lastname) - - - def test_forgot_password_wrong_mail(self): + + + def test_forgot_password_wrong_mail(self): response = self.app.post(url(controller='login', action='password_reset'), {'email':'marcin@wrongmail.org', }) - - assert "That e-mail address doesn't exist" in response.body, 'Missing error message about wrong email' - + + assert "This e-mail address doesn't exist" in response.body, 'Missing error message about wrong email' + def test_forgot_password(self): response = self.app.get(url(controller='login', action='password_reset')) assert response.status == '200 OK', 'Wrong response from login page got %s' % response.status @@ -129,19 +211,20 @@ class TestLoginController(TestController email = 'marcin@python-works.com' name = 'passwd' lastname = 'reset' - + response = self.app.post(url(controller='login', action='register'), {'username':username, 'password':password, + 'password_confirmation':password, 'email':email, 'name':name, - 'lastname':lastname}) + 'lastname':lastname}) #register new user for email test response = self.app.post(url(controller='login', action='password_reset'), {'email':email, }) print response.session['flash'] assert 'You have successfully registered into rhodecode' in response.session['flash'][0], 'No flash message about user registration' assert 'Your new password was sent' in response.session['flash'][1], 'No flash message about password reset' - - - + + + diff --git a/rhodecode/tests/functional/test_search.py b/rhodecode/tests/functional/test_search.py --- a/rhodecode/tests/functional/test_search.py +++ b/rhodecode/tests/functional/test_search.py @@ -1,5 +1,4 @@ from rhodecode.tests import * -from rhodecode.lib.indexers import IDX_LOCATION import os from nose.plugins.skip import SkipTest @@ -13,26 +12,25 @@ class TestSearchController(TestControlle # Test response... def test_empty_search(self): - - if os.path.isdir(IDX_LOCATION): + if os.path.isdir(self.index_location): raise SkipTest('skipped due to existing index') else: self.log_user() - response = self.app.get(url(controller='search', action='index'), {'q':'vcs_test'}) + response = self.app.get(url(controller='search', action='index'), {'q':HG_REPO}) assert 'There is no index to search in. Please run whoosh indexer' in response.body, 'No error message about empty index' - + def test_normal_search(self): self.log_user() response = self.app.get(url(controller='search', action='index'), {'q':'def repo'}) print response.body assert '10 results' in response.body, 'no message about proper search results' assert 'Permission denied' not in response.body, 'Wrong permissions settings for that repo and user' - - + + def test_repo_search(self): self.log_user() - response = self.app.get(url(controller='search', action='index'), {'q':'repository:vcs_test def test'}) + response = self.app.get(url(controller='search', action='index'), {'q':'repository:%s def test' % HG_REPO}) print response.body assert '4 results' in response.body, 'no message about proper search results' assert 'Permission denied' not in response.body, 'Wrong permissions settings for that repo and user' - + diff --git a/rhodecode/tests/functional/test_settings.py b/rhodecode/tests/functional/test_settings.py --- a/rhodecode/tests/functional/test_settings.py +++ b/rhodecode/tests/functional/test_settings.py @@ -6,40 +6,38 @@ class TestSettingsController(TestControl def test_index(self): self.log_user() response = self.app.get(url(controller='settings', action='index', - repo_name='vcs_test')) + repo_name=HG_REPO)) # Test response... - + def test_fork(self): self.log_user() response = self.app.get(url(controller='settings', action='fork', - repo_name='vcs_test')) - + repo_name=HG_REPO)) + def test_fork_create(self): self.log_user() - fork_name = 'vcs_test_fork' + fork_name = HG_FORK description = 'fork of vcs test' - repo_name = 'vcs_test' + repo_name = HG_REPO response = self.app.post(url(controller='settings', action='fork_create', repo_name=repo_name), {'fork_name':fork_name, + 'repo_type':'hg', 'description':description, 'private':'False'}) - - - print response - + #test if we have a message that fork is ok - assert 'fork %s repository as %s task added' \ + assert 'forked %s repository as %s' \ % (repo_name, fork_name) in response.session['flash'][0], 'No flash message about fork' - + #test if the fork was created in the database fork_repo = self.sa.query(Repository).filter(Repository.repo_name == fork_name).one() - + assert fork_repo.repo_name == fork_name, 'wrong name of repo name in new db fork repo' assert fork_repo.fork.repo_name == repo_name, 'wrong fork parrent' - - + + #test if fork is visible in the list ? response = response.follow() @@ -47,9 +45,6 @@ class TestSettingsController(TestControl #check if fork is marked as fork response = self.app.get(url(controller='summary', action='index', repo_name=fork_name)) - - - print response - + assert 'Fork of %s' % repo_name in response.body, 'no message about that this repo is a fork' - + diff --git a/rhodecode/tests/functional/test_shortlog.py b/rhodecode/tests/functional/test_shortlog.py --- a/rhodecode/tests/functional/test_shortlog.py +++ b/rhodecode/tests/functional/test_shortlog.py @@ -4,5 +4,5 @@ class TestShortlogController(TestControl def test_index(self): self.log_user() - response = self.app.get(url(controller='shortlog', action='index',repo_name='vcs_test')) + response = self.app.get(url(controller='shortlog', action='index',repo_name=HG_REPO)) # Test response... diff --git a/rhodecode/tests/functional/test_summary.py b/rhodecode/tests/functional/test_summary.py --- a/rhodecode/tests/functional/test_summary.py +++ b/rhodecode/tests/functional/test_summary.py @@ -4,8 +4,16 @@ class TestSummaryController(TestControll def test_index(self): self.log_user() - response = self.app.get(url(controller='summary', action='index', repo_name='vcs_test')) - print response - assert """public""" in response.body - - # Test response... + response = self.app.get(url(controller='summary', action='index', repo_name=HG_REPO)) + + #repo type + assert """Mercurial repository""" in response.body + assert """public repository""" in response.body + + #codes stats + assert """var data = {"Python": 42, "Rst": 11, "Bash": 2, "Makefile": 1, "Batch": 1, "Ini": 1, "Css": 1};""" in response.body, 'wrong info about % of codes stats' + + # clone url... + assert """""" % HG_REPO in response.body + + diff --git a/rhodecode/tests/functional/test_tags.py b/rhodecode/tests/functional/test_tags.py --- a/rhodecode/tests/functional/test_tags.py +++ b/rhodecode/tests/functional/test_tags.py @@ -4,5 +4,10 @@ class TestTagsController(TestController) def test_index(self): self.log_user() - response = self.app.get(url(controller='tags', action='index',repo_name='vcs_test')) + response = self.app.get(url(controller='tags', action='index', repo_name=HG_REPO)) + assert """tip""" % HG_REPO, 'wrong info about tip tag' + assert """0.1.4""" % HG_REPO, 'wrong info about 0.1.4 tag' + assert """0.1.3""" % HG_REPO, 'wrong info about 0.1.3 tag' + assert """0.1.2""" % HG_REPO, 'wrong info about 0.1.2 tag' + assert """0.1.1""" % HG_REPO, 'wrong info about 0.1.1 tag' # Test response... diff --git a/rhodecode/tests/test_hg_operations.sh b/rhodecode/tests/test_hg_operations.sh new file mode 100755 --- /dev/null +++ b/rhodecode/tests/test_hg_operations.sh @@ -0,0 +1,24 @@ +#!/bin/bash +repo=/tmp/vcs_test_hg_clone +repo_name=vcs_test_hg +user=test_admin +password=test12 +echo 'removing repo '$repo +rm -rf '$repo' +hg clone http://$user:$password@127.0.0.1:5000/$repo_name $repo +cd $repo +echo 'some' >> $repo/setup.py && hg ci -m 'ci1' && \ +echo 'some' >> $repo/setup.py && hg ci -m 'ci2' && \ +echo 'some' >> $repo/setup.py && hg ci -m 'ci3' && \ +echo 'some' >> $repo/setup.py && hg ci -m 'ci4' && \ +hg push + +echo 'new file' >> $repo/new_file.py +hg add $repo/new_file.py + +for i in {1..15} +do + echo "line $i" >> $repo/new_file.py && hg ci -m "autocommit $i" +done + +hg push \ No newline at end of file diff --git a/rhodecode/tests/vcs_test.tar.gz b/rhodecode/tests/vcs_test.tar.gz deleted file mode 100644 index f0156b56eec65853396254c9d9124df8861e8674..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@+PbRStDBYAMaoA(d`YxbQ7(!k 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%0AgE&%N#sa(OakWp*>L#D&BVKY_?AB|OfdRA3yQY8 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 eGAT^_#iQ_V{EffyH~z+7jQ + :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; 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. + +import os +import logging + from rhodecode.config.environment import load_environment from rhodecode.lib.db_manage import DbManage -import logging -import os + log = logging.getLogger(__name__) def setup_app(command, conf, vars): """Place any commands to setup rhodecode here""" - dbname = os.path.split(conf['sqlalchemy.db1.url'])[-1] - dbmanage = DbManage(log_sql=True, dbname=dbname, root=conf['here'], - tests=False) + dbconf = conf['sqlalchemy.db1.url'] + dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=conf['here'], tests=False) dbmanage.create_tables(override=True) + dbmanage.set_db_version() dbmanage.config_prompt(None) dbmanage.create_default_user() dbmanage.admin_prompt() dbmanage.create_permissions() dbmanage.populate_default_permissions() - + load_environment(conf.global_conf, conf.local_conf, initial=True) diff --git a/setup.py b/setup.py --- a/setup.py +++ b/setup.py @@ -1,27 +1,28 @@ -from rhodecode import get_version import sys py_version = sys.version_info +from rhodecode import get_version + requirements = [ - "Pylons>=1.0.0", + "Pylons==1.0.0", "SQLAlchemy==0.6.5", - "Mako>=0.3.2", - "vcs==0.1.8", - "pygments>=1.3.0", - "mercurial==1.6.4", - "whoosh==1.2.5", - "celery==2.1.3", + "Mako==0.3.6", + "vcs==0.1.10", + "pygments==1.3.1", + "mercurial==1.7.2", + "whoosh==1.3.4", + "celery==2.1.4", "py-bcrypt", "babel", ] -classifiers = ["Development Status :: 5 - Production/Stable", - 'Environment :: Web Environment', - 'Framework :: Pylons', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', ] +classifiers = ['Development Status :: 5 - Production/Stable', + 'Environment :: Web Environment', + 'Framework :: Pylons', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', ] if sys.version_info < (2, 6): requirements.append("simplejson") @@ -34,14 +35,19 @@ data_files = [] #additional files that goes into package itself package_data = {'rhodecode': ['i18n/*/LC_MESSAGES/*.mo', ], } -description = 'Mercurial repository serving and browsing app' +description = ('Mercurial repository browser/management with ' + 'build in push/pull server and full text search') #long description try: readme_file = 'README.rst' - long_description = open(readme_file).read() + changelog_file = 'docs/changelog.rst' + long_description = open(readme_file).read() + '/n/n' + \ + open(changelog_file).read() + except IOError, err: sys.stderr.write("[WARNING] Cannot find file specified as " - "long_description (%s)\n skipping that file" % readme_file) + "long_description (%s)\n or changelog (%s) skipping that file" \ + % (readme_file, changelog_file)) long_description = description @@ -59,7 +65,7 @@ setup( version=get_version(), description=description, long_description=long_description, - keywords='rhodiumcode mercurial web hgwebdir replacement serving hgweb rhodecode', + keywords='rhodiumcode mercurial web hgwebdir gitweb git replacement serving hgweb rhodecode', license='BSD', author='Marcin Kuzminski', author_email='marcin@python-works.com', @@ -84,5 +90,14 @@ setup( [paste.app_install] main = pylons.util:PylonsInstaller + + [paste.global_paster_command] + make-index = rhodecode.lib.indexers:MakeIndex + upgrade-db = rhodecode.lib.dbmigrate:UpgradeDb + celeryd=rhodecode.lib.celerypylons.commands:CeleryDaemonCommand + celerybeat=rhodecode.lib.celerypylons.commands:CeleryBeatCommand + camqadm=rhodecode.lib.celerypylons.commands:CAMQPAdminCommand + celeryev=rhodecode.lib.celerypylons.commands:CeleryEventCommand + """, ) diff --git a/test.ini b/test.ini --- a/test.ini +++ b/test.ini @@ -43,6 +43,35 @@ full_stack = true static_files = true lang=en cache_dir = %(here)s/data +index_dir = /tmp/index +cut_off_limit = 256000 + +#################################### +### CELERY CONFIG #### +#################################### +use_celery = false +broker.host = localhost +broker.vhost = rabbitmqhost +broker.port = 5672 +broker.user = rabbitmq +broker.password = qweqwe + +celery.imports = rhodecode.lib.celerylib.tasks + +celery.result.backend = amqp +celery.result.dburi = amqp:// +celery.result.serialier = json + +#celery.send.task.error.emails = true +#celery.amqp.task.result.expires = 18000 + +celeryd.concurrency = 2 +#celeryd.log.file = celeryd.log +celeryd.log.level = debug +celeryd.max.tasks.per.child = 3 + +#tasks will never be sent to the queue, but executed locally instead. +celery.always.eager = false #################################### ### BEAKER CACHE ####

${_('date')}${_('revision')}${_('name')}${_('date')}${_('name')}${_('author')}${_('revision')} ${_('links')}
${h.age(tag[1]._ctx.date())}r${tag[1].revision}:${tag[1].short_id} - - ${h.link_to(tag[0], - h.url('changeset_home',repo_name=c.repo_name,revision=tag[1].short_id))} - -
+ ${tag[1].date} + + + ${h.link_to(tag[0], + h.url('changeset_home',repo_name=c.repo_name,revision=tag[1].raw_id))} + + ${h.person(tag[1].author)}r${tag[1].revision}:${h.short_id(tag[1].raw_id)} - ${h.link_to(_('changeset'),h.url('changeset_home',repo_name=c.repo_name,revision=tag[1].short_id))} + ${h.link_to(_('changeset'),h.url('changeset_home',repo_name=c.repo_name,revision=tag[1].raw_id))} | - ${h.link_to(_('files'),h.url('files_home',repo_name=c.repo_name,revision=tag[1].short_id))} + ${h.link_to(_('files'),h.url('files_home',repo_name=c.repo_name,revision=tag[1].raw_id))}