Building my photos web site with CubicWeb part IV: let's make it more user friendly
Step 0: updating code to CubicWeb 3.9 / cubicweb-file 1.9
CubicWeb 3.9 brings several improvements that we'll want to use, and the 1.9 version of the file cube has a major change: the Image type has been dropped in favor of an IImage adapter that makes code globally much cleaner (although this is not directly visible here). So the first thing to do is to upgrade our cube to the 3.9 API. As CubicWeb releases are mostly backward compatible, this is not mandatory but it's easier to follow changes as they come than having a huge upgrade to do at some point. Also, this remove deprecation warnings which are a bit tedious...
Since we only have very few lines of code, this step is pretty simple. Actually the main thing we have to do is to upgrade our schema, to remove occurrences of the Image type or replace them by the File type. Here is the (striped) diff:
class comments(RelationDefinition):
subject = 'Comment'
- object = ('File', 'Image')
+ object = 'File'
cardinality = '1*'
composite = 'object'
class tags(RelationDefinition):
subject = 'Tag'
- object = ('File', 'Image')
+ object = 'File'
class displayed_on(RelationDefinition):
subject = 'Person'
- object = 'Image'
+ object = 'File'
class situated_in(RelationDefinition):
- subject = 'Image'
+ subject = 'File'
object = 'Zone'
class filed_under(RelationDefinition):
- subject = ('File', 'Image')
+ subject = 'File'
object = 'Folder'
class visibility(RelationDefinition):
- subject = ('Folder', 'File', 'Image', 'Comment')
+ subject = ('Folder', 'File', 'Comment')
object = 'String'
constraints = [StaticVocabularyConstraint(('public', 'authenticated',
'restricted', 'parent'))]
class may_be_readen_by(RelationDefinition):
- subject = ('Folder', 'File', 'Image', 'Comment',)
+ subject = ('Folder', 'File', 'Comment',)
object = 'CWUser'
-from cubes.file.schema import File, Image
+from cubes.file.schema import File
File.__permissions__ = VISIBILITY_PERMISSIONS
-Image.__permissions__ = VISIBILITY_PERMISSIONS
Now, let's set the dependency in the pkginfo file. As 3.8 simplifies this file, we can merge depends_cubes (as introduced in the first blog of this series) with depends to get the following result:
__depends__ = \{'cubicweb': '>= 3.9.0',
'cubicweb-file': '>= 1.9.0',
'cubicweb-folder': None,
'cubicweb-person': None,
'cubicweb-zone': None,
'cubicweb-comment': None,
'cubicweb-tag': None,
\}
If your cube is packaged for debian, it's a good idea to update the debian/control file at the same time, so you won't forget it.
That's it for the API update, CubicWeb and cubicweb-file will handle other stuff for us. Easy, no?
We can now start some more fun stuff...
Step 1: let's improve site's usability for our visitors
The first thing I've noticed is that people to whom I send links to photos with some login/password authentication get lost, because they don't grasp they have to login by clicking on the 'authenticate' link. That's probably because they only get a 404 when trying to access an unauthorized folder, and the site doesn't make clear that 1. you're not authenticated, 2. you could get more content by authenticating yourself.
So, to improve this situation, I decided that I should:
- make a login box appears for anonymous, so they see at a first glance a place to put the login / password information I provided
- customize the 404 page, proposing to login to anonymous.
Here is the code, samples from my cube's views.py file:
from cubicweb.selectors import is_instance
from cubicweb.web import box
from cubicweb.web.views import basetemplates, error
class FourOhFour(error.FourOhFour):
__select__ = error.FourOhFour.__select__ & anonymous_user()
def call(self):
self.w(u"<h1>%s</h1>" % self._cw._('this resource does not exist'))
self.w(u"<p>%s</p>" % self._cw._('have you tried to login?'))
class LoginBox(box.BoxTemplate, basetemplates.LogFormView):
"""display a box containing links to all startup views"""
__regid__ = 'sytweb.loginbox'
__select__ = box.BoxTemplate.__select__ & anonymous_user()
title = _('Authenticate yourself')
order = 70
def call(self, **kwargs):
self.w(u'<div class="sideBoxTitle"><span>%s</span></div>' % self.title)
self.w(u'<div class="sideBox"><div class="sideBoxBody">')
self.login_form('loginBox')
self.w(u'</div></div>')
The first class provides a new specific implementation of the default page you get on a 404 error, to display an explicit message for anonymous users.
Note
Thanks to the selection mechanism, it will be selected for anonymous users, since the additional anonymous_user() selector gives it a higher score than the default, and not for authenticated since this selector will return 0 otherwise (hence the object won't be selectable).
The second class defines a simple box, that will be displayed by default with boxes in the left column, thanks to default box.BoxTemplate'selector. The HTML is written to match default CubicWeb boxes style. To get the actual login form, we inherit from the LogFormView view which provides a login_form method (handling some stuff under the cover for us, hence the multiple inheritance), that we simply have to call to get the form's HTML.
Step 2: providing a custom index page
Another thing we can easily do to improve the site is... A nicer index page (e.g. the first page you get when accessing the web site)! The default one is quite intimidating (that should change in a near future). I will provide a much simpler index page that simply list available folders (e.g. photo albums in that site).
from cubicweb.web.views import startup
class IndexView(startup.IndexView):
def call(self, **kwargs):
self.w(u'<div>\n')
if self._cw.cnx.anonymous_connection:
self.w(u'<h4>%s</h4>\n' % self._cw._('Public Albums'))
else:
self.w(u'<h4>%s</h4>\n' % self._cw._('Albums for %s') % self._cw.user.login)
self._cw.vreg['views'].select('tree', self._cw).render(w=self.w)
self.w(u'</div>\n')
def registration_callback(vreg):
vreg.register_all(globals().values(), __name__, (IndexView,))
vreg.register_and_replace(IndexView, startup.IndexView)
As you can see, we override the default index view found in cubicweb.web.views.startup, getting back nothing but its identifier and selector since we override the top level view's call method.
Note
In that case, we want our index view to replace the existing one. We implement the registration_callback function, in which we code a registeration of everything in the module but our IndexView, then we register it instead of the former index view.
Also, we added a title that tries to make it more evident that the visitor is authenticated, or not. Hopefully people will get it now!
Step 4: preparing the release and migrating the instance
Now that greatly enhanced our cube, it's time to release it and to upgrade production site. I'll probably detail that process later, but I currently simply transfer the new code to the server running the web site.
However, there's some commands to get things done properly... First, as I've added some translatable string, I have to run:
$ cubicweb-ctl i18ncube sytweb
To update the cube's gettext catalogs (the '.po' files under the cube's i18n directory). Once the above command is executed, I'll then update translations.
To see if everything is ok on my test instance, I do:
$ cubicweb-ctl i18ninstance sytweb $ cubicweb-ctl start -D sytweb
The first command compile i18n catalogs (e.g. generates '.mo' files) for my test instance. The second command starts it in debug mode, so I can open my browser and navigate through the web site to see if everything is ok...
Note
In the 'cubicweb-ctl i18ncube' command, sytweb refers to the cube, while in the two other, it refers to the instance (if you can't see the difference, reread CubicWeb's concept chapter !).
Once I've checked it's ok, I simply have to bump the version number in the pkginfo module to trigger a migration once I'll have updated the code on the production site. I can check the migration is also going fine, by first restoring a dump from the production site, then upgrading my test instance.
To generate a dump from the production site:
$ cubicweb-ctl db-dump sytweb pg_dump -Fc --username=syt --no-owner --file /home/syt/etc/cubicweb.d/sytweb/backup/tmpYIN0YI/system sytweb -> backup file /home/syt/etc/cubicweb.d/sytweb/backup/sytweb-2010-07-13_10-22-40.tar.gz
I can now get back the dump file ('sytweb-2010-07-13_10-22-40.tar.gz') to my test machine (using scp for instance) to restore it and start migration:
$ cubicweb-ctl db-restore sytweb sytweb-2010-07-13_10-22-40.tar.gz $ cubicweb-ctl upgrade sytweb
You'll have to answer some questions, as we've seen in an earlier post.
Now that everything is tested, I can transfer the new code to the production server, apt-get upgrade cubicweb 3.9 and its dependencies, and eventually upgrade the production instance.
Conclusion
This is a somewhat long post that starts showing you the way CubicWeb provides a highly configurable user interface, as well as powerful and reusable components. And there are a lot of others like those!
So see you next time for part V, where we'll probably want to do more ui stuff!