Real live migration case
| Status: | Contributed, Work in progress |
|---|
Here is how to upgrade a TurboGears 1.1 project to TurboGears 2.0
Outline
These are the steps we'll follow
- create a working environment of the tg1 project
- install tg2 on that environment
- upgrade the project structure
- upgrade the templates
- upgrade the model
- upgrading the controller
- run under tg2
- make it so that it will run on tg1 (maybe)
Use case
We'll use the excelent SpammCan pastebin-like app by Christopher Arndt
Project Structure
- spammcan [1]
- SpammCan [2]
- tg1-2 [3]
- SpammCan [4]
- simply a wrapper directory
- An svn checkout of the code from svn://chrisarndt.de/projects/SpammCan/trunk
- a virtualenv as in http://turbogears.org/2.0/docs/main/DownloadInstall.html#setting-up-a-virtual-environment
- This will be different things, first a tg2 quickstart then a link to our original code in [1]
Setting the Environment
I actually cheated and used bazaar but that is because I found this as a great oportunity to test bzr-svn
So lets start with
$ cd spammcan $ bzr branch svn://chrisarndt.de/projects/SpammCan/trunk SpammCan $ cd tg1-2 $ source bin/acticate $ ln -s ../SpammCan/ . $ cd SpammCan $ python setup.py develop
Testing our instalation
- Now lets see if everything runs as expected::
- $ bootstrap-spammcan $ start-spammcan $ firefox http://0.0.0.0:8080/
That should take you to http://0.0.0.0:8080/new and everything should work, go ahead and paste some spamm!
Installing TurboGears 2
- So back to work, shut down cherrypy and let's install TG2 into the virtualenv::
- $ easy_install -i http://www.turbogears.org/2.0/downloads/current/index tg.devtools
- you may have to run::
- $ easy_install -U setuptools
Upgrading our code structure
- The steps we are going to follow are:
- create a tg2 project by the same name
- move all the files that are new
- move all the files that have changed place or name
- do changes to the files (imports and such)
- Do complex changes to the files
Now we will temporally delete our SpammCan project from the virtualenv and create a tg2 project by the same name, The reason we are doing this is to preserve the package name:
$ rm lib/python2.5/site-packages/SpammCan.egg-link $ vi lib/python2.5/site-packages/easy-install.pth
In the last file we need to delete the line that contains SpammCan on it, it's a little tricky but unfortunaly setuptools doesn't have a command for this. Anyway if it did it will delete all our tg1 dependencies which we don't want.
Now On to the TG2 project:
$ cd .. $ rm SpammCan $ paster quickstart Enter project name: SpammCan Enter package name [spammcan]: Do you need authentication and authorization in this project? [yes]
We'll reply yes to auth even if that isn't used fully in SpammCan to provide a more complete example.
Upgrading the files
I'll use a very nice diff/merge tool for linux called meld, so you can diff the trees, on windows you could use winmerge
We'll diff so our original code is on the left and the tg2 stub is on the right:
$ meld ../SpammCan/ SpammCan/
Files to ignore
- the .bzr, .svn directories
- egg-info dir as this is rebuild by setuptools
Files to copy without changes (new files)
- ez_setup , this will provide a bootstrap of your tg project
- config/environment.py
- config/middleware.py
- i18n (new i18n package)
- lib (a common place to but generic code)
- tests/__init__.py (empty in tg1)
- websetup.py
- README.TXT
- anything still revelant to your app from the templates directory (debug.html is interesting)
Files that changed names or location
- tests/test_controllers.py -> tests/functional
- tests/test_model.py -> tests/test_models.py
- static -> public
You should rename your static dir public and diff again so you could take the advantage of the new icon set:
$ cp ../SpammCan/spammcan/static/ ../SpammCan/spammcan/public -r $ meld ../SpammCan/spammcan/public/ SpammCan/spammcan/public/ .. note :: We need to upgrade the TG2 quickstart with the new CSS layout from 1.1 and then add a section here on why it's so great
Depending on your TG1 quickstart project (which is mostly everyone as tgbig was a big mistery) you need to upgrade your model.py and controllers.py files into packages, both packages are totally flexible but this is the recommended setup.
$ cd ../SpammCan/spammcan $ mkdir controllers model
- model now contains 3 files controller contrains 5 files you should copy all those over::
- $ meld . ../../tg1-2/SpammCan/spammcan/
Files that changed completely
- For now we'll just copy these over
- config/app_cfg.py
- development.ini
- test.ini
Special attention
- These two are tricky and you should do a merge of their content which is outside the scope of this tutorial
- setup.cfg (if you havent changed this you could copy the TG2 file below the tg1 file)
- setup.py (copy the first 6 lines of tg2's if you want ez_setup to work,
I suggest you copy the tg2 setup function to your setup.py I have renamed it setup_tg2 with the following little hack:
def setup_tg2(**kargs):
pass
setup_tg2(...
)- We are now done with the files we can delete the TG2 quickstart and replace it with our original project::
- $ cd ../../tg1-2/ $ rm -r SpammCan/ $ ln -s ../SpammCan/ .
Upgrading the imports
- And lets see if everything still runs::
- $ cd SpammCan $ python setup.py develop $ start-spammcan
Sadly this probably didn't work you need to fix your model and controller imports as the package takes precedence over the file. You can accomplish this by copying the file to the directory and adding the following imports:
$ cp model.py model/tg1.py
$ cp controllers.py controllers/tg1.py
$ vi controllers.__init__.py
from controllers import *
$ vi model.__init__.py
from model import *Although you will probably want to give them better names
- Now lets try again and our TG1.1 app should still work::
- $ start-spammcan
Upgrading the template
This step is trivial if you are using genshi you just need to copy over the files. And your done
The only change you should probably do is master.html, which is documented below
Upgrading the model
We are going to split our model.py into two files auth.py and spamm.py, if you haven't done any changes to the auth code you can skip the auth.py part as that is 100% backwards compatible.
As for the rest of the code if you are already using SQLAlchemy you should just change some imports
turbogears.database.* doesn't exists anymore so you will have to import SQLAlchemy directly except for the metadata defined in your model.__init__.py
again we can use model.template as a base for our diff
turbogears.database.mapper -> sqlalchemy.orm.mapper turbogears.database.metadata -> p.model.metadata
TODO add a comment about SAClass.query
Upgrading the controller
We are going to try to keep things running for both TG1 and TG2 therefore all our code imports will happen in __init__.py and we could switch this to tg1 or tg2 controllers
- The process here will be:
- copy the controller to a tg2 file (ticket #2075 provides a good template),
- run paster serve , see what fails
- fix the imports
- run paster serve
- fix other code
List of imports that changed
- turbogears.controllers -> p.lib.base.BaseController
- turbogears.expose -> tg.expose
- turbogears.flash -> tg.flash
- turbogears.redirect -> tg.redirect
- turbogears.validate -> tg.validate
- turbogears.url -> tg.url
- turbogears.validators -> formencode.validators
- turbogears.config -> turbogears.configuration
- cherrypy.request -> tg.request
- p.model.session -> p.model.DBSession
List of components that changed
- @error_handler isn't a decorator anymore but a parameter to validate
- identity -> repoze.who/what *
- cherrypy.request -> tg.request
- cherrypy.NotFound -> abort(404)
- SAclass.query -> p.model.DBSession.query *
for the running part we could trick setuptools like this:
def setup_tg2(**kargs): pass def setup_tg1(**kargs): pass #setup_tg2 = setup setup_tg1 = setup setup_tg2(...) setup_tg1(...)
SpammCan Specific
By this point any standard TG project should work, below several features used by SpammCan and their ports, therefore every section below is optional
SQLite Database File
devdata.sqlite becomes devdata.db, this is simple but could confuse you if you never changed the default dburi
tg_css,tg_js,static_filter_dir
Have all been removed, use tg.url
static_filter_dir is now config.paths.static
variable providers
In TG1 we used to have a trick where we will stick aditional variables to all templates by providing a function and appending it's results to the 'variable_providers' for example:
.. code-block :: from turbogears.view import variable_providers def add_global_tmpl_vars(vars): vars['motd'] = message_of_the_day() vars['abs_url'] = absolute_url vars['tg_version'] = tg_version variable_providers.append(add_global_tmpl_vars)
in TG2 this behavior as been standarized in a config call, you will need a function that returns a dict or a Bunch and then add a line to your app_cfg.py for example in lib/helpers.py:
.. code-block ::
def add_global_tmpl_vars():
return dict(
motd = message_of_the_day(),
abs_url = absolute_url,
tg_version = tg_version
)- in config/app_cfg.py::
- from spammcan.lib import app_globals, helpers (note this line is the default import) base_config.variable_provider = helpers.add_global_tmpl_vars
Database Initialization Script
- The bootstrap module is replaced with websetup.py which should be called as::
- $ paster setup-app development.ini
util.py -> lib/helpers.py
in general helpers.py is a set of functions you could use anywhere on your code you could optionally add another module to lib, but helpers has the advantage of being autopopulated to the templates as h.function()
redirect no longer supports a list (see #2080)
In TG1 you could do this redirect(['/paste', guid]) and it will redirect to /paste/guid where guid is the variable of course
cherrpy.request -> webob.request
TODO see:http://pythonpaste.org/webob/differences.html