cubicweb_web - And how to use it with CubicWeb
Contents
Tutorials#
Here are a few tutorials with different difficulty levels.
Beginners will want to start with the blog building tutorial giving a short introduction to the basic concepts. Then the photo gallery construction tutorial highlights more advanced concepts such as unit tests, security settings and migration scripts.
The other tutorials cover specific topics you can learn about when you understand the basics.
Building a simple blog with CubicWeb#
CubicWeb is a semantic web application framework which favors reuse and object-oriented designs.
This tutorial is designed to help you make your very first steps with CubicWeb. It will guide you through basic concepts such as:
getting an application running by using existing components
discovering the default user interface
extending and customizing the look and feel of that application
More advanced concepts are covered in Building a photo gallery with CubicWeb.
Some vocabulary#
CubicWeb comes with a few words of vocabulary that you should know to understand what we’re talking about. To follow this tutorial, you should at least know that:
a cube is a component that usually includes a model defining some data types and a set of views to display them. A cube can be built by assembling other cubes;
an instance is a specific installation of one or more cubes and includes configuration files, a web server and a database.
Reading The Core Concepts of CubicWeb for more vocabulary will be required at some point.
Now, let’s start the hot stuff!
Get a blog running in five minutes!#
First choose and follow the installation method of your choice.
Once you have CubicWeb setup, install the blog cube using the following command:
pip install cubicweb-blog
Then you can create and initialize your blog instance:
cubicweb-ctl create blog myblog
Here the blog
argument tells the command to use the blog
cube as a base for your instance named myblog
.
Note
If you get a permission error of the kind OSError: [Errno 13] Permission denied: '/etc/cubicweb.d/myblog'
, read the next section.
This command will ask you a series of question. The first one is about the database engine to use (SQLite or PostgreSQL). For this tutorial, we will use SQLite as it is easier to setup and does not need a database server. In production environments, PostgreSQL is recommended as it offers better performances. More information on database configuration can be found here.
The command will also create a user used to manage your instance, for which you will be asked to give a name and password.
You can leave the remaining questions to their default by simply pressing Enter
.
Note
If you get errors during installation such as:
while handling language es: [Errno 2] No such file or directory: 'msgcat': 'msgcat'
while handling language en: [Errno 2] No such file or directory: 'msgcat': 'msgcat'
while handling language fr: [Errno 2] No such file or directory: 'msgcat': 'msgcat'
This means you are missing the gettext
dependency. To fix this, follow the instructions in the section Installing Dependencies.
Then either restart the installation process or run cubicweb-ctl i18ncubicweb && cubicweb-ctl i18ncube blog
after installation. More information in Internationalization.
Then you need to tell CubicWeb your instance is going to run on the localhost by editing ~/etc/cubicweb.d/myblog/all-in-one.conf
.
In this file under the [MAIN]
section, replace the line #host=` by `host=localhost
.
Once this process is complete (including database initialisation), you can start your instance by using:
cubicweb-ctl pyramid -D myblog
The -D
option activates the debugging mode. Removing it will launch the instance
as a daemon in the background.
This is it, your blog is functional and running at http://localhost:8080!
About file system permissions#
Unless you installed from source, the above commands will initialize your instance as a regular user in your home directory (under ~/etc/cubicweb.d/). If you installed from source, your instance will be created in system directories and thus will require root privileges. To change this behavior, please have a look at the Resource mode section.
Instance parameters#
If you would like to change database parameters such as the database host or the
user name used to connect to the database, edit the sources
file located in the
/etc/cubicweb.d/myblog
directory.
Then relaunch the database creation:
cubicweb-ctl db-create myblog
Other parameters, like web server or emails parameters, can be modified in the
/etc/cubicweb.d/myblog/all-in-one.conf
file (or ~/etc/cubicweb.d/myblog/all-in-one.conf
depending on your configuration.)
You’ll have to restart the instance after modification in one of those files.
Discovering the web interface#
You can now access your web instance to create blogs and post messages by visiting the URL http://localhost:8080.
By default, anonymous access is disabled, so a login form will appear.

If you asked to allow anonymous access when initializing the instance, click on the
‘login’ link in the top right hand corner. To login, you need to use the admin
account you specified at the time you initialized the database with
cubicweb-ctl create
.

Once authenticated, you can start playing with your instance. You will notice the index page has changed compared to
the anonymous access view. There are more entries in the Manage section and and some new [+]
buttons have appeared
next to the entities. These allow you to edit and add new entries in the database.

Note
If you find untranslated strings such as blog.latest_blogs
in the sidebar:

This means you are missing the gettext
dependency. To fix this, follow the instructions in the section Installing Dependencies.
Then either restart the installation process or run cubicweb-ctl i18ncubicweb && cubicweb-ctl i18ncube blog
after installation. More information in Internationalization.
Minimal configuration#
Before creating entities, let’s change the unset title
string in the header.
This string is set by a CubicWeb system properties and represents the site’s title. To modify it,
click on the site configuration
link in the Manage
section.
This will open a new page with different categories. You will find the site’s title in the ui
section.
Simply set it to the desired value and click the ‘button_ok’ button.

You should see a changes applied
message in green at the top of the section. You can now go back to the
index page by clicking on the CubicWeb logo in the upper left-hand corner.
You will much likely still see unset title
at this point. This is because by
default the index page is cached for performance reasons. Force a refresh of the page (Ctrl-R
in Firefox) and you should now see the title you entered.
Adding entities#
The blog
cube defines several entity types. For example, the Blog
entity is a
container for a BlogEntry
(i.e. posts) on a particular topic. We can get a
graphical view of the schema by clicking on the data model schema
link in the Manage
section of the index page:

Note
If you get the error FileNotFoundError: [Errno 2] File not found: dot: 'dot'
when accessing the page, this means
you are missing the package graphviz
. To fix this, follow the instructions in the section Installing Dependencies.

Notice that like most other things we will see in this tutorial,
this schema is generated by the framework according to the application’s model. In our
case the model is defined by the blog
cube.
Now let’s create a few of those entities.
Adding a blog#
Clicking on the [+]
at the left of the Blog
link on the index page will open an HTML form to create a new blog.

For instance, call this new blog Tech-blog
and type in everything about
technology
as the description , then validate the form by clicking on
validate
. You will be redirected to the primary view of the newly created blog.

Adding a blog post#
There are several ways to add a blog entry. The simplest is to click on the add
blog entry
link in the actions box on the left while viewing the blog you just created.
You will then see a form to create a post, with a blog entry of
field preset
to the blog you are coming from. Enter a title, some content, click the validate
button and you’re done. You will be redirected to the blog’s primary view, though you
now see that it contains the blog post you have just created.

Notice how some new items appeared in the left column.
You can achieve the same result by clicking on the [+]
at the left of the Blog entry
link on
the index page. Since there is no context information, the blog entry of
selector will not be preset to a blog
if you have more than one.
If you click on the modify
link in the action box, you will be taken back to
the form to edit the entity you just created. But the form will now
have another section with a combo-box entitled add relation
providing
a generic way to edit relations. Choose the relation you want to add and a second combo box
will appear where you can pick existing entities. If there are too many
of them, you will be offered to navigate to the target entity.
This will open a new page and you will be taken back to your form once
you have selected an entity.

This combo-box cannot appear until the entity is actually created, explaining why you
could not see it at creation time using the first form. Another way to show this combo-box is
to hit apply
instead of validate
to create the entity without closing the form.
About UI auto-adaptation#
One of the things making CubicWeb different from other frameworks is its automatic user interface adapting itself according to the data being displayed. Let’s see an example.
If you go back to the home page and click on the Blog
link, you will be redirected
to the blog’s primary view as we have seen earlier. Now add another
blog, go back to the index page, and click again on this link. You will see
a very different view (namely the list view).

In the first case the framework chose to use the primary view since there was only one entity in the data to be displayed. Now that there are two entities, the list view is more appropriate and hence is being used.
There are various other places where CubicWeb adapts to display data in the best way, the main being provided by the view selection mechanism that will be detailed later.
Digging deeper#
By following the principles explained above you should now be able to create new users for your application and to configure your instance. You will notice that the index page lists a lot of types we did not talk know about. Most are built-in types provided by the framework to make the whole system work. You may ignore them in a first time and discover them as time goes.
One thing that is worth playing with is the search box. It may be used in various ways, from simple full text search to advanced queries using the RQL syntax .
Customizing your application#
Usually you won’t get enough by assembling cubes out-of-the-box. You will want to customize them to get personal look and feel, add your own data model and so on. Or maybe start from scratch?
So let’s get a bit deeper and start coding our own cube. In our case, we want to customize the blog we created to add more features to it.
Creating your own cube#
Once your CubicWeb development environment is set up, you can create a new cube:
cubicweb-ctl newcube mycube
This will create a a directory named cubicweb-mycube
reflecting the
structure described in Standard structure for a cube.
To install your new cube on the virtual environment created previously, run
the following command in cubicweb-mycube
directory:
pip install -e .
All cubicweb-ctl commands are described in details in cubicweb-ctl tool.
Cube metadata#
The folder cubicweb_mycube/
contains the actual code and metadata for your cube.
In this folder, a simple set of metadata about your cube are stored in the __pkginfo__.py
file. In our case, we want to extend the blog cube, so we have to tell that our
cube depends on this cube by modifying the __depends__
dictionary in that
file:
__depends__ = {"cubicweb": ">= 3.35.0", "cubicweb-blog": None}
where None
means we do not depend on a particular version of the cube.
Extending the data model#
The data model or schema is the core of your CubicWeb application. It defines
the type of content your application will handle. It is defined in the file
schema.py
of the cube.
Defining our model#
Let’s say we want a new entity type named Community with a name and a description. A Community will hold several blogs.
We can edit the schema.py
as follows:
from yams.buildobjs import EntityType, RelationDefinition, String, RichString
class Community(EntityType):
name = String(maxsize=50, required=True)
description = RichString()
class community_blog(RelationDefinition):
subject = 'Community'
object = 'Blog'
cardinality = '*?'
composite = 'subject'
The import from the yams
package provides necessary classes to build the schema.
This file defines the following:
a Community has a name and a description as attributes
the name is a string which is required and cannot be longer than 50 characters
the description is an unconstrained string and may contains rich content such as HTML or Restructured text.
a Community may be linked to a Blog using the community_blog relation
*
means a community may be linked from 0 to N blog,?
means a blog may be linked to 0 to 1 community. For completeness, you can also use+
for 1 to N, and1
for a single mandatory relation (e.g. one to one);this is a composite relation where Community (e.g. the subject of the relation) is the composite. That means that if you delete a community, its blog will be deleted as well.
Of course, there are a lot of other data types and relations such as constraints, permissions, etc, that may be defined in the schema but those will not be covered in this tutorial.
Notice that our schema refers to the Blog entity type which is not defined here. But we know this type is available since we depend on the blog cube defining it.
Applying changes from the model into our instance#
The problem is that we created an instance using the blog
cube, not our
mycube cube. If we do not do anything there is no way we’ll see anything
changing in the myblog
instance.
As we do not have any really valuable data in the instance, an easy way would be to trash it and recreated it.
First stop the running instance by pressing Ctrl-C
in the terminal running the server in debug mode.
Then run the following commands:
cubicweb-ctl delete myblog
cubicweb-ctl create mycube myblog
cubicweb-ctl pyramid -D myblog
Another way is to add our cube to the instance using the cubicweb-ctl shell
facility. It is a python shell connected to the instance with some special
commands available to manipulate it (the same as you’ll have in migration
scripts, which are not covered in this tutorial). In that case, we are interested
in the add_cube
command. First stop the instance by pressing Ctrl-C
in the terminal running the server in debug mode
and enter the shell using the following command:
cubicweb-ctl shell myblog
Then in the python shell, type the add_cube
command:
add_cube('mycube')
Press Ctrl-D
to exit then restart your instance:
cubicweb-ctl pyramid -D myblog
The add_cube
command is enough since it automatically updates our
application to the cube’s schema. There are plenty of other migration
commands of a more finer grain. They are described in Migration
If you take another look at the schema on your instance, you will see that changes to the data model have actually been applied (meaning database schema updates and all necessary actions have been done).

If you follow the Site information
link in the home page, you will also see that the
instance is using blog and mycube cubes (sioc is a dependency of the blog cube).

You can now add some communities and link them to a blog. You will see that the framework provides default views for this entity type (we have not yet defined any view for it!), and also that the blog primary view will show the community it is linked to if any. All this thanks to the model driven interface provided by the framework.
We will now see how to redefine each of them according to your needs and preferences.
Defining your views#
CubicWeb provides a lot of standard views in the directory
cubicweb/web/views/
. We already talked about primary and list views,
which are views applying to one or more entities.
A view is defined by a python class which includes:
an identifier: all objects used to build the user interface in CubicWeb are recorded in a registry and this identifier will be used as a key in that registry to store the view. There may be multiple views for the same identifier.
a selector, which is a kind of filter telling how well a view suits to a particular context. When looking for a particular view (e.g. given an identifier), CubicWeb computes for each available view with that identifier a score which is returned by the selector. Then the view with the highest score is used. The standard library of predicates is in
cubicweb.predicates
.
A view has a set of methods inherited from the cubicweb.view.View
class,
though you do not usually derive directly from this class but from one of its more
specific child class.
Last but not least, CubicWeb provides a set of default views accepting any kind of entities.
To illustrate this, we will create a community as we already have done for other entity types through the index page. You will get a screen similar to this:

Changing the layout of the application#
The layout is the general organization of the pages in the website. Views generating
the layout are sometimes referred to as templates. They are implemented by the
framework in the module cubicweb.web.views.basetemplates
. By overriding
classes in this module, you can customize whatever part you wish of the default
layout.
CubicWeb provides many other ways to customize the interface thanks to actions and components (which you can individually (de)activate, control their location, customize their look…) as well as “simple” CSS customization. You should first try to achieve your goal using such fine grained parametrization rather then overriding a whole template, which usually embeds customisation access points that you may loose in the process.
But for the sake of example, let’s say we want to change the generic page
footer. We can simply add in the file cubicweb_mycube/views.py
the code below:
from cubicweb.web.views import basetemplates
class MyHTMLPageFooter(basetemplates.HTMLPageFooter):
def footer_content(self):
self.w(u'This website has been created with <a href="http://cubicweb.org">CubicWeb</a>.')
def registration_callback(vreg):
vreg.register_all(globals().values(), __name__, (MyHTMLPageFooter,))
vreg.register_and_replace(MyHTMLPageFooter, basetemplates.HTMLPageFooter)
Our class inherits from the default page footer to ease getting things right, but this is not mandatory.
When we want to write something to the output stream, we simply call self.w, which must be passed a unicode string.
Since both
HTMLPageFooter
andMyHTMLPageFooter
have the same selector, hence the same score the framework would not be able to choose which footer to use. In this case we want our footer to replace the default one, so we have to define aregistration_callback()
function to control object registration. The first instruction tells to register everything in the module but theMyHTMLPageFooter
class, then the second to register it instead ofHTMLPageFooter
. Without this function, everything in the module is registered blindly.
Note
When a view is modified while running in debug mode, it is not required to restart the instance server. Save the Python file and reload the page in your web browser to view the changes.
You will now see this simple footer on every page of the website.
Primary view customization#
The primary view (i.e. any view with the identifier set to primary) is the one used to display all the information about a single entity. The standard primary view is one of the most sophisticated views of all. It has several customisation points, but its power comes with uicfg allowing you to control it without having to subclass it.
However this is a bit off-topic for this first tutorial. Let’s say we simply want a
custom primary view for the Community
entity type, using directly the view
interface without trying to benefit from the default implementation (you should
do that though if you’re rewriting reusable cubes; everything is described in more
details in The Primary View).
here is the code that we will put in the file cubicweb_mycube/views.py
of our cube:
from cubicweb.predicates import is_instance
from cubicweb.web.views import primary
class CommunityPrimaryView(primary.PrimaryView):
__select__ = is_instance('Community')
def cell_call(self, row, col):
entity = self.cw_rset.get_entity(row, col)
self.w(u'<h1>Welcome to the "%s" community</h1>' % entity.printable_value('name'))
if entity.description:
self.w(u'<p>%s</p>' % entity.printable_value('description'))
What’s going on here?
Our class inherits from the default primary view, here mainly to get the correct view identifier, since we do not use any of its features.
We set on it a selector telling that it only applies when trying to display some entity of the Community type. This is enough to get an higher score than the default view for entities of this type.
A view that applies to an entity usually has to define the method
cell_call
as an entry point. This receives the argumentsrow
andcol
telling to which entity in the result set the view is applied. We can then get this entity from the result set (self.cw_rset
) by using theget_entity
method.To ease thing, we access our entity’s attribute to display using its
printable_value
method, which will handle formatting and escaping when necessary. As you can see, you can also access attributes by their name on the entity to get the raw value.
You can now reload the page of the community we just created and see the changes.

We have seen here a lot of thing you will have to deal with to write views in CubicWeb. The good news is that this is almost everything that is used to build higher level layers.
Note
As things get complicated and the volume of code in your cube increases, you can of course still split your views module into a python package with subpackages.
You can find more details about views and selectors in Principles.
Write entities to add logic in your data#
CubicWeb provides an ORM (Object-Relational Mapper) to programmatically manipulate
entities (just like the one we have fetched earlier by calling
get_entity
on a result set). By default, entity
types are instances of the AnyEntity
class, which holds a set of
predefined methods as well as properties automatically generated for
attributes/relations of the type it represents.
You can redefine each entity to provide additional methods or whatever you want to help you write your application. Customizing an entity requires that your entity:
inherits from
cubicweb.entities.AnyEntity
or any subclassdefines a
__regid__
linked to the corresponding data type of your schema
You may then want to add your own methods, override default implementation of some
method, etc… To do so, write this code in mycube/entities.py
:
from cubicweb.entities import AnyEntity, fetch_config
class Community(AnyEntity):
"""customized class for Community entities"""
__regid__ = 'Community'
fetch_attrs, cw_fetch_order = fetch_config(['name'])
def dc_title(self):
return self.name
def display_cw_logo(self):
return 'CubicWeb' in self.name
In this example:
we used the
fetch_config()
convenience function to tell which attributes should be prefetched by the ORM when looking for some related entities of this type, and how they should be orderedwe overrode the standard
dc_title()
method, used in various place in the interface to display the entity (though in this case the default implementation would have had the same result)we implemented here a method
display_cw_logo()
which tests if the community title contains CubicWeb. It can then be used when you are writing code involving Community entities in your views, hooks, etc. For instance, you can modify your previous views as follows:
class CommunityPrimaryView(primary.PrimaryView):
__select__ = is_instance('Community')
def cell_call(self, row, col):
entity = self.cw_rset.get_entity(row, col)
self.w(u'<h1>Welcome to the "%s" community</h1>' % entity.printable_value('name'))
if entity.display_cw_logo():
self.w(u'<img src="https://docs.cubicweb.org/_static/logo-cubicweb.svg"/>')
if entity.description:
self.w(u'<p>%s</p>' % entity.printable_value('description'))
Then each community whose description contains ‘CW’ is shown with the CubicWeb logo in front of it.
Note
As for view, you don’t have to restart your instance when modifying some entity classes while your server is running in debug mode, the code will be automatically reloaded.
Extending the application by using more cubes!#
One of the goals of the CubicWeb framework is to have truly reusable components. To do so they must behave nicely when plugged into the application and be easily customisable, from the data model to the user interface. Thanks to systems such as the selection mechanism and the choice to write views as python code, we can build our pages using true object oriented programming techniques to achieve this goal.
A library of standard cubes is available at the CubicWeb Forge
to address a lot of common problems such as manipulating files, people, todos, etc. In
our community blog case, we could be interested for instance in functionalities
provided by the comment and
tag cubes. comment
provides threaded
discussion functionalities and tag
a simple tag mechanism to classify content.
We will first modify our cube’s __pkginfo__.py
file to add those cubes as dependencies:
__depends__ = {'cubicweb': '>= 3.35.0',
'cubicweb-blog': None,
'cubicweb-comment': None,
'cubicweb-tag': None}
Now we will simply tell on which entity types we want to activate the comment
and tag
cubes by adding respectively the comments
and tags
relations on
them in our schema (schema.py
).
class comments(RelationDefinition):
subject = 'Comment'
object = 'BlogEntry'
cardinality = '1*'
composite = 'object'
class tags(RelationDefinition):
subject = 'Tag'
object = ('Community', 'BlogEntry')
In the above code we activated comments on BlogEntry
entities and tags on
both Community
and BlogEntry
. Various views from both comment
and tag
cubes will then be automatically displayed when one of those relations is supported.
Let’s install the cubes and synchronize the data model as we’ve done earlier. So first install the cubes:
pip install cubicweb-comment cubicweb-tag
Stop the instance by pressing Ctrl-C
in the terminal running the server in debug mode and enter the migration shell:
cubicweb-ctl shell myblog
Add the new cubes and exit with Ctrl-D
:
add_cubes(('comment', 'tag'))
Then restart the instance with cubicweb-ctl pyramid -D myblog
and open a blog entry:

As you can see, we now have a box displaying tags and a section proposing to add a comment and displaying existing one below the post. All this without changing anything in our views, thanks to the design of generic views provided by the framework. Though if we take a look at a community, we will not see the tags box! This is because by default this box tries to locate itself in the right column within the white frame, and this column is handled by the primary view we overrode. Let’s change our view to make it more extensible, by keeping both our custom rendering but also extension points provided by the default implementation.
Add the following code in cubicweb_mycube/views.py
:
class CommunityPrimaryView(primary.PrimaryView):
__select__ = is_instance('Community')
def render_entity_title(self, entity):
self.w(u'<h1>Welcome to the "%s" community</h1>' % entity.printable_value('name'))
def render_entity_attributes(self, entity):
if entity.display_cw_logo():
self.w(u'<img src="https://docs.cubicweb.org/_static/logo-cubicweb.svg"/>')
if entity.description:
self.w(u'<p>%s</p>' % entity.printable_value('description'))
By reloading the Community page, it will now appear properly:

You can control part of the interface independently from each others, piece by piece.
What’s next?#
In this tutorial we have seen you can build a web application in a few minutes simply by defining a data model. You get a working application which you can then customize without breaking your workflow. You can show results to customers right from the beginning to make the right decisions early in the process. This is important in agile development practices.
The next steps will be to discover hooks, security, data sources, digging deeper into view writing and interface customisation… Still a lot of fun stuff to discover! You will find more tutorials and howtos in the blog published on the CubicWeb.org website.
Building a photo gallery with CubicWeb#
Desired features#
basically a photo gallery
photo stored on the file system and displayed dynamically through a web interface
navigation through folder (album), tags, geographical zone, people on the picture… using facets
advanced security (not everyone can see everything). More on this later.
Cube creation and schema definition#
Step 1: creating a virtual environment#
Fisrt I need a python virtual environment with cubicweb:
python3 -m venv venv
source venv/bin/activate
pip install cubicweb
Step 2: creating a new cube for my web site#
One note about my development environment: I wanted to use the packaged version of CubicWeb and cubes while keeping my cube in the current directory, let’s say ~/src/cubes:
cd ~/src/cubes
CW_MODE=user
I can now create the cube which will hold custom code for this web site using:
cubicweb-ctl newcube sytweb
Enter a short description and this will create your new cube in the cubicweb-sytweb folder.
Step 3: pick building blocks into existing cubes#
Almost everything I want to handle in my web-site is somehow already modelized in existing cubes that I’ll extend for my need. So I’ll pick the following cubes:
folder, containing the Folder entity type, which will be used as both ‘album’ and a way to map file system folders. Entities are added to a given folder using the filed_under relation.
file, containing File entity type, gallery view, and a file system import utility.
person, containing the Person entity type plus some basic views.
comment, providing a full commenting system allowing one to comment entity types supporting the comments relation by adding a Comment entity.
tag, providing a full tagging system as an easy and powerful way to classify entities supporting the tags relation by linking the to Tag entities. This will allows navigation into a large number of picture.
Ok, now I’ll tell my cube requires all this by editing cubicweb-sytweb/cubicweb_sytweb/__pkginfo__.py
:
__depends__ = {'cubicweb': '>= 3.32.7', 'cubicweb-file': '>= 1.9.0', 'cubicweb-folder': '>= 1.1.0', 'cubicweb-person': '>= 1.2.0', 'cubicweb-comment': '>= 1.2.0', 'cubicweb-tag': '>= 1.2.0' }
Notice that you can express minimal version of the cube that should be used, None meaning whatever version available. All packages starting with ‘cubicweb-’ will be recognized as being cube, not bare python packages.
Now, I need to install all the dependencies:
cd cubicweb-sytweb
pip install -e .
pip install cubicweb
pip install psycopg2-binary # for postgresql
Step 4: glue everything together in my cube’s schema#
Put this code in cubicweb-sytweb/cubicweb_sytweb/schema.py
:
from yams.buildobjs import RelationDefinition
class comments(RelationDefinition):
subject = 'Comment'
object = 'File'
# a Comment can be on only one File
# but a File can have several comments
cardinality = '1*'
composite = 'object'
class tags(RelationDefinition):
subject = 'Tag'
object = 'File'
class filed_under(RelationDefinition):
subject = 'File'
object = 'Folder'
class displayed_on(RelationDefinition):
subject = 'Person'
object = 'File'
This schema:
allows to comment and tag on File entity type by adding the comments and tags relations. This should be all we’ve to do for this feature since the related cubes provide ‘pluggable section’ which are automatically displayed on the primary view of entity types supporting the relation.
adds a situated_in relation definition so that image entities can be geolocalized.
add a new relation displayed_on relation telling who can be seen on a picture.
This schema will probably have to evolve as time goes (for security handling at least), but since the possibility to let a schema evolve is one of CubicWeb’s features (and goals), we won’t worry about it for now and see that later when needed.
Step 5: creating the instance#
Now that I have a schema, I want to create an instance. To do so using this new ‘sytweb’ cube, I run:
cubicweb-ctl create sytweb sytweb_instance
For simplicity you should use the sqlite database, it won’t require configuration.
Don’t forget to say “yes” to the question: Allow anonymous access ? [y/N]:
Hint: if you get an error while the database is initialized, you can avoid having to answer the questions again by running:
cubicweb-ctl db-create sytweb_instance
This will use your already configured instance and start directly from the create database step, thus skipping questions asked by the ‘create’ command.
Once the instance and database are fully initialized, run
cubicweb-ctl pyramid -D sytweb_instance
to start the instance, check you can connect on it, etc… then go on http://localhost:8080 (or with another port if you’ve modified it)
Security, testing and migration#
This part will cover various topics:
configuring security
migrating existing instance
writing some unit tests
Here is the read
security model I want:
folders, files, images and comments should have one of the following visibility:
public
, everyone can see itauthenticated
, only authenticated users can see itrestricted
, only a subset of authenticated users can see it
managers (e.g. me) can see everything
only authenticated users can see people
everyone can see classifier entities, such as tag
Also:
unless explicitly specified, the visibility of an image should be the same as
its parent folder
* the visibility of a comment should be the same as the commented entity
* If there is no parent entity, the default visibility is authenticated
.
Regarding write security, that’s much easier:
anonymous can’t write anything
authenticated users can only add comment
managers will add the remaining stuff
Now, let’s implement that!
Proper security in CubicWeb is done at the schema level, so you don’t have to bother with it in views: users will only see what they can see automatically.
Step 1: configuring security into the schema#
In the schema, you can grant access according to:
groups
to some RQL expressions: users get access if the expression returns some results
To implement the read security defined earlier, groups are not enough, we’ll need some RQL expression. Here is the idea:
add a visibility attribute on Folder, File and Comment, which may be one of the value explained above
add a may_be_read_by relation from Folder, File and Comment to users, which will define who can see the entity
security propagation will be done in hooks
Note
What makes visibility an attribute and not a relation is that its object is a primitive type, here String.
Other builtin primitives are String, Int, BigInt, Float, Decimal, Boolean, Date, Datetime, Time, Interval, Byte and Password and for more information read Entity type
So the first thing to do is to modify my cube’s schema.py
to define those
relations:
from yams.constraints import StaticVocabularyConstraint
class visibility(RelationDefinition):
subject = ('Folder', 'File', 'Comment')
object = 'String'
constraints = [StaticVocabularyConstraint(('public', 'authenticated',
'restricted', 'parent'))]
default = 'parent'
cardinality = '11' # required
class may_be_read_by(RelationDefinition):
__permissions__ = {
'read': ('managers', 'users'),
'add': ('managers',),
'delete': ('managers',),
}
subject = ('Folder', 'File', 'Comment',)
object = 'CWUser'
We can note the following points:
we’ve added a new visibility attribute to Folder, File, Image and Comment using a RelationDefinition
cardinality = ‘11’ means this attribute is required. This is usually hidden under the required argument given to the String constructor, but we can rely on this here (same thing for StaticVocabularyConstraint, which is usually hidden by the vocabulary argument)
the parent possible value will be used for visibility propagation
think to secure the may_be_read_by permissions, else any user can add/delete it by default, which somewhat breaks our security model…
Now, we should be able to define security rules in the schema, based on these new
attribute and relation. Here is the code to add to schema.py
:
from cubicweb.schema import ERQLExpression
VISIBILITY_PERMISSIONS = {
'read': ('managers',
ERQLExpression('X visibility "public"'),
ERQLExpression('X may_be_read_by U')),
'add': ('managers',),
'update': ('managers', 'owners',),
'delete': ('managers', 'owners'),
}
AUTH_ONLY_PERMISSIONS = {
'read': ('managers', 'users'),
'add': ('managers',),
'update': ('managers', 'owners',),
'delete': ('managers', 'owners'),
}
CLASSIFIERS_PERMISSIONS = {
'read': ('managers', 'users', 'guests'),
'add': ('managers',),
'update': ('managers', 'owners',),
'delete': ('managers', 'owners'),
}
from cubicweb_folder.schema import Folder
from cubicweb_file.schema import File
from cubicweb_comment.schema import Comment
from cubicweb_person.schema import Person
from cubicweb_tag.schema import Tag
Folder.__permissions__ = VISIBILITY_PERMISSIONS
File.__permissions__ = VISIBILITY_PERMISSIONS
Comment.__permissions__ = VISIBILITY_PERMISSIONS.copy()
Comment.__permissions__['add'] = ('managers', 'users',)
Person.__permissions__ = AUTH_ONLY_PERMISSIONS
Tag.__permissions__ = CLASSIFIERS_PERMISSIONS
What’s important in there:
VISIBILITY_PERMISSIONS provides read access to managers group, if visibility attribute’s value is ‘public’, or if user (designed by the ‘U’ variable in the expression) is linked to the entity (the ‘X’ variable) through the may_be_read_by permission
we modify permissions of the entity types we use by importing them and modifying their __permissions__ attribute
notice the .copy(): we only want to modify ‘add’ permission for Comment, not for all entity types using VISIBILITY_PERMISSIONS!
the remaining part of the security model is done using regular groups:
users is the group to which all authenticated users will belong
guests is the group of anonymous users
Step 2: security propagation in hooks#
To fullfill the requirements defined earlier, we have to implement:
Also, unless explicity specified, visibility of an image should be the same as its parent folder, as well as visibility of a comment should be the same as the commented entity.
This kind of active rule will be done using CubicWeb’s hook system. Hooks are triggered on database events such as addition of a new entity or relation.
The tricky part of the requirement is in unless explicitly specified, notably because when the entity is added, we don’t know yet its ‘parent’ entity (e.g. Folder of an File, File commented by a Comment). To handle such things, CubicWeb provides Operation, which allow to schedule things to do at commit time.
In our case we will:
on entity creation, schedule an operation that will set default visibility
when a parent relation is added, propagate parent’s visibility unless the child already has a visibility set
Here is the code in cube’s hooks.py
:
from cubicweb.predicates import is_instance
from cubicweb.server import hook
class SetVisibilityOp(hook.DataOperationMixIn, hook.Operation):
def precommit_event(self):
for eid in self.get_data():
entity = self.cnx.entity_from_eid(eid)
if entity.visibility == 'parent':
entity.cw_set(visibility=u'authenticated')
class SetVisibilityHook(hook.Hook):
__regid__ = 'sytweb.setvisibility'
__select__ = hook.Hook.__select__ & is_instance('Folder', 'File', 'Comment')
events = ('after_add_entity',)
def __call__(self):
SetVisibilityOp.get_instance(self._cw).add_data(self.entity.eid)
class SetParentVisibilityHook(hook.Hook):
__regid__ = 'sytweb.setparentvisibility'
__select__ = hook.Hook.__select__ & hook.match_rtype('filed_under', 'comments')
events = ('after_add_relation',)
def __call__(self):
parent = self._cw.entity_from_eid(self.eidto)
child = self._cw.entity_from_eid(self.eidfrom)
if child.visibility == 'parent':
child.cw_set(visibility=parent.visibility)
Notice:
hooks are application objects, hence have selectors that should match entity or relation types to which the hook applies. To match a relation type, we use the hook specific match_rtype selector.
usage of DataOperationMixIn: instead of adding an operation for each added entity, DataOperationMixIn allows to create a single one and to store entity’s eids to be processed in the transaction data. This is a good pratice to avoid heavy operations manipulation cost when creating a lot of entities in the same transaction.
the precommit_event method of the operation will be called at transaction’s commit time.
in a hook, self._cw is the repository session, not a web request as usually in views
according to hook’s event, you have access to different attributes on the hook instance. Here:
self.entity is the newly added entity on ‘after_add_entity’ events
self.eidfrom / self.eidto are the eid of the subject / object entity on ‘after_add_relation’ events (you may also get the relation type using self.rtype)
The parent visibility value is used to tell “propagate using parent security” because we want that attribute to be required, so we can’t use None value else we’ll get an error before we get any chance to propagate…
Now, we also want to propagate the may_be_read_by relation. Fortunately,
CubicWeb provides some base hook classes for such things, so we only have to add
the following code to hooks.py
:
# relations where the "parent" entity is the subject
S_RELS = set()
# relations where the "parent" entity is the object
O_RELS = set(('filed_under', 'comments',))
class AddEntitySecurityPropagationHook(hook.PropagateRelationHook):
"""propagate permissions when new entity are added"""
__regid__ = 'sytweb.addentity_security_propagation'
__select__ = (hook.PropagateRelationHook.__select__
& hook.match_rtype_sets(S_RELS, O_RELS))
main_rtype = 'may_be_read_by'
subject_relations = S_RELS
object_relations = O_RELS
class AddPermissionSecurityPropagationHook(hook.PropagateRelationAddHook):
"""propagate permissions when new entity are added"""
__regid__ = 'sytweb.addperm_security_propagation'
__select__ = (hook.PropagateRelationAddHook.__select__
& hook.match_rtype('may_be_read_by',))
subject_relations = S_RELS
object_relations = O_RELS
class DelPermissionSecurityPropagationHook(hook.PropagateRelationDelHook):
__regid__ = 'sytweb.delperm_security_propagation'
__select__ = (hook.PropagateRelationDelHook.__select__
& hook.match_rtype('may_be_read_by',))
subject_relations = S_RELS
object_relations = O_RELS
the AddEntitySecurityPropagationHook will propagate the relation when filed_under or comments relations are added
the S_RELS and O_RELS set as well as the match_rtype_sets selector are used here so that if my cube is used by another one, it’ll be able to configure security propagation by simply adding relation to one of the two sets.
the two others will propagate permissions changes on parent entities to children entities
Step 3: testing our security#
Security is tricky. Writing some tests for it is a very good idea. You should even write them first, as Test Driven Development recommends!
Here is a small test case that will check the basis of our security
model, in test/test_sytweb.py
:
from cubicweb.devtools import testlib
from cubicweb import Binary
class SecurityTC(testlib.CubicWebTC):
def test_visibility_propagation(self):
with self.admin_access.repo_cnx() as cnx:
# create a user for later security checks
toto = self.create_user(cnx, 'toto')
cnx.commit()
# init some data using the default manager connection
folder = cnx.create_entity('Folder',
name=u'restricted',
visibility=u'restricted')
photo1 = cnx.create_entity('File',
data_name=u'photo1.jpg',
data=Binary(b'xxx'),
filed_under=folder)
cnx.commit()
# visibility propagation
self.assertEquals(photo1.visibility, 'restricted')
# unless explicitly specified
photo2 = cnx.create_entity('File',
data_name=u'photo2.jpg',
data=Binary(b'xxx'),
visibility=u'public',
filed_under=folder)
cnx.commit()
self.assertEquals(photo2.visibility, 'public')
with self.new_access('toto').repo_cnx() as cnx:
# test security
self.assertEqual(1, len(cnx.execute('File X'))) # only the public one
self.assertEqual(0, len(cnx.execute('Folder X'))) # restricted...
with self.admin_access.repo_cnx() as cnx:
# may_be_read_by propagation
folder = cnx.entity_from_eid(folder.eid)
folder.cw_set(may_be_read_by=toto)
cnx.commit()
with self.new_access('toto').repo_cnx() as cnx:
photo1 = cnx.entity_from_eid(photo1.eid)
self.failUnless(photo1.may_be_read_by)
# test security with permissions
self.assertEquals(2, len(cnx.execute('File X'))) # now toto has access to photo2
self.assertEquals(1, len(cnx.execute('Folder X'))) # and to restricted folder
if __name__ == '__main__':
from unittest import main
main()
It’s not complete, but shows most things you’ll want to do in tests: adding some content, creating users and connecting as them in the test, etc…
To run it type:
$ python3 test/test_sytweb.py
======================================================================
-> creating tables [====================]
-> inserting default user and default groups.
-> storing the schema in the database [====================]
-> database for instance data initialized.
.
----------------------------------------------------------------------
Ran 1 test in 22.547s
OK
The first execution is taking time, since it creates a sqlite database for the test instance. The second one will be much quicker:
$ python3 test/test_sytweb.py
======================================================================
.
----------------------------------------------------------------------
Ran 1 test in 2.662s
OK
If you do some changes in your schema, you’ll have to force regeneration of that database. You do that by removing the tmpdb files before running the test:
$ rm data/database/tmpdb*
Step 4: writing the migration script and migrating the instance#
Prior to those changes, I created an instance, fed it with some data, so I don’t want to create a new one, but to migrate the existing one. Let’s see how to do that.
Migration commands should be put in the cube’s migration
directory, in a
file named <X.Y.Z>_Any.py
(‘Any’ being there mostly for historical
reasons and ‘<X.Y.Z>’ being the version number of the cube we are going to release.)
Here I’ll create a migration/0.2.0_Any.py
file containing the following
instructions:
add_relation_type('may_be_read_by')
add_relation_type('visibility')
sync_schema_props_perms()
Then I update the version number in the cube’s __pkginfo__.py
to 0.2.0. And
that’s it! Those instructions will:
update the instance’s schema by adding our two new relations and update the underlying database tables accordingly (the first two instructions)
update schema’s permissions definition (the last instruction)
To migrate my instance I simply type:
cubicweb-ctl upgrade sytweb_instance
You’ll then be asked some questions to do the migration step by step. You should say YES when it asks if a backup of your database should be done, so you can get back to initial state if anything goes wrong…
Storing images on the file-system#
Step 1: configuring the BytesFileSystem storage#
To avoid cluttering my database, and to ease file manipulation, I don’t want them
to be stored in the database. I want to be able create File entities for some
files on the server file system, where those file will be accessed to get
entities data. To do so I’ve to set a custom BytesFileSystemStorage
storage for the File ‘data’ attribute, which hold the actual file’s content.
Since the function to register a custom storage needs to have a repository
instance as first argument, we’ve to call it in a server startup hook. So I added
in cubicweb_sytweb/hooks.py
:
from os import makedirs
from os.path import join, exists
from cubicweb.server import hook
from cubicweb.server.sources import storages
class ServerStartupHook(hook.Hook):
__regid__ = 'sytweb.serverstartup'
events = ('server_startup', 'server_maintenance')
def __call__(self):
bfssdir = join(self.repo.config.appdatahome, 'bfss')
if not exists(bfssdir):
makedirs(bfssdir)
print('created', bfssdir)
storage = storages.BytesFileSystemStorage(bfssdir)
storages.set_attribute_storage(self.repo, 'File', 'data', storage)
Note
how we built the hook’s registry identifier (__regid__): you can introduce ‘namespaces’ by using there python module like naming identifiers. This is especially important for hooks where you usually want a new custom hook, not overriding / specializing an existant one, but the concept may be applied to any application objects
we catch two events here: “server_startup” and “server_maintenance”. The first is called on regular repository startup (eg, as a server), the other for maintenance task such as shell or upgrade. In both cases, we need to have the storage set, else we’ll be in trouble…
the path given to the storage is the place where file added through the ui (or in the database before migration) will be located
beware that by doing this, you can’t anymore write queries that will try to restrict on File data attribute. Hopefuly we don’t do that usually on file’s content or more generally on attributes for the Bytes type
Now, if you’ve already added some photos through the web ui, you’ll have to migrate existing data so file’s content will be stored on the file-system instead of the database. There is a migration command to do so, let’s run it in the cubicweb shell (in real life, you would have to put it in a migration script as we have seen last time):
$ cubicweb-ctl shell sytweb_instance
entering the migration python shell
just type migration commands or arbitrary python code and type ENTER to execute it
type "exit" or Ctrl-D to quit the shell and resume operation
>>> storage_changed('File', 'data')
[========================]
That’s it. Now, files added through the web ui will have their content stored on the file-system, and you’ll also be able to import files from the file-system as explained in the next part.
Step 2: importing some data into the instance#
Hey, we start to have some nice features, let us give a try to this new web site. For instance if I have a ‘photos/201005WePyrenees’ containing pictures for a particular event, I can import it to my web site by typing
$ cubicweb-ctl fsimport -F sytweb_instance photos/201005WePyrenees/
** importing directory /home/syt/photos/201005WePyrenees
importing IMG_8314.JPG
importing IMG_8274.JPG
importing IMG_8286.JPG
importing IMG_8308.JPG
importing IMG_8304.JPG
Note
The -F option means that folders should be mapped, hence my photos will be linked to a Folder entity corresponding to the file-system folder.
Let’s take a look at the web ui:

Nothing different, I can’t see the new folder… But remember our security model! By default, files are only accessible to authenticated users, and I’m looking at the site as anonymous, e.g. not authenticated. If I login, I can now see:

Yeah, it’s there! You will notice that I can see some entities as well as folders and images the anonymous user can’t. It just works everywhere in the ui since it’s handled at the repository level, thanks to our security model.
Now if I click on the recently inserted folder, I can see

Great! There is even my pictures in the folder. I can know give to this folder a nicer name (provided I don’t intend to import from it anymore, else already imported photos will be reimported), change permissions, title for some pictures, etc… Having a good content is much more difficult than having a good web site ;)
Conclusion#
We started to see here an advanced feature of our repository: the ability
to store some parts of our data-model into a custom storage, outside the
database. There is currently only the BytesFileSystemStorage
available,
but you can expect to see more coming in a near future (or write your own!).
Also, we can know start to feed our web-site with some nice pictures! The site isn’t perfect (far from it actually) but it’s usable, and we can now start using it and improve it on the way. The Incremental Cubic Way :)
Let’s make it more user friendly#
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 much 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 import _
from cubicweb.web import component
from cubicweb.web.views import error
from cubicweb.predicates import anonymous_user
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(component.CtxComponent):
"""display a box containing links to all startup views"""
__regid__ = 'sytweb.loginbox'
__select__ = component.CtxComponent.__select__ & anonymous_user()
title = _('Authenticate yourself')
order = 70
def render_body(self, w):
cw = self._cw
form = cw.vreg['forms'].select('logform', cw)
form.render(w=w, table_class='', display_progress_div=False)
The first class provides a new specific implementation of the default page you get on 404 error, to display an adapted message to anonymous user.
Note
Thanks to the selection mecanism, it will be selected for anoymous user, since the additional anonymous_user() selector gives it a higher score than the default, and not for authenticated since this selector will return 0 in such case (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 component.CtxComponent
selector. The HTML is written to match default CubicWeb boxes style. The code
fetch the actual login form and render it.

The login box and the custom 404 page for an anonymous visitor (translated in french)#
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).
Here is the code, samples from my cube’s views.py
file:
from cubicweb.web.views import startup
class IndexView(startup.IndexView):
def call(self, **kwargs):
self.w(u'<div>\n')
if self._cw.cnx.session.anonymous_session:
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. To do so we’ve to implements the registration_callback function, in which we tell to register 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!

The default index page#

Our simpler, less intimidating, index page (still translated in french)#
Step 4: preparing the release and migrating the instance#
Now that greatly enhanced our cube, it’s time to release it 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, I’ve still today some step to respect to get things done properly…
First, as I’ve added some translatable string, I’ve 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_instance
$ cubicweb-ctl pyramid -D sytweb_instance
The first command compile i18n catalogs (e.g. generates ‘.mo’ files) for my test instance. The second command start 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 then 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_instance
# if it's postgresql
pg_dump -Fc --username=syt --no-owner --file /home/syt/etc/cubicweb.d/sytweb/backup/tmpYIN0YI/system sytweb
# if it's sqlite
gzip -c /home/psycojoker/etc/cubicweb.d/sytweb_instance/sytweb_instance.sqlite
-> 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_instance /path/path/to/sytweb-2010-07-13_10-22-40.tar.gz
$ cubicweb-ctl upgrade sytweb_instance
You might have to answer some questions, as we’ve seen in a previous part.
Now that everything is tested, I can transfer the new code to the production server, pip install CubicWeb and its dependencies, and eventually upgrade the production instance.
Building my photos web site with CubicWeb part V: let’s make it even more user friendly#
Step 1: tired of the default look?#
OK… Now our site has its most desired features. But… I would like to make it look somewhat like my website. It is not www.cubicweb.org after all. Let’s tackle this first!
The first thing we can to is to change the logo. There are various way to achieve
this. The easiest way is to put a logo.png
file into the cube’s data
directory. As data files are looked at according to cubes order (CubicWeb
resources coming last), that file will be selected instead of CubicWeb’s one.
Note
As the location for static resources are cached, you’ll have to restart your instance for this to be taken into account.
Though there are some cases where you don’t want to use a logo.png
file.
For instance if it’s a JPEG file. You can still change the logo by defining in
the cube’s uiprops.py
file:
LOGO = data('logo.jpg')
Note
If the file uiprops.py
doesn’t exist in your cube, simply create it.
The uiprops machinery is used to define some static file resources, such as the logo, default Javascript / CSS files, as well as CSS properties (we’ll see that later).
Note
This file is imported specifically by CubicWeb, with a predefined name space, containing for instance the data function, telling the file is somewhere in a cube or CubicWeb’s data directory.
One side effect of this is that it can’t be imported as a regular python module.
The nice thing is that in debug mode, change to a uiprops.py
file are detected
and then automatically reloaded.
Now, as it’s a photos web-site, I would like to have a photo of mine as background… After some trials I won’t detail here, I’ve found a working recipe explained here. All I’ve to do is to override some stuff of the default CubicWeb user interface to apply it as explained.
The first thing to to get the <img/>
tag as first element after the
<body>
tag. If you know a way to avoid this by simply specifying the image
in the CSS, tell me! The easiest way to do so is to override the
HTMLPageHeader
view, since that’s the one that is directly called once
the <body>
has been written. How did I find this? By looking in the
cubiweb.web.views.basetemplates
module, since I know that global page
layouts sits there. I could also have grep the “body” tag in
cubicweb.web.views
… Finding this was the hardest part. Now all I need is
to customize it to write that img
tag, as below in views.py
:
from cubicweb.web.views import basetemplates
class HTMLPageHeader(basetemplates.HTMLPageHeader):
# override this since it's the easier way to have our bg image
# as the first element following <body>
def call(self, **kwargs):
self.w(u'<img id="bg-image" src="%sbackground.jpg" alt="background image"/>'
% self._cw.datadir_url)
super(HTMLPageHeader, self).call(**kwargs)
def registration_callback(vreg):
vreg.register_all(globals().values(), __name__, (HTMLPageHeader))
vreg.register_and_replace(HTMLPageHeader, basetemplates.HTMLPageHeader)
As you may have guessed, my background image is in a background.jpg
file
in the cube’s data
directory, but there are still some things to explain
to newcomers here:
The
call()
method is there the main access point of the view. It’s called by the view’srender()
method. It is not the only access point for a view, but this will be detailed later.Calling self.w writes something to the output stream. Except for binary views (which do not generate text), it must be passed an Unicode string.
The proper way to get a file in
data
directory is to use the datadir_url attribute of the incoming request (e.g. self._cw).
I won’t explain again the registration_callback()
stuff, you should understand it
now! If not, go back to previous post in the series :)
Fine. Now all I’ve to do is to add a bit of CSS to get it to behave nicely (which
is not the case at all for now). I’ll put all this in a cubes.sytweb.css
file, stored as usual in our data
directory:
/* fixed full screen background image
* as explained on http://webdesign.about.com/od/css3/f/blfaqbgsize.htm
*
* syt update: set z-index=0 on the img instead of z-index=1 on div#page & co to
* avoid pb with the user actions menu
*/
img#bg-image {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
}
div#page, table#header, div#footer {
background: transparent;
position: relative;
}
/* add some space around the logo
*/
img#logo {
padding: 5px 15px 0px 15px;
}
/* more dark font for metadata to have a chance to see them with the background
* image
*/
div.metadata {
color: black;
}
You can see here stuff explained in the cited page, with only a slight modification explained in the comments, plus some additional rules to make things somewhat cleaner:
a bit of padding around the logo
darker metadata which appears by default below the content (the white frame in the page)
To get this CSS file used everywhere in the site, I have to modify the uiprops.py
file
introduced above:
STYLESHEETS = sheet['STYLESHEETS'] + [data('cubes.sytweb.css')]
Note
sheet is another predefined variable containing values defined by already process :file:`uiprops.py` file, notably the CubicWeb’s one.
Here we simply want our CSS in addition to CubicWeb’s base CSS files, so we redefine the STYLESHEETS variable to existing CSS (accessed through the sheet variable) with our one added. I could also have done:
sheet['STYLESHEETS'].append(data('cubes.sytweb.css'))
But this is less interesting since we don’t see the overriding mechanism…
At this point, the site should start looking good, the background image being resized to fit the screen.

The final touch: let’s customize CubicWeb’s CSS to get less orange… By simply adding
contextualBoxTitleBg = incontextBoxTitleBg = '#AAAAAA'
and reloading the page we’ve just seen, we know have a nice greyed box instead of the orange one:

This is because CubicWeb’s CSS include some variables which are
expanded by values defined in uiprops.py
file. In our case we controlled the
properties of the CSS background property of boxes with CSS class
contextualBoxTitleBg and incontextBoxTitleBg.
Step 2: configuring boxes#
Boxes present to the user some ways to use the application. Let’s first do a few
user interface tweaks in our views.py
file:
from cubicweb.predicates import none_rset
from cubicweb.web.views import bookmark
from cubicweb_zone import views as zone
from cubicweb_tag import views as tag
# change bookmarks box selector so it's only displayed on startup views
bookmark.BookmarksBox.__select__ = bookmark.BookmarksBox.__select__ & none_rset()
# move zone box to the left instead of in the context frame and tweak its order
zone.ZoneBox.context = 'left'
zone.ZoneBox.order = 100
# move tags box to the left instead of in the context frame and tweak its order
tag.TagsBox.context = 'left'
tag.TagsBox.order = 102
# hide similarity box, not interested
tag.SimilarityBox.visible = False
The idea is to move all boxes in the left column, so we get more space for the
photos. Now, serious things: I want a box similar to the tags box but to handle
the Person displayed_on File relation. We can do this simply by adding a
AjaxEditRelationCtxComponent
subclass to our views, as below:
from cubicweb import _
from logilab.common.decorators import monkeypatch
from cubicweb import ValidationError
from cubicweb.web.views import uicfg, component
from cubicweb.web.views import basecontrollers
# hide displayed_on relation using uicfg since it will be displayed by the box below
uicfg.primaryview_section.tag_object_of(('*', 'displayed_on', '*'), 'hidden')
class PersonBox(component.AjaxEditRelationCtxComponent):
__regid__ = 'sytweb.displayed-on-box'
# box position
order = 101
context = 'left'
# define relation to be handled
rtype = 'displayed_on'
role = 'object'
target_etype = 'Person'
# messages
added_msg = _('person has been added')
removed_msg = _('person has been removed')
# bind to js_* methods of the json controller
fname_vocabulary = 'unrelated_persons'
fname_validate = 'link_to_person'
fname_remove = 'unlink_person'
@monkeypatch(basecontrollers.JSonController)
@basecontrollers.jsonize
def js_unrelated_persons(self, eid):
"""return tag unrelated to an entity"""
rql = "Any F + ' ' + S WHERE P surname S, P firstname F, X eid %(x)s, NOT P displayed_on X"
return [name for (name,) in self._cw.execute(rql, {'x' : eid})]
@monkeypatch(basecontrollers.JSonController)
def js_link_to_person(self, eid, people):
req = self._cw
for name in people:
name = name.strip().title()
if not name:
continue
try:
firstname, surname = name.split(None, 1)
except:
raise ValidationError(eid, {('displayed_on', 'object'): 'provide <first name> <surname>'})
rset = req.execute('Person P WHERE '
'P firstname %(firstname)s, P surname %(surname)s',
locals())
if rset:
person = rset.get_entity(0, 0)
else:
person = req.create_entity('Person', firstname=firstname,
surname=surname)
req.execute('SET P displayed_on X WHERE '
'P eid %(p)s, X eid %(x)s, NOT P displayed_on X',
{'p': person.eid, 'x' : eid})
@monkeypatch(basecontrollers.JSonController)
def js_unlink_person(self, eid, personeid):
self._cw.execute('DELETE P displayed_on X WHERE P eid %(p)s, X eid %(x)s',
{'p': personeid, 'x': eid})
You basically subclass to configure with some class attributes. The fname_* attributes give the name of methods that should be defined on the json control to make the AJAX part of the widget work: one to get the vocabulary, one to add a relation and another to delete a relation. These methods must start by a js_ prefix and are added to the controller using the @monkeypatch decorator. In my case, the most complicated method is the one which adds a relation, since it tries to see if the person already exists, and else automatically create it, assuming the user entered “firstname surname”.
Let’s see how it looks like on a file primary view:

Great, it’s now as easy for me to link my pictures to people than to tag them. Also, visitors get a consistent display of these two pieces of information.
Note
The ui component system has been refactored in CubicWeb 3.10, which also
introduced the AjaxEditRelationCtxComponent
class.
Step 3: configuring facets#
The last feature we’ll add today is facet configuration. If you access to the
‘/file’ url, you’ll see a set of ‘facets’ appearing in the left column. Facets
provide an intuitive way to build a query incrementally, by proposing to the user
various way to restrict the result set. For instance CubicWeb proposes a facet to
restrict based on who created an entity; the tag cube proposes a facet to
restrict based on tags; the zoe cube a facet to restrict based on geographical
location, and so on. In that gist, I want to propose a facet to restrict based on
the people displayed on the picture. To do so, there are various classes in the
cubicweb.web.facet
module which simply have to be configured using class
attributes as we’ve done for the box. In our case, we’ll define a subclass of
RelationFacet
.
Note
Since that’s ui stuff, we’ll continue to add code below to our
views.py
file. Though we begin to have a lot of various code their, so
it’s may be a good time to split our views module into submodules of a view
package. In our case of a simple application (glue) cube, we could start using
for instance the layout below:
views/__init__.py # uicfg configuration, facets
views/layout.py # header/footer/background stuff
views/components.py # boxes, adapters
views/pages.py # index view, 404 view
from cubicweb.web import facet
class DisplayedOnFacet(facet.RelationFacet):
__regid__ = 'displayed_on-facet'
# relation to be displayed
rtype = 'displayed_on'
role = 'object'
# view to use to display persons
label_vid = 'combobox'
Let’s say we also want to filter according to the visibility attribute. This is
even simpler as we just have to derive from the AttributeFacet
class:
class VisibilityFacet(facet.AttributeFacet):
__regid__ = 'visibility-facet'
rtype = 'visibility'
Now if I search for some pictures on my site, I get the following facets available:

Note
By default a facet must be applyable to every entity in the result set and provide at leat two elements of vocabulary to be displayed (for instance you won’t see the created_by facet if the same user has created all entities). This may explain why you don’t see yours…
Conclusion#
We started to see the power behind the infrastructure provided by the framework, both on the pure ui (CSS, Javascript) side and on the Python side (high level generic classes for components, including boxes and facets). We now have, with a few lines of code, a full-featured web site with a personalized look.
Of course we’ll probably want more as time goes, but we can now concentrate on making good pictures, publishing albums and sharing them with friends…
Use Windmill with CubicWeb#
Windmill implements cross browser testing, in-browser recording and playback, and functionality for fast accurate debugging and test environment integration.
Online features list is available.
Installation#
Windmill#
You have to install Windmill manually for now. If you’re using Debian, there is no binary package (yet).
The simplest solution is to use a setuptools/pip command (for a clean environment, take a look to the virtualenv project as well):
$ pip install windmill
$ curl -O http://github.com/windmill/windmill/tarball/master
However, the Windmill project doesn’t release frequently. Our recommandation is to used the last snapshot of the Git repository:
$ git clone git://github.com/windmill/windmill.git HEAD
$ cd windmill
$ python3 setup.py develop
Install instructions are available.
Be sure to have the windmill module in your PYTHONPATH afterwards:
$ python3 -c "import windmill"
X dummy#
In order to reduce unecessary system load from your test machines, It’s recommended to use X dummy server for testing the Unix web clients, you need a dummy video X driver (as xserver-xorg-video-dummy package in Debian) coupled with a light X server as Xvfb.
The dummy driver is a special driver available with the XFree86 DDX. To use the dummy driver, simply substitue it for your normal card driver in the Device section of your xorg.conf configuration file. For example, if you normally uses an ati driver, then you will have a Device section with Driver “ati” to let the X server know that you want it to load and use the ati driver; however, for these conformance tests, you would change that line to Driver “dummy” and remove any other ati specific options from the Device section.
From: http://www.x.org/wiki/XorgTesting
Then, you can run the X server with the following command
$ /usr/bin/X11/Xvfb :1 -ac -screen 0 1280x1024x8 -fbdir /tmp
Windmill usage#
Record your use case#
start your instance manually
start Windmill with url site as last argument (read Usage or use ‘-h’ option to find required command line arguments)
use the record button
click on save to obtain python code of your use case
copy the content to a new file in a windmill directory
If you are using firefox as client, consider the “firebug” option.
If you have a running instance, you can refine the test by the loadtest windmill option:
$ windmill -m firebug loadtest=<test_file.py> <instance url>
Or use the internal windmill shell to explore available commands:
$ windmill -m firebug shell <instance url>
And enter python commands:
>>> load_test(<your test file>)
>>> run_test(<your test file>)
Integrate Windmill tests into CubicWeb#
Set environment#
You have to create a new unit test file and a windmill directory and copy all your windmill use case into it.
# test_windmill.py
# Run all scenarii found in windmill directory
from cubicweb.devtools.cwwindmill import (CubicWebWindmillUseCase,
unittest_main)
if __name__ == '__main__':
unittest_main()
Run your tests#
You can easily run your windmill test suite through pytest or unittest
.
You have to copy a test_windmill.py file from web.test
.
To run your test series:
$ pytest test/test_windmill.py
By default, CubicWeb will use firefox as the default browser and will try to run test instance server on localhost. In the general case, You’ve no need to change anything.
Check cubicweb.devtools.cwwindmill.CubicWebWindmillUseCase
for
Windmill configuration. You can edit windmill settings with following class attributes:
browser identification string (firefox|ie|safari|chrome) (firefox by default)
test_dir testing file path or directory (windmill directory under your unit case file by default)
edit_test load and edit test for debugging (False by default)
Examples:
browser = 'firefox'
test_dir = osp.join(__file__, 'windmill')
edit_test = False
If you want to change cubicweb test server parameters, you can check class
variables from CubicWebServerConfig
or inherit it with overriding the
configcls
attribute in CubicWebServerTC
.. sourcecode:: python
- class OtherCubicWebServerConfig(CubicWebServerConfig):
port = 9999
- class NewCubicWebServerTC(CubicWebServerTC):
configcls = OtherCubicWebServerConfig
For instance, CubicWeb framework windmill tests can be manually run by:
$ pytest web/test/test_windmill.py
Edit your tests#
You can toggle the edit_test variable to enable test edition.
But if you are using pytest as test runner, use the -i option directly. The test series will be loaded and you can run assertions step-by-step:
$ pytest -i test/test_windmill.py
In this case, the firebug extension will be loaded automatically for you.
Afterwards, don’t forget to save your edited test into the right file (no autosave feature).
Best practises#
Don’t run another instance on the same port. You risk to silence some regressions (test runner will automatically fail in further versions).
Start your use case by using an assert on the expected primary url page. Otherwise all your tests could fail without clear explanation of the used navigation.
In the same location of the test_windmill.py, create a windmill/ with your windmill recorded use cases.
Caveats#
File Upload#
Windmill can’t do file uploads. This is a limitation of browser Javascript support / sandboxing, not of Windmill per se. It would be nice if there were some command that would prime the Windmill HTTP proxy to add a particular file to the next HTTP request that comes through, so that uploads could at least be faked.
Preferences#
A .windmill/prefs.py could be used to redefine default configuration values.
For managing browser extensions, read advanced topic chapter.
More configuration examples could be seen in windmill/conf/global_settings.py as template.
Writing text reports with RestructuredText#
CubicWeb offers several text formats for the RichString type used in schemas, including restructuredtext.
Three additional restructuredtext roles are defined by CubicWeb:
Importing relational data into a CubicWeb instance#
Introduction#
This tutorial explains how to import data from an external source (e.g. a collection of files) into a CubicWeb cube instance.
First, once we know the format of the data we wish to import, we devise a
data model, that is, a CubicWeb (Yams) schema which reflects the way the data
is structured. This schema is implemented in the schema.py
file.
In this tutorial, we will describe such a schema for a particular data set,
the Diseasome data (see below).
Once the schema is defined, we create a cube and an instance. The cube is a specification of an application, whereas an instance is the application per se.
Once the schema is defined and the instance is created, the import can be performed, via the following steps:
Build a custom parser for the data to be imported. Thus, one obtains a Python memory representation of the data.
Map the parsed data to the data model defined in
schema.py
.Perform the actual import of the data. This comes down to “populating” the data model with the memory representation obtained at 1, according to the mapping defined at 2.
This tutorial illustrates all the above steps in the context of relational data stored in the RDF format.
More specifically, we describe the import of Diseasome RDF/OWL data.
Building a data model#
The first thing to do when using CubicWeb for creating an application from scratch is to devise a data model, that is, a relational representation of the problem to be modeled or of the structure of the data to be imported.
In such a schema, we define
an entity type (EntityType
objects) for each type of entity to import. Each such type
has several attributes. If the attributes are of known CubicWeb (Yams) types, viz. numbers,
strings or characters, then they are defined as attributes, as e.g. attribute = Int()
for an attribute named attribute
which is an integer.
Each such type also has a set of relations, which are defined like the attributes, except that they represent, in fact, relations between the entities of the type under discussion and the objects of a type which is specified in the relation definition.
For example, for the Diseasome data, we have two types of entities, genes and diseases.
Thus, we create two classes which inherit from EntityType
:
class Disease(EntityType):
# Corresponds to http://www.w3.org/2000/01/rdf-schema#label
label = String(maxsize=512, fulltextindexed=True)
...
#Corresponds to http://www4.wiwiss.fu-berlin.de/diseasome/resource/diseasome/associatedGene
associated_genes = SubjectRelation('Gene', cardinality='**')
...
#Corresponds to 'http://www4.wiwiss.fu-berlin.de/diseasome/resource/diseasome/chromosomalLocation'
chromosomal_location = SubjectRelation('ExternalUri', cardinality='?*', inlined=True)
class Gene(EntityType):
...
In this schema, there are attributes whose values are numbers or strings. Thus, they are
defined by using the CubicWeb / Yams primitive types, e.g., label = String(maxsize=12)
.
These types can have several constraints or attributes, such as maxsize
.
There are also relations, either between the entity types themselves, or between them
and a CubicWeb type, ExternalUri
. The latter defines a class of URI objects in
CubicWeb. For instance, the chromosomal_location
attribute is a relation between
a Disease
entity and an ExternalUri
entity. The relation is marked by the CubicWeb /
Yams SubjectRelation
method. The latter can have several optional keyword arguments, such as
cardinality
which specifies the number of subjects and objects related by the relation type
specified. For example, the '?*'
cardinality in the chromosomal_relation
relation type says
that zero or more Disease
entities are related to zero or one ExternalUri
entities.
In other words, a Disease
entity is related to at most one ExternalUri
entity via the
chromosomal_location
relation type, and that we can have zero or more Disease
entities in the
data base.
For a relation between the entity types themselves, the associated_genes
between a Disease
entity and a Gene
entity is defined, so that any number of Gene
entities can be associated
to a Disease
, and there can be any number of Disease
s if a Gene
exists.
Of course, before being able to use the CubicWeb / Yams built-in objects, we need to import them:
from yams.buildobjs import EntityType, SubjectRelation, String, Int
from cubicweb.schemas.base import ExternalUri
Building a custom data parser#
The data we wish to import is structured in the RDF format, as a text file containing a set of lines. On each line, there are three fields. The first two fields are URIs (“Universal Resource Identifiers”). The third field is either an URI or a string. Each field bares a particular meaning:
the leftmost field is an URI that holds the entity to be imported. Note that the entities defined in the data model (i.e., in
schema.py
) should correspond to the entities whose URIs are specified in the import file.the middle field is an URI that holds a relation whose subject is the entity defined by the leftmost field. Note that this should also correspond to the definitions in the data model.
the rightmost field is either an URI or a string. When this field is an URI, it gives the object of the relation defined by the middle field. When the rightmost field is a string, the middle field is interpreted as an attribute of the subject (introduced by the leftmost field) and the rightmost field is interpreted as the value of the attribute.
Note however that some attributes (i.e. relations whose objects are strings)
have their objects defined as strings followed by ^^
and by another URI;
we ignore this part.
Let us show some examples:
of line holding an attribute definition:
<http://www4.wiwiss.fu-berlin.de/diseasome/resource/genes/CYP17A1> <http://www.w3.org/2000/01/rdf-schema#label> "CYP17A1" .
The line contains the definition of thelabel
attribute of an entity of typegene
. The value oflabel
is ‘CYP17A1
’.of line holding a relation definition:
<http://www4.wiwiss.fu-berlin.de/diseasome/resource/diseases/1> <http://www4.wiwiss.fu-berlin.de/diseasome/resource/diseasome/associatedGene> <http://www4.wiwiss.fu-berlin.de/diseasome/resource/genes/HADH2> .
The line contains the definition of theassociatedGene
relation between adisease
subject entity identified by1
and agene
object entity defined byHADH2
.
Thus, for parsing the data, we can (:note: see the diseasome_parser
module):
define a couple of regular expressions for parsing the two kinds of lines,
RE_ATTS
for parsing the attribute definitions, andRE_RELS
for parsing the relation definitions.define a function that iterates through the lines of the file and retrieves (
yield
s) a (subject, relation, object) tuple for each line. We called it_retrieve_structure
in thediseasome_parser
module. The function needs the file name and the types for which information should be retrieved.
Alternatively, instead of hand-making the parser, one could use the RDF parser provided
in the dataio
cube.
Once we get to have the (subject, relation, object) triples, we need to map them into the data model.
Mapping the data to the schema#
In the case of diseasome data, we can just define two dictionaries for mapping
the names of the relations as extracted by the parser, to the names of the relations
as defined in the schema.py
data model. In the diseasome_parser
module
they are called MAPPING_ATTS
and MAPPING_RELS
.
Given that the relation and attribute names are given in CamelCase in the original data,
mappings are necessary if we follow the PEP08 when naming the attributes in the data model.
For example, the RDF relation chromosomalLocation
is mapped into the schema relation
chromosomal_location
.
Once these mappings have been defined, we just iterate over the (subject, relation, object)
tuples provided by the parser and we extract the entities, with their attributes and relations.
For each entity, we thus have a dictionary with two keys, attributes
and relations
.
The value associated to the attributes
key is a dictionary containing (attribute: value)
pairs, where “value” is a string, plus the cwuri
key / attribute holding the URI of
the entity itself.
The value associated to the relations
key is a dictionary containing (relation: value)
pairs, where “value” is an URI.
This is implemented in the entities_from_rdf
interface function of the module
diseasome_parser
. This function provides an iterator on the dictionaries containing
the attributes
and relations
keys for all entities.
However, this is a simple case. In real life, things can get much more complicated, and the mapping can be far from trivial, especially when several data sources (which can follow different formatting and even structuring conventions) must be mapped into the same data model.
Importing the data#
The data import code should be placed in a Python module. Let us call it
diseasome_import.py
. Then, this module should be called via
cubicweb-ctl
, as follows:
cubicweb-ctl shell diseasome_import.py -- <other arguments e.g. data file>
In the import module, we should use a store for doing the import. A store is an object which provides three kinds of methods for importing data:
a method for importing the entities, along with the values of their attributes.
a method for importing the relations between the entities.
a method for committing the imports to the database.
In CubicWeb, we have four stores:
ObjectStore
base class for the stores in CubicWeb. It only provides a skeleton for all other stores and provides the means for creating the memory structures (dictionaries) that hold the entities and the relations between them.RQLObjectStore
: store which uses the RQL language for performing database insertions and updates. It relies on all the CubicWeb hooks machinery, especially for dealing with security issues (database access permissions).
NoHookRQLObjectStore
: store which uses the RQL language for performing database insertions and updates, but for which all hooks are deactivated. This implies that certain checks with respect to the CubicWeb / Yams schema (data model) are not performed. However, all SQL queries obtained from the RQL ones are executed in a sequential manner, one query per inserted entity.
SQLGenObjectStore
: store which uses the SQL language directly. It inserts entities either sequentially, by executing an SQL query for each entity, or directly by using one PostGRESCOPY FROM
query for a set of similarly structured entities.
For really massive imports (millions or billions of entities), there
is a cube dataio
which contains another store, called
MassiveObjectStore
. This store is similar to SQLGenObjectStore
,
except that anything related to CubicWeb is bypassed. That is, even the
CubicWeb EID entity identifiers are not handled. This store is the fastest,
but has a slightly different API from the other four stores mentioned above.
Moreover, it has an important limitation, in that it doesn’t insert inlined 1
relations in the database.
- 1
An inlined relation is a relation defined in the schema with the keyword argument
inlined=True
. Such a relation is inserted in the database as an attribute of the entity whose subject it is.
In the following section we will see how to import data by using the stores
in CubicWeb’s dataimport
module.
Using the stores in dataimport
#
ObjectStore
is seldom used in real life for importing data, since it is
only the base store for the other stores and it doesn’t perform an actual
import of the data. Nevertheless, the other three stores, which import data,
are based on ObjectStore
and provide the same API.
All three stores RQLObjectStore
, NoHookRQLObjectStore
and
SQLGenObjectStore
provide exactly the same API for importing data, that is
entities and relations, in an SQL database.
Before using a store, one must import the dataimport
module and then initialize
the store, with the current session
as a parameter:
import cubicweb.dataimport as cwdi
...
store = cwdi.RQLObjectStore(session)
Each such store provides three methods for data import:
create_entity(Etype, **attributes)
, which allows us to add an entity of the Yams typeEtype
to the database. This entity’s attributes are specified in theattributes
dictionary. The method returns the entity created in the database. For example, we add two entities, a person, ofPerson
type, and a location, ofLocation
type:person = store.create_entity('Person', name='Toto', age='18', height='190') location = store.create_entity('Location', town='Paris', arrondissement='13')
relate(subject_eid, r_type, object_eid)
, which allows us to add a relation of the Yams typer_type
to the database. The relation’s subject is an entity whose EID issubject_eid
; its object is another entity, whose EID isobject_eid
. For example 2:store.relate(person.eid(), 'lives_in', location.eid(), **kwargs)
kwargs
is only used by theSQLGenObjectStore
’srelate
method and is here to allow us to specify the type of the subject of the relation, when the relation is defined as inlined in the schema.
- 2
- The
eid
method of an entity defined viacreate_entity
returns the entity identifier as assigned by CubicWeb when creating the entity. This only works for entities defined via the stores in the CubicWeb’s
dataimport
module.
The keyword argument that is understood by
SQLGenObjectStore
is calledsubjtype
and holds the type of the subject entity. For the example considered here, this comes to having 3:store.relate(person.eid(), 'lives_in', location.eid(), subjtype=person.cw_etype)
If
subjtype
is not specified, then the store tries to infer the type of the subject. However, this doesn’t always work, e.g. when there are several possible subject types for a given relation type.- The
- 3
- The
cw_etype
attribute of an entity defined viacreate_entity
holds the type of the entity just created. This only works for entities defined via the stores in the CubicWeb’s
dataimport
module. In the example considered here,person.cw_etype
holds'Person'
.
All the other stores but
SQLGenObjectStore
ignore thekwargs
parameters.- The
flush()
, which allows us to perform the actual commit into the database, along with some cleanup operations. Ideally, this method should be called as often as possible, that is after each insertion in the database, so that database sessions are kept as atomic as possible. In practice, we usually call this method twice: first, after all the entities have been created, second, after all relations have been created.Note however that before each commit the database insertions have to be consistent with the schema. Thus, if, for instance, an entity has an attribute defined through a relation (viz. a
SubjectRelation
) with a"1"
or"+"
object cardinality, we have to create the entity under discussion, the object entity of the relation under discussion, and the relation itself, before committing the additions to the database.The
flush
method is simply called as:store.flush().
Using the MassiveObjectStore
in the dataio
cube#
This store, available in the dataio
cube, allows us to
fully dispense with the CubicWeb import mechanisms and hence
to interact directly with the database server, via SQL queries.
Moreover, these queries rely on PostGreSQL’s COPY FROM
instruction
to create several entities in a single query. This brings tremendous
performance improvements with respect to the RQL-based data insertion
procedures.
However, the API of this store is slightly different from the API of
the stores in CubicWeb’s dataimport
module.
Before using the store, one has to import the dataio
cube’s
dataimport
module, then initialize the store by giving it the
session
parameter:
from cubicweb_dataio import dataimport as mcwdi
...
store = mcwdi.MassiveObjectStore(session)
The MassiveObjectStore
provides six methods for inserting data
into the database:
init_rtype_table(SubjEtype, r_type, ObjEtype)
, which specifies the creation of the tables associated to the relation types in the database. Each such table has three column, the type of the subject entity, the type of the relation (that is, the name of the attribute in the subject entity which is defined via the relation), and the type of the object entity. For example:store.init_rtype_table('Person', 'lives_in', 'Location')
Please note that these tables can be created before the entities, since they only specify their types, not their unique identifiers.
create_entity(Etype, **attributes)
, which allows us to add new entities, whose attributes are given in theattributes
dictionary. Please note however that, by default, this method does not return the created entity. The method is called, for example, as in:store.create_entity('Person', name='Toto', age='18', height='190', uri='http://link/to/person/toto_18_190') store.create_entity('Location', town='Paris', arrondissement='13', uri='http://link/to/location/paris_13')
In order to be able to link these entities via the relations when needed, we must provide ourselves a means for uniquely identifying the entities. In general, this is done via URIs, stored in attributes like
uri
orcwuri
. The name of the attribute is irrelevant as long as its value is unique for each entity.relate_by_iid(subject_iid, r_type, object_iid)
allows us to actually relate the entities uniquely identified bysubject_iid
andobject_iid
via a relation of typer_type
. For example:store.relate_by_iid('http://link/to/person/toto_18_190', 'lives_in', 'http://link/to/location/paris_13')
Please note that this method does not work for inlined relations!
convert_relations(SubjEtype, r_type, ObjEtype, subj_iid_attribute, obj_iid_attribute)
allows us to actually insert the relations in the database. At one call of this method, one inserts all the relations of typertype
between entities of given types.subj_iid_attribute
andobject_iid_attribute
are the names of the attributes which store the unique identifiers of the entities, as assigned by the user. These names can be identical, as long as their values are unique. For example, for inserting all relations of typelives_in
betweenPeople
andLocation
entities, we write:store.convert_relations('Person', 'lives_in', 'Location', 'uri', 'uri')
flush()
performs the actual commit in the database. It only needs to be called aftercreate_entity
andrelate_by_iid
calls. Please note thatrelate_by_iid
does not perform insertions into the database, hence callingflush()
for it would have no effect.cleanup()
performs database cleanups, by removing temporary tables. It should only be called at the end of the import.
Application to the Diseasome data#
Import setup#
We define an import function, diseasome_import
, which does basically four things:
creates and initializes the store to be used, via a line such as:
store = cwdi.SQLGenObjectStore(session)
where
cwdi
is the importedcubicweb.dataimport
orcubicweb_dataio.dataimport
.calls the diseasome parser, that is, the
entities_from_rdf
function in thediseasome_parser
module and iterates on its result, in a line such as:for entity, relations in parser.entities_from_rdf(filename, ('gene', 'disease')):
where
parser
is the importeddiseasome_parser
module, andfilename
is the name of the file containing the data (with its path), e.g.../data/diseasome_dump.nt
.creates the entities to be inserted in the database; for Diseasome, there are two kinds of entities:
entities defined in the data model, viz.
Gene
andDisease
in our case.entities which are built in CubicWeb / Yams, viz.
ExternalUri
which define URIs.
As we are working with RDF data, each entity is defined through a series of URIs. Hence, each “relational attribute” 4 of an entity is defined via an URI, that is, in CubicWeb terms, via an
ExternalUri
entity. The entities are created, in the loop presented above, as such:ent = store.create_entity(etype, **entity)
where
etype
is the appropriate entity type, eitherGene
orDisease
.
- 4
- By “relational attribute” we denote an attribute (of an entity) which
is defined through a relation, e.g. the
chromosomal_location
attribute ofDisease
entities, which is defined through a relation between aDisease
and anExternalUri
.
The
ExternalUri
entities are as many as URIs in the data file. For them, we define a unique attribute,uri
, which holds the URI under discussion:extu = store.create_entity('ExternalUri', uri="http://path/of/the/uri")
creates the relations between the entities. We have relations between:
entities defined in the schema, e.g. between
Disease
andGene
entities, such as theassociated_genes
relation defined forDisease
entities.entities defined in the schema and
ExternalUri
entities, such asgene_id
.
The way relations are added to the database depends on the store:
for the stores in the CubicWeb
dataimport
module, we only usestore.relate
, in another loop, on the relations (that is, a loop inside the preceding one, mentioned at step 2):for rtype, rels in relations.iteritems(): ... store.relate(ent.eid(), rtype, extu.eid(), **kwargs)
where
kwargs
is a dictionary designed to accommodate the need for specifying the type of the subject entity of the relation, when the relation is inlined andSQLGenObjectStore
is used. For example:... store.relate(ent.eid(), 'chromosomal_location', extu.eid(), subjtype='Disease')
for the
MassiveObjectStore
in thedataio
cube’sdataimport
module, the relations are created in three steps:first, a table is created for each relation type, as in:
... store.init_rtype_table(ent.cw_etype, rtype, extu.cw_etype)
which comes down to lines such as:
store.init_rtype_table('Disease', 'associated_genes', 'Gene') store.init_rtype_table('Gene', 'gene_id', 'ExternalUri')
second, the URI of each entity will be used as its identifier, in the
relate_by_iid
method, such as:disease_uri = 'http://www4.wiwiss.fu-berlin.de/diseasome/resource/diseases/3' gene_uri = '<http://www4.wiwiss.fu-berlin.de/diseasome/resource/genes/HSD3B2' store.relate_by_iid(disease_uri, 'associated_genes', gene_uri)
third, the relations for each relation type will be added to the database, via the
convert_relations
method, such as in:store.convert_relations('Disease', 'associated_genes', 'Gene', 'cwuri', 'cwuri')
and:
store.convert_relations('Gene', 'hgnc_id', 'ExternalUri', 'cwuri', 'uri')
where
cwuri
anduri
are the attributes which store the URIs of the entities defined in the data model, and of theExternalUri
entities, respectively.
flushes all relations and entities:
store.flush()
which performs the actual commit of the inserted entities and relations in the database.
If the MassiveObjectStore
is used, then a cleanup of temporary SQL tables should be performed
at the end of the import:
store.cleanup()
Timing benchmarks#
In order to time the import script, we just decorate the import function with the timed
decorator:
from logilab.common.decorators import timed
...
@timed
def diseasome_import(session, filename):
...
After running the import function as shown in the “Importing the data” section, we obtain two time measurements:
diseasome_import clock: ... / time: ...
Here, the meanings of these measurements are 5:
clock
is the time spent by CubicWeb, on the server side (i.e. hooks and data pre- / post-processing on SQL queries),time
is the sum betweenclock
and the time spent in PostGreSQL.
- 5
The meanings of the
clock
andtime
measurements, when using the@timed
decorators, were taken from a blog post on massive data import in CubicWeb.
The import function is put in an import module, named diseasome_import
here. The module is called
directly from the CubicWeb shell, as follows:
cubicweb-ctl shell diseasome_instance diseasome_import.py \
-- -df diseasome_import_file.nt -st StoreName
The module accepts two arguments:
the data file, introduced by
-df [--datafile]
, andthe store, introduced by
-st [--store]
.
The timings (in seconds) for different stores are given in the following table, for
importing 4213 Disease
entities and 3919 Gene
entities with the import module
just described:
Store |
CubicWeb time (clock) |
PostGreSQL time (time - clock) |
Total time |
---|---|---|---|
|
225.98 |
62.05 |
288.03 |
|
62.73 |
51.38 |
114.11 |
|
20.41 |
11.03 |
31.44 |
|
4.84 |
6.93 |
11.77 |
Conclusions#
In this tutorial we have seen how to import data in a CubicWeb application instance. We have first seen how to create a schema, then how to create a parser of the data and a mapping of the data to the schema. Finally, we have seen four ways of importing data into CubicWeb.
Three of those are integrated into CubicWeb, namely the RQLObjectStore
, NoHookRQLObjectStore
and
SQLGenObjectStore
stores, which have a common API:
RQLObjectStore
is by far the slowest, especially its time spent on the CubicWeb side, and so it should be used only for small amounts of “sensitive” data (i.e. where security is a concern).NoHookRQLObjectStore
slashes by almost four the time spent on the CubicWeb side, but is also quite slow; on the PostGres side it is as slow as the previous store. It should be used for data where security is not a concern, but consistency (with the data model) is.SQLGenObjectStore
slashes by three the time spent on the CubicWeb side and by five the time spent on the PostGreSQL side. It should be used for relatively great amounts of data, where security and data consistency are not a concern. Compared to the previous store, it has the disadvantage that, for inlined relations, we must specify their subjects’ types.
For really huge amounts of data there is a fourth store, MassiveObjectStore
, available
from the dataio
cube. It provides a blazing performance with respect to all other stores:
it is almost 25 times faster than RQLObjectStore
and almost three times faster than
SQLGenObjectStore
. However, it has a few usage caveats that should be taken into account:
it cannot insert relations defined as inlined in the schema,
no security or consistency check is performed on the data,
its API is slightly different from the other stores.
Hence, this store should be used when security and data consistency are not a concern, and there are no inlined relations in the schema.
Create a Website from scratch with CubicWeb#
Introduction#
This tutorial aims to demonstrate how to create a website using CubicWeb. This website will present museums from French Ministry of Culture data, available here.
First, we will start with installation and creation of our website, and a short presentation of out of the box CubicWeb functionalities. Then, we will see how to enhance our views using Jinja2 templates or React components to have a better looking site. Finally, we will see how to manage more data, and how to serialize them in RDF.
At the end of this tutorial, you will have a website giving information about all France’s museums, describes them in RDF and present them on a map.
You can find the code of the finished tutorial in our forge, look for the cube tuto.
Getting started#
Installation of CubicWeb and dependencies#
In this tutorial, we choose to install CubicWeb as a Python Package in a Python3 virtual
environment, with pip
; instead of using Debian installation.
We will also need to install psycopg2-binary if we use a postgresql database:
python3 -m venv venv-tuto
source venv-tuto/bin/activate
pip install cubicweb
pip install psycopg2-binary
Create a cube#
Now we have CubicWeb installed, we will need to create a cube, which will contain our application.
We will call our cube tuto
:
cubicweb-ctl newcube tuto -d tuto
This command will lead to several questions, as a short description of the new cube. Then, it will
create a directory named tuto
(as we specified it with -d) reflecting the structure
described in Standard structure for a cube.
To install our new cube on the virtual environment, run in the tuto/cubicweb-tuto
directory:
pip install -e .
All cubicweb-ctl commands are described in details in cubicweb-ctl tool.
Create and start our instance#
Now we created our cube, we need to instantiate it to launch our website:
cubicweb-ctl create tuto tuto_instance
Several questions will be asked to parameter our new instance, most of them can be answered with default value, some (as DB user and password) can’t. At the end, it will be asked if we want to create the database now. We do not need it right now, as we will create in further steps.
Then, we can launch our instance in debug mod (with -D
option):
cubicweb-ctl pyramid -D tuto_instance
You can now access the instance from http://localhost:8080

As you can see, we already have several functionalities which come out-of-the-box, for instance user management, data model schema displaying, etc.
Now, we need to design our data model, to be able to create and display some museums.
Defining our data model#
We want to display some museums, each have a name, a postal address, maybe one or several director, a geographical position (latitude and longitude) and are in a city. Some of these concepts will be classes, others attributes.
Thus, we will write the following code in our tuto/cubicweb_tuto/schema.py
file:
from yams.buildobjs import EntityType, String, Float, RelationDefinition, Int
class Museum(EntityType):
name = String()
latitude = Float()
longitude = Float()
postal_address = String()
class City(EntityType):
name = String()
zip_code = Int()
class Person(EntityType):
name = String()
email = String()
class is_in(RelationDefinition):
subject = 'Museum'
object = 'City'
cardinality = '1*'
class director(RelationDefinition):
subject = 'Museum'
object = 'Person'
cardinality = '**'
The first step is the import from the yams
package necessary classes to build
the schema.
This file defines the following:
a Museum has a name, a latitude, a longitude and a postal address as attributes.
the name and postal address are strings;
the latitude and longitude are floating numbers.
a City has a name and a zip code as attributes.
a Person has a name and an email as attributes
a Museum must be linked to a City using the is_in relation
*
means a City may be linked to 0 to N Museum,1
means a Museum must be linked to one and only one City. For completeness, you can also use+
for 1 to N, and?
for 0 or 1.
a Museum can be linked to 0 or several Person using the director relation, and a Person can be linked to 0 or several Museum.
Of course, there are a lot of other data types and things such as constraints, permissions, etc, that may be defined in the schema, but those will not be covered in this tutorial.
In our case, our relations have only on subject type. Thus, we can define them directly in Museum class, using SubjectRelation, like this:
from yams.buildobjs import EntityType, String, Float, SubjectRelation, Int
class Museum(EntityType):
name = String()
latitude = Float()
longitude = Float()
is_in = SubjectRelation("City", cardinality="1*")
director = SubjectRelation("Person", cardinality="**")
postal_address = String()
class City(EntityType):
name = String()
zip_code = Int()
class Person(EntityType):
name = String()
email = String()
Once the schema created, we need to create our database, and then initialise it with the aforementioned schema:
cubicweb-ctl db-create tuto_instance
cubicweb-ctl db-init tuto_instance
Note
At the end of the db-create, it is asked if we want to run database initialisation, thus we can avoid running db-init.
Note
In our case, we had no existing database, so we had to initialize a database. But with a pre-existing database and schema, we have to use migration scripts, see Migration for more information about this topic.
If we launch again our instance, we should see our new entity types in the homepage: City, Museum, Person; and for each, the number of instance of these types (currently 0, as we don’t have any of these entities).

By clicking on data model schema, we can see our data model, with our three classes and two relations.

Adding data#
Now we have our entity types defined, we will see how to add some entities. To do this, we need to be connected as administrator, using the login button at right top of the site, or visiting http://localhost:8080/login. As you can see, we have more choices in the homepage, and beside each entity type, we have a +, allowing to create a new entity of this type.

As we built our schema, a Museum have to be linked to a City, so we first need to create a City before adding a museum. To do this, we just have to click on the + beside City (0), and fill the form.

As you can see, all the fields comes directly from the schema and the form is automatically generated by CubicWeb.
When all the fields are fulfilled, we just have to validate, and we are redirected on the city page, where we can see its different attributes, and on the left, several possible action; as modify our entity or delete it.

Now we have our first city, we will add its three museums. As for the city creation, we have an autogenerated form; but with a little particularity: a field to choose the city to link with our museum. This field must be fulfilled to create our entity.

As for the city, we are redirected on the entity view after its creation.

We then add two other museums. When we go back to the homepage, we can see all three museums when we click on Museum_plural (3).

If we click on City in the homepage, we do not have a list view, but our single entity view. This is because in the first case, the framework chose to use the ‘primary’ view since there is only one entity in the data to be displayed. As we have three museums, the ‘list’ view is more appropriate and hence is being used.
There are various other places where CubicWeb adapts to display data in the best way, the main being provided by the view selection mechanism that will be detailed later.
Customize museum primary view#
The ‘primary’ view (i.e. any view with the identifier set to ‘primary’) is the one used to display all the information about a single entity. The standard primary view is one of the most sophisticated views of all. It has several customisation points, but its power comes with uicfg, allowing you to control it without having to subclass it. More information are available here : The Primary View.
Now we have several museums, we want an easier way to identify its city when we are on the
museum page. To achieve this, we will subclass PrimaryView and override render_entity_title
method in tuto/cubicweb_tuto/views.py
:
from cubicweb.predicates import is_instance
from cubicweb.web.views.primary import PrimaryView
class MuseumPrimaryView(PrimaryView):
__select__ = is_instance("Museum")
def render_entity_title(self, entity):
"""Renders the entity title.
"""
city_name = entity.is_in[0].name
self.w(f"<h1>{entity.name} ({city_name})</h1>")
As stated before, CubicWeb comes with a system of views selection. This system is, among other things, based on selectors declared with __select__ (you’ll find more information about this in the Registries and application objects chapter). As we want to customize museum primary view, we use __select__ = is_instance(“Museum”) to tell CubicWeb this is only applicable when we display a Museum entity.
Then, we just override the method used to compute title to add the city name. To reach the city name, we use the relation is_in and choose the first and only one linked city, then ask for its name.

Use entities.py to add more logic#
CubicWeb provides an ORM to easily programmaticaly manipulate
entities. By default, entity types are instances of the AnyEntity
class,
which holds a set of predefined methods as well as property automatically generated for
attributes/relations of the type it represents.
You can redefine each entity to provide additional methods or whatever you want to help you write your application. Customizing an entity requires that your entity:
inherits from
cubicweb.entities.AnyEntity
or any subclassdefines a
__regid__
linked to the corresponding data type of your schema
You may then want to add your own methods, override default implementation of some method, etc…
As we may want reuse our custom museum title (with city name, as defined in previous section), we will define it as a property of our Museum class.
To do so, write this code in tuto/cubicweb_tuto/entities.py
:
from cubicweb.entities import AnyEntity, fetch_config
class Museum(AnyEntity):
__regid__ = "Museum"
@property
def title_with_city(self):
return f"{self.name} ({self.is_in[0].name})"
Then, we just have to use it our previously defined view in tuto/cubicweb_tuto/views.py
:
from cubicweb.predicates import is_instance
from cubicweb.web.views.primary import PrimaryView
class MuseumPrimaryView(PrimaryView):
__select__ = is_instance("Museum")
def render_entity_title(self, entity):
"""Renders the entity title.
"""
self.w(f"<h1>{entity.title_with_city}</h1>")
Conclusion#
In this first part, we laid the cornerstone of our futur site, and discovered some core functionalities of CubicWeb. In next parts, we will improve views and see how to import all our data.
Enhance views#
In Getting started, we saw how to develop our views by writing html code directly in CubicWeb views. In this part, we will see how to customize our web application using different methods : with pyramid views using jinja2 templates and with React.
Pyramid and Jinja2#
React in a CubicWeb view#
In this section, we want to add a map in museum pages to display where is the museum associated with the page.
To do this, we will use React simple maps, a React library. Our goal is to add a react component inside our museum primary view.
First, we will setup our environment. At logilab, we use Typescript when it is possible, so we will use it also in this tutorial. As module builder, we will use Webpack.
Thus, we need to create three files at the root of our cube: package.json, tsconfig.json and webpack.config.js. A lot of documentation can be find on the Web about how to configure a React/Typescript environment, so we are not going to dwell on it in this tutorial; and we will simply copy and paste the following files.
package.json:
{
"name": "cubicweb_tuto",
"version": "1.0.0",
"description": "Summary ------- A cube for new CW tutorial",
"directories": {
"test": "test"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"watch": "webpack --watch --mode=development"
},
"author": "Logilab",
"license": "GPL-2.0-or-later",
"dependencies": {
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@types/react-simple-maps": "^1.0.3",
"prop-types": "^15.7.2",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-simple-maps": "^2.3.0",
"ts-loader": "^8.0.14",
"typescript": "^4.1.3",
"webpack": "^5.18.0",
"webpack-cli": "^4.4.0"
}
}
tsconfig.json:
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"jsx": "react",
"strict": true,
"esModuleInterop": true
}
}
webpack.config.js:
const path = require("path");
module.exports = {
entry: {
"map.js": "./appjs/geomap.tsx",
},
output: {
filename: "[name]",
path: path.resolve(__dirname, "./cubicweb_tuto/data/")
},
resolve: {
extensions: [".tsx", ".ts", ".jsx", ".js"]
},
module: {
rules: [
{
test: [/\.tsx?$/],
exclude: /node_modules/,
use: ["ts-loader"]
}
]
},
plugins: []
};
Now we have our configuration files, we have to install NodeJS and then install our project using npm.
sudo apt-get install nodejs
npm install
They are two last things to do:
create a component to display a museum on the map;
integrate our component in a CubicWeb view.
By convention, we put our js files in a appjs directory, and bundle are built in cubicweb_tuto/data (as you can see in our webpack.config.js). Then, we will create a file geomap.tsx in appjs/.
For our component, we will need three parameters: our museum name, its latitude and its longitude. These parameters will be defined in our CubicWeb view when we will call our script. Our file geomap.tsx can be written like this:
import React from 'react';
import ReactDOM from 'react-dom';
import {
ComposableMap,
Geographies,
Geography,
Marker,
Point
} from "react-simple-maps";
const geoUrl = "https://raw.githubusercontent.com/zcreativelabs/react-simple-maps/master/topojson-maps/world-110m.json";
declare const data: {
name: string,
latitude: number,
longitude: number,
}
const MapChart = () => {
return (
<ComposableMap>
<Geographies geography={geoUrl}>
{({ geographies }) =>
geographies
.map(geo => (
<Geography
key={geo.rsmKey}
geography={geo}
fill="#EAEAEC"
stroke="#D6D6DA"
/>
))
}
</Geographies>
<Marker coordinates={[data.longitude, data.latitude] as Point}>
<g
fill="none"
stroke="#FF5533"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
transform="translate(-12, -24)"
>
<circle cx="12" cy="10" r="3" />
<path d="M12 21.7C17.3 17 20 13 20 10a8 8 0 1 0-16 0c0 3 2.7 6.9 8 11.7z" />
</g>
<text
textAnchor="middle"
y={10}
style={{ fontFamily: "system-ui", fill: "#5D5A6D" }}
>
{data.name}
</text>
</Marker>
</ComposableMap>
);
};
function App() {
return <MapChart/>
}
const root = document.getElementById("awesome-map");
ReactDOM.render(<App/>, root);
Now we will override the render_entity(self, entity) function of the Museum PrimaryView, in
cubicweb-tuto/views.py
to add:
the bundle javascript including our component;
a div with the id awesome-map which will be used by our component.
class MuseumPrimaryView(PrimaryView):
__select__ = is_instance("Museum")
def render_entity(self, entity):
self.render_entity_toolbox(entity)
self.render_entity_title(entity)
# entity's attributes and relations, excluding meta data
# if the entity isn't meta itself
if self.is_primary():
boxes = self._prepare_side_boxes(entity)
else:
boxes = None
if boxes or hasattr(self, "render_side_related"):
self.w('<table width="100%"><tr><td style="width: 75%">')
self.w('<div class="mainInfo">')
self.content_navigation_components("navcontenttop")
self.render_entity_attributes(entity)
if self.main_related_section:
self.render_entity_relations(entity)
self.render_map(entity)
self.content_navigation_components("navcontentbottom")
self.w("</div>")
# side boxes
if boxes or hasattr(self, "render_side_related"):
self.w("</td><td>")
self.w('<div class="primaryRight">')
self.render_side_boxes(boxes)
self.w("</div>")
self.w("</td></tr></table>")
def render_entity_title(self, entity):
"""Renders the entity title, by default using entity's
:meth:`dc_title()` method.
"""
self.w(f"<h1>{entity.title_with_city}</h1>")
def render_map(self, entity):
"""Renders a map displaying where the museum is."""
if not (entity.latitude and entity.longitude):
return
js_file = f"{self._cw.vreg.config.datadir_url}map.js"
data = json_dumps(entity)
self.w('<div id="awesome-map"></div>')
self.w(
f"""
<script type="text/javascript">
const data = {data};
</script>
<script src={js_file}></script>
"""
)
Most part of render_entity(self, entity) are the same as its definition in PrimaryView, except that we add a call to render_map(self, entity); which will add a div tag with a specific id and a script tag adding our javascript bundle, and define variables containing information to display a museum on the map. The specific id must be the same as the one we defined in our javascript file, awesome-map.
Now, it’s time to build the javascript bundle using:
npm run build
And then, run our application:
cubicweb-ctl pyramid -D tuto_instance
We now have a world map displaying the location of our museum on museum pages. A lot of things could be done to have a better result, like center the map on the museum, but it’s out of the scope of this tutorial.

React in a Pyramid view#
Data management with CubicWeb#
Import data#
With our application customized, let’s see how to import more data. There is several ways to import data in CubicWeb. In our tutorial, we want to import our museums from a csv file. This file is provided by the France’s Ministry of Culture, and is available here.
There are several ways to import data in CubicWeb; in this tutorial, we will use one of them, the others are described here: Data Import.
First of all, we define in tuto/cubicweb_tuto/dataimport.py
a function which will read a file
from a filepath and create the corresponding entities, using a CubicWeb connection:
import csv
def import_museums(cnx, filepath):
existing_cities = dict(cnx.execute("Any Z, C Where C is City, C zip_code Z"))
existing_cities_nb = len(existing_cities)
created_museum_nb = 0
with open(filepath) as fileobj:
reader = csv.DictReader(fileobj, delimiter=";")
for record in reader:
museum_name = record["NOM DU MUSEE"]
street = record["ADR"]
zip_code = record["CP"]
city_name = record["VILLE"]
try:
lat, lng = record["coordonnees_finales"].split(",")
lat_long = {
"latitude": lat,
"longitude": lng,
}
except (AttributeError, ValueError):
lat_long = {}
try:
city = existing_cities[zip_code]
except KeyError:
city = cnx.create_entity("City", name=city_name, zip_code=zip_code)
existing_cities[zip_code] = city.eid
cnx.create_entity(
"Museum",
name=museum_name,
postal_address=f"{street}, {zip_code} {city_name}",
is_in=city,
**lat_long,
)
created_museum_nb += 1
print(
"Import finished! {} existing cities, {} cities created, {} museums created.".format(
existing_cities_nb,
len(existing_cities) - existing_cities_nb,
created_museum_nb,
)
)
To be sure we don’t have several time the same city, we first query CubicWeb to ask for all existing city. To do this, we use a specific language called RQL. As for SPARQL, it’s a query language designed to query linked data. See Introduction for more information about it.
Then, we put existing cities in a dictionary, using zip code as key. In the real world, a zip code can concern several cities, but it’s not really an issue in this tutorial.
For each line of our csv file, we will check if we already have the city in our base. If not, we create it. Then, we create our Museum entity with all needed arguments.
To create an entity, we use the create_entity method of the CubicWeb connection. This method takes
as first argument the type of the entity (ie: the name of the corresponding class previously
defined in tuto/cubicweb_tuto/schema.py
), and then all arguments of the entity type.
In our example, a city needs a name and a zip code. A museum needs a name, a postal address, a latitude, a longitude and a city. As is_in is a relation, we give to the corresponding argument the eid of the city.
Note
As we have defined Museum in the schema, we have to link each instance of Museum to a City, that’s why we create the city before the museum, and give this city as argument of the museum.
If the city wasn’t mandatory, we could add it later, using:
museum_entity.cw_set(is_in=city)
To use our function we need to create a CubicWeb command that will call it. First, we create a file
tuto/cubicweb_tuto/ccplugin.py
(the name doesn’t matter, but it is commonly used for all new
CubicWeb commands). Then, we write the following code:
from cubicweb.cwctl import CWCTL
from cubicweb.toolsutils import Command
from cubicweb.utils import admincnx
from cubicweb_tuto.dataimport import import_museums
@CWCTL.register
class ImportMuseums(Command):
"""
Import Museums and Cities from a CSV from:
https://data.culture.gouv.fr/explore/dataset/liste-et-localisation-des-musees-de-france/export/
"""
arguments = "<instance> <csv_file>"
name = "import-museums"
min_args = max_args = 2
def run(self, args):
appid, csv_file = args[:2]
with admincnx(appid) as cnx:
import_museums(cnx, csv_file)
cnx.commit()
@CWCTL.register
allows to register the command and then make it available withcubicweb-ctl
command by itsname
.arguments
defines which arguments take our command.name
defines the name of the command.with admincnx(appid) as cnx
allows to have an admin access to our instance, and then be able to create new entities.
Thus, to execute our import command, we just have to enter in our shell (within our virtual env):
cubicweb-ctl import-museums tuto_instance <path_to_the_csv>
After this script, we should be able to see that we have much more cities and museums by visiting the homepage of our CubicWeb instance:

RDF serialisation#
Content negotiation#
Setup and Administration#
This part is for installation and administration of the CubicWeb framework and instances based on that framework.
Install a CubicWeb environment#
Official releases are available from the CubicWeb.org forge and from PyPI. Since CubicWeb is developed using Agile software development techniques, releases happen frequently. In a version numbered X.Y.Z, X changes after a few years when the API breaks, Y changes after a few weeks when features are added and Z changes after a few days when bugs are fixed.
There are several ways to install CubicWeb depending on your needs:
If you are a power-user and need the very latest features, you can choose the following methods:
Additional configuration can be found in the section Configure a CubicWeb environment for better control and advanced features of CubicWeb.
Installing Dependencies#
No matter your installation method, you will need to install the following Debian packages:
apt install gettext graphviz
gettext
is used for translations (see Internationalization), and graphviz
to display relation schemas within the website.
Installing CubicWeb#
Docker install#
Detailed instructions on how to deploy CubicWeb using docker can be found on the docker hub.
Images are built using the source code available in the docker-cubicweb repository.
Virtualenv install#
CubicWeb can be safely installed, used and contained inside a virtualenv. To create and activate a virtualenv, use the following commands:
pip install --user virtualenv
virtualenv venv
source venv/bin/activate
Then you can use either pip or easy_install to install CubicWeb inside an activated virtual environment.
pip install#
pip is a python tool that helps downloading, building, installing, and managing Python packages and their dependencies. It is fully compatible with virtualenv and installs the packages from sources published on the The Python Package Index.
A working compilation chain is needed to build modules which include C extensions. If you really do not want to compile anything, installing lxml, and libgecode will help.
For Debian, these minimal dependencies can be obtained by doing:
apt install gcc python3-pip python3-dev python3-lxml
or, if you prefer to get as much as possible from pip:
apt install gcc python3-pip python3-dev libxslt1-dev libxml2-dev
For Windows, you can install pre-built packages (possible source). For a minimal setup, install:
setuptools http://www.lfd.uci.edu/~gohlke/pythonlibs/#setuptools
libxml-python http://www.lfd.uci.edu/~gohlke/pythonlibs/#libxml-python>
Make sure to choose the correct architecture and version of Python.
Finally, install CubicWeb and its dependencies by running:
pip install cubicweb
Install from source#
You can download the archive containing the sources from CubicWeb forge downloads section.
Make sure you also have all the dependencies installed.
Once uncompressed, you can install the framework from inside the uncompressed folder with:
python3 setup.py install
Or you can run CubicWeb directly from the source directory by setting the resource mode to user. This will ease the development with the framework.
Install from version control system#
To keep-up with on-going development, clone the Mercurial repository:
hg clone -u 'last(tag())' https://forge.extranet.logilab.fr/cubicweb/cubicweb # stable version
hg clone https://forge.extranet.logilab.fr/cubicweb/cubicweb # development branch
Make sure you also have all the Installation dependencies.
Installing cubes#
Many other cubes are available. Those cubes can help expanding the functionalities offered by CubicWeb. A list is available at PyPI or at the CubicWeb.org forge.
For example the blog cube can be installed using:
pip install cubicweb-blog
Configure a CubicWeb environment#
You can configure the database system of your choice:
For advanced features, have a look to:
Databases configuration#
Each instance can be configured with its own database connection information,
that will be stored in the instance’s sources
file. The database to use
will be chosen when creating the instance. CubicWeb is known to run with
Postgresql (recommended) and SQLite.
Other possible sources of data include CubicWeb, LDAP and Mercurial, but at least one relational database is required for CubicWeb to work. You do not need to install a backend that you do not intend to use for one of your instances. SQLite is not fit for production use, but it works well for testing and ships with Python, which saves installation time when you want to get started quickly.
PostgreSQL#
Many Linux distributions ship with the appropriate PostgreSQL packages. Basically, you need to install the following packages:
postgresql and postgresql-client, which will pull the respective versioned packages (e.g. postgresql-9.1 and postgresql-client-9.1) and, optionally,
a postgresql-plpython-X.Y package with a version corresponding to that of the aforementioned packages (e.g. postgresql-plpython-9.1). (Not needed now by default)
If you run postgres on another host than the CubicWeb repository, you should install the postgresql-client package on the CubicWeb host, and others on the database host.
For extra details concerning installation, please refer to the PostgreSQL project online documentation.
Database cluster#
If you already have an existing cluster and PostgreSQL server running, you do not need to execute the initilization step of your PostgreSQL database unless you want a specific cluster for CubicWeb databases or if your existing cluster doesn’t use the UTF8 encoding (see note below).
To initialize a PostgreSQL cluster, use the command initdb
:
$ initdb -E UTF8 -D /path/to/pgsql
Note: initdb
might not be in the PATH, so you may have to use its
absolute path instead (usually something like
/usr/lib/postgresql/9.4/bin/initdb
).
Notice the encoding specification. This is necessary since CubicWeb usually want UTF8 encoded database. If you use a cluster with the wrong encoding, you’ll get error like:
new encoding (UTF8) is incompatible with the encoding of the template database (SQL_ASCII)
HINT: Use the same encoding as in the template database, or use template0 as template.
Once initialized, start the database server PostgreSQL with the command:
$ postgres -D /path/to/psql
If you cannot execute this command due to permission issues, please make sure that your username has write access on the database.
$ chown username /path/to/pgsql
Database authentication#
The database authentication is configured in pg_hba.conf. It can be either set to ident sameuser or md5. If set to md5, make sure to use an existing user of your database. If set to ident sameuser, make sure that your client’s operating system user name has a matching user in the database. If not, please do as follow to create a user:
$ su
$ su - postgres
$ createuser -s -P <dbuser>
The option -P (for password prompt), will encrypt the password with the
method set in the configuration file pg_hba.conf
. If you do not use this
option -P, then the default value will be null and you will need to set it
with:
$ su postgres -c "echo ALTER USER <dbuser> WITH PASSWORD '<dbpassword>' | psql"
The above login/password will be requested when you will create an instance with cubicweb-ctl create to initialize the database of your instance.
Database creation#
If you create the database by hand (instead of using the cubicweb-ctl db-create tool), you may want to make sure that the local settings are properly set. For example, if you need to handle french accents properly for indexing and sorting, you may need to create the database with something like:
$ createdb --encoding=UTF-8 --locale=fr_FR.UTF-8 -t template0 -O <owner> <dbname>
Notice that the cubicweb-ctl db-create does database initialization that may requires a postgres superuser. That’s why a login/password is explicitly asked at this step, so you can use there a superuser without using this user when running the instance. Things that require special privileges at this step:
database creation, require the ‘create database’ permission
Where pgadmin is a postgres superuser.
SQLite#
SQLite has the great advantage of requiring almost no configuration. Simply use ‘sqlite’ as db-driver, and set path to the dabase as db-name. Don’t specify anything for db-user and db-password, they will be ignore anyway.
Note
SQLite is great for testing and to play with cubicweb but is not suited for production environments.
Cubicweb resources configuration#
Resource mode#
Standard resource mode#
A resource mode is a predefined set of settings for various resources directories, such as cubes, instances, etc. to ease development with the framework. There are two running modes with CubicWeb:
system: resources are searched / created in the system directories (eg usually requiring root access):
instances are stored in
<INSTALL_PREFIX>/etc/cubicweb.d
temporary files (such as pid file) in
<INSTALL_PREFIX>/var/run/cubicweb
where <INSTALL_PREFIX> is the detected installation prefix (‘/usr/local’ for instance).
user: resources are searched / created in the user home directory:
instances are stored in
~/etc/cubicweb.d
temporary files (such as pid file) in
/tmp
Within virtual environment#
When installed within a virtualenv, CubicWeb will look for instances data as in user mode by default, that is in $HOME/etc/cubicweb.d. However the CW_INSTANCES_DIR environment variable should be preferably used.
Custom resource location#
Notice that each resource path may be explicitly set using an environment variable if the default doesn’t suit your needs. Here are the default resource directories that are affected according to mode:
system:
CW_INSTANCES_DIR = <INSTALL_PREFIX>/etc/cubicweb.d/ CW_INSTANCES_DATA_DIR = <INSTALL_PREFIX>/var/lib/cubicweb/instances/ CW_RUNTIME_DIR = <INSTALL_PREFIX>/var/run/cubicweb/
user:
CW_INSTANCES_DIR = ~/etc/cubicweb.d/ CW_INSTANCES_DATA_DIR = ~/etc/cubicweb.d/ CW_RUNTIME_DIR = /tmp
Cubes search path is also affected, see the Cubes section.
Setting Cubicweb Mode#
By default, the mode is set to ‘system’ for standard installation. The mode is
set to ‘user’ if cubicweb is used from a mercurial repository. You can force
this by setting the CW_MODE
environment variable to either ‘user’ or
‘system’ so you can easily:
use system wide installation but user specific instances and all, without root privileges on the system (export CW_MODE=user)
use local checkout of cubicweb on system wide instances (requires root privileges on the system (export CW_MODE=system)
If you’ve a doubt about the mode you’re currently running, check the first line outputed by the cubicweb-ctl list command.
Development Mode (source)#
If .hg
directory is found into the cubicweb package, there are
specific resource rules.
<CW_SOFTWARE_ROOT> is the source checkout’s cubicweb
directory:
cubicweb migration files are searched in <CW_SOFTWARE_ROOT>/misc/migration instead of <INSTALL_PREFIX>/share/cubicweb/migration/.
Development Mode (virtualenv)#
If a virtualenv is found to be activated (i.e. a VIRTUAL_ENV variable is found
in environment), the virtualenv root is used as <INSTALL_PREFIX>. This, in
particular, makes it possible to work in setuptools development mode
(python setup.py develop
) without any further configuration.
Environment configuration#
Python#
If you installed CubicWeb by cloning the Mercurial shell repository or from source distribution, then you will need to update the environment variable PYTHONPATH by adding the path to cubicweb:
Add the following lines to either .bashrc
or .bash_profile
to
configure your development environment
export PYTHONPATH=/full/path/to/grshell-cubicweb
If you installed CubicWeb with packages, no configuration is required and your new cubes will be placed in /usr/share/cubicweb/cubes and your instances will be placed in /etc/cubicweb.d.
CubicWeb#
Here are all environment variables that may be used to configure CubicWeb:
- CW_MODE#
Resource mode: user or system, as explained in Resource mode.
- CW_INSTANCES_DIR#
Directory where cubicweb instances will be found.
- CW_INSTANCES_DATA_DIR#
Directory where cubicweb instances data will be written (backup file…)
- CW_RUNTIME_DIR#
Directory where pid files will be written
Deploy a CubicWeb application#
Deployment with uwsgi#
uWSGI is often used to deploy CubicWeb applications.
Short version is install uwsgi:
apt install uwsgi
Deploy a configuration file for your application /etc/uwsgi/apps-enabled/example.ini. Don’t forget to replace example with the instance name to deploy:
[uwsgi]
master = true
http = 0.0.0.0:8080
env = CW_INSTANCE=example
module = cubicweb.pyramid:wsgi_application()
processes = 2
threads = 8
plugins = http,python3
auto-procname = true
lazy-apps = true
log-master = true
disable-logging = true
You can run it manualliy with:
uwsgi --ini /etc/uwsgi/apps-enabled/example.ini
Apache configuration#
It is possible to use apache (for example) as proxy in front of uwsgi.
For this to work you have to activate the following apache modules :
rewrite
proxy
http_proxy
The command on Debian based systems for that is
a2enmod rewrite http_proxy proxy
/etc/init.d/apache2 restart
- Example
For an apache redirection of a site accessible via http://localhost/demo while cubicweb is actually running on port 8080::
ProxyPreserveHost On RewriteEngine On RewriteCond %{REQUEST_URI} ^/demo RewriteRule ^/demo$ /demo/ RewriteRule ^/demo/(.*) http://127.0.0.1:8080/$1 [L,P]
and we will configure the base-url in the all-in-one.conf of the instance::
base-url = http://localhost/demo
Deployment with SaltStack#
To deploy with SaltStack one can refer themselves to the cubicweb-formula.
Deployment with Docker#
To deploy in a docker container cluster, you should use our docker image. The source code is also in the forge. For a standard cube with no apt dependencies, the following dockerfile is fine:
FROM logilab/cubicweb:3.29
USER root
COPY . /src
RUN pip install -e /src
USER cubicweb
RUN docker-cubicweb-helper create-instance
To run your instance, don’t forget the port redirection and change the image name:
docker run --rm -it -p 8080:8080 example:latest
If you need to customize the variables in the files all-in-one.conf or sources, you should pass them as environnement variables. For example, the database name is read from CW_DB_NAME. The admin password is read from CW_PASSWORD. Also if the database is on the host, it has to be accessible from the container:
docker run --rm -it -p 8080:8080 --env-file ./.env -v /var/run/postgresql:/var/run/postgresql example:latest
If your instance needs a scheduler, it has to be run in a separate container from the same image:
docker run --rm -it --env-file ./.env -v /var/run/postgresql:/var/run/postgresql example:latest cubicweb-ctl scheduler instance
Don’t forget to change the image name example:latest and the instance name name.
Deployment with Kubernetes#
To deploy in a Kubernetes cluster, you can take inspiration from the deploy instructions included in the fresh cube. It includes nginx to serve static files, one container for the application and one for the scheduler and also an initContainer to automatically upgrade the database in case of new version.
cubicweb-ctl
tool#
cubicweb-ctl is the swiss knife to manage CubicWeb instances. The general syntax is
cubicweb-ctl <command> [options command] <arguments commands>
To view available commands
cubicweb-ctl
cubicweb-ctl --help
Please note that the commands available depends on the CubicWeb packages and cubes that have been installed.
To view the help menu on specific command
cubicweb-ctl <command> --help
Listing available cubes and instance#
list
, provides a list of the available configuration, cubes and instances.
Creation of a new cube#
Create your new cube cube
cubicweb-ctl newcube -d <target directory>
This will create a new cube <target directory>
.
Create an instance#
You must ensure ~/etc/cubicweb.d/ exists prior to this. On windows, the ‘~’ part will probably expand to ‘Documents and Settings/user’.
To create an instance from an existing cube, execute the following command
cubicweb-ctl create <cube_name> <instance_name>
This command will create the configuration files of an instance in
~/etc/cubicweb.d/<instance_name>
.
The tool cubicweb-ctl
executes the command db-create
and
db-init
when you run create
so that you can complete an
instance creation in a single command. But of course it is possible
to issue these separate commands separately, at a later stage.
Command to create/initialize an instance database#
db-create
, creates the system database of an instance (tables and extensions only)db-init
, initializes the system database of an instance (schema, groups, users, workflows…)
Run an instance#
To start an instance during development, use
cubicweb-ctl pyramid [-D] [-l <log-level>] <instance-id>
without -D
, the instance will be start in the background, as a daemon.
See The ‘pyramid’ command for more details.
In production, it is recommended to run CubicWeb through a WSGI server like
uWSGI or Gunicorn. See cubicweb.pyramid
more details.
Commands to maintain instances#
upgrade
, launches the existing instances migration when a new version of CubicWeb or the cubes installed is availableshell
, opens a (Python based) migration shell for manual maintenance of the instancedb-dump
, creates a dump of the system databasedb-restore
, restores a dump of the system databasedb-check
, checks data integrity of an instance. If the automatic correction is activated, it is recommanded to create a dump before this operation.schema-sync
, synchronizes the persistent schema of an instance with the instance schema. It is recommanded to create a dump before this operation.
Commands to maintain i18n catalogs#
i18ncubicweb
, regenerates messages catalogs of the CubicWeb libraryi18ncube
, regenerates the messages catalogs of a cubei18ninstance
, recompiles the messages catalogs of an instance. This is automatically done while upgrading.
See also chapter Internationalization.
Other commands#
delete
, deletes an instance (configuration files and database)
Creation of your first instance#
Instance creation#
Now that we created a cube, we can create an instance and access it via a web browser. We will use a all-in-one configuration to simplify things
cubicweb-ctl create -c all-in-one mycube myinstance
Note
Please note that we created a new cube for a demo purposes but you could have used an existing cube available in our standard library such as blog or person for example.
A series of questions will be prompted to you, the default answer is usually sufficient. You can anyway modify the configuration later on by editing configuration files. When a login/password are requested to access the database please use the credentials you created at the time you configured the database (PostgreSQL).
It is important to distinguish here the user used to access the database and the user used to login to the cubicweb instance. When an instance starts, it uses the login/password for the database to get the schema and handle low level transaction. But, when cubicweb-ctl create asks for a manager login/psswd of CubicWeb, it refers to the user you will use during the development to administrate your web instance. It will be possible, later on, to use this user to create other users for your final web instance.
Instance administration#
start / stop#
When this command is completed, the definition of your instance is
located in ~/etc/cubicweb.d/myinstance/*
. To launch it, you
just type
cubicweb-ctl pyramid -D myinstance
The option -D specifies the debug mode : the instance is not running in server mode and does not disconnect from the terminal, which simplifies debugging in case the instance is not properly launched. You can see how it looks by visiting the URL http://localhost:8080 (the port number depends of your configuration). To login, please use the cubicweb administrator login/password you defined when you created the instance.
To shutdown the instance, Crtl-C in the terminal window is enough. If you did not use the option -D, then type
cubicweb-ctl stop myinstance
This is it! All is settled down to start developping your data model…
Note
The output of cubicweb-ctl pyramid -D myinstance can be overwhelming. It is possible to reduce the log level with the –loglevel parameter as in cubicweb-ctl pyramid -D myinstance -l info to filter out all logs under info gravity.
upgrade#
A manual upgrade step is necessary whenever a new version of CubicWeb or a cube is installed, in order to synchronise the instance’s configuration and schema with the new code. The command is:
cubicweb-ctl upgrade myinstance
A series of questions will be asked. It always starts with a proposal to make a backup of your sources (where it applies). Unless you know exactly what you are doing (i.e. typically fiddling in debug mode, but definitely NOT migrating a production instance), you should answer YES to that.
The remaining questions concern the migration steps of CubicWeb, then of the cubes that form the whole application, in reverse dependency order.
In principle, if the migration scripts have been properly written and tested, you should answer YES to all questions.
Somtimes, typically while debugging a migration script, something goes wrong and the migration fails. Unfortunately the databse may be in an incoherent state. You have two options here:
fix the bug, restore the database and restart the migration process from scratch (quite recommended in a production environement)
try to replay the migration up to the last successful commit, that is answering NO to all questions up to the step that failed, and finish by answering YES to the remaining questions.
Configure an instance#
While creating an instance, a configuration file is generated in:
$ (CW_INSTANCES_DIR) / <instance> / <configuration name>.conf
For example:
/etc/cubicweb.d/myblog/all-in-one.conf
It is a simple text file in the INI format (http://en.wikipedia.org/wiki/INI_file). In the following description, each option name is prefixed with its own section and followed by its default value if necessary, e.g. “<section>.<option> [value].”
Note
At runtime, configuration options can be overriden by environments
variables which name follows the option name with -
replaced by _
and a CW_
prefix. For instance CW_BASE_URL=https://www.example.com
would override the base-url
configuration option.
Configuring the Web server#
- web.auth-model [cookie]
authentication mode, cookie or http
- web.realm
realm of the instance in http authentication mode
- web.http-session-time [0]
period of inactivity of an HTTP session before it closes automatically. Duration in seconds, 0 meaning no expiration (or more exactly at the closing of the browser client)
- main.anonymous-user, main.anonymous-password
login and password to use to connect to the RQL server with HTTP anonymous connection. CWUser account should exist.
- main.base-url
url base site to be used to generate the urls of web pages
Setting up the web client#
- web.embed-allowed
regular expression matching sites which could be “embedded” in the site (controllers ‘embed’)
- web.submit-url
url where the bugs encountered in the instance can be mailed to
RQL server configuration#
- main.host
host name if it can not be detected correctly
- main.pid-file
file where will be written the server pid
- main.uid
user account to use for launching the server when it is root launched by init
- main.session-time [30*60]
timeout of a RQL session
- main.query-log-file
file where all requests RQL executed by the server are written
Configuring e-mail#
RQL and web server side:
- email.mangle-mails [no]
indicates whether the email addresses must be displayed as is or transformed
RQL server side:
- email.smtp-host [mail]
hostname hosting the SMTP server to use for outgoing mail
- email.smtp-port [25]
SMTP server port to use for outgoing mail
- email.smtp-username
SMTP server username for authenticated email sending
- email.smtp-password
SMTP server password for authenticated email sending
- email.sender-name
name to use for outgoing mail of the instance
- email.sender-addr
address for outgoing mail of the instance
- email.default dest-addrs
destination addresses by default, if used by the configuration of the dissemination of the model (separated by commas)
- email.supervising-addrs
destination addresses of e-mails of supervision (separated by commas)
Configuring logging#
- main.log-threshold
level of filtering messages (DEBUG, INFO, WARNING, ERROR)
- main.log-file
file to write messages
Configuring persistent properties#
Other configuration settings are in the form of entities CWProperty in the database. It must be edited via the web interface or by RQL queries.
- ui.encoding
Character encoding to use for the web
- navigation.short-line-size
number of characters for “short” display
- navigation.page-size
maximum number of entities to show per results page
- navigation.related-limit
number of related entities to show up on primary entity view
- navigation.combobox-limit
number of entities unrelated to show up on the drop-down lists of the sight on an editing entity view
Cross-Origin Resource Sharing#
CubicWeb’s support for the CORS protocol is provided by the wsgicors middleware at the Pyramid level. For now, the provided implementation only deals with access to a CubicWeb instance as a whole. Support for a finer granularity may be considered in the future.
A few parameters can be set to configure the CORS capabilities of CubicWeb, the values are passed to the wsgicors.CORS() middleware constructor, hence the wsgicors documentation can be used for more details.
- access-control-allow-origin
comma-separated list of allowed origin domains or “*” for any domain
- access-control-allow-methods
comma-separated list of allowed HTTP methods
- access-control-allow-headers
comma-separated list of allowed HTTP custom headers (used in simple requests)
- access-control-expose-headers
comma-separated list of allowed HTTP custom headers (used in preflight requests)
- access-control-max-age
maximum age of cross-origin resource sharing (in seconds)
credentials is always set to true and is not configurable.
User interface for web site configuration#

This panel allows you to configure the appearance of your instance site. Six menus are available and we will go through each of them to explain how to use them.
UI#
This menu provides you a way to customize the user interface settings such as date format or encoding in the produced html. Follows the detailled list of available options :
ui.date-format : how to format date in the ui (“man strftime” for format description)
ui.datetime-format : how to format date and time in the ui (“man strftime” for format description)
ui.default-text-format : default text format for rich text fields.
ui.encoding : user interface encoding
ui.fckeditor :should html fields being edited using fckeditor (a HTML WYSIWYG editor). You should also select text/html as default text format to actually get fckeditor.
ui.float-format : how to format float numbers in the ui
ui.language : language of the user interface
ui.main-template : id of main template used to render pages
ui.site-title : site title, which is displayed right next to the logo in the header
ui.time-format : how to format time in the ui (“man strftime” for format description)
Actions#
This menu provides a way to configure the context in which you expect the actions to be displayed to the user and if you want the action to be visible or not. You must have notice that when you view a list of entities, an action box is available on the left column which display some actions as well as a drop-down menu for more actions.
The context available are :
mainactions : actions listed in the left box
moreactions : actions listed in the more menu of the left box
addrelated : add actions listed in the left box
useractions : actions listed in the first section of drop-down menu accessible from the right corner user login link
siteactions : actions listed in the second section of drop-down menu accessible from the right corner user login link
hidden : select this to hide the specific action
Boxes#
The instance has already a pre-defined set of boxes you can use right away. This configuration section allows you to place those boxes where you want in the instance interface to customize it.
The available boxes are :
actions box : box listing the applicable actions on the displayed data
boxes_blog_archives_box : box listing the blog archives
possible views box : box listing the possible views for the displayed data
rss box : RSS icon to get displayed data as a RSS thread
search box : search box
startup views box : box listing the configuration options available for the instance site, such as Preferences and Site Configuration
Components#
[WRITE ME]
Contextual components#
[WRITE ME]
Multiple sources of data#
Data sources include SQL, LDAP, RQL, mercurial and subversion.
LDAP integration#
Overview#
Using LDAP as a source for user credentials and information is quite easy. The most difficult part lies in building an LDAP schema or using an existing one.
At cube creation time, one is asked if more sources are wanted. LDAP is one possible option at this time. Of course, it is always possible to set it up later using the CWSource entity type, which we discuss there.
It is possible to add as many LDAP sources as wanted, which translates in as many CWSource entities as needed.
The general principle of the LDAP source is, given a proper configuration, to create local users matching the users available in the directory and deriving local user attributes from directory users attributes. Then a periodic task ensures local user information synchronization with the directory.
Users handled by such a source should not be edited directly from within the application instance itself. Rather, updates should happen at the LDAP server level.
Credential checks are _always_ done against the LDAP server.
Note
There are currently two ldap source types: the older ldapuser and the newer ldapfeed. The older will be deprecated anytime soon, as the newer has now gained all the features of the old and does not suffer from some of its illnesses.
The ldapfeed creates real CWUser entities, and then activate/deactivate them depending on their presence/absence in the corresponding LDAP source. Their attribute and state (activated/deactivated) are hence managed by the source mechanism; they should not be altered by other means (as such alterations may be overridden in some subsequent source synchronisation).
Configuration of an LDAPfeed source#
Additional sources are created at cube creation time or later through the user interface.
Configure an ldapfeed source from the user interface under Manage then data sources:
At this point type has been set to ldapfeed.
The parser attribute shall be set to ldapfeed.
The url attribute shall be set to an URL such as ldap://ldapserver.domain/.
The configuration attribute contains many options. They are described in detail in the next paragraph.
Options of an LDAPfeed source#
Let us enumerate the options by categories (LDAP server connection, LDAP schema mapping information).
LDAP server connection options:
auth-mode, (choices are simple, cram_md5, digest_md5, gssapi, support for the later being partial as of now)
auth-realm, realm to use when using gssapi/kerberos authentication
data-cnx-dn, user dn to use to open data connection to the ldap (eg used to respond to rql queries)
data-cnx-password, password to use to open data connection to the ldap (eg used to respond to rql queries)
start-tls, starting TLS before bind (valid values: “true”, “false”)
If the LDAP server accepts anonymous binds, then it is possible to leave data-cnx-dn and data-cnx-password empty. This is, however, quite unlikely in practice. Beware that the LDAP server might hide attributes such as “userPassword” while the rest of the attributes remain visible through an anonymous binding.
LDAP schema mapping options:
user-base-dn, base DN to lookup for users
user-scope, user search scope (valid values: “BASE”, “ONELEVEL”, “SUBTREE”)
user-classes, classes of user (with Active Directory, you want to say “user” here)
user-filter, additional filters to be set in the ldap query to find valid users
user-login-attr, attribute used as login on authentication (with Active Directory, you want to use “sAMAccountName” here)
user-default-group, name of a group in which ldap users will be by default. You can set multiple groups by separating them by a comma
user-attrs-map, map from ldap user attributes to cubicweb attributes (with Active Directory, you want to use sAMAccountName:login,mail:email,givenName:firstname,sn:surname)
Other notes#
Cubicweb is able to start if ldap cannot be reached… If some source ldap server cannot be used while an instance is running, the corresponding users won’t be authenticated but their status will not change (e.g. they will not be deactivated)
The user-base-dn is a key that helps cubicweb map CWUsers to LDAP users: beware updating it
When a user is removed from an LDAP source, it is deactivated in the CubicWeb instance; when a deactivated user comes back in the LDAP source, it (automatically) is activated again
You can use the
CWSourceHostConfig
to have variants for a source configuration according to the host the instance is running on. To do so go on the source’s view from the sources management view.
RQL logs#
You can configure the CubicWeb instance to keep a log
of the queries executed against your database. To do so,
edit the configuration file of your instance
.../etc/cubicweb.d/myapp/all-in-one.conf
and uncomment the
variable query-log-file
:
# web instance query log file
query-log-file=/tmp/rql-myapp.log
Web Frontend Development#
In this chapter, we will describe the core APIs for web development in the CubicWeb framework.
Publisher#
What happens when an HTTP request is issued ?
The story begins with the CubicWebPublisher.main_publish
method. We do not get upper in the bootstrap process because it is
dependant on the used HTTP library.
What main_publish does:
get a controller id and a result set from the path (this is actually delegated to the urlpublisher component)
the controller is then selected (if not, this is considered an authorization failure and signaled as such) and called
then either a proper result is returned, in which case the request/connection object issues a
commit
and returns the resultor error handling must happen:
ValidationErrors
pop up there and may lead to a redirect to a previously arranged url or standard error handling appliesan HTTP 500 error (Internal Server Error) is issued
Now, let’s turn to the controller. There are many of them in
cubicweb.web.views.basecontrollers
. We can just follow the
default view controller that is selected on a view path. See the
Controllers chapter for more information on controllers.
The View controller’s entry point is the publish method. It does the following:
compute the main view to be applied, using either the given result set or building one from a user provided rql string (rql and vid can be forced from the url GET parameters), that is:
compute the vid using the result set and the schema (see cubicweb.web.views.vid_from_rset)
handle all error cases that could happen in this phase
do some cache management chores
select a main template (typically TheMainTemplate, see chapter Templates)
call it with the result set and the computed view.
What happens next actually depends on the template and the view, but in general this is the rendering phase.
CubicWebPublisher API#
Controllers#
Overview#
Controllers are responsible for taking action upon user requests (loosely following the terminology of the MVC meta pattern).
The following controllers are provided out-of-the box in CubicWeb. We
list them by category. They are all defined in
(cubicweb.web.views.basecontrollers
).
Browsing:
the View controller is associated with most browsing actions within a CubicWeb application: it always instantiates a TheMainTemplate and lets the ResultSet/Views dispatch system build up the whole content; it handles
ObjectNotFound
andNoSelectableObject
errors that may bubble up to its entry point, in an end-user-friendly way (but other programming errors will slip through)the JSonpController is a wrapper around the
ViewController
that provides jsonp services. Padding can be specified with thecallback
request parameter. Only jsonexport / ejsonexport views can be used. If anothervid
is specified, it will be ignored and replaced by jsonexport. Request is anonymized to avoid returning sensitive data and reduce the risks of CSRF attacks;the Login/Logout controllers make effective user login or logout requests
Edition:
the Edit controller (see The edit controller) handles CRUD operations in response to a form being submitted; it works in close association with the Forms, to which it delegates some of the work
the
Form validator controller
provides form validation from Ajax context, using the Edit controller, to implement the classic form handling loop (user edits, hits submit/apply, validation occurs server-side by way of the Form validator controller, and the UI is decorated with failure information, either global or per-field , until it is valid)
Other:
the
SendMail controller
(web/views/basecontrollers.py) is reponsible for outgoing email notificationsthe MailBugReport controller (web/views/basecontrollers.py) allows to quickly have a reportbug feature in one’s application
the
cubicweb.web.views.ajaxcontroller.AjaxController
(cubicweb.web.views.ajaxcontroller
) provides services for Ajax calls, typically using JSON as a serialization format for input, and sometimes using either JSON or XML for output. See Ajax chapter for more information.
Registration#
All controllers (should) live in the ‘controllers’ namespace within the global registry.
Concrete controllers#
Most API details should be resolved by source code inspection, as the various controllers have differing goals. See for instance the The edit controller chapter.
cubicweb.web.controller
contains the top-level abstract
Controller class and its unimplemented entry point
publish(rset=None) method.
A handful of helpers are also provided there:
process_rql builds a result set from an rql query typically issued from the browser (and available through _cw.form[‘rql’])
validate_cache will force cache validation handling with respect to the HTTP Cache directives (that were typically originally issued from a previous server -> client response); concrete Controller implementations dealing with HTTP (thus, for instance, not the SendMail controller) may very well call this in their publication process.
The Request class (cubicweb.web.request)#
Overview#
A request instance is created when an HTTP request is sent to the web server. It contains informations such as form parameters, authenticated user, etc. It is a very prevalent object and is used throughout all of the framework and applications, as you’ll access to almost every resources through it.
A request represents a user query, either through HTTP or not (we also talk about RQL queries on the server side for example).
Here is a non-exhaustive list of attributes and methods available on request objects (grouped by category):
Browser control:
ie_browser: tells if the browser belong to the Internet Explorer family
User and identification:
user, instance of cubicweb.entities.authobjs.CWUser corresponding to the authenticated user
Session data handling
session.data is the dictionary of the session data; it can be manipulated like an ordinary Python dictionary
Edition (utilities for edition control):
cancel_edition: resets error url and cleans up pending operations
create_entity: utility to create an entity (from an etype, attributes and relation values)
datadir_url: returns the url to the merged external resources (CubicWeb’s web/data directory plus all data directories of used cubes)
edited_eids: returns the list of eids of entities that are edited under the current http request
eid_rset(eid): utility which returns a result set from an eid
entity_from_eid(eid): returns an entity instance from the given eid
encoding: returns the encoding of the current HTTP request
ensure_ro_rql(rql): ensure some rql query is a data request
etype_rset
form, dictionary containing the values of a web form
encoding, character encoding to use in the response
HTTP
authmode: returns a string describing the authentication mode (http, cookie, …)
lang: returns the user agents/browser’s language as carried by the http request
demote_to_html(): in the context of an XHTML compliant browser, this will force emission of the response as an HTML document (using the http content negociation)
Cookies handling
get_cookie(), returns a dictionary containing the value of the header HTTP ‘Cookie’
set_cookie(cookie, key, maxage=300), adds a header HTTP Set-Cookie, with a minimal 5 minutes length of duration by default (maxage = None returns a session cookie which will expire when the user closes the browser window)
remove_cookie(cookie, key), forces a value to expire
URL handling
build_url(__vid, *args, **kwargs): return an absolute URL using params dictionary key/values as URL parameters. Values are automatically URL quoted, and the publishing method to use may be specified or will be guessed.
build_url_params(**kwargs): returns a properly prepared (quoted, separators, …) string from the given parameters
url(), returns the full URL of the HTTP request
base_url(), returns the root URL of the web application
relative_path(), returns the relative path of the request
Web resource (.css, .js files, etc.) handling:
add_css(cssfiles): adds the given list of css resources to the current html headers
add_js(jsfiles): adds the given list of javascript resources to the current html headers
add_onload(jscode): inject the given jscode fragment (a unicode string) into the current html headers, wrapped inside a document.ready(…) or another ajax-friendly one-time trigger event
add_header(header, values): adds the header/value pair to the current html headers
status_out: control the HTTP status of the response
And more…
set_content_type(content_type, filename=None), adds the header HTTP ‘Content-Type’
get_header(header), returns the value associated to an arbitrary header of the HTTP request
set_header(header, value), adds an arbitrary header in the response
execute(*args, **kwargs), executes an RQL query and return the result set
property_value(key), properties management (CWProperty)
dictionary data to store data to share informations between components while a request is executed
Please note that this class is abstract and that a concrete implementation will be provided by the frontend web used. For the views or others that are executed on the server side, most of the interface of Request is defined in the session associated to the client.
API#
The elements we gave in overview for above are built in three layers,
from cubicweb.req.RequestSessionBase
, cubicweb.repoapi.Connection
and
cubicweb.web.ConnectionCubicWebRequestBase
.
- class cubicweb.req.RequestSessionBase(*args, **kwargs)#
- class cubicweb.repoapi.Connection(repo, user)[source]#
Repository Connection
Holds all connection related data
Database connection resources:
hooks_in_progress
, boolean flag telling if the executing query is coming from a repoapi connection or is a query from within the repository (e.g. started by hooks)cnxset
, the connections set to use to execute queries on sources. If the transaction is read only, the connection set may be freed between actual queries. This allows multiple connections with a reasonably low connection set pool size.mode
, string telling the connections set handling mode, may be one of ‘read’ (connections set may be freed), ‘write’ (some write was done in the connections set, it can’t be freed before end of the transaction), ‘transaction’ (we want to keep the connections set during all the transaction, with or without writing)Shared data:
data
is a dictionary bound to the underlying session, who will be present for the life time of the session. This may be useful for web clients that rely on the server for managing bits of session-scoped data.transaction_data
is a dictionary cleared at the end of the transaction. Hooks and operations may put arbitrary data in there.Internal state:
pending_operations
, ordered list of operations to be processed on commit/rollbackcommit_state
, describing the transaction commit state, may be one of None (not yet committing), ‘precommit’ (calling precommit event on operations), ‘postcommit’ (calling postcommit event on operations), ‘uncommitable’ (someValidationError
orUnauthorized
error has been raised during the transaction and so it must be rolled back).Hooks controls:
- deny_all_hooks_but(*categories)[source]#
Context manager to disable all hooks but those in the given categories.
- allow_all_hooks_but(*categories)[source]#
Context manager to enable all hooks but those in the given categories.
Security level Management:
read_security
andwrite_security
, boolean flags telling if read/write security is currently activated.- add_operation(operation, index=None)[source]#
add an operation to be executed at the end of the transaction
- add_relation(fromeid, rtype, toeid)[source]#
provide direct access to the repository method to add a relation.
This is equivalent to the following rql query:
SET X rtype Y WHERE X eid fromeid, T eid toeid
without read security check but also all the burden of rql execution. You may use this in hooks when you know both eids of the relation you want to add.
- add_relations(relations)[source]#
set many relation using a shortcut similar to the one in add_relation
relations is a list of 2-uples, the first element of each 2-uple is the rtype, and the second is a list of (fromeid, toeid) tuples
- added_in_transaction(eid)[source]#
return True if the entity of the given eid is being created in the current transaction
- allow_all_hooks_but(*categories)[source]#
Context manager to enable all hooks but those in the given categories.
- commit_state#
(None, ‘precommit’, ‘postcommit’, ‘uncommitable’)
- critical(msg, *args, **kwargs)#
Log ‘msg % args’ with severity ‘CRITICAL’.
To pass exception information, use the keyword argument exc_info with a true value, e.g.
logger.critical(“Houston, we have a %s”, “major disaster”, exc_info=1)
- debug(msg, *args, **kwargs)#
Log ‘msg % args’ with severity ‘DEBUG’.
To pass exception information, use the keyword argument exc_info with a true value, e.g.
logger.debug(“Houston, we have a %s”, “thorny problem”, exc_info=1)
- delete_relation(fromeid, rtype, toeid)[source]#
provide direct access to the repository method to delete a relation.
This is equivalent to the following rql query:
DELETE X rtype Y WHERE X eid fromeid, T eid toeid
without read security check but also all the burden of rql execution. You may use this in hooks when you know both eids of the relation you want to delete.
- deleted_in_transaction(eid)[source]#
return True if the entity of the given eid is being deleted in the current transaction
- deny_all_hooks_but(*categories)[source]#
Context manager to disable all hooks but those in the given categories.
- error(msg, *args, **kwargs)#
Log ‘msg % args’ with severity ‘ERROR’.
To pass exception information, use the keyword argument exc_info with a true value, e.g.
logger.error(“Houston, we have a %s”, “major problem”, exc_info=1)
- exception(msg, *args, exc_info=True, **kwargs)#
Convenience method for logging an ERROR with exception information.
- execute(rql, kwargs=None, build_descr=True)[source]#
db-api like method directly linked to the querier execute method.
See
cubicweb.dbapi.Cursor.execute()
documentation.
- info(msg, *args, **kwargs)#
Log ‘msg % args’ with severity ‘INFO’.
To pass exception information, use the keyword argument exc_info with a true value, e.g.
logger.info(“Houston, we have a %s”, “interesting problem”, exc_info=1)
- is_hook_activated(hook)[source]#
return a boolean telling if the given hook class is currently activated or not
- is_hook_category_activated(category)[source]#
return a boolean telling if the given category is currently activated or not
- pending_operations#
ordered list of operations to be processed on commit/rollback
- repo#
server.Repository object
- running_hooks_ops()[source]#
this context manager should be called whenever hooks or operations are about to be run (but after hook selection)
It will help the undo logic record pertinent metadata or some hooks to run (or not) depending on who/what issued the query.
- system_sql(sql, args=None, rollback_on_failure=True, rql_query_tracing_token=None)[source]#
return a sql cursor on the system database
- transaction_actions(txuuid, public=True)[source]#
Return an ordered list of actions effectued during that transaction.
If public is true, return only ‘public’ actions, i.e. not ones triggered under the cover by hooks, else return all actions.
raise NoSuchTransaction if the transaction is not found or if the user is not allowed (eg not in managers group).
- transaction_data#
dict containing arbitrary data cleared at the end of the transaction
- transaction_info(txuuid)[source]#
Return transaction object for the given uid.
raise NoSuchTransaction if not found or if session’s user is not allowed (eg not in managers group and the transaction doesn’t belong to him).
- undo_transaction(txuuid)[source]#
Undo the given transaction. Return potential restoration errors.
raise NoSuchTransaction if not found or if user is not allowed (eg not in managers group).
- undoable_transactions(ueid=None, **actionfilters)[source]#
Return a list of undoable transaction objects by the connection’s user, ordered by descendant transaction time.
Managers may filter according to user (eid) who has done the transaction using the ueid argument. Others will only see their own transactions.
Additional filtering capabilities is provided by using the following named arguments:
etype to get only transactions creating/updating/deleting entities of the given type
eid to get only transactions applied to entity of the given eid
action to get only transactions doing the given action (action in ‘C’, ‘U’, ‘D’, ‘A’, ‘R’). If etype, action can only be ‘C’, ‘U’ or ‘D’.
public: when additional filtering is provided, they are by default only searched in ‘public’ actions, unless a public argument is given and set to false.
- warning(msg, *args, **kwargs)#
Log ‘msg % args’ with severity ‘WARNING’.
To pass exception information, use the keyword argument exc_info with a true value, e.g.
logger.warning(“Houston, we have a %s”, “bit of a problem”, exc_info=1)
RQL search bar#
The RQL search bar is a visual component, hidden by default, the tiny search input being enough for common use cases.
An autocompletion helper is provided to help you type valid queries, both in terms of syntax and in terms of schema validity.
How search is performed#
You can use the rql search bar to either type RQL queries, plain text queries or standard shortcuts such as <EntityType> or <EntityType> <attrname> <value>.
Ultimately, all queries are translated to rql since it’s the only
language understood on the server (data) side. To transform the user
query into RQL, CubicWeb uses the so-called magicsearch component,
defined in cubicweb.web.views.magicsearch
, which in turn
delegates to a number of query preprocessor that are responsible of
interpreting the user query and generating corresponding RQL.
The code of the main processor loop is easy to understand:
for proc in self.processors:
try:
return proc.process_query(uquery, req)
except (RQLSyntaxError, BadRQLQuery):
pass
The idea is simple: for each query processor, try to translate the query. If it fails, try with the next processor, if it succeeds, we’re done and the RQL query will be executed.
The View system#
This chapter aims to describe the concept of a view used all along the development of a web application and how it has been implemented in CubicWeb.
Principles#
We’ll start with a description of the interface providing a basic understanding of the available classes and methods, then detail the view selection principle.
A View is an object responsible for the rendering of data from the model into an end-user consummable form. They typically churn out an XHTML stream, but there are views concerned with email other non-html outputs.
Discovering possible views#
It is possible to configure the web user interface to have a left box showing all the views than can be applied to the current result set.
To enable this, click on your login at the top right corner. Chose “user preferences”, then “boxes”, then “possible views box” and check “visible = yes” before validating your changes.
The views listed there we either not selected because of a lower score, or they were deliberately excluded by the main template logic.
Basic class for views#
Class View
#
The basic interface for views is as follows (remember that the result set has a tabular structure with rows and columns, hence cells):
render(**context), render the view by calling call or cell_call depending on the context
call(**kwargs), call the view for a complete result set or null (the default implementation calls cell_call() on each cell of the result set)
cell_call(row, col, **kwargs), call the view for a given cell of a result set (row and col being integers used to access the cell)
url(), returns the URL enabling us to get the view with the current result set
wview(__vid, rset, __fallback_vid=None, **kwargs), call the view of identifier __vid on the given result set. It is possible to give a fallback view identifier that will be used if the requested view is not applicable to the result set.
html_headers(), returns a list of HTML headers to be set by the main template
page_title(), returns the title to use in the HTML header title
Other basic view classes#
Here are some of the subclasses of View
defined in cubicweb.view
that are more concrete as they relate to data rendering within the application:
Examples of views class#
Using templatable, content_type and HTTP cache configuration
class RSSView(XMLView):
__regid__ = 'rss'
title = _('rss')
templatable = False
content_type = 'text/xml'
http_cache_manager = MaxAgeHTTPCacheManager
cache_max_age = 60*60*2 # stay in http cache for 2 hours by default
Using a custom selector
class SearchForAssociationView(EntityView):
"""view called by the edition view when the user asks
to search for something to link to the edited eid
"""
__regid__ = 'search-associate'
title = _('search for association')
__select__ = one_line_rset() & match_search_state('linksearch') & is_instance('Any')
XML views, binaries views…#
For views generating other formats than HTML (an image generated dynamically for example), and which can not simply be included in the HTML page generated by the main template (see above), you have to:
set the attribute templatable of the class to False
set, through the attribute content_type of the class, the MIME type generated by the view to application/octet-stream or any relevant and more specialised mime type
For views dedicated to binary content creation (like dynamically generated images), we have to set the attribute binary of the class to True (which implies that templatable == False, so that the attribute w of the view could be replaced by a binary flow instead of unicode).
Templates#
Templates are the entry point for the CubicWeb view system. As seen in Discovering possible views, there are two kinds of views: the templatable and non-templatable.
Non-templatable views#
Non-templatable views are standalone. They are responsible for all the details such as setting a proper content type (or mime type), the proper document headers, namespaces, etc. Examples are pure xml views such as RSS or Semantic Web views (SIOC, DOAP, FOAF, Linked Data, etc.), and views which generate binary files (pdf, excel files, etc.)
To notice that a view is not templatable, you just have to set the view’s class attribute templatable to False. In this case, it should set the content_type class attribute to the correct MIME type. By default, it is text/xhtml. Additionally, if your view generate a binary file, you have to set the view’s class attribute binary to True too.
Templatable views#
Templatable views are not concerned with such pesky details. They leave it to the template. Conversely, the template’s main job is to:
set up the proper document header and content type
define the general layout of a document
invoke adequate views in the various sections of the document
Look at cubicweb.web.views.basetemplates
and you will find the base
templates used to generate (X)HTML for your application. The most important
template there is TheMainTemplate
.
TheMainTemplate#
Layout and sections#
A page is composed as indicated on the schema below :

The sections dispatches specific views:
header: the rendering of the header is delegated to the htmlheader view, whose default implementation can be found in
basetemplates.py
and which does the following things:inject the favicon if there is one
inject the global style sheets and javascript resources
call and display a link to an rss component if there is one available
it also sets up the page title, and fills the actual header section with top-level components, using the header view, which:
tries to display a logo, the name of the application and the breadcrumbs
provides a login status area
provides a login box (hiden by default)
left column: this is filled with all selectable boxes matching the left context (there is also a right column but nowadays it is seldom used due to bad usability)
contentcol: this is the central column; it is filled with:
the rqlinput view (hidden by default)
the applmessages component
the contentheader view which in turns dispatches all available content navigation components having the navtop context (this is used to navigate through entities implementing the IPrevNext interface)
the view that was given as input to the template’s call method, also dealing with pagination concerns
the contentfooter
footer: adds all footer actions
Note
How and why a view object is given to the main template is explained in the Publisher chapter.
Configure the main template#
You can overload some methods of the
TheMainTemplate
, in order to fulfil
your needs. There are also some attributes and methods which can be defined on a
view to modify the base template behaviour:
paginable: if the result set is bigger than a configurable size, your result page will be paginated by default. You can set this attribute to False to avoid this.
binary: boolean flag telling if the view generates some text or a binary stream. Default to False. When view generates text argument given to self.w must be a unicode string, encoded string otherwise.
content_type, view’s content type, default to ‘text/xhtml’
templatable, boolean flag telling if the view’s content should be returned directly (when False) or included in the main template layout (including header, boxes and so on).
page_title(), method that should return a title that will be set as page title in the html headers.
html_headers(), method that should return a list of HTML headers to be included the html headers.
You can also modify certain aspects of the main template of a page when building a url or setting these parameters in the req.form:
__notemplate, if present (whatever the value assigned), only the content view is returned
__force_display, if present and its value is not null, no pagination whatever the number of entities to display (e.g. similar effect as view’s paginable attribute described above.
__method, if the result set to render contains only one entity and this parameter is set, it refers to a method to call on the entity by passing it the dictionary of the forms parameters, before going the classic way (through step 1 and 2 described juste above)
vtitle, a title to be set as <h1> of the content
Other templates#
There are also the following other standard templates:
cubicweb.web.views.basetemplates.LogInTemplate
cubicweb.web.views.basetemplates.LogOutTemplate
cubicweb.web.views.basetemplates.ErrorTemplate
specializesTheMainTemplate
to do proper end-user output if an error occurs during the computation of TheMainTemplate (it is a fallback view).
The Primary View#
By default, CubicWeb provides a view that fits every available entity type. This is the first view you might be interested in modifying. It is also one of the richest and most complex.
It is automatically selected on a one line result set containing an entity.
It lives in the cubicweb.web.views.primary
module.
The primary view is supposed to render a maximum of informations about the entity.
Layout#
The primary view has the following layout.

Primary view configuration#
If you want to customize the primary view of an entity, overriding the primary view class may not be necessary. For simple adjustments (attributes or relations display locations and styles), a much simpler way is to use uicfg.
Attributes/relations display location#
In the primary view, there are three sections where attributes and relations can be displayed (represented in pink in the image above):
‘attributes’
‘relations’
‘sideboxes’
- Attributes can only be displayed in the attributes section (default
behavior). They can also be hidden. By default, attributes of type Password and Bytes are hidden.
For instance, to hide the title
attribute of the Blog
entity:
from cubicweb.web.views import uicfg
uicfg.primaryview_section.tag_attribute(('Blog', 'title'), 'hidden')
Relations can be either displayed in one of the three sections or hidden.
For relations, there are two methods:
tag_object_of
for modifying the primary view of the objecttag_subject_of
for modifying the primary view of the subject
These two methods take two arguments:
a triplet
(subject, relation_name, object)
, where subject or object can be replaced with'*'
the section name or
hidden
pv_section = uicfg.primaryview_section
# hide every relation `entry_of` in the `Blog` primary view
pv_section.tag_object_of(('*', 'entry_of', 'Blog'), 'hidden')
# display `entry_of` relations in the `relations`
# section in the `BlogEntry` primary view
pv_section.tag_subject_of(('BlogEntry', 'entry_of', '*'), 'relations')
Display content#
You can use primaryview_display_ctrl
to customize the display of attributes
or relations. Values of primaryview_display_ctrl
are dictionaries.
Common keys for attributes and relations are:
vid
: specifies the regid of the view for displaying the attribute or the relation.- If
vid
is not specified, the default value depends on the section: attributes
section: ‘reledit’ viewrelations
section: ‘autolimited’ viewsideboxes
section: ‘sidebox’ view
- If
order
: int used to control order within a section. When not specified, automatically set according to order in which tags are added.label
: label for the relations section or side boxshowlabel
: boolean telling whether the label is displayed
# let us remind the schema of a blog entry
class BlogEntry(EntityType):
title = String(required=True, fulltextindexed=True, maxsize=256)
publish_date = Date(default='TODAY')
content = String(required=True, fulltextindexed=True)
entry_of = SubjectRelation('Blog', cardinality='?*')
# now, we want to show attributes
# with an order different from that in the schema definition
view_ctrl = uicfg.primaryview_display_ctrl
for index, attr in enumerate('title', 'content', 'publish_date'):
view_ctrl.tag_attribute(('BlogEntry', attr), {'order': index})
By default, relations displayed in the ‘relations’ section are being displayed by the ‘autolimited’ view. This view will use comma separated values, or list view and/or limit your rset if there is too much items in it (and generate the “view all” link in this case).
You can control this view by setting the following values in the primaryview_display_ctrl relation tag:
limit, maximum number of entities to display. The value of the ‘navigation.related-limit’ cwproperty is used by default (which is 8 by default). If None, no limit.
use_list_limit, number of entities until which they should be display as a list (eg using the ‘list’ view). Below that limit, the ‘csv’ view is used. If None, display using ‘csv’ anyway.
subvid, the subview identifier (eg view that should be used of each item in the list)
Notice you can also use the filter key to set up a callback taking the related result set as argument and returning it filtered, to do some arbitrary filtering that can’t be done using rql for instance.
pv_section = uicfg.primaryview_section
# in `CWUser` primary view, display `created_by`
# relations in relations section
pv_section.tag_object_of(('*', 'created_by', 'CWUser'), 'relations')
# display this relation as a list, sets the label,
# limit the number of results and filters on comments
def filter_comment(rset):
return rset.filtered_rset(lambda x: x.e_schema == 'Comment')
pv_ctrl = uicfg.primaryview_display_ctrl
pv_ctrl.tag_object_of(('*', 'created_by', 'CWUser'),
{'vid': 'list', 'label': _('latest comment(s):'),
'limit': True,
'filter': filter_comment})
Warning
with the primaryview_display_ctrl
rtag, the subject or the
object of the relation is ignored for respectively tag_object_of
or
tag_subject_of
. To avoid warnings during execution, they should be set to
'*'
.
Example of customization and creation#
We’ll show you now an example of a primary
view and how to customize it.
If you want to change the way a BlogEntry
is displayed, just
override the method cell_call()
of the view primary
in
BlogDemo/views.py
.
from cubicweb.predicates import is_instance
from cubicweb.web.views.primary import Primaryview
class BlogEntryPrimaryView(PrimaryView):
__select__ = PrimaryView.__select__ & is_instance('BlogEntry')
def render_entity_attributes(self, entity):
self.w(u'<p>published on %s</p>' %
entity.publish_date.strftime('%Y-%m-%d'))
super(BlogEntryPrimaryView, self).render_entity_attributes(entity)
The above source code defines a new primary view for
BlogEntry
. The __reid__ class attribute is not repeated there since it
is inherited through the primary.PrimaryView class.
The selector for this view chains the selector of the inherited class with its own specific criterion.
The view method self.w()
is used to output data. Here lines
08-09 output HTML for the publication date of the entry.

Let us now improve the primary view of a blog
from logilab.mtconverter import xml_escape
from cubicweb.predicates import is_instance, one_line_rset
from cubicweb.web.views.primary import Primaryview
class BlogPrimaryView(PrimaryView):
__regid__ = 'primary'
__select__ = PrimaryView.__select__ & is_instance('Blog')
rql = 'Any BE ORDERBY D DESC WHERE BE entry_of B, BE publish_date D, B eid %(b)s'
def render_entity_relations(self, entity):
rset = self._cw.execute(self.rql, {'b' : entity.eid})
for entry in rset.entities():
self.w(u'<p>%s</p>' % entry.view('inblogcontext'))
class BlogEntryInBlogView(EntityView):
__regid__ = 'inblogcontext'
__select__ = is_instance('BlogEntry')
def cell_call(self, row, col):
entity = self.cw_rset.get_entity(row, col)
self.w(u'<a href="%s" title="%s">%s</a>' %
entity.absolute_url(),
xml_escape(entity.content[:50]),
xml_escape(entity.description))
This happens in two places. First we override the render_entity_relations method of a Blog’s primary view. Here we want to display our blog entries in a custom way.
At line 10, a simple request is made to build a result set with all
the entities linked to the current Blog
entity by the relationship
entry_of
. The part of the framework handling the request knows
about the schema and infers that such entities have to be of the
BlogEntry
kind and retrieves them (in the prescribed publish_date
order).
The request returns a selection of data called a result set. Result set objects have an .entities() method returning a generator on requested entities (going transparently through the ORM layer).
At line 13 the view ‘inblogcontext’ is applied to each blog entry to output HTML. (Note that the ‘inblogcontext’ view is not defined whatsoever in CubicWeb. You are absolutely free to define whole view families.) We juste arrange to wrap each blogentry output in a ‘p’ html element.
Next, we define the ‘inblogcontext’ view. This is NOT a primary view, with its well-defined sections (title, metadata, attribtues, relations/boxes). All a basic view has to define is cell_call.
Since views are applied to result sets which can be tables of data, we have to recover the entity from its (row,col)-coordinates (line 20). Then we can spit some HTML.
Warning
Be careful: all strings manipulated in CubicWeb are actually unicode strings. While web browsers are usually tolerant to incoherent encodings they are being served, we should not abuse it. Hence we have to properly escape our data. The xml_escape() function has to be used to safely fill (X)HTML elements from Python unicode strings.
Assuming we added entries to the blog titled MyLife, displaying it now allows to read its description and all its entries.

The “Click and Edit” (also reledit) View#
The principal way to update data through the Web UI is through the modify action on entities, which brings a full form. This is described in the HTML form construction chapter.
There is however another way to perform piecewise edition of entities
and relations, using a specific reledit (for relation edition)
view from the cubicweb.web.views.reledit
module.
This is typically applied from the default Primary View (see The Primary View) on the attributes and relation section. It makes small editions more convenient.
Of course, this can be used customely in any other view. Here come some explanation about its capabilities and instructions on the way to use it.
Using reledit#
Let’s start again with a simple example:
class Company(EntityType):
name = String(required=True, unique=True)
boss = SubjectRelation('Person', cardinality='1*')
status = SubjectRelation('File', cardinality='?*', composite='subject')
In some view code we might want to show these attributes/relations and allow the user to edit each of them in turn without having to leave the current page. We would write code as below:
company.view('reledit', rtype='name', default_value='<name>') # editable name attribute
company.view('reledit', rtype='boss') # editable boss relation
company.view('reledit', rtype='status') # editable attribute-like relation
If one wanted to edit the company from a boss’s point of view, one would have to indicate the proper relation’s role. By default the role is subject.
person.view('reledit', rtype='boss', role='object')
Each of these will provide with a different editing widget. The name attribute will obviously get a text input field. The boss relation will be edited through a selection box, allowing to pick another Person as boss. The status relation, given that it defines Company as a composite entity with one file inside, will provide additional actions
to add a File when there is one
to delete the File (if the cardinality allows it)
Moreover, editing the relation or using the add action leads to an embedded edition/creation form allowing edition of the target entity (which is File in our example) instead of merely allowing to choose amongst existing files.
The reledit_ctrl rtag#
The behaviour of reledited attributes/relations can be finely
controlled using the reledit_ctrl rtag, defined in
cubicweb.web.views.uicfg
.
This rtag provides four control variables:
default_value
: alternative default valueThe default value is what is shown when there is no value.
reload
: boolean, eid (to reload to) or function taking subjectand returning bool/eid This is useful when editing a relation (or attribute) that impacts the url or another parts of the current displayed page. Defaults to false.
rvid
: alternative view id (as str) for relation or compositeedition Default is ‘incontext’ or ‘csv’ depending on the cardinality. They can also be statically changed by subclassing ClickAndEditFormView and redefining _one_rvid (resp. _many_rvid).
edit_target
: ‘rtype’ (to edit the relation) or ‘related’ (toedit the related entity) This controls whether to edit the relation or the target entity of the relation. Currently only one-to-one relations support target entity edition. By default, the ‘related’ option is taken whenever the relation is composite and one-to-one.
Let’s see how to use these controls.
from logilab.mtconverter import xml_escape
from cubicweb.web.views.uicfg import reledit_ctrl
reledit_ctrl.tag_attribute(('Company', 'name'),
{'reload': lambda x:x.eid,
'default_value': xml_escape(u'<logilab tastes better>')})
reledit_ctrl.tag_object_of(('*', 'boss', 'Person'), {'edit_target': 'related'})
The default_value needs to be an xml escaped unicode string.
The edit_target tag on the boss relation being set to related will ensure edition of the Person entity instead (using a standard automatic form) of the association of Company and Person.
Finally, the reload key accepts either a boolean, an eid or a unicode string representing a url. If an eid is provided, it will be internally transformed into a url. The eid/url case helps when one needs to reload and the current url is inappropriate. A common case is edition of a key attribute, which is part of the current url. If one user changed the Company’s name from lozilab to logilab, reloading on http://myapp/company/lozilab would fail. Providing the entity’s eid, then, forces to reload on something like http://myapp/company/42, which always work.
Disable reledit#
By default, reledit is available on attributes and relations displayed in the ‘attribute’ section of the default primary view. If you want to disable it for some attribute or relation, you have use uicfg:
from cubicweb.web.views.uicfg import primaryview_display_ctrl as _pvdc
_pvdc.tag_attribute(('Company', 'name'), {'vid': 'incontext'})
To deactivate it everywhere it’s used automatically, you may use the code snippet below somewhere in your cube’s views:
from cubicweb.web.views import reledit
class DeactivatedAutoClickAndEditFormView(reledit.AutoClickAndEditFormView):
def _should_edit_attribute(self, rschema):
return False
def _should_edit_attribute(self, rschema, role):
return False
def registration_callback(vreg):
vreg.register_and_replace(DeactivatedAutoClickAndEditFormView,
reledit.AutoClickAndEditFormView)
Base views#
CubicWeb provides a lot of standard views, that can be found in
cubicweb.web.views
sub-modules.
A certain number of views are used to build the web interface, which apply to one
or more entities. As other appobjects, their identifier is what distinguish them
from each others. The most generic ones, found in
cubicweb.web.views.baseviews
, are described below.
You’ll probably want to customize one or more of the described views which are default, generic, implementations.
You will also find modules providing some specific services:
Startup views#
Startup views are views requiring no context, from which you usually start
browsing (for instance the index page). The usual selectors are
none_rset
or yes
.
You’ll find here a description of startup views provided by the framework.
Other startup views:
- schema
A view dedicated to the display of the schema of the instance
Boxes#
(cubicweb.web.views.boxes
)
- sidebox
This view displays usually a side box of some related entities in a primary view.
The action box#
The add_related
is an automatic menu in the action box that allows to create
an entity automatically related to the initial entity (context in
which the box is displayed). By default, the links generated in this
box are computed from the schema properties of the displayed entity,
but it is possible to explicitly specify them thanks to the
cubicweb.web.views.uicfg.rmode relation tag:
link, indicates that a relation is in general created pointing to an existing entity and that we should not to display a link for this relation
create, indicates that a relation is in general created pointing to new entities and that we should display a link to create a new entity and link to it automatically
If necessary, it is possible to overwrite the method relation_mode(rtype, targettype, x=’subject’) to dynamically compute a relation creation category.
Please note that if at least one action belongs to the addrelated category, the automatic behavior is desactivated in favor of an explicit behavior (e.g. display of addrelated category actions only).
Table views#
Example#
Let us take an example from the timesheet cube:
class ActivityResourcesTable(EntityView):
__regid__ = 'activity.resources.table'
__select__ = is_instance('Activity')
def call(self, showresource=True):
eids = ','.join(str(row[0]) for row in self.cw_rset)
rql = ('Any R,D,DUR,WO,DESCR,S,A, SN,RT,WT ORDERBY D DESC '
'WHERE '
' A is Activity, A done_by R, R title RT, '
' A diem D, A duration DUR, '
' A done_for WO, WO title WT, '
' A description DESCR, A in_state S, S name SN, '
' A eid IN (%s)' % eids)
rset = self._cw.execute(rql)
self.wview('resource.table', rset, 'null')
class ResourcesTable(RsetTableView):
__regid__ = 'resource.table'
# notice you may wish a stricter selector to check rql's shape
__select__ = is_instance('Resource')
# my table headers
headers = ['Resource', 'diem', 'duration', 'workpackage', 'description', 'state']
# I want a table where attributes are editable (reledit inside)
finalvid = 'editable-final'
cellvids = {3: 'editable-final'}
# display facets and actions with a menu
layout_args = {'display_filter': 'top',
'add_view_actions': None}
To obtain an editable table, you may specify the ‘editable-table’ view identifier using some of cellvids, finalvid or nonfinalvid.
The previous example results in:

In order to activate table filter mechanism, the display_filter option is given as a layout argument. A small arrow will be displayed at the table’s top right corner. Clicking on show filter form action, will display the filter form as below:

By the same way, you can display additional actions for the selected entities
by setting add_view_actions layout option to True. This will add actions
returned by the view’s table_actions()
.
You can notice that all columns of the result set are not displayed. This is because of given headers, implying to display only columns from 0 to len(headers).
Also Notice that the ResourcesTable view relies on a particular rql shape (which is not ensured by the way, the only checked thing is that the result set contains instance of the Resource type). That usually implies that you can’t use this view for user specific queries (e.g. generated by facets or typed manually).
So another option would be to write this view using
EntityTableView
, as below.
class ResourcesTable(EntityTableView):
__regid__ = 'resource.table'
__select__ = is_instance('Resource')
# table columns definition
columns = ['resource', 'diem', 'duration', 'workpackage', 'description', 'in_state']
# I want a table where attributes are editable (reledit inside)
finalvid = 'editable-final'
# display facets and actions with a menu
layout_args = {'display_filter': 'top',
'add_view_actions': None}
def workpackage_cell(entity):
activity = entity.reverse_done_in[0]
activity.view('reledit', rtype='done_for', role='subject', w=w)
def workpackage_sortvalue(entity):
activity = entity.reverse_done_in[0]
return activity.done_for[0].sortvalue()
column_renderers = {
'resource': MainEntityColRenderer(),
'workpackage': EntityTableColRenderer(
header='Workpackage',
renderfunc=workpackage_cell,
sortfunc=workpackage_sortvalue,),
'in_state': EntityTableColRenderer(
renderfunc=lambda w,x: w(x.cw_adapt_to('IWorkflowable').printable_state),
sortfunc=lambda x: x.cw_adapt_to('IWorkflowable').printable_state),
}
Notice the following point:
cell_<column>(w, entity) will be searched for rendering the content of a cell. If not found, column is expected to be an attribute of entity.
cell_sortvalue_<column>(entity) should return a typed value to use for javascript sorting or None for not sortable columns (the default).
The
etable_entity_sortvalue()
decorator will set a ‘sortvalue’ function for the column containing the main entity (the one given as argument to all methods), which will call entity.sortvalue().You can set a column header using the
etable_header_title()
decorator. This header will be translated. If it’s not an already existing msgid, think to mark it using _() (the example supposes headers are schema defined msgid).
Pro/cons of each approach#
EntityTableView
and RsetableView
provides basically the same
set of features, though they don’t share the same properties. Let’s try to sum
up pro and cons of each class.
EntityTableView view is:
more verbose, but usually easier to understand
easily extended (easy to add/remove columns for instance)
doesn’t rely on a particular rset shape. Simply give it a title and will be listed in the ‘possible views’ box if any.
RsetTableView view is:
hard to beat to display barely a result set, or for cases where some of headers, displaycols or cellvids could be defined to enhance the table while you don’t care about e.g. pagination or facets.
hardly extensible, as you usually have to change places where the view is called to modify the RQL (hence the view’s result set shape).
XML and RSS views#
(cubicweb.web.views.xmlrss
)
Overview#
- rss
Creates a RSS/XML view and call the view rssitem for each entity of the result set.
- rssitem
Create a RSS/XML view for each entity based on the results of the dublin core methods of the entity (dc_*)
RSS Channel Example#
Assuming you have several blog entries, click on the title of the search box in the left column. A larger search box should appear. Enter:
Any X ORDERBY D WHERE X is BlogEntry, X creation_date D
and you get a list of blog entries.
Click on your login at the top right corner. Chose “user preferences”, then “boxes”, then “possible views box” and check “visible = yes” before validating your changes.
Enter the same query in the search box and you will see the same list, plus a box titled “possible views” in the left column. Click on “entityview”, then “RSS”.
You just applied the “RSS” view to the RQL selection you requested.
That’s it, you have a RSS channel for your blog.
Try again with:
Any X ORDERBY D WHERE X is BlogEntry, X creation_date D,
X entry_of B, B title "MyLife"
Another RSS channel, but a bit more focused.
A last one for the road:
Any C ORDERBY D WHERE C is Comment, C creation_date D LIMIT 15
displayed with the RSS view, that’s a channel for the last fifteen comments posted.
[WRITE ME]
show that the RSS view can be used to display an ordered selection of blog entries, thus providing a RSS channel
show that a different selection (by category) means a different channel
URL publishing#
(cubicweb.web.views.urlpublishing
)
You can write your own URLPathEvaluator class to handle custom paths. For instance, if you want /my-card-id to redirect to the corresponding card’s primary view, you would write:
class CardWikiidEvaluator(URLPathEvaluator):
priority = 3 # make it be evaluated *before* RestPathEvaluator
def evaluate_path(self, req, segments):
if len(segments) != 1:
raise PathDontMatch()
rset = req.execute('Any C WHERE C wikiid %(w)s',
{'w': segments[0]})
if len(rset) == 0:
# Raise NotFound if no card is found
raise PathDontMatch()
return None, rset
On the other hand, you can also deactivate some of the standard evaluators in your final application. The only thing you have to do is to unregister them, for instance in a registration_callback in your cube:
def registration_callback(vreg):
vreg.unregister(RestPathEvaluator)
You can even replace the cubicweb.web.views.urlpublishing.URLPublisherComponent
class if you want to customize the whole toolchain process or if you want
to plug into an early enough extension point to control your request
parameters:
class SanitizerPublisherComponent(URLPublisherComponent):
"""override default publisher component to explicitly ignore
unauthorized request parameters in anonymous mode.
"""
unauthorized_form_params = ('rql', 'vid', '__login', '__password')
def process(self, req, path):
if req.session.anonymous_session:
self._remove_unauthorized_params(req)
return super(SanitizerPublisherComponent, self).process(req, path)
def _remove_unauthorized_params(self, req):
for param in req.form.keys():
if param in self.unauthorized_form_params:
req.form.pop(param)
def registration_callback(vreg):
vreg.register_and_replace(SanitizerPublisherComponent, URLPublisherComponent)
URL rewriting#
(cubicweb.web.views.urlrewrite
)
SimpleReqRewriter
is enough for a certain number of simple cases. If it is not sufficient, SchemaBasedRewriter
allows to do more elaborate things.
Here is an example of SimpleReqRewriter
usage with plain string:
from cubicweb.web.views.urlrewrite import SimpleReqRewriter
class TrackerSimpleReqRewriter(SimpleReqRewriter):
rules = [
('/versions', dict(vid='versionsinfo')),
]
When the url is <base_url>/versions, the view with the __regid__ versionsinfo is displayed.
Here is an example of SimpleReqRewriter
usage with regular expressions:
from cubicweb.web.views.urlrewrite import (
SimpleReqRewriter, rgx)
class BlogReqRewriter(SimpleReqRewriter):
rules = [
(rgx('/blogentry/([a-z_]+)\.rss'),
dict(rql=('Any X ORDERBY CD DESC LIMIT 20 WHERE X is BlogEntry,'
'X creation_date CD, X created_by U, '
'U login "%(user)s"'
% {'user': r'\1'}), vid='rss'))
]
When a url matches the regular expression, the view with the __regid__ rss which match the result set is displayed.
To deal with URL rewriting with an underlying RQL query, it is possible to specify the behaviour in the case of an empty rset with the option empty_rset_raises_404.
The following example shows a SimpleReqRewriter
usage with the
empty_rset_raises_404 option set to True.
In this case, the path mycwetypeurl/pouet will return a 404. Without this option,
it would return a 200.
from cubicweb.web.views.urlrewrite import (
SimpleReqRewriter, rgx)
class MyRewriter(SimpleReqRewriter):
rules = [(rgx(r'/mycwetypeurl/([^/]+)'),
dict(vid='primary',
rql=r'Any T WHERE T is CWEType, T name "\1"',
empty_rset_raises_404=True),)]
Here is an example of SchemaBasedRewriter
usage:
from cubicweb.web.views.urlrewrite import (
SchemaBasedRewriter, rgx, build_rset)
class TrackerURLRewriter(SchemaBasedRewriter):
rules = [
(rgx('/project/([^/]+)/([^/]+)/tests'),
build_rset(rql='Version X WHERE X version_of P, P name %(project)s, X num %(num)s',
rgxgroups=[('project', 1), ('num', 2)], vid='versiontests')),
]
The ‘download’ views#
Components#
Download views#
Embedded views#
Online documentation system#
Help views#
Actions#
Configuring the user interface#
The uicfg module#
Note
The part of uicfg that deals with primary views is in the Primary view configuration chapter.
The uihelper module#
Ajax#
Warning
This approach is deprecated in favor of using cwclientlibjs. If your use react for your UI, try the react components from the cwelements library. The documentation is kept here as reference.
For historical reference of what Ajax is and used to be, one can read the wikipedia article about Ajax.
CubicWeb provides a few helpers to facilitate javascript <-> python communications.
You can, for instance, register some python functions that will become
callable from javascript through ajax calls. All the ajax URLs are handled
by the cubicweb.web.views.ajaxcontroller.AjaxController
controller.
Javascript#
CubicWeb uses quite a bit of javascript in its user interface and ships with jquery (1.3.x) and parts of the jquery UI library, plus a number of homegrown files and also other third party libraries.
All javascript files are stored in cubicweb/web/data/. There are around thirty js files there. In a cube it goes to data/.
Obviously one does not want javascript pieces to be loaded all at once, hence the framework provides a number of mechanisms and conventions to deal with javascript resources.
Conventions#
It is good practice to name cube specific js files after the name of the cube, like this : ‘cube.mycube.js’, so as to avoid name clashes.
Server-side Javascript API#
Javascript resources are typically loaded on demand, from views. The request object (available as self._cw from most application objects, for instance views and entities objects) has a few methods to do that:
add_js(self, jsfiles, localfile=True) which takes a sequence of javascript files and writes proper entries into the HTML header section. The localfile parameter allows to declare resources which are not from web/data (for instance, residing on a content delivery network).
add_onload(self, jscode) which adds one raw javascript code snippet inline in the html headers. This is quite useful for setting up early jQuery(document).ready(…) initialisations.
Javascript events#
server-response
: this event is triggered on HTTP responses (both standard and ajax). The two following extra parameters are passed to callbacks :ajax
: a boolean that says if the reponse was issued by an ajax requestnode
: the DOM node returned by the server in case of an ajax request, otherwise the document itself for standard HTTP requests.
Important javascript AJAX APIS#
asyncRemoteExec and remoteExec are the base building blocks for doing arbitrary async (resp. sync) communications with the server
reloadComponent is a convenience function to replace a DOM node with server supplied content coming from a specific registry (this is quite handy to refresh the content of some boxes for instances)
jQuery.fn.loadxhtml is an important extension to jQuery which allows proper loading and in-place DOM update of xhtml views. It is suitably augmented to trigger necessary events, and process CubicWeb specific elements such as the facet system, fckeditor, etc.
A simple example with asyncRemoteExec#
On the python side, we have to define an
cubicweb.web.views.ajaxcontroller.AjaxFunction
object. The
simplest way to do that is to use the
cubicweb.web.views.ajaxcontroller.ajaxfunc()
decorator (for more
details on this, refer to Ajax).
On the javascript side, we do the asynchronous call. Notice how it creates a deferred object. Proper treatment of the return value or error handling has to be done through the addCallback and addErrback methods.
Anatomy of a reloadComponent call#
reloadComponent allows to dynamically replace some DOM node with new elements. It has the following signature:
compid (mandatory) is the name of the component to be reloaded
rql (optional) will be used to generate a result set given as argument to the selected component
registry (optional) defaults to ‘components’ but can be any other valid registry name
nodeid (optional) defaults to compid + ‘Component’ but can be any explicitly specified DOM node id
extraargs (optional) should be a dictionary of values that will be given to the cell_call method of the component
A simple reloadComponent example#
The server side implementation of reloadComponent is the
cubicweb.web.views.ajaxcontroller.component()
AjaxFunction appobject.
The following function implements a two-steps method to delete a standard bookmark and refresh the UI, while keeping the UI responsive.
function removeBookmark(beid) {
d = asyncRemoteExec('delete_bookmark', beid);
d.addCallback(function(boxcontent) {
reloadComponent('bookmarks_box', '', 'boxes', 'bookmarks_box');
document.location.hash = '#header';
updateMessage(_("bookmark has been removed"));
});
}
reloadComponent is called with the id of the bookmark box as argument, no rql expression (because the bookmarks display is actually independant of any dataset context), a reference to the ‘boxes’ registry (which hosts all left, right and contextual boxes) and finally an explicit ‘bookmarks_box’ nodeid argument that stipulates the target DOM node.
Anatomy of a loadxhtml call#
jQuery.fn.loadxhtml is an important extension to jQuery which allows proper loading and in-place DOM update of xhtml views. The existing jQuery.load function does not handle xhtml, hence the addition. The API of loadxhtml is roughly similar to that of jQuery.load.
url (mandatory) should be a complete url (typically referencing the
cubicweb.web.views.ajaxcontroller.AjaxController
, but this is not strictly mandatory)data (optional) is a dictionary of values given to the controller specified through an url argument; some keys may have a special meaning depending on the choosen controller (such as fname for the JSonController); the callback key, if present, must refer to a function to be called at the end of loadxhtml (more on this below)
reqtype (optional) specifies the request method to be used (get or post); if the argument is ‘post’, then the post method is used, otherwise the get method is used
mode (optional) is one of replace (the default) which means the loaded node will replace the current node content, swap to replace the current node with the loaded node, and append which will append the loaded node to the current node content
About the callback option:
it is called with two parameters: the current node, and a list containing the loaded (and post-processed node)
whenever it returns another function, this function is called in turn with the same parameters as above
This mechanism allows callback chaining.
A simple example with loadxhtml#
Here we are concerned with the retrieval of a specific view to be injected in the live DOM. The view will be of course selected server-side using an entity eid provided by the client side.
from cubicweb.web.views.ajaxcontroller import ajaxfunc
@ajaxfunc(output_type='xhtml')
def frob_status(self, eid, frobname):
entity = self._cw.entity_from_eid(eid)
return entity.view('frob', name=frobname)
function updateSomeDiv(divid, eid, frobname) {
var params = {fname:'frob_status', eid: eid, frobname:frobname};
jQuery('#'+divid).loadxhtml(JSON_BASE_URL, params, 'post');
}
In this example, the url argument is the base json url of a cube instance (it should contain something like http://myinstance/ajax?). The actual AjaxController method name is encoded in the params dictionary using the fname key.
A more real-life example#
A frequent need of Web 2 applications is the delayed (or demand driven) loading of pieces of the DOM. This is typically achieved using some preparation of the initial DOM nodes, jQuery event handling and proper use of loadxhtml.
We present here a skeletal version of the mecanism used in CubicWeb and available in web/views/tabs.py, in the LazyViewMixin class.
def lazyview(self, vid, rql=None):
""" a lazy version of wview """
self._cw.add_js('cubicweb.lazy.js')
urlparams = {'vid' : vid, 'fname' : 'view'}
if rql is not None:
urlparams['rql'] = rql
self.w(u'<div id="lazy-%s" cubicweb:loadurl="%s">',
vid, xml_escape(self._cw.build_url('json', **urlparams)))
self.w(u'</div>')
self._cw.add_onload(u"""
jQuery('#lazy-%(vid)s').bind('%(event)s', function() {
loadNow('#lazy-%(vid)s');});"""
% {'event': 'load_%s' % vid, 'vid': vid})
This creates a div with a specific event associated to it.
The full version deals with:
optional parameters such as an entity eid, an rset
the ability to further reload the fragment
the ability to display a spinning wheel while the fragment is still not loaded
handling of browsers that do not support ajax (search engines, text-based browsers such as lynx, etc.)
The javascript side is quite simple, due to loadxhtml awesomeness.
function loadNow(eltsel) {
var lazydiv = jQuery(eltsel);
lazydiv.loadxhtml(lazydiv.attr('cubicweb:loadurl'));
}
This is all significantly different of the previous simple example (albeit this example actually comes from real-life code).
Notice how the cubicweb:loadurl is used to convey the url information. The base of this url is similar to the global javascript JSON_BASE_URL. According to the pattern described earlier, the fname parameter refers to the standard js_view method of the JSonController. This method renders an arbitrary view provided a view id (or vid) is provided, and most likely an rql expression yielding a result set against which a proper view instance will be selected.
The cubicweb:loadurl is one of the 29 attributes extensions to XHTML in a specific cubicweb namespace. It is a means to pass information without breaking HTML nor XHTML compliance and without resorting to ungodly hacks.
Given all this, it is easy to add a small nevertheless useful feature to force the loading of a lazy view (for instance, a very computation-intensive web page could be scinded into one fast-loading part and a delayed part).
On the server side, a simple call to a javascript function is sufficient.
def forceview(self, vid):
"""trigger an event that will force immediate loading of the view
on dom readyness
"""
self._cw.add_onload("triggerLoad('%s');" % vid)
The browser-side definition follows.
function triggerLoad(divid) {
jQuery('#lazy-' + divd).trigger('load_' + divid);
}
Javascript library: overview#
jquery.* : jquery and jquery UI library
cubicweb.ajax.js : concentrates all ajax related facilities (it extends jQuery with the loahxhtml function, provides a handfull of high-level ajaxy operations like asyncRemoteExec, reloadComponent, replacePageChunk, getDomFromResponse)
cubicweb.python.js : adds a number of practical extension to stdanrd javascript objects (on Date, Array, String, some list and dictionary operations), and a pythonesque way to build classes. Defines a CubicWeb namespace.
cubicweb.htmlhelpers.js : a small bag of convenience functions used in various other cubicweb javascript resources (baseuri, progress cursor handling, popup login box, html2dom function, etc.)
cubicweb.widgets.js : provides a widget namespace and constructors and helpers for various widgets (mainly facets and timeline)
cubicweb.edition.js : used by edition forms
cubicweb.preferences.js : used by the preference form
cubicweb.facets.js : used by the facets mechanism
There is also javascript support for massmailing, gmap (google maps), fckcwconfig (fck editor), timeline, calendar, goa (CubicWeb over AppEngine), flot (charts drawing), tabs and bookmarks.
API#
Javascript API#
Testing javascript#
You with the cubicweb.qunit.QUnitTestCase
can include standard Qunit tests
inside the python unittest run . You simply have to define a new class that
inherit from QUnitTestCase
and register your javascript test file in the
all_js_tests
lclass attribut. This all_js_tests
is a sequence a
3-tuple (<test_file, [<dependencies> ,] [<data_files>]):
The <test_file> should contains the qunit test. <dependencies> defines the list
of javascript file that must be imported before the test script. Dependencies
are included their definition order. <data_files> are additional files copied in the
test directory. both <dependencies> and <data_files> are optionnal.
jquery.js
is preincluded in for all test.
from cubicweb.qunit import QUnitTestCase
class MyQUnitTest(QUnitTestCase):
all_js_tests = (
("relative/path/to/my_simple_testcase.js",)
("relative/path/to/my_qunit_testcase.js",(
"rel/path/to/dependency_1.js",
"rel/path/to/dependency_2.js",)),
("relative/path/to/my_complexe_qunit_testcase.js",(
"rel/path/to/dependency_1.js",
"rel/path/to/dependency_2.js",
),(
"rel/path/file_dependency.html",
"path/file_dependency.json")
),
)
CSS Stylesheet#
Conventions#
Extending / overriding existing styles#
We cannot modify the order in which the application is reading the CSS. In
the case we want to create new CSS style, the best is to define it a in a new
CSS located under myapp/data/
and use those new styles while writing
customized views and templates.
If you want to modify an existing CSS styling property, you will have to use
!important
declaration to override the existing property. The application
apply a higher priority on the default CSS and you can not change that.
Customized CSS will not be read first.
CubicWeb stylesheets#
Edition control#
This chapter covers the editing capabilities of CubicWeb. It explains html Form construction, the Edit Controller and their interactions.
HTML form construction#
CubicWeb provides the somewhat usual form / field / widget / renderer abstraction to provide generic building blocks which will greatly help you in building forms properly integrated with CubicWeb (coherent display, error handling, etc…), while keeping things as flexible as possible.
A form
basically only holds a set of fields
, and has te be bound to a
renderer
which is responsible to layout them. Each field is bound to a
widget
that will be used to fill in value(s) for that field (at form
generation time) and ‘decode’ (fetch and give a proper Python type to) values
sent back by the browser.
The field
should be used according to the type of what you want to edit.
E.g. if you want to edit some date, you’ll have to use the
cubicweb.web.formfields.DateField
. Then you can choose among multiple
widgets to edit it, for instance cubicweb.web.formwidgets.TextInput
(a
bare text field), DateTimePicker
(a simple
calendar) or even JQueryDatePicker
(the JQuery
calendar). You can of course also write your own widget.
Exploring the available forms#
A small excursion into a CubicWeb shell is the quickest way to discover available forms (or application objects in general).
>>> from pprint import pprint
>>> pprint( session.vreg['forms'] )
{'base': [<class 'cubicweb.web.views.forms.FieldsForm'>,
<class 'cubicweb.web.views.forms.EntityFieldsForm'>],
'changestate': [<class 'cubicweb.web.views.workflow.ChangeStateForm'>,
<class 'cubicweb_tracker.views.forms.VersionChangeStateForm'>],
'composite': [<class 'cubicweb.web.views.forms.CompositeForm'>,
<class 'cubicweb.web.views.forms.CompositeEntityForm'>],
'deleteconf': [<class 'cubicweb.web.views.editforms.DeleteConfForm'>],
'edition': [<class 'cubicweb.web.views.autoform.AutomaticEntityForm'>,
<class 'cubicweb.web.views.workflow.TransitionEditionForm'>,
<class 'cubicweb.web.views.workflow.StateEditionForm'>],
'logform': [<class 'cubicweb.web.views.basetemplates.LogForm'>],
'massmailing': [<class 'cubicweb.web.views.massmailing.MassMailingForm'>],
'muledit': [<class 'cubicweb.web.views.editforms.TableEditForm'>]}
The two most important form families here (for all practical purposes) are base
and edition. Most of the time one wants alterations of the
AutomaticEntityForm
to generate custom forms to handle edition of an
entity.
The Automatic Entity Form#
Anatomy of a choices function#
Let’s have a look at the ticket_done_in_choices function given to the choices parameter of the relation tag that is applied to the (‘Ticket’, ‘done_in’, ‘*’) relation definition, as it is both typical and sophisticated enough. This is a code snippet from the tracker cube.
The Ticket
entity type can be related to a Project
and a
Version
, respectively through the concerns
and done_in
relations. When a user is about to edit a ticket, we want to fill the
combo box for the done_in
relation with values pertinent with
respect to the context. The important context here is:
creation or modification (we cannot fetch values the same way in either case)
__linkto
url parameter given in a creation context
from cubicweb.web import formfields
def ticket_done_in_choices(form, field):
entity = form.edited_entity
# first see if its specified by __linkto form parameters
linkedto = form.linked_to[('done_in', 'subject')]
if linkedto:
return linkedto
# it isn't, get initial values
vocab = field.relvoc_init(form)
veid = None
# try to fetch the (already or pending) related version and project
if not entity.has_eid():
peids = form.linked_to[('concerns', 'subject')]
peid = peids and peids[0]
else:
peid = entity.project.eid
veid = entity.done_in and entity.done_in[0].eid
if peid:
# we can complete the vocabulary with relevant values
rschema = form._cw.vreg.schema['done_in'].rdef('Ticket', 'Version')
rset = form._cw.execute(
'Any V, VN ORDERBY version_sort_value(VN) '
'WHERE V version_of P, P eid %(p)s, V num VN, '
'V in_state ST, NOT ST name "published"', {'p': peid}, 'p')
vocab += [(v.view('combobox'), v.eid) for v in rset.entities()
if rschema.has_perm(form._cw, 'add', toeid=v.eid)
and v.eid != veid]
return vocab
The first thing we have to do is fetch potential values from the __linkto
url
parameter that is often found in entity creation contexts (the creation action
provides such a parameter with a predetermined value; for instance in this case,
ticket creation could occur in the context of a Version entity). The
RelationField
field class provides a
relvoc_linkedto()
method that gets a
list suitably filled with vocabulary values.
linkedto = field.relvoc_linkedto(form)
if linkedto:
return linkedto
Then, if no __linkto
argument was given, we must prepare the vocabulary with
an initial empty value (because done_in is not mandatory, we must allow the
user to not select a verson) and already linked values. This is done with the
relvoc_init()
method.
vocab = field.relvoc_init(form)
But then, we have to give more: if the ticket is related to a project, we should provide all the non published versions of this project (Version and Project can be related through the version_of relation). Conversely, if we do not know yet the project, it would not make sense to propose all existing versions as it could potentially lead to incoherences. Even if these will be caught by some RQLConstraint, it is wise not to tempt the user with error-inducing candidate values.
The “ticket is related to a project” part must be decomposed as:
this is a new ticket which is created is the context of a project
this is an already existing ticket, linked to a project (through the concerns relation)
there is no related project (quite unlikely given the cardinality of the concerns relation, so it can only mean that we are creating a new ticket, and a project is about to be selected but there is no
__linkto
argument)
Note
the last situation could happen in several ways, but of course in a polished application, the paths to ticket creation should be controlled so as to avoid a suboptimal end-user experience
Hence, we try to fetch the related project.
veid = None
if not entity.has_eid():
peids = form.linked_to[('concerns', 'subject')]
peid = peids and peids[0]
else:
peid = entity.project.eid
veid = entity.done_in and entity.done_in[0].eid
We distinguish between entity creation and entity modification using
the Entity.has_eid()
method, which returns False on creation. At
creation time the only way to get a project is through the
__linkto
parameter. Notice that we fetch the version in which the
ticket is done_in if any, for later.
Note
the implementation above assumes that if there is a __linkto
parameter, it is only about a project. While it makes sense most of
the time, it is not an absolute. Depending on how an entity creation
action action url is built, several outcomes could be possible
there
If the ticket is already linked to a project, fetching it is trivial. Then we add the relevant version to the initial vocabulary.
if peid:
rschema = form._cw.vreg.schema['done_in'].rdef('Ticket', 'Version')
rset = form._cw.execute(
'Any V, VN ORDERBY version_sort_value(VN) '
'WHERE V version_of P, P eid %(p)s, V num VN, '
'V in_state ST, NOT ST name "published"', {'p': peid})
vocab += [(v.view('combobox'), v.eid) for v in rset.entities()
if rschema.has_perm(form._cw, 'add', toeid=v.eid)
and v.eid != veid]
Warning
we have to defend ourselves against lack of a project eid. Given the cardinality of the concerns relation, there must be a project, but this rule can only be enforced at validation time, which will happen of course only after form subsmission
Here, given a project eid, we complete the vocabulary with all unpublished versions defined in the project (sorted by number) for which the current user is allowed to establish the relation.
Building self-posted form with custom fields/widgets#
Sometimes you want a form that is not related to entity edition. For those, you’ll have to handle form posting by yourself. Here is a complete example on how to achieve this (and more).
Imagine you want a form that selects a month period. There are no proper field/widget to handle this in CubicWeb, so let’s start by defining them:
# let's have the whole import list at the beginning, even those necessary for
# subsequent snippets
from logilab.common import date
from logilab.mtconverter import xml_escape
from cubicweb.view import View
from cubicweb.predicates import match_kwargs
from cubicweb.web import RequestError, ProcessFormError
from cubicweb.web import formfields as fields, formwidgets as wdgs
from cubicweb.web.views import forms, calendar
class MonthSelect(wdgs.Select):
"""Custom widget to display month and year. Expect value to be given as a
date instance.
"""
def format_value(self, form, field, value):
return u'%s/%s' % (value.year, value.month)
def process_field_data(self, form, field):
val = super(MonthSelect, self).process_field_data(form, field)
try:
year, month = val.split('/')
year = int(year)
month = int(month)
return date.date(year, month, 1)
except ValueError:
raise ProcessFormError(
form._cw._('badly formated date string %s') % val)
class MonthPeriodField(fields.CompoundField):
"""custom field composed of two subfields, 'begin_month' and 'end_month'.
It expects to be used on form that has 'mindate' and 'maxdate' in its
extra arguments, telling the range of month to display.
"""
def __init__(self, *args, **kwargs):
kwargs.setdefault('widget', wdgs.IntervalWidget())
super(MonthPeriodField, self).__init__(
[fields.StringField(name='begin_month',
choices=self.get_range, sort=False,
value=self.get_mindate,
widget=MonthSelect()),
fields.StringField(name='end_month',
choices=self.get_range, sort=False,
value=self.get_maxdate,
widget=MonthSelect())], *args, **kwargs)
@staticmethod
def get_range(form, field):
mindate = date.todate(form.cw_extra_kwargs['mindate'])
maxdate = date.todate(form.cw_extra_kwargs['maxdate'])
assert mindate <= maxdate
_ = form._cw._
months = []
while mindate <= maxdate:
label = '%s %s' % (_(calendar.MONTHNAMES[mindate.month - 1]),
mindate.year)
value = field.widget.format_value(form, field, mindate)
months.append( (label, value) )
mindate = date.next_month(mindate)
return months
@staticmethod
def get_mindate(form, field):
return form.cw_extra_kwargs['mindate']
@staticmethod
def get_maxdate(form, field):
return form.cw_extra_kwargs['maxdate']
def process_posted(self, form):
for field, value in super(MonthPeriodField, self).process_posted(form):
if field.name == 'end_month':
value = date.last_day(value)
yield field, value
Here we first define a widget that will be used to select the beginning and the end of the period, displaying months like ‘<month> YYYY’ but using ‘YYYY/mm’ as actual value.
We then define a field that will actually hold two fields, one for the beginning
and another for the end of the period. Each subfield uses the widget we defined
earlier, and the outer field itself uses the standard
IntervalWidget
. The field adds some logic:
a vocabulary generation function get_range, used to populate each sub-field
two ‘value’ functions get_mindate and get_maxdate, used to tell to subfields which value they should consider on form initialization
overriding of process_posted, called when the form is being posted, so that the end of the period is properly set to the last day of the month.
Now, we can define a very simple form:
class MonthPeriodSelectorForm(forms.FieldsForm):
__regid__ = 'myform'
__select__ = match_kwargs('mindate', 'maxdate')
form_buttons = [wdgs.SubmitButton()]
form_renderer_id = 'onerowtable'
period = MonthPeriodField()
where we simply add our field, set a submit button and use a very simple renderer (try others!). Also we specify a selector that ensures form will have arguments necessary to our field.
Now, we need a view that will wrap the form and handle post when it occurs, simply displaying posted values in the page:
class SelfPostingForm(View):
__regid__ = 'myformview'
def call(self):
mindate, maxdate = date.date(2010, 1, 1), date.date(2012, 1, 1)
form = self._cw.vreg['forms'].select(
'myform', self._cw, mindate=mindate, maxdate=maxdate, action='')
try:
posted = form.process_posted()
self.w(u'<p>posted values %s</p>' % xml_escape(repr(posted)))
except RequestError: # no specified period asked
pass
form.render(w=self.w, formvalues=self._cw.form)
Notice usage of the process_posted()
method, that will return a dictionary
of typed values (because they have been processed by the field). In our case, when
the form is posted you should see a dictionary with ‘begin_month’ and ‘end_month’
as keys with the selected dates as value (as a python date object).
APIs#
Dissection of an entity form#
This is done (again) with a vanilla instance of the tracker cube. We will populate the database with a bunch of entities and see what kind of job the automatic entity form does.
Populating the database#
We should start by setting up a bit of context: a project with two unpublished versions, and a ticket linked to the project and the first version.
>>> p = rql('INSERT Project P: P name "cubicweb"')
>>> for num in ('0.1.0', '0.2.0'):
... rql('INSERT Version V: V num "%s", V version_of P WHERE P eid %%(p)s' % num, {'p': p[0][0]})
...
<resultset 'INSERT Version V: V num "0.1.0", V version_of P WHERE P eid %(p)s' (1 rows): [765L] (('Version',))>
<resultset 'INSERT Version V: V num "0.2.0", V version_of P WHERE P eid %(p)s' (1 rows): [766L] (('Version',))>
>>> t = rql('INSERT Ticket T: T title "let us write more doc", T done_in V, '
'T concerns P WHERE V num "0.1.0"', P eid %(p)s', {'p': p[0][0]})
>>> commit()
Now let’s see what the edition form builds for us.
>>> cnx.use_web_compatible_requests('http://fakeurl.com')
>>> req = cnx.request()
>>> form = req.vreg['forms'].select('edition', req, rset=rql('Ticket T'))
>>> html = form.render()
Note
In order to play interactively with web side application objects, we have to
cheat a bit to have request object that will looks like HTTP request object, by
calling use_web_compatible_requests()
on the connection.
This creates an automatic entity form. The .render()
call yields
an html (unicode) string. The html output is shown below (with
internal fieldset omitted).
Looking at the html output#
The form enveloppe#
<div class="iformTitle"><span>main informations</span></div>
<div class="formBody">
<form action="http://crater:9999/validateform" method="post" enctype="application/x-www-form-urlencoded"
id="entityForm" onsubmit="return freezeFormButtons('entityForm');"
class="entityForm" target="eformframe">
<div id="progress">validating...</div>
<fieldset>
<input name="__form_id" type="hidden" value="edition" />
<input name="__errorurl" type="hidden" value="http://perdu.com#entityForm" />
<input name="__domid" type="hidden" value="entityForm" />
<input name="__type:763" type="hidden" value="Ticket" />
<input name="eid" type="hidden" value="763" />
<input name="__maineid" type="hidden" value="763" />
<input name="_cw_edited_fields:763" type="hidden"
value="concerns-subject,done_in-subject,priority-subject,type-subject,title-subject,description-subject,__type,_cw_generic_field" />
...
</fieldset>
<iframe width="0px" height="0px" name="eformframe" id="eformframe" src="javascript: void(0);"></iframe>
</form>
</div>
The main fieldset encloses a set of hidden fields containing various metadata, that will be used by the edit controller to process it back correctly.
The freezeFormButtons(…) javascript callback defined on the
onlick
event of the form element prevents accidental multiple
clicks in a row.
The action
of the form is mapped to the validateform
controller
(situated in cubicweb.web.views.basecontrollers
).
A full explanation of the validation loop is given in The form validation process.
The attributes section#
We can have a look at some of the inner nodes of the form. Some fields are omitted as they are redundant for our purposes.
<fieldset class="default">
<table class="attributeForm">
<tr class="title_subject_row">
<th class="labelCol"><label class="required" for="title-subject:763">title</label></th>
<td>
<input id="title-subject:763" maxlength="128" name="title-subject:763" size="45"
type="text" value="let us write more doc" />
</td>
</tr>
... (description field omitted) ...
<tr class="priority_subject_row">
<th class="labelCol"><label class="required" for="priority-subject:763">priority</label></th>
<td>
<select id="priority-subject:763" name="priority-subject:763" size="1">
<option value="important">important</option>
<option selected="selected" value="normal">normal</option>
<option value="minor">minor</option>
</select>
<div class="helper">importance</div>
</td>
</tr>
... (type field omitted) ...
<tr class="concerns_subject_row">
<th class="labelCol"><label class="required" for="concerns-subject:763">concerns</label></th>
<td>
<select id="concerns-subject:763" name="concerns-subject:763" size="1">
<option selected="selected" value="760">Foo</option>
</select>
</td>
</tr>
<tr class="done_in_subject_row">
<th class="labelCol"><label for="done_in-subject:763">done in</label></th>
<td>
<select id="done_in-subject:763" name="done_in-subject:763" size="1">
<option value="__cubicweb_internal_field__"></option>
<option selected="selected" value="761">Foo 0.1.0</option>
<option value="762">Foo 0.2.0</option>
</select>
<div class="helper">version in which this ticket will be / has been done</div>
</td>
</tr>
</table>
</fieldset>
Note that the whole form layout has been computed by the form renderer. It is the renderer which produces the table structure. Otherwise, the fields html structure is emitted by their associated widget.
While it is called the attributes section of the form, it actually contains attributes and mandatory relations. For each field, we observe:
a dedicated row with a specific class, such as
title_subject_row
(responsability of the form renderer)an html widget (input, select, …) with:
an id built from the
rtype-role:eid
patterna name built from the same pattern
possible values or preselected options
The relations section#
<fieldset class="This ticket :">
<legend>This ticket :</legend>
<table class="attributeForm">
<tr class="_cw_generic_field_None_row">
<td colspan="2">
<table id="relatedEntities">
<tr><th> </th><td> </td></tr>
<tr id="relationSelectorRow_763" class="separator">
<th class="labelCol">
<select id="relationSelector_763"
onchange="javascript:showMatchingSelect(this.options[this.selectedIndex].value,763);">
<option value="">select a relation</option>
<option value="appeared_in_subject">appeared in</option>
<option value="custom_workflow_subject">custom workflow</option>
<option value="depends_on_object">dependency of</option>
<option value="depends_on_subject">depends on</option>
<option value="identical_to_subject">identical to</option>
<option value="see_also_subject">see also</option>
</select>
</th>
<td id="unrelatedDivs_763"></td>
</tr>
</table>
</td>
</tr>
</table>
</fieldset>
The optional relations are grouped into a drop-down combo box. Selection of an item triggers a javascript function which will:
show already related entities in the div of id relatedentities using a two-colown layout, with an action to allow deletion of individual relations (there are none in this example)
provide a relation selector in the div of id relationSelector_EID to allow the user to set up relations and trigger dynamic action on the last div
fill the div of id unrelatedDivs_EID with a dynamically computed selection widget allowing direct selection of an unrelated (but relatable) entity or a switch towards the search mode of CubicWeb which allows full browsing and selection of an entity using a dedicated action situated in the left column boxes.
The form validation process#
Validation loop#
On form submission, the form.action is invoked. Basically, the
validateform
controller is called and its output lands in the
specified target
, an invisible <iframe>
at the end of the
form.
Hence, the main page is not replaced, only the iframe contents. The
validateform
controller only outputs a tiny javascript fragment
which is then immediately executed.
<iframe width="0px" height="0px" name="eformframe" id="eformframe" src="javascript: void(0);">
<script type="text/javascript">
window.parent.handleFormValidationResponse('entityForm', null, null,
[false, [2164, {"name-subject": "required field"}], null],
null);
</script>
</iframe>
The window.parent
part ensures the javascript function is called
on the right context (that is: the form element). We will describe its
parameters:
first comes the form id (entityForm)
then two optional callbacks for the success and failure case
an array containing:
a boolean which indicates status (success or failure), and then, on error:
an array structured as
[eid, {'rtype-role': 'error msg'}, ...]
on success:
a url (string) representing the next thing to jump to
Given the array structure described above, it is quite simple to manipulate the DOM to show the errors at appropriate places.
Explanation#
This mecanism may seem a bit overcomplicated but we have to deal with two realities:
in the (strict) XHTML world, there are no iframes (hence the dynamic inclusion, tolerated by Firefox)
no (or not all) browser(s) support file input field handling through ajax.
The edit controller#
It can be found in (cubicweb.web.views.editcontroller
). This
controller processes data received from an html form to create or
update entities.
Edition handling#
The parameters related to entities to edit are specified as follows (first seen in The attributes section):
<rtype-role>:<entity eid>
where entity eid could be a letter in case of an entity to create. We name those parameters as qualified.
Retrieval of entities to edit is done by using the forms parameters eid and __type
For all the attributes and the relations of an entity to edit (attributes and relations are handled a bit differently but these details are not much relevant here) :
using the
rtype
,role
and__type
information, fetch an appropriate field instancecheck if the field has been modified (if not, proceed to the next relation)
build an rql expression to update the entity
At the end, all rql expressions are executed.
For each entity to edit:
if a qualified parameter __linkto is specified, its value has to be a string (or a list of strings) such as:
<relation type>:<eids>:<target>
where <target> is either subject or object and each eid could be separated from the others by a _. Target specifies if the edited entity is subject or object of the relation and each relation specified will be inserted.
if a qualified parameter __clone_eid is specified for an entity, the relations of the specified entity passed as value of this parameter are copied on the edited entity.
if a qualified parameter __delete is specified, its value must be a string or a list of string such as follows:
<subjects eids>:<relation type>:<objects eids>
where each eid subject or object can be seperated from the other by _. Each specified relation will be deleted.
If no entity is edited but the form contains the parameters __linkto and eid, this one is interpreted by using the value specified for eid to designate the entity on which to add the relations.
Note
if the parameter __action_delete is found, all the entities specified as to be edited will be deleted.
if the parameter __action_cancel is found, no action is completed.
if the parameter __action_apply is found, the editing is applied normally but the redirection is done on the form (see Redirection control).
if no entity is found to be edited and if there is no parameter __action_delete, __action_cancel, __linkto, __delete or __insert, an error is raised.
using the parameter __message in the form will allow to use its value as a message to provide the user once the editing is completed.
Redirection control#
Once editing is completed, there is still an issue left: where should we go now? If nothing is specified, the controller will do his job but it does not mean we will be happy with the result. We can control that by using the following parameters:
__redirectpath: path of the URL (relative to the root URL of the site, no form parameters
__redirectparams: forms parameters to add to the path
__redirectrql: redirection RQL request
__redirectvid: redirection view identifier
__errorurl: initial form URL, used for redirecting in case a validation error is raised during editing. If this one is not specified, an error page is displayed instead of going back to the form (which is, if necessary, responsible for displaying the errors)
__form_id: initial view form identifier, used if __action_apply is found
In general we use either __redirectpath and __redirectparams or __redirectrql and __redirectvid.
Examples#
(Automatic) Entity form#
Looking at some cubes available on the cubicweb forge we find some
with form manipulation. The following example comes from the the
conference cube. It extends the change state form for the case
where a Talk
entity is getting into submitted
state. The goal
is to select reviewers for the submitted talk.
from cubicweb.web import formfields as ff, formwidgets as fwdgs
class SendToReviewerStatusChangeView(ChangeStateFormView):
__select__ = (ChangeStateFormView.__select__ &
is_instance('Talk') &
rql_condition('X in_state S, S name "submitted"'))
def get_form(self, entity, transition, **kwargs):
form = super(SendToReviewerStatusChangeView, self).get_form(entity, transition, **kwargs)
relation = ff.RelationField(name='reviews', role='object',
eidparam=True,
label=_('select reviewers'),
widget=fwdgs.Select(multiple=True))
form.append_field(relation)
return form
Simple extension of a form can be done from within the FormView
wrapping the form. FormView instances have a handy get_form
method
that returns the form to be rendered. Here we add a RelationField
to the base state change form.
One notable point is the eidparam
argument: it tells both the
field and the edit controller
that the field is linked to a
specific entity.
It is hence entirely possible to add ad-hoc fields that will be processed by some specialized instance of the edit controller.
Ad-hoc fields form#
We want to define a form doing something else than editing an entity. The idea is
to propose a form to send an email to entities in a resultset which implements
IEmailable
. Let’s take a simplified version of what you’ll find in
cubicweb.web.views.massmailing
.
Here is the source code:
def sender_value(form, field):
return '%s <%s>' % (form._cw.user.dc_title(), form._cw.user.get_email())
def recipient_choices(form, field):
return [(e.get_email(), e.eid)
for e in form.cw_rset.entities()
if e.get_email()]
def recipient_value(form, field):
return [e.eid for e in form.cw_rset.entities()
if e.get_email()]
class MassMailingForm(forms.FieldsForm):
__regid__ = 'massmailing'
needs_js = ('cubicweb.widgets.js',)
domid = 'sendmail'
action = 'sendmail'
sender = ff.StringField(widget=TextInput({'disabled': 'disabled'}),
label=_('From:'),
value=sender_value)
recipient = ff.StringField(widget=CheckBox(),
label=_('Recipients:'),
choices=recipient_choices,
value=recipients_value)
subject = ff.StringField(label=_('Subject:'), max_length=256)
mailbody = ff.StringField(widget=AjaxWidget(wdgtype='TemplateTextField',
inputid='mailbody'))
form_buttons = [ImgButton('sendbutton', "javascript: $('#sendmail').submit()",
_('send email'), 'SEND_EMAIL_ICON'),
ImgButton('cancelbutton', "javascript: history.back()",
stdmsgs.BUTTON_CANCEL, 'CANCEL_EMAIL_ICON')]
Let’s detail what’s going on up there. Our form will hold four fields:
a sender field, which is disabled and will simply contains the user’s name and email
a recipients field, which will be displayed as a list of users in the context result set with checkboxes so user can still choose who will receive his mailing by checking or not the checkboxes. By default all of them will be checked since field’s value return a list containing same eids as those returned by the vocabulary function.
a subject field, limited to 256 characters (hence we know a
TextInput
will be used, as explained inStringField
)a mailbody field. This field use an ajax widget, defined in cubicweb.widgets.js, and whose definition won’t be shown here. Notice though that we tell this form need this javascript file by using needs_js
Last but not least, we add two buttons control: one to post the form using
javascript ($(‘#sendmail’) being the jQuery call to get the element with DOM id
set to ‘sendmail’, which is our form DOM id as specified by its domid
attribute), another to cancel the form which will go back to the previous page
using another javascript call. Also we specify an image to use as button icon as a
resource identifier (see Step 1: tired of the default look?) given as last argument to
cubicweb.web.formwidgets.ImgButton
.
To see this form, we still have to wrap it in a view. This is pretty simple:
class MassMailingFormView(form.FormViewMixIn, EntityView):
__regid__ = 'massmailing'
__select__ = is_instance(IEmailable) & authenticated_user()
def call(self):
form = self._cw.vreg['forms'].select('massmailing', self._cw,
rset=self.cw_rset)
form.render(w=self.w)
As you see, we simply define a view with proper selector so it only apply to a
result set containing IEmailable
entities, and so that only users in the
managers or users group can use it. Then in the call() method for this view we
simply select the above form and call its .render() method with our output
stream as argument.
When this form is submitted, a controller with id ‘sendmail’ will be called (as specified using action). This controller will be responsible to actually send the mail to specified recipients.
Here is what it looks like:
class SendMailController(Controller):
__regid__ = 'sendmail'
__select__ = (authenticated_user() &
match_form_params('recipient', 'mailbody', 'subject'))
def publish(self, rset=None):
body = self._cw.form['mailbody']
subject = self._cw.form['subject']
eids = self._cw.form['recipient']
# eids may be a string if only one recipient was specified
if isinstance(eids, basestring):
rset = self._cw.execute('Any X WHERE X eid %(x)s', {'x': eids})
else:
rset = self._cw.execute('Any X WHERE X eid in (%s)' % (','.join(eids)))
recipients = list(rset.entities())
msg = format_mail({'email' : self._cw.user.get_email(),
'name' : self._cw.user.dc_title()},
recipients, body, subject)
if not self._cw.vreg.config.sendmails([(msg, recipients)]):
msg = self._cw._('could not connect to the SMTP server')
else:
msg = self._cw._('emails successfully sent')
raise Redirect(self._cw.build_url(__message=msg))
The entry point of a controller is the publish method. In that case we simply get
back post values in request’s form attribute, get user instances according
to eids found in the ‘recipient’ form value, and send email after calling
format_mail()
to get a proper email message. If we can’t send email or
if we successfully sent email, we redirect to the index page with proper message
to inform the user.
Also notice that our controller has a selector that deny access to it to anonymous users (we don’t want our instance to be used as a spam relay), but also checks if the expected parameters are specified in forms. That avoids later defensive programming (though it’s not enough to handle all possible error cases).
To conclude our example, suppose we wish a different form layout and that existent renderers are not satisfying (we would check that first of course :). We would then have to define our own renderer:
class MassMailingFormRenderer(formrenderers.FormRenderer):
__regid__ = 'massmailing'
def _render_fields(self, fields, w, form):
w(u'<table class="headersform">')
for field in fields:
if field.name == 'mailbody':
w(u'</table>')
w(u'<div id="toolbar">')
w(u'<ul>')
for button in form.form_buttons:
w(u'<li>%s</li>' % button.render(form))
w(u'</ul>')
w(u'</div>')
w(u'<div>')
w(field.render(form, self))
w(u'</div>')
else:
w(u'<tr>')
w(u'<td class="hlabel">%s</td>' %
self.render_label(form, field))
w(u'<td class="hvalue">')
w(field.render(form, self))
w(u'</td></tr>')
def render_buttons(self, w, form):
pass
We simply override the _render_fields and render_buttons method of the base form renderer to arrange fields as we desire it: here we’ll have first a two columns table with label and value of the sender, recipients and subject field (form order respected), then form controls, then a div containing the textarea for the email’s content.
To bind this renderer to our form, we should add to our form definition above:
form_renderer_id = 'massmailing'
The facets system#
Facets allow to restrict searches according to some user friendly criterias. CubicWeb has a builtin facet system to define restrictions filters really as easily as possible.
Here is an exemple of the facets rendering picked from our http://www.cubicweb.org web site:

Facets will appear on each page presenting more than one entity that may be filtered according to some known criteria.
Base classes for facets#
Internationalization#
Cubicweb fully supports the internalization of its content and interface.
Cubicweb’s interface internationalization is based on the translation project GNU gettext.
Cubicweb’ internalization involves two steps:
in your Python code and cubicweb-tal templates : mark translatable strings
in your instance : handle the translation catalog, edit translations
String internationalization#
User defined string#
In the Python code and cubicweb-tal templates translatable strings can be marked in one of the following ways :
by using the built-in function _:
class PrimaryView(EntityView): """the full view of an non final entity""" __regid__ = 'primary' title = _('primary')OR
by using the equivalent request’s method:
class NoResultView(View): """default view when no result has been found""" __regid__ = 'noresult' def call(self, **kwargs): self.w(u'<div class="searchMessage"><strong>%s</strong></div>\n' % self._cw._('No result matching query'))
The goal of the built-in function _ is only to mark the translatable strings, it will only return the string to translate itself, but not its translation (it’s actually another name for the unicode builtin).
In the other hand the request’s method self._cw._ is also meant to retrieve the proper translation of translation strings in the requested language.
Finally you can also use the __ (two underscores) attribute of request object to get a translation for a string which should not itself added to the catalog, usually in case where the actual msgid is created by string interpolation
self._cw.__('This %s' % etype)
In this example ._cw.__ is used instead of ._cw._ so we don’t have ‘This %s’ in messages catalogs.
Translations in cubicweb-tal template can also be done with TAL tags i18n:content and i18n:replace.
If you need to mark other messages as translatable, you can create a file named i18n/static-messages.pot, see for example Specialize translation for an application cube.
You could put there messages not found in the python sources or overrides some messages that are in cubes used in the dependencies.
Generated string#
We do not need to mark the translation strings of entities/relations used by a particular instance’s schema as they are generated automatically. String for various actions are also generated.
For exemple the following schema:
class EntityA(EntityType):
relation_a2b = SubjectRelation('EntityB')
class EntityB(EntityType):
pass
May generate the following message
add EntityA relation_a2b EntityB subject
This message will be used in views of EntityA
for creation of a new
EntityB
with a preset relation relation_a2b
between the current
EntityA
and the new EntityB
. The opposite message
add EntityA relation_a2b EntityB object
Is used for similar creation of an EntityA
from a view of EntityB
. The
title of they respective creation form will be
creating EntityB (EntityA %(linkto)s relation_a2b EntityB)
creating EntityA (EntityA relation_a2b %(linkto)s EntityA)
In the translated string you can use %(linkto)s
for reference to the source
entity
.
Handling the translation catalog#
Once the internationalization is done in your code, you need to populate and update the translation catalog. Cubicweb provides the following commands for this purpose:
i18ncubicweb updates Cubicweb framework’s translation catalogs. Unless you actually work on the framework itself, you don’t need to use this command.
i18ncube updates the translation catalogs of one particular cube (or of all cubes). After this command is executed you must update the translation files .po in the “i18n” directory of your cube. This command will of course not remove existing translations still in use. It will mark unused translation but not remove them.
i18ninstance recompiles the translation catalogs of one particular instance (or of all instances) after the translation catalogs of its cubes have been updated. This command is automatically called every time you create or update your instance. The compiled catalogs (.mo) are stored in the i18n/<lang>/LC_MESSAGES of instance where lang is the language identifier (‘en’ or ‘fr’ for exemple).
Example#
You have added and/or modified some translation strings in your cube (after creating a new view or modifying the cube’s schema for exemple). To update the translation catalogs you need to do:
cubicweb-ctl i18ncube <cube>
Edit the <cube>/i18n/xxx.po files and add missing translations (those with an empty msgstr)
hg ci -m “updated i18n catalogs”
cubicweb-ctl i18ninstance <myinstance>
Customizing the messages extraction process#
The messages extraction performed by the i18ncommand
collects messages
from a few different sources:
the schema and application definition (entity names, docstrings, help messages, uicfg),
the source files:
i18n:content
ori18n:replace
directives from TAL files (with.pt
extension),strings prefixed by an underscore (
_
) in python files,strings with double quotes prefixed by an underscore in javascript files.
The source files are collected by walking through the cube directory,
but ignoring a few directories like .hg
, .tox
, test
or
node_modules
.
If you need to customize this behaviour in your cube, you have to
extend the cubicweb.devtools.devctl.I18nCubeMessageExtractor
. The
example below will collect strings from jinja2
files and ignore
the static
directory during the messages collection phase:
# mymodule.py
from cubicweb.devtools import devctl
class MyMessageExtractor(devctl.I18nCubeMessageExtractor):
blacklist = devctl.I18nCubeMessageExtractor | {'static'}
formats = devctl.I18nCubeMessageExtractor.formats + ['jinja2']
def collect_jinja2(self):
return self.find('.jinja2')
def extract_jinja2(self, files):
return self._xgettext(files, output='jinja.pot',
extraopts='-L python --from-code=utf-8')
Then, you’ll have to register it with a cubicweb.i18ncube
entry point
in your cube’s setup.py:
setup(
# ...
entry_points={
# ...
'cubicweb.i18ncube': [
'mycube=cubicweb_mycube.mymodule:MyMessageExtractor',
],
},
# ...
)
Editing po files#
Using a PO aware editor#
Many tools exist to help maintain .po (PO) files. Common editors or development environment provides modes for these. One can also find dedicated PO files editor, such as poedit.
While usage of such a tool is commendable, PO files are perfectly editable with a (unicode aware) plain text editor. It is also useful to know their structure for troubleshooting purposes.
Structure of a PO file#
In this section, we selectively quote passages of the GNU gettext manual chapter on PO files, available there:
https://www.gnu.org/software/hello/manual/gettext/PO-Files.html
One PO file entry has the following schematic structure:
white-space
# translator-comments
#. extracted-comments
#: reference...
#, flag...
#| msgid previous-untranslated-string
msgid untranslated-string
msgstr translated-string
A simple entry can look like this:
#: lib/error.c:116
msgid "Unknown system error"
msgstr "Error desconegut del sistema"
It is also possible to have entries with a context specifier. They look like this:
white-space
# translator-comments
#. extracted-comments
#: reference...
#, flag...
#| msgctxt previous-context
#| msgid previous-untranslated-string
msgctxt context
msgid untranslated-string
msgstr translated-string
The context serves to disambiguate messages with the same untranslated-string. It is possible to have several entries with the same untranslated-string in a PO file, provided that they each have a different context. Note that an empty context string and an absent msgctxt line do not mean the same thing.
Contexts and CubicWeb#
CubicWeb PO files have both non-contextual and contextual msgids.
Contextual entries are automatically used in some cases. For instance, entity.dc_type(), eschema.display_name(req) or display_name(etype, req, form, context) methods/function calls will use them.
It is also possible to explicitly use a context with _cw.pgettext(context, msgid).
Specialize translation for an application cube#
Every cube has its own translation files. For a specific application cube it can be useful to specialize translations of other cubes. You can either mark those strings for translation using _ in the python code, or add a static-messages.pot file into the i18n directory. This file looks like:
msgid ""
msgstr ""
"PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
msgig "expression to be translated"
msgstr ""
Doing this, expression to be translated
will be taken into account by
the i18ncube
command and additional messages will then appear in .po files
of the cube.
The property mecanism#
Property API#
Registering and using your own property#
HTTP cache management#
Cache policies#
Exception#
Helper functions#
Locate resources#
Static files handling#
Pyramid#
cubicweb.pyramid
provides a way to bind a CubicWeb data repository to a
Pyramid WSGI web application.
It can be used in two different ways:
Through the pyramid command or through
cubicweb.pyramid.wsgi_application()
WSGI application factory, one can run anall-in-one
CubicWeb instance with the web part served by a Pyramid application. This is referred to as the backwards compatible mode.Through the
pyramid
configuration type, one can setup a CubicWeb instance which repository can be used from within a Pyramid application. Such an instance may be launched throughpserve
or any WSGI server as would any plain Pyramid application.
Quick start#
Prerequites#
Install the pyramid flavour of CubicWeb (here with pip, possibly in a virtualenv):
pip install cubicweb
Instance creation and running#
In backwards compatible mode#
In this mode, you can simply create an instance of kind all-in-one
with
the cubicweb-ctl create
command. You’ll then need to add a pyramid.ini
file in your instance directory, see Pyramid Settings file for details about the
content of this file.
Start the instance with the ‘pyramid’ command instead of ‘start’:
cubicweb-ctl pyramid --debug myinstance
Without backwards compatibility#
In this mode, you can create an instance of kind pyramid
as follow:
cubicweb-ctl create -c pyramid <cube_name> <instance_name>
This will bootstrap a development.ini
file typical of a Pyramid
application in the instance’s directory. The new instance may then be launched
by any WSGI server, for instance with pserve:
pserve etc/cubicweb.d/<instance_name>/development.ini
In a pyramid application#
Create a pyramid application
Include cubicweb.pyramid:
def includeme(config): # ... config.include('cubicweb.pyramid') # ...
Configure the instance name (in the .ini file):
cubicweb.instance = myinstance
Configure the base-url in all-in-one.conf to match the ones of the pyramid configuration (this is a temporary limitation).
The ‘pyramid’ command#
The ‘pyramid’ command is a replacement for the ‘start’ command of cubicweb-ctl tool. It provides the same options and a few other ones.
Options#
- --no-daemon#
Run the server in the foreground.
- --debug-mode#
Activate the repository debug mode (logs in the console and the debug toolbar). Implies
--no-daemon
.Also force the following pyramid options:
pyramid.debug_authorization = yes pyramid.debug_notfound = yes pyramid.debug_routematch = yes pyramid.reload_templates = yes
- -D, --debug#
Equals to
--debug-mode
--no-daemon
--reload
- --reload#
Restart the server if any source file is changed
- --reload-interval=RELOAD_INTERVAL#
Interval, in seconds, between file modifications checks [current: 1]
- -l <log level>, --loglevel=<log level>#
Set the loglevel. debug if -D is set, error otherwise
- --profile-output=PROFILE_OUTPUT#
Profiling output file (default: “program.prof”)
- --profile-dump-every=N#
Dump profile stats to ouput every N requests (default: 100)
Settings#
Cubicweb Settings#
Pyramid CubicWeb will not make use of the configuration entries found in the cubicweb configuration (a.k.a. all-in-one.conf) for any pyramid related configuration value.
Pyramid Settings file#
In backwards compatibility mode, Pyramid settings will be looked for in a
pyramid.ini
file in the instance home directory (where the
all-in-one.conf
file is), its [main]
section will be read and used as
the settings
of the pyramid Configurator.
This configuration file is almost the same as the one read by pserve
, which
allow to easily add any pyramid extension and configure it.
A typical pyramid.ini
file is:
[main]
pyramid.includes =
pyramid_redis_sessions
cubicweb.defaults = no
cubicweb.includes =
cubicweb.pyramid.auth
cubicweb.pyramid.login
cubicweb.profile = no
redis.sessions.secret = your_cookie_signing_secret
redis.sessions.timeout = 1200
redis.sessions.host = mywheezy
Without backwards compatibility a standard development.ini
file can be
used with any useful CubicWeb-specific settings added.
Pyramid CubicWeb configuration entries#
The Pyramid CubicWeb specific configuration entries are:
- cubicweb.instance (string)#
A CubicWeb instance name. Useful when the application is not run by The ‘pyramid’ command.
- cubicweb.debug (bool)#
Enables the cubicweb debugmode. Works only if the instance is setup by
cubicweb.instance
.Unlike when the debugmode is set by the
cubicweb-ctl pyramid --debug-mode
command, the pyramid debug options are untouched.
- cubicweb.includes (list)#
Same as
pyramid.includes
, but the includes are done after the cubicweb specific registry entries are initialized.Useful to include extensions that requires these entries.
- cubicweb.bwcompat (bool)#
(True) Enable/disable backward compatibility. This only applies to “all-in-one” configuration type.
- cubicweb.bwcompat.errorhandler (bool)#
(True) Enable/disable the backward compatibility error handler. Set to ‘no’ if you need to define your own error handlers.
- cubicweb.defaults (bool)#
(True) Enable/disable defaults. See cubicweb.pyramid.defaults.
- cubicweb.auth.update_login_time (bool)#
(True) Add a
cubicweb.pyramid.auth.UpdateLoginTimeAuthenticationPolicy
policy, that update the CWUser.login_time attribute when a user login.
- cubicweb.auth.authtkt (bool)#
(True) Enables the 2 cookie-base auth policies, which activate/deactivate depending on the persistent argument passed to remember.
The default login views set persistent to True if a __setauthcookie parameters is passed to them, and evals to True in
pyramid.settings.asbool()
.The configuration values of the policies are arguments for
pyramid.authentication.AuthTktAuthenticationPolicy
.The first policy handles session authentication. It doesn’t get activated if remember() is called with persistent=False:
- cubicweb.auth.authtkt.session.cookie_name (str)#
(‘auth_tkt’) The cookie name. Must be different from the persistent authentication cookie name.
- cubicweb.auth.authtkt.session.samesite (str)#
(‘auth_tkt’) Allows you to declare if your cookie should be restricted to a first-party or same-site context. See here for more information.
- cubicweb.auth.authtkt.session.timeout (int)#
Cookie timeout.
- cubicweb.auth.authtkt.session.reissue_time (int)#
Reissue time.
The second policy handles persistent authentication. It doesn’t get activated if remember() is called with persistent=True:
- cubicweb.auth.authtkt.persistent.cookie_name (str)#
(‘auth_tkt’) The cookie name. Must be different from the session authentication cookie name.
- cubicweb.auth.authtkt.persistent.samesite (str)#
(‘auth_tkt’) Allows you to declare if your cookie should be restricted to a first-party or same-site context. See here for more information.
- cubicweb.auth.authtkt.persistent.max_age (int)#
(30 days) Max age in seconds.
- cubicweb.auth.authtkt.persistent.reissue_time (int)#
(1 day) Reissue time in seconds.
Both policies set the
secure
flag toTrue
by default, meaning that cookies will only be sent back over a secure connection (see Authentication Policies documentation for details). This can be configured throughcubicweb.auth.authtkt.persistent.secure
andcubicweb.auth.authtkt.session.secure
configuration options.
- cubicweb.auth.groups_principals (bool)#
(True) Setup a callback on the authentication stack that inject the user groups in the principals.
Authentication#
Overview#
A default authentication stack is provided by the cubicweb.pyramid.auth
module, which is included by cubicweb.pyramid.default
.
The authentication stack is built around pyramid_multiauth, and provides a few default policies that reproduce the default cubicweb behavior.
Note
Note that this module only provides an authentication policy, not the views that handle the login form. See cubicweb.pyramid.login
Customize#
The default policies can be individually deactivated, as well as the default authentication callback that returns the current user groups as principals.
The following settings can be set to False:
cubicweb.auth.update_login_time
. Activate the policy that update the user login_time when remember is called.cubicweb.auth.authtkt
and all its subvalues.cubicweb.auth.groups_principals
Additionnal policies can be added by accessing the MultiAuthenticationPolicy instance in the registry:
mypolicy = SomePolicy()
authpolicy = config.registry['cubicweb.authpolicy']
authpolicy._policies.append(mypolicy)
The pyramid debug toolbar#
The pyramid webserver comes with an integrated debug toolbar that offers a lot of information to ease development. To ease the development process in CubicWeb a series of custom debug panels have been developped especially for that purpose.
To use the pyramid debug toolbar in CubicWeb, you need to:
install it either by doing a pip install pyramid_debugtoolbar or following the official installation instructions
launch the pyramid command adding the -t/–toolbar argument to enable it like so: cubicweb-ctl pyramid my_instance -t (you probably want to add -D to activate the debug mode during development)
navigate to the website and click on the icon on the right like on this screenshot:

And you’ll have access to the debug toolbar content for this page.
Custom panels#
A series of custom debug panels have been written to offer more useful debug information during development. Here is the list:
General ‘CubicWeb’ Panel#
Provides:
currently selected controller for this with and uri/requests information
current instance configuration, options that differs from default ones are in bold
a list of useful links like on the default CW home
Screenshot:

Registry Decisions Panel#
Provides:
a list of all decisions taken in all registry during this page construction
the arguments given to take the decision
all the selection entities during decisions with their score
which one has won if any

Registry Store#
Provides:
a listing of all the content of the different registries
for each entity its detailed information

RQL#
Provides:
a list of all executed RQL queries during this page creation
for each RQL query all the generated SQL queries
detail information like the result, the args and the description of each query
the call stack on each query to see where it has been called


SQL#
Provides:
a list of all executed SQL queries during this page creation
for each SQL query the RQL query that has generated it, if any (some aren’t)
detail information like the result, the args and if the query has rollback
the call stack on each query to see where it has been called

Accessing the sources of the class/functions/method listing the debug panels#
A traversal of all those custom panels is the see the source code of all listing class/functions/methods. You can access those by:
clicking on the [source] close to the target when available
clicking on the file path in the traceback stack


You be sent to a page looking like this:

Contributing#
All source code for the custom panels is located here and the documentation of how to write custom toolbar panels here.
Additional Services#
In this chapter, we introduce services crossing the web - repository - administration organisation of the first parts of the CubicWeb book. Those services can be either proper services (like the undo functionality) or mere topical cross-sections across CubicWeb.
Undoing changes in CubicWeb#
Many desktop applications offer the possibility for the user to undo its last changes : this undo feature has now been integrated into the CubicWeb framework. This document will introduce you to the undo feature both from the end-user and the application developer point of view.
But because a semantic web application and a common desktop application are not the same thing at all, especially as far as undoing is concerned, we will first introduce what is the undo feature for now.
What’s undoing in a CubicWeb application#
What is an undo feature is quite intuitive in the context of a desktop application. But it is a bit subtler in the context of a Semantic Web application. This section introduces some of the main differences between a classical desktop and a Semantic Web applications to keep in mind in order to state precisely what we want.
The notion transactions#
A CubicWeb application acts upon an Entity-Relationship model, described by a schema. This allows to ensure some data integrity properties. It also implies that changes are made by all-or-none groups called transactions, such that the data integrity is preserved whether the transaction is completely applied or none of it is applied.
A transaction can thus include more actions than just those directly required by the main purpose of the user. For example, when a user just writes a new blog entry, the underlying transaction holds several actions as illustrated below :
By admin on 2012/02/17 15:18 - Created Blog entry : Torototo
Created Blog entry : Torototo
Added relation : Torototo owned by admin
Added relation : Torototo blog entry of Undo Blog
Added relation : Torototo in state draft (draft)
Added relation : Torototo created by admin
Because of the very nature (all-or-none) of the transactions, the “undoable stuff” are the transactions and not the actions !
Public and private actions within a transaction#
Actually, within the transaction “Created Blog entry : Torototo”, two of those actions are said to be public and the others are said to be private. Public here means that the public actions (1 and 3) were directly requested by the end user ; whereas private means that the other actions (2, 4, 5) were triggered “under the hood” to fulfill various requirements for the user operation (ensuring integrity, security, … ).
And because quite a lot of actions can be triggered by a “simple” end-user request, most of which the end-user is not (and does not need or wish to be) aware, only the so-called public actions will appear 1 in the description of the an undoable transaction.
By admin on 2012/02/17 15:18 - Created Blog entry : Torototo
Created Blog entry : Torototo
Added relation : Torototo blog entry of Undo Blog
But note that both public and private actions will be undone together when the transaction is undone.
(In)dependent transactions : the simple case#
A CubicWeb application can be used simultaneously by different users (whereas a single user works on an given office document at a given time), so that there is not always a single history time-line in the CubicWeb case. Moreover CubicWeb provides security through the mechanism of permissions granted to each user. This can lead to some transactions not being undoable in some contexts.
In the simple case two (unprivileged) users Alice and Bob make relatively independent changes : then both Alice and Bob can undo their changes. But in some case there is a clean dependency between Alice’s and Bob’s actions or between actions of one of them. For example let’s suppose that :
Alice has created a blog,
then has published a first post inside,
then Bob has published a second post in the same blog,
and finally Alice has updated its post contents.
Then it is clear that Alice can undo her contents changes and Bob can undo his post creation independently. But Alice can not undo her post creation while she has not first undone her changes. It is also clear that Bob should not have the permissions to undo any of Alice’s transactions.
More complex dependencies between transactions#
But more surprising things can quickly happen. Going back to the previous example, Alice can undo the creation of the blog after Bob has published its post in it ! But this is possible only because the schema does not require for a post to be in a blog. Would the blog entry of relation have been mandatory, then Alice could not have undone the blog creation because it would have broken integrity constraint for Bob’s post.
When a user attempts to undo a transaction the system will check whether a later transaction has explicit dependency on the would-be-undone transaction. In this case the system will not even attempt the undo operation and inform the user.
If no such dependency is detected the system will attempt the undo operation but it can fail, typically because of integrity constraint violations. In such a case the undo operation is completely 3 rollbacked.
The undo feature for CubicWeb end-users#
The exposition of the undo feature to the end-user through a Web interface is still quite basic and will be improved toward a greater usability. But it is already fully functional. For now there are two ways to access the undo feature as long as the it has been activated in the instance configuration file with the option undo-support=yes.
Immediately after having done the change to be canceled through the undo link in the message. This allows to undo an hastily action immediately. For example, just after having validated the creation of the blog entry A second blog entry we get the following message, allowing to undo the creation.

At any time we can access the undo-history view accessible from the start-up page.

This view will provide inspection of the transaction and their (public) actions. Each transaction provides its own undo link. Only the transactions the user has permissions to see and undo will be shown.

If the user attempts to undo a transaction which can’t be undone or whose undoing fails, then a message will explain the situation and no partial undoing will be left behind.
This is all for the end-user side of the undo mechanism : this is quite simple indeed ! Now, in the following section, we are going to introduce the developer side of the undo mechanism.
The undo feature for CubicWeb application developers#
A word of warning : this section is intended for developers, already having some knowledge of what’s under CubicWeb’s hood. If it is not yet the case, please refer to CubicWeb documentation http://docs.cubicweb.org/ .
Overview#
The core of the undo mechanisms is at work in the native source, beyond the RQL. This does mean that transactions and actions are no entities. Instead they are represented at the SQL level and exposed through the DB-API supported by the repository Connection objects.
Once the undo feature has been activated in the instance configuration file with the option undo-support=yes, each mutating operation (cf. 2) will be recorded in some special SQL table along with its associated transaction. Transaction are identified by a txuuid through which the functions of the DB-API handle them.
On the web side the last commited transaction txuuid is remembered in the request’s data to allow for imediate undoing whereas the undo-history view relies upon the DB-API to list the accessible transactions. The actual undoing is performed by the UndoController accessible at URL of the form www.my.host/my/instance/undo?txuuid=…
The repository side#
Please refer to the file cubicweb/server/sources/native.py and cubicweb/transaction.py for the details.
The undoing information is mainly stored in three SQL tables:
- transactions
Stores the txuuid, the user eid and the date-and-time of the transaction. This table is referenced by the two others.
- tx_entity_actions
Stores the undo information for actions on entities.
- tx_relation_actions
Stores the undo information for the actions on relations.
When the undo support is activated, entries are added to those tables for each mutating operation on the data repository, and are deleted on each transaction undoing.
Those table are accessible through the following methods of the repository Connection object :
- undoable_transactions
Returns a list of Transaction objects accessible to the user and according to the specified filter(s) if any.
- tx_info
Returns a Transaction object from a txuuid
- undo_transaction
Returns the list of Action object for the given txuuid.
NB: By default it only return public actions.
The web side#
The exposure of the undo feature to the end-user through the Web interface relies on the DB-API introduced above. This implies that the transactions and actions are not entities linked by relations on which the usual views can be applied directly.
That’s why the file cubicweb/web/views/undohistory.py defines some dedicated views to access the undo information :
- UndoHistoryView
This is a StartupView, the one accessible from the home page of the instance which list all transactions.
- UndoableTransactionView
This view handles the display of a single Transaction object.
- UndoableActionBaseView
This (abstract) base class provides private methods to build the display of actions whatever their nature.
- Undoable[Add|Remove|Create|Delete|Update]ActionView
Those views all inherit from UndoableActionBaseView and each handles a specific kind of action.
- UndoableActionPredicate
This predicate is used as a selector to pick the appropriate view for actions.
Apart from this main undo-history view a txuuid is stored in the request’s data last_undoable_transaction in order to allow immediate undoing of a hastily validated operation. This is handled in cubicweb/web/application.py in the main_publish and add_undo_link_to_msg methods for the storing and displaying respectively.
Once the undo information is accessible, typically through a txuuid in an undo URL, the actual undo operation can be performed by the UndoController defined in cubicweb/web/views/basecontrollers.py. This controller basically extracts the txuuid and performs a call to undo_transaction and in case of an undo-specific error, lets the top level publisher handle it as a validation error.
Conclusion#
The undo mechanism relies upon a low level recording of the mutating operation on the repository. Those records are accessible through some method added to the DB-API and exposed to the end-user either through a whole history view of through an immediate undoing link in the message box.
The undo feature is functional but the interface and configuration options are still quite reduced. One major improvement would be to be able to filter with a finer grain which transactions or actions one wants to see in the undo-history view. Another critical improvement would be to enable the undo feature on a part only of the entity-relationship schema to avoid storing too much useless data and reduce the underlying overhead.
But both functionality are related to the strong design choice not to represent transactions and actions as entities and relations. This has huge benefits in terms of safety and conceptual simplicity but prevents from using lots of convenient CubicWeb features such as facets to access undo information.
Before developing further the undo feature or eventually revising this design choice, it appears that some return of experience is strongly needed. So don’t hesitate to try the undo feature in your application and send us some feedback.
Notes#
- 1
The end-user Web interface could be improved to enable user to choose whether he wishes to see private actions.
- 2
There is only five kind of elementary actions (beyond merely accessing data for reading):
C : creating an entity
D : deleting an entity
U : updating an entity attributes
A : adding a relation
R : removing a relation
- 3
Meaning none of the actions in the transaction is undone. Depending upon the application, it might make sense to enable partial undo. That is to say undo in which some actions could not be undo without preventing to undo the others actions in the transaction (as long as it does not break schema integrity). This is not forbidden by the back-end but is deliberately not supported by the front-end (for now at least).
Appendixes#
The following chapters are reference material.
Frequently Asked Questions (FAQ)#
Generalities#
Why do you use the LGPL license to prevent me from doing X ?#
LGPL means that if you redistribute your application, you need to redistribute the changes you made to CubicWeb under the LGPL licence.
Publishing a web site has nothing to do with redistributing source code according to the terms of the LGPL. A fair amount of companies use modified LGPL code for internal use. And someone could publish a CubicWeb component under a BSD licence for others to plug into a LGPL framework without any problem. The only thing we are trying to prevent here is someone taking the framework and packaging it as closed source to his own clients.
Why does not CubicWeb have a template language ?#
There are enough template languages out there. You can use your preferred template language if you want.
CubicWeb does not define its own templating language as this was not our goal. Based on our experience, we realized that we could gain productivity by letting designers use design tools and developpers develop without the use of the templating language as an intermediary that could not be anyway efficient for both parties. Python is the templating language that we use in CubicWeb, but again, it does not prevent you from using a templating language.
Moreover, CubicWeb currently supports simpletal out of the box and it is also possible to use the cwtags library to build html trees using the with statement with more comfort than raw strings.
Why do you think using pure python is better than using a template language ?#
Python is an Object Oriented Programming language and as such it already provides a consistent and strong architecture and syntax a templating language would not reach.
Using Python instead of a template langage for describing the user interface makes it to maintain with real functions/classes/contexts without the need of learning a new dialect. By using Python, we use standard OOP techniques and this is a key factor in a robust application.
CubicWeb looks pretty recent. Is it stable ?#
It is constantly evolving, piece by piece. The framework has evolved since 2001 and data has been migrated from one schema to the other ever since. There is a well-defined way to handle data and schema migration.
You can see the roadmap there: https://forge.extranet.logilab.fr/cubicweb/cubicweb/-/boards.
Why is the RQL query language looking similar to X ?#
It may remind you of SQL but it is higher level than SQL, more like SPARQL. Except that SPARQL did not exist when we started the project. With version 3.4, CubicWeb has support for SPARQL.
The RQL language is what is going to make a difference with django- like frameworks for several reasons.
accessing data is much easier with it. One can write complex queries with RQL that would be tedious to define and hard to maintain using an object/filter suite of method calls.
it offers an abstraction layer allowing your applications to run on multiple back-ends. That means not only various SQL backends (postgresql, sqlite, sqlserver, mysql), but also non-SQL data stores like LDAP directories and subversion/mercurial repositories (see the vcsfile component).
Which ajax library is CubicWeb using ?#
CubicWeb uses jQuery and provides a few helpers on top of that. Additionally, some jQuery plugins are provided (some are provided in specific cubes).
Development#
How to change the instance logo ?#
The logo is managed by css. You must provide a custom css that will contain the code below:
#logo {
background-image: url("logo.jpg");
}
logo.jpg
is in mycube/data
directory.
How to create an anonymous user ?#
This allows to browse the site without being authenticated. In the
all-in-one.conf
file of your instance, define the anonymous user
as follows
# login of the CubicWeb user account to use for anonymous user (if you want to
# allow anonymous)
anonymous-user=anon
# password of the CubicWeb user account matching login
anonymous-password=anon
You also must ensure that this anon user is a registered user of the DB backend. If not, you can create through the administation interface of your instance by adding a user with in the group guests.
Note
While creating a new instance, you can decide to allow access to anonymous user, which will automatically execute what is decribed above.
How to format an entity date attribute ?#
If your schema has an attribute of type Date or Datetime, you usually want to
format it when displaying it. First, you should define your preferred format
using the site configuration panel
http://appurl/view?vid=systempropertiesform
and then set ui.date
and/or
ui.datetime
. Then in the view code, use:
entity.printable_value(date_attribute)
which will always return a string whatever the attribute’s type (so it’s recommended also for other attribute types). By default it expects to generate HTML, so it deals with rich text formating, xml escaping…
How to update a database after a schema modification ?#
It depends on what has been modified in the schema.
update the permissions and properties of an entity or a relation:
sync_schema_props_perms('MyEntityOrRelation')
.add an attribute:
add_attribute('MyEntityType', 'myattr')
.add a relation:
add_relation_definition('SubjRelation', 'MyRelation', 'ObjRelation')
.
I get NoSelectableObject exceptions, how do I debug selectors ?#
You just need to put the appropriate context manager around view/component selection. One standard place for components is in cubicweb/vregistry.py:
def possible_objects(self, *args, **kwargs):
"""return an iterator on possible objects in this registry for the given
context
"""
from logilab.common.registry import traced_selection
with traced_selection():
for appobjects in self.itervalues():
try:
yield self._select_best(appobjects, *args, **kwargs)
except NoSelectableObject:
continue
This will yield additional WARNINGs, like this:
2009-01-09 16:43:52 - (cubicweb.selectors) WARNING: selector one_line_rset returned 0 for <class 'cubicweb.web.views.basecomponents.WFHistoryVComponent'>
For views, you can put this context in cubicweb/web/views/basecontrollers.py in the ViewController:
def _select_view_and_rset(self, rset):
...
try:
from logilab.common.registry import traced_selection
with traced_selection():
view = self._cw.vreg['views'].select(vid, req, rset=rset)
except ObjectNotFound:
self.warning("the view %s could not be found", vid)
req.set_message(req._("The view %s could not be found") % vid)
vid = vid_from_rset(req, rset, self._cw.vreg.schema)
view = self._cw.vreg['views'].select(vid, req, rset=rset)
...
I get “database is locked” when executing tests#
If you have “database is locked” as error when you are executing security tests, it is usually because commit or rollback are missing before login() calls.
You can also use a context manager, to avoid such errors, as described here: Managing connections or users.
What are hooks used for ?#
Hooks are executed around (actually before or after) events. The most common events are data creation, update and deletion. They permit additional constraint checking (those not expressible at the schema level), pre and post computations depending on data movements.
As such, they are a vital part of the framework.
Other kinds of hooks, called Operations, are available for execution just before commit.
For more information, read Hooks section.
Configuration#
How to configure a LDAP source ?#
See LDAP integration.
How to import LDAP users in CubicWeb ?#
Here is a useful script which enables you to import LDAP users into your CubicWeb instance by running the following:
import os
import pwd
import sys
from logilab.database import get_connection
def getlogin():
"""avoid using os.getlogin() because of strange tty/stdin problems
(man 3 getlogin)
Another solution would be to use $LOGNAME, $USER or $USERNAME
"""
return pwd.getpwuid(os.getuid())[0]
try:
database = sys.argv[1]
except IndexError:
print 'USAGE: python ldap2system.py <database>'
sys.exit(1)
if input('update %s db ? [y/n]: ' % database).strip().lower().startswith('y'):
cnx = get_connection(user=getlogin(), database=database)
cursor = cnx.cursor()
insert = ('INSERT INTO euser (creation_date, eid, modification_date, login, '
' firstname, surname, last_login_time, upassword) '
"VALUES (%(mtime)s, %(eid)s, %(mtime)s, %(login)s, %(firstname)s, "
"%(surname)s, %(mtime)s, './fqEz5LeZnT6');")
update = "UPDATE entities SET source='system' WHERE eid=%(eid)s;"
cursor.execute("SELECT eid,type,source,extid,mtime FROM entities WHERE source!='system'")
for eid, type, source, extid, mtime in cursor.fetchall():
if type != 'CWUser':
print "don't know what to do with entity type", type
continue
if source != 'ldapuser':
print "don't know what to do with source type", source
continue
ldapinfos = dict(x.strip().split('=') for x in extid.split(','))
login = ldapinfos['uid']
firstname = ldapinfos['uid'][0].upper()
surname = ldapinfos['uid'][1:].capitalize()
if login != 'jcuissinat':
args = dict(eid=eid, type=type, source=source, login=login,
firstname=firstname, surname=surname, mtime=mtime)
print args
cursor.execute(insert, args)
cursor.execute(update, args)
cnx.commit()
cnx.close()
Security#
How to reset the password for user joe ?#
If you want to reset the admin password for myinstance
, do:
$ cubicweb-ctl reset-admin-pwd myinstance
You need to generate a new encrypted password:
$ python
>>> from cubicweb.server.utils import crypt_password
>>> crypt_password('joepass')
'qHO8282QN5Utg'
>>>
and paste it in the database:
$ psql mydb
mydb=> update cw_cwuser set cw_upassword='qHO8282QN5Utg' where cw_login='joe';
UPDATE 1
if you’re running over SQL Server, you need to use the CONVERT function to convert the string to varbinary(255). The SQL query is therefore:
update cw_cwuser set cw_upassword=CONVERT(varbinary(255), 'qHO8282QN5Utg') where cw_login='joe';
Be careful, the encryption algorithm is different on Windows and on Unix. You cannot therefore use a hash generated on Unix to fill in a Windows database, nor the other way round.
You can prefer use a migration script similar to this shell invocation instead:
$ cubicweb-ctl shell <instance>
>>> from cubicweb import Binary
>>> from cubicweb.server.utils import crypt_password
>>> crypted = crypt_password('joepass')
>>> rset = rql('Any U WHERE U is CWUser, U login "joe"')
>>> joe = rset.get_entity(0,0)
>>> joe.cw_set(upassword=Binary(crypted))
Please, refer to the script example is provided in the misc/examples/chpasswd.py file.
The more experimented people would use RQL request directly:
>>> rql('SET X upassword %(a)s WHERE X is CWUser, X login "joe"',
... {'a': crypted})
I’ve just created a user in a group and it doesn’t work !#
You are probably getting errors such as
remove {'PR': 'Project', 'C': 'CWUser'} from solutions since your_user has no read access to cost
This is because you have to put your user in the “users” group. The user has to be in both groups.
How is security implemented ?#
The basis for security is a mapping from operations to groups or arbitrary RQL expressions. These mappings are scoped to entities and relations.
This is an example for an Entity Type definition:
class Version(EntityType):
"""a version is defining the content of a particular project's
release"""
# definition of attributes is voluntarily missing
__permissions__ = {'read': ('managers', 'users', 'guests',),
'update': ('managers', 'logilab', 'owners'),
'delete': ('managers',),
'add': ('managers', 'logilab',
ERQLExpression('X version_of PROJ, U in_group G, '
'PROJ require_permission P, '
'P name "add_version", P require_group G'),)}
The above means that permission to read a Version is granted to any user that is part of one of the groups ‘managers’, ‘users’, ‘guests’. The ‘add’ permission is granted to users in group ‘managers’ or ‘logilab’ or to users in group G, if G is linked by a permission entity named “add_version” to the version’s project.
An example for a Relation Definition (RelationType both defines a relation type and implicitly one relation definition, on which the permissions actually apply):
class version_of(RelationType):
"""link a version to its project. A version is necessarily linked
to one and only one project. """
# some lines voluntarily missing
__permissions__ = {'read': ('managers', 'users', 'guests',),
'delete': ('managers', ),
'add': ('managers', 'logilab',
RRQLExpression('O require_permission P, P name "add_version", '
'U in_group G, P require_group G'),) }
The main difference lies in the basic available operations (there is no ‘update’ operation) and the usage of an RRQLExpression (rql expression for a relation) instead of an ERQLExpression (rql expression for an entity).
You can find additional information in the section The security model.
Is it possible to bypass security from the UI (web front) part ?#
No. Only Hooks/Operations can do that.
Can PostgreSQL and CubicWeb authentication work with kerberos ?#
If you have PostgreSQL set up to accept kerberos authentication, you can set the db-host, db-name and db-user parameters in the sources configuration file while leaving the password blank. It should be enough for your instance to connect to postgresql with a kerberos ticket.
Relation Query Language (RQL)#
This chapter describes the Relation Query Language syntax and its implementation in CubicWeb.
Introduction#
Goals of RQL#
The goal is to have a semantic language in order to:
query relations in a clear syntax
empowers access to data repository manipulation
making attributes/relations browsing easy
As such, attributes will be regarded as cases of special relations (in terms of usage, the user should see no syntactic difference between an attribute and a relation).
Comparison with existing languages#
SQL#
RQL may remind of SQL but works at a higher abstraction level (the CubicWeb framework generates SQL from RQL to fetch data from relation databases). RQL is focused on browsing relations. The user needs only to know about the CubicWeb data model he is querying, but not about the underlying SQL model.
Sparql#
The query language most similar to RQL is SPARQL, defined by the W3C to serve for the semantic web.
Versa#
We should look in more detail, but here are already some ideas for the moment … Versa is the language most similar to what we wanted to do, but the model underlying data being RDF, there are some things such as namespaces or handling of the RDF types which does not interest us. On the functionality level, Versa is very comprehensive including through many functions of conversion and basic types manipulation, which we may want to look at one time or another. Finally, the syntax is a little esoteric.
Datalog#
Datalog is a prolog derived query langage which applies to relational databases. It is more expressive than RQL in that it accepts either extensional and intensional predicates (or relations). As of now, RQL only deals with intensional relations.
The different types of queries#
- Search (Any)
Extract entities and attributes of entities.
- Insert entities (INSERT)
Insert new entities or relations in the database. It can also directly create relationships for the newly created entities.
- Update entities, create relations (SET)
Update existing entities in the database, or create relations between existing entities.
- Delete entities or relationship (DELETE)
Remove entities or relations existing in the database.
RQL relation expressions#
RQL expressions apply to a live database defined by a Yams schema. Apart from the main type, or head, of the expression (search, insert, etc.) the most common constituent of an RQL expression is a (set of) relation expression(s).
An RQL relation expression contains three components:
the subject, which is an entity type
the predicate, which is a relation definition (an arc of the schema)
the object, which is either an attribute or a relation to another entity

Warning
A relation is always expressed in the order: subject
,
predicate
, object
.
It is important to determine if the entity type is subject or object to construct a valid expression. Inverting the subject/object is an error since the relation cannot be found in the schema.
If one does not have access to the code, one can find the order by looking at the schema image in manager views (the subject is located at the beginning of the arrow).
An example of two related relation expressions:
P works_for C, P name N
RQL variables represent typed entities. The type of entities is
either automatically inferred (by looking at the possible relation
definitions, see Relation definition) or explicitely constrained
using the is
meta relation.
In the example above, we barely need to look at the schema. If variable names (in the RQL expression) and relation type names (in the schema) are expresssively designed, the human reader can infer as much as the CubicWeb querier.
The P
variable is used twice but it always represent the same set
of entities. Hence P works_for C
and P name N
must be
compatible in the sense that all the Ps (which can refer to
different entity types) must accept the works_for
and name
relation types. This does restrict the set of possible values of P.
Adding another relation expression:
P works_for C, P name N, C name "logilab"
This further restricts the possible values of P through an indirect
constraint on the possible values of C
. The RQL-level unification
happening there is translated to one (or several) joins at the
database level.
Note
In CubicWeb, the term relation is often found without ambiguity instead of predicate. This predicate is also known as the property of the triple in RDF concepts
RQL Operators#
An RQL expression’s head can be completed using various operators such
as ORDERBY
, GROUPBY
, HAVING
, LIMIT
etc.
RQL relation expressions can be grouped with UNION
or
WITH
. Predicate oriented keywords such as EXISTS
, OR
,
NOT
are available.
The complete zoo of RQL operators is described extensively in the following chapter (RQL syntax).
RQL syntax#
Reserved keywords#
AND, ASC, BEING, DELETE, DESC, DISTINCT, EXISTS, FALSE, GROUPBY,
HAVING, ILIKE, INSERT, LIKE, LIMIT, NOT, NOW, NULL, NULLSFIRST, NULLSLAST,
OFFSET, OR, ORDERBY, SET, TODAY, TRUE, UNION, WHERE, WITH
The keywords are not case sensitive. You should not use them when defining your schema, or as RQL variable names.
Case#
Variables should be all upper-cased.
Relation should be all lower-cased and match exactly names of relations defined in the schema.
Entity types should start with an upper cased letter and be followed by at least a lower cased latter.
Variables and typing#
Entities and values to browse and/or select are represented in the query by variables that must be written in capital letters.
With RQL, we do not distinguish between entities and attributes. The value of an attribute is considered as an entity of a particular type (see below), linked to one (real) entity by a relation called the name of the attribute, where the entity is the subject and the attribute the object.
The possible type(s) for each variable is derived from the schema according to the constraints expressed above and thanks to the relations between each variable.
We can restrict the possible types for a variable using the special relation is in the restrictions.
Virtual relations#
Those relations may only be used in RQL query but are not actual attributes of your entities.
has_text: relation to use to query the full text index (only for entities having fulltextindexed attributes).
identity: relation to use to tell that a RQL variable is the same as another when you’ve to use two different variables for querying purpose. On the opposite it’s also useful together with the
NOT
operator to tell that two variables should not identify the same entity
Literal expressions#
Bases types supported by RQL are those supported by yams schema. Literal values are expressed as explained below:
string should be between double or single quotes. If the value contains a quote, it should be preceded by a backslash ‘\’
floats separator is dot ‘.’
boolean values are
TRUE
andFALSE
keywordsdate and time should be expressed as a string with ISO notation : YYYY/MM/DD [hh:mm], or using keywords
TODAY
andNOW
You may also use the NULL
keyword, meaning ‘unspecified’.
Operators#
Logical operators#
AND, OR, NOT, ','
‘,’ is equivalent to ‘AND’ but with the smallest among the priority of logical operators (see Operators priority).
Mathematical operators#
Operator |
Description |
Example |
Result |
---|---|---|---|
+ |
addition |
2 + 3 |
5 |
- |
subtraction |
2 - 3 |
-1 |
* |
multiplication |
2 * 3 |
6 |
/ |
division |
4 / 2 |
2 |
% |
modulo (remainder) |
5 % 4 |
1 |
^ |
exponentiation |
2.0 ^ 3.0 |
8 |
& |
bitwise AND |
91 & 15 |
11 |
| |
bitwise OR |
32 | 3 |
35 |
# |
bitwise XOR |
17 # 5 |
20 |
~ |
bitwise NOT |
~1 |
-2 |
<< |
bitwise shift left |
1 << 4 |
16 |
>> |
bitwise shift right |
8 >> 2 |
2 |
Notice integer division truncates results depending on the backend behaviour. For instance, postgresql does.
Comparison operators#
=, !=, <, <=, >=, >, IN
The syntax to use comparison operators is:
VARIABLE attribute <operator> VALUE
The = operator is the default operator and can be omitted, i.e. :
VARIABLE attribute = VALUE
is equivalent to
VARIABLE attribute VALUE
The operator IN provides a list of possible values:
Any X WHERE X name IN ('chauvat', 'fayolle', 'di mascio', 'thenault')
String operators#
LIKE, ILIKE, ~=, REGEXP
The LIKE
string operator can be used with the special character % in
a string as wild-card:
-- match every entity whose name starts with 'Th'
Any X WHERE X name ~= 'Th%'
-- match every entity whose name endswith 'lt'
Any X WHERE X name LIKE '%lt'
-- match every entity whose name contains a 'l' and a 't'
Any X WHERE X name LIKE '%l%t%'
ILIKE
is the case insensitive version of LIKE
. It’s not
available on all backend (e.g. sqlite doesn’t support it). If not available for
your backend, ILIKE
will behave like LIKE
.
~= is a shortcut version of ILIKE
, or of LIKE
when the
former is not available on the back-end.
The REGEXP
is an alternative to LIKE
that supports POSIX
regular expressions:
-- match entities whose title starts with a digit
Any X WHERE X title REGEXP "^[0-9].*"
The underlying SQL operator used is back-end-dependent :
the
~
operator is used for postgresql,the
REGEXP
operator for mysql and sqlite.
Other back-ends are not supported yet.
Operators priority#
(, )
^, <<, >>
*, /, %, &
+, -, |, #
NOT
AND
OR
,
Search Query#
Simplified grammar of search query:
[ `DISTINCT`] `Any` V1 (, V2) \*
[ `GROUPBY` V1 (, V2) \*] [ `ORDERBY` <orderterms>]
[ `LIMIT` <value>] [ `OFFSET` <value>]
[ `WHERE` <triplet restrictions>]
[ `WITH` V1 (, V2)\* BEING (<query>)]
[ `HAVING` <other restrictions>]
[ `UNION` <query>]
Selection#
The fist occuring clause is the selection of terms that should be in the result set. Terms may be variable, literals, function calls, arithmetic, etc. and each term is separated by a comma.
There will be as much column in the result set as term in this clause, respecting order.
Syntax for function call is somewhat intuitive, for instance:
Any UPPER(N) WHERE P firstname N
Grouping and aggregating#
The GROUPBY
keyword is followed by a list of terms on which results
should be grouped. They are usually used with aggregate functions, responsible to
aggregate values for each group (see Aggregate functions).
For grouped queries, all selected variables must be either aggregated (i.e. used
by an aggregate function) or grouped (i.e. listed in the GROUPBY
clause).
Sorting#
The ORDERBY
keyword if followed by the definition of the selection
order: variable or column number followed by sorting method (ASC
,
DESC
), ASC
being the default. If the sorting method is not
specified, then the sorting is ascendant (ASC).
It is also possible to precise a specific ordering for NULL values. The NULLSFIRST and NULLSLAST options can be used to determine whether nulls appear before or after non-null values in the sort ordering. By default, null values sort as if larger than any non-null value; that is, NULLSFIRST is the default for DESC order, and NULLSLAST otherwise. These options are written after the sorting method when it is specified. For instance, this request will return all projects ordered by creation date in descending order, with projects with no date in last position.
Any X ORDERBY Y DESC NULLSLAST WHERE X creation_date Y
Pagination#
The LIMIT
and OFFSET
keywords may be respectively used to
limit the number of results and to tell from which result line to start (for
instance, use LIMIT 20 to get the first 20 results, then LIMIT 20 OFFSET 20
to get the next 20.
Restrictions#
The WHERE
keyword introduce one of the “main” part of the query, where
you “define” variables and add some restrictions telling what you’re interested
in.
It’s a list of triplets “subject relation object”, e.g. V1 relation (V2 | <static value>). Triplets are separated using Logical operators.
Note
About the negation operator (NOT
):
NOT X relation Y
is equivalent toNOT EXISTS(X relation Y)
Any X WHERE NOT X owned_by U
means “entities that have no relationowned_by
”.Any X WHERE NOT X owned_by U, U login "syt"
means “the entity have no relationowned_by
with the user syt”. They may have a relation “owned_by” with another user.
In this clause, you can also use EXISTS
when you want to know if some
expression is true and do not need the complete set of elements that make it
true. Testing for existence is much faster than fetching the complete set of
results, especially when you think about using OR
against several expressions. For instance
if you want to retrieve versions which are in state “ready” or tagged by
“priority”, you should write :
Any X ORDERBY PN,N
WHERE X num N, X version_of P, P name PN,
EXISTS(X in_state S, S name "ready")
OR EXISTS(T tags X, T name "priority")
not
Any X ORDERBY PN,N
WHERE X num N, X version_of P, P name PN,
(X in_state S, S name "ready")
OR (T tags X, T name "priority")
Both queries aren’t at all equivalent :
the former will retrieve all versions, then check for each one which are in the matching state of or tagged by the expected tag,
the later will retrieve all versions, state and tags (cartesian product!), compute join and then exclude each row which are in the matching state or tagged by the expected tag. This implies that you won’t get any result if the in_state or tag tables are empty (ie there is no such relation in the application). This is usually NOT what you want.
Another common case where you may want to use EXISTS
is when you
find yourself using DISTINCT
at the beginning of your query to
remove duplicate results. The typical case is when you have a
multivalued relation such as Version version_of Project and you want
to retrieve projects which have a version:
Any P WHERE V version_of P
will return each project number of versions times. So you may be tempted to use:
DISTINCT Any P WHERE V version_of P
This will work, but is not efficient, as it will use the SELECT
DISTINCT
SQL predicate, which needs to retrieve all projects, then
sort them and discard duplicates, which can have a very high cost for
large result sets. So the best way to write this is:
Any P WHERE EXISTS(V version_of P)
You can also use the question mark (?) to mark optional relations. This allows you to select entities related or not to another. It is a similar concept to Left outer join:
the result of a left outer join (or simply left join) for table A and B always contains all records of the “left” table (A), even if the join-condition does not find any matching record in the “right” table (B).
You must use the ? behind a variable to specify that the relation to that variable is optional. For instance:
Bugs of a project attached or not to a version
Any X, V WHERE X concerns P, P eid 42, X corrected_in V?
You will get a result set containing all the project’s tickets, with either the version in which it’s fixed or None for tickets not related to a version.
All cards and the project they document if any
Any C, P WHERE C is Card, P? documented_by C
Notice you may also use outer join:
on the RHS of attribute relation, e.g.
Any X WHERE X ref XR, Y name XR?
so that Y is outer joined on X by ref/name attributes comparison
on any side of an
HAVING
expression, e.g.Any X WHERE X creation_date XC, Y creation_date YC HAVING YEAR(XC)=YEAR(YC)?
so that Y is outer joined on X by comparison of the year extracted from their creation date.
Any X WHERE X creation_date XC, Y creation_date YC HAVING YEAR(XC)?=YEAR(YC)
would outer join X on Y instead.
Having restrictions#
The HAVING
clause, as in SQL, may be used to restrict a query
according to value returned by an aggregate function, e.g.
Any X GROUPBY X WHERE X relation Y HAVING COUNT(Y) > 10
It may however be used for something else: In the WHERE
clause, we are
limited to triplet expressions, so some things may not be expressed there. Let’s
take an example : if you want to get people whose upper-cased first name equals to
another person upper-cased first name. There is no proper way to express this
using triplet, so you should use something like:
Any X WHERE X firstname XFN, Y firstname YFN, NOT X identity Y HAVING UPPER(XFN) = UPPER(YFN)
Another example: imagine you want person born in 2000:
Any X WHERE X birthday XB HAVING YEAR(XB) = 2000
Notice that while we would like this to work without the HAVING clause, this can’t be currently be done because it introduces an ambiguity in RQL’s grammar that can’t be handled by Yapps, the parser’s generator we’re using.
Sub-queries#
The WITH
keyword introduce sub-queries clause. Each sub-query has the
form:
V1(,V2) BEING (rql query)
Variables at the left of the BEING
keyword defines into which
variables results from the sub-query will be mapped to into the outer query.
Sub-queries are separated from each other using a comma.
Let’s say we want to retrieve for each project its number of versions and its number of tickets. Due to the nature of relational algebra behind the scene, this can’t be achieved using a single query. You have to write something along the line of:
Any X, VC, TC WHERE X identity XX
WITH X, VC BEING (Any X, COUNT(V) GROUPBY X WHERE V version_of X),
XX, TC BEING (Any X, COUNT(T) GROUPBY X WHERE T ticket_of X)
Notice that we can’t reuse a same variable name as alias for two different sub-queries, hence the usage of ‘X’ and ‘XX’ in this example, which are then unified using the special identity relation (see Virtual relations).
Warning
Sub-queries define a new variable scope, so even if a variable has the same name in the outer query and in the sub-query, they technically aren’t the same variable. So:
Any W, REF WITH W, REF BEING
(Any W, REF WHERE W is Workcase, W ref REF,
W concerned_by D, D name "Logilab")
could be written:
Any W, REF WITH W, REF BEING
(Any W1, REF1 WHERE W1 is Workcase, W1 ref REF1,
W1 concerned_by D, D name "Logilab")
Also, when a variable is coming from a sub-query, you currently can’t reference its attribute or inlined relations in the outer query, you’ve to fetch them in the sub-query. For instance, let’s say we want to sort by project name in our first example, we would have to write:
Any X, VC, TC ORDERBY XN WHERE X identity XX
WITH X, XN, VC BEING (Any X, COUNT(V) GROUPBY X,XN WHERE V version_of X, X name XN),
XX, TC BEING (Any X, COUNT(T) GROUPBY X WHERE T ticket_of X)
instead of:
Any X, VC, TC ORDERBY XN WHERE X identity XX, X name XN,
WITH X, XN, VC BEING (Any X, COUNT(V) GROUPBY X WHERE V version_of X),
XX, TC BEING (Any X, COUNT(T) GROUPBY X WHERE T ticket_of X)
which would result in a SQL execution error.
Union#
You may get a result set containing the concatenation of several queries using
the UNION
. The selection of each query should have the same number of
columns.
(Any X, XN WHERE X is Person, X surname XN) UNION (Any X,XN WHERE X is Company, X name XN)
Available functions#
Below is the list of aggregate and transformation functions that are supported natively by the framework. Notice that cubes may define additional functions.
Aggregate functions#
|
return the number of rows |
|
return the minimum value |
|
return the maximum value |
|
return the average value |
|
return the sum of values |
|
return each unique value separated by a comma (for string only) |
All aggregate functions above take a single argument. Take care some aggregate
functions (e.g. MAX
, MIN
) may return None if there is no
result row.
String transformation functions#
|
upper case the string |
|
lower case the string |
|
return the length of the string |
|
extract from the string a string starting at given index and of given length |
|
if the length of the string is greater than given max size, strip it and add ellipsis (”…”). The resulting string will hence have max size + 3 characters |
|
similar to the above, but allow to specify the MIME type of the text contained by the string. Supported formats are text/html, text/xhtml and text/xml. All others will be considered as plain text. For non plain text format, sgml tags will be first removed before limiting the string. |
Date extraction functions#
|
return the year of a date or datetime |
|
return the month of a date or datetime |
|
return the day of a date or datetime |
|
return the hours of a datetime |
|
return the minutes of a datetime |
|
return the seconds of a datetime |
|
return the day of week of a date or datetime. Sunday == 1, Saturday == 7. |
Other functions#
|
return the absolute value of a number |
|
return a pseudo-random value from 0.0 to 1.0 |
|
expect X to be an attribute whose value is stored in a
|
|
expect X to be an entity used in a has_text relation, and return a number corresponding to the rank order of each resulting entity |
|
expect X to be an attribute and return it casted into the given final type |
Examples#
Search for the object of identifier 53
Any X WHERE X eid 53
Search material such as comics, owned by syt and available
Any X WHERE X is Document, X occurence_of F, F class C, C name 'Comics', X owned_by U, U login 'syt', X available TRUE
Looking for people working for eurocopter interested in training
Any P WHERE P is Person, P work_for S, S name 'Eurocopter', P interested_by T, T name 'training'
Search note less than 10 days old written by jphc or ocy
Any N WHERE N is Note, N written_on D, D day> (today -10), N written_by P, P name 'jphc' or P name 'ocy'
Looking for people interested in training or living in Paris
Any P WHERE P is Person, EXISTS(P interested_by T, T name 'training') OR (P city 'Paris')
The surname and firstname of all people
Any N, P WHERE X is Person, X name N, X firstname P
Note that the selection of several entities generally force the use of “Any” because the type specification applies otherwise to all the selected variables. We could write here
String N, P WHERE X is Person, X name N, X first_name P
Note: You can not specify several types with * … where X is FirstType or X is SecondType*. To specify several types explicitly, you have to do
Any X WHERE X is IN (FirstType, SecondType)
Insertion query#
INSERT <entity type> V1 (, <entity type> V2) * : <assignments> [ WHERE <restriction>]
- assignments
list of relations to assign in the form V1 relationship V2 | <static value>
The restriction can define variables used in assignments.
Caution, if a restriction is specified, the insertion is done for each line result returned by the restriction.
Insert a new person named ‘foo’
INSERT Person X: X name 'foo'
Insert a new person named ‘foo’, another called ‘nice’ and a ‘friend’ relation between them
INSERT Person X, Person Y: X name 'foo', Y name 'nice', X friend Y
Insert a new person named ‘foo’ and a ‘friend’ relation with an existing person called ‘nice’
INSERT Person X: X name 'foo', X friend Y WHERE Y name 'nice'
Update and relation creation queries#
SET <assignements> [ WHERE <restriction>]
Caution, if a restriction is specified, the update is done for each result line returned by the restriction.
Renaming of the person named ‘foo’ to ‘bar’ with the first name changed
SET X name 'bar', X firstname 'original' WHERE X is Person, X name 'foo'
Insert a relation of type ‘know’ between objects linked by the relation of type ‘friend’
SET X know Y WHERE X friend Y
Deletion query#
DELETE (<entity type> V) | (V1 relation v2 ),… [ WHERE <restriction>]
Caution, if a restriction is specified, the deletion is made for each line result returned by the restriction.
Deletion of the person named ‘foo’
DELETE Person X WHERE X name 'foo'
Removal of all relations of type ‘friend’ from the person named ‘foo’
DELETE X friend Y WHERE X is Person, X name 'foo'
Debugging RQL#
Available levels#
Server debugging flags. They may be combined using binary operators.
- cubicweb.server.DBG_NONE = 0#
no debug information
- cubicweb.server.DBG_RQL = 1#
rql execution information
- cubicweb.server.DBG_SQL = 2#
executed sql
- cubicweb.server.DBG_REPO = 4#
repository events
- cubicweb.server.DBG_HOOKS = 16#
hooks
- cubicweb.server.DBG_OPS = 32#
operations
- cubicweb.server.DBG_MORE = 128#
more verbosity
- cubicweb.server.DBG_ALL = 247#
all level enabled
Enable verbose output#
To debug your RQL statements, it can be useful to enable a verbose output:
from cubicweb import server
server.set_debug(server.DBG_RQL|server.DBG_SQL|server.DBG_ALL)
Another example showing how to debug hooks at a specific code site:
from cubicweb.server import debugged, DBG_HOOKS
with debugged(DBG_HOOKS):
person.cw_set(works_for=company)
Detect largest RQL queries#
See Profiling and performance chapter (see Profiling and performance).
API#
- class cubicweb.server.debugged(debugmode)[source]#
Context manager and decorator to help debug the repository.
It can be used either as a context manager:
>>> with debugged('DBG_RQL | DBG_REPO'): ... # some code in which you want to debug repository activity, ... # seing information about RQL being executed an repository events.
or as a function decorator:
>>> @debugged('DBG_RQL | DBG_REPO') ... def some_function(): ... # some code in which you want to debug repository activity, ... # seing information about RQL being executed an repository events
The debug mode will be reset to its original value when leaving the “with” block or the decorated function.
RQL usecases#
Search bar#
The search bar is available on a CubicWeb instance to use RQL and it’s use and configuration is described in the doc.
Use of RQL in Card documents - ReST#
With a CubicWeb instance supporting object types with ReST content (for example Card), one can build content based on RQL queries as dynamic documents.
For this, use the rql and rql-table ReST directive, for more information about custom ReST directives head over to the sphinx documentation which uses them extensively.
rql directive#
The rql directive takes as input an RQL expression and a view to apply to the result.
For example, create a Card content by opening http://cubicweb_example.org/add/Card and add the following content, as an example : a table of blog entries (10 most recent blog entries table with user and date information)
Recent blog entries
-------------------
:rql:`Any B,U,D ORDERBY D DESC LIMIT 10 WHERE B is BlogEntry, B title T, B creation_date D, B created_by U:table`

rql-table directive#
rql-table enables more customization, enabling you to modify the column (header) contents, and the view applied for a specific column (colvids).
For example, create a Card content by opening http://cubicweb_example.org/add/Card and add the following content
Blog entries with rql-table
-----------------------------
.. rql-table::
:vid: table
:headers: Title with link, who wrote it, at what date
:colvids: 1=sameetypelist
Any B,U,D ORDERBY D DESC LIMIT 10 WHERE B is BlogEntry, B title T, B creation_date D, B created_by U
All fields but the RQL string are optional. The :headers:
option can
contain empty column names.

Use in python projects and CLI#
cwclientlib <https://pypi.org/project/cwclientlib/> enables you to use RQL in your python projects using only web requests. This project also provides a remote command line interface (CLI) you can use to replace a server side cubicweb-ctl shell.
Use in JavaScript/React components#
cwclientelements <https://forge.extranet.logilab.fr/open-source/cwclientelements> is a library of reusable React components for building web application with cubicweb and RQL.
Implementation#
BNF grammar#
The terminal elements are in capital letters, non-terminal in lowercase. The value of the terminal elements (between quotes) is a Python regular expression.
statement ::= (select | delete | insert | update) ';'
# select specific rules
select ::= 'DISTINCT'? E_TYPE selected_terms restriction? group? sort?
selected_terms ::= expression ( ',' expression)*
group ::= 'GROUPBY' VARIABLE ( ',' VARIABLE)*
sort ::= 'ORDERBY' sort_term ( ',' sort_term)*
sort_term ::= VARIABLE sort_method =?
sort_method ::= 'ASC' | 'DESC'
# delete specific rules
delete ::= 'DELETE' (variables_declaration | relations_declaration) restriction?
# insert specific rules
insert ::= 'INSERT' variables_declaration ( ':' relations_declaration)? restriction?
# update specific rules
update ::= 'SET' relations_declaration restriction
# common rules
variables_declaration ::= E_TYPE VARIABLE (',' E_TYPE VARIABLE)*
relations_declaration ::= simple_relation (',' simple_relation)*
simple_relation ::= VARIABLE R_TYPE expression
restriction ::= 'WHERE' relations
relations ::= relation (LOGIC_OP relation)*
| '(' relations')'
relation ::= 'NOT'? VARIABLE R_TYPE COMP_OP? expression
| 'NOT'? R_TYPE VARIABLE 'IN' '(' expression (',' expression)* ')'
expression ::= var_or_func_or_const (MATH_OP var_or_func_or_const) *
| '(' expression ')'
var_or_func_or_const ::= VARIABLE | function | constant
function ::= FUNCTION '(' expression ( ',' expression) * ')'
constant ::= KEYWORD | STRING | FLOAT | INT
# tokens
LOGIC_OP ::= ',' | 'OR' | 'AND'
MATH_OP ::= '+' | '-' | '/' | '*'
COMP_OP ::= '>' | '>=' | '=' | '<=' | '<' | '~=' | 'LIKE'
FUNCTION ::= 'MIN' | 'MAX' | 'SUM' | 'AVG' | 'COUNT' | 'UPPER' | 'LOWER'
VARIABLE ::= '[A-Z][A-Z0-9]*'
E_TYPE ::= '[A-Z]\w*'
R_TYPE ::= '[a-z_]+'
KEYWORD ::= 'TRUE' | 'FALSE' | 'NULL' | 'TODAY' | 'NOW'
STRING ::= "'([^'\]|\\.)*'" |'"([^\"]|\\.)*\"'
FLOAT ::= '\d+\.\d*'
INT ::= '\d+'
Internal representation (syntactic tree)#
The tree research does not contain the selected variables (e.g. there is only what follows “WHERE”).
The insertion tree does not contain the variables inserted or relations defined on these variables (e.g. there is only what follows “WHERE”).
The removal tree does not contain the deleted variables and relations (e.g. there is only what follows the “WHERE”).
The update tree does not contain the variables and relations updated (e.g. there is only what follows the “WHERE”).
Select ((Relationship | And | Or)?, Group?, Sort?)
Insert (Relations | And | Or)?
Delete (Relationship | And | Or)?
Update (Relations | And | Or)?
And ((Relationship | And | Or), (Relationship | And | Or))
Or ((Relationship | And | Or), (Relationship | And | Or))
Relationship ((VariableRef, Comparison))
Comparison ((Function | MathExpression | Keyword | Constant | VariableRef) +)
Function (())
MathExpression ((MathExpression | Keyword | Constant | VariableRef), (MathExpression | Keyword | Constant | VariableRef))
Group (VariableRef +)
Sort (SortTerm +)
SortTerm (VariableRef +)
VariableRef ()
Variable ()
Keyword ()
Constant ()
Known limitations#
The current implementation does not support linking two relations of type ‘is’ with an OR. I do not think that the negation is supported on this type of relation (XXX to be confirmed).
missing COALESCE and certainly other things…
writing an rql query requires knowledge of the used schema (with real relation names and entities, not those viewed in the user interface). On the other hand, we cannot really bypass that, and it is the job of a user interface to hide the RQL.
Topics#
It would be convenient to express the schema matching relations (non-recursive rules):
Document class Type <-> Document occurence_of Fiche class Type
Sheet class Type <-> Form collection Collection class Type
Therefore 1. becomes:
Document X where
X class C, C name 'Cartoon'
X owned_by U, U login 'syt'
X available true
I’m not sure that we should handle this at RQL level …
There should also be a special relation ‘anonymous’.
Introducing Mercurial#
Introduction#
Mercurial manages a distributed repository containing revisions trees (each revision indicates the changes required to obtain the next, and so on). Locally, we have a repository containing revisions tree, and a working directory. It is possible to put in its working directory, one of the versions of its local repository, modify and then push it in its repository. It is also possible to get revisions from another repository or to export its own revisions from the local repository to another repository.
In contrast to CVS/Subversion, we usually create a repository per project to manage.
In a collaborative development, we usually create a central repository accessible to all developers of the project. These central repository is used as a reference. According to their needs, everyone can have a local repository, that they will have to synchronize with the central repository from time to time.
Major commands#
Create a local repository:
hg clone ssh://myhost//home/src/repo
See the contents of the local repository (graphical tool in Qt):
hgview
Add a sub-directory or file in the current directory:
hg add subdir
Move to the working directory a specific revision (or last revision) from the local repository:
hg update [identifier-revision] hg up [identifier-revision]
Get in its local repository, the tree of revisions contained in a remote repository (this does not change the local directory):
hg pull ssh://myhost//home/src/repo hg pull -u ssh://myhost//home/src/repo # equivalent to pull + update
See what are the heads of branches of the local repository if a pull returned a new branch:
hg heads
Submit the working directory in the local repository (and create a new revision):
hg commit hg ci
Merge with the mother revision of local directory, another revision from the local respository (the new revision will be then two mothers revisions):
hg merge identifier-revision
Export to a remote repository, the tree of revisions in its content local respository (this does not change the local directory):
hg push ssh://myhost//home/src/repo
See what local revisions are not in another repository:
hg outgoing ssh://myhost//home/src/repo
See what are the revisions of a repository not found locally:
hg incoming ssh://myhost//home/src/repo
See what is the revision of the local repository which has been taken out from the working directory and amended:
hg parent
See the differences between the working directory and the mother revision of the local repository, possibly to submit them in the local repository:
hg diff hg commit-tool hg ct
Best Practices#
- Remember to hg pull -u regularly, and particularly before
a hg commit.
Remember to hg push when your repository contains a version relatively stable of your changes.
If a hg pull -u created a new branch head:
find its identifier with hg head
merge with hg merge
hg ci
hg push
More information#
For more information about Mercurial, please refer to the Mercurial project online documentation.
Installation dependencies#
When you run CubicWeb from source, either by downloading the tarball or cloning the mercurial tree, here is the list of tools and libraries you need to have installed in order for CubicWeb to work:
yapps - http://theory.stanford.edu/~amitp/yapps/ - http://pypi.python.org/pypi/Yapps2
pygraphviz - http://networkx.lanl.gov/pygraphviz/ - http://pypi.python.org/pypi/pygraphviz
docutils - http://docutils.sourceforge.net/ - http://pypi.python.org/pypi/docutils
lxml - http://codespeak.net/lxml - http://pypi.python.org/pypi/lxml
logilab-common - https://www.logilab.org/project/logilab-common - http://pypi.python.org/pypi/logilab-common/
logilab-database - https://www.logilab.org/project/logilab-database - http://pypi.python.org/pypi/logilab-database/
logilab-constraint - https://www.logilab.org/project/logilab-constraint - http://pypi.python.org/pypi/constraint/
logilab-mtconverter - https://www.logilab.org/project/logilab-mtconverter - http://pypi.python.org/pypi/logilab-mtconverter
rql - https://www.logilab.org/project/rql - http://pypi.python.org/pypi/rql
yams - https://www.logilab.org/project/yams - http://pypi.python.org/pypi/yams
indexer - https://www.logilab.org/project/indexer - http://pypi.python.org/pypi/indexer
passlib - https://code.google.com/p/passlib/ - http://pypi.python.org/pypi/passlib
If you’re using a Postgresql database (recommended):
Other optional packages:
fyzz - https://www.logilab.org/project/fyzz - http://pypi.python.org/pypi/fyzz to activate Sparql querying
Any help with the packaging of CubicWeb for more than Debian/Ubuntu (including eggs, buildouts, etc) will be greatly appreciated.
Javascript docstrings#
Whereas in Python source code we only need to include a module docstrings using the directive .. automodule:: mypythonmodule, we will have to explicitely define Javascript modules and functions in the doctrings since there is no native directive to include Javascript files.
Rest generation#
pyjsrest is a small utility parsing Javascript doctrings and generating the corresponding Restructured file used by Sphinx to generate HTML documentation. This script will have the following structure:
===========
filename.js
===========
.. module:: filename.js
We use the .. module:: directive to register a javascript library as a Python module for Sphinx. This provides an entry in the module index.
The contents of the docstring found in the javascript file will be added as is following the module declaration. No treatment will be done on the doctring. All the documentation structure will be in the docstrings and will comply with the following rules.
Docstring structure#
Basically we document javascript with RestructuredText docstring following the same convention as documenting Python code.
The doctring in Javascript files must be contained in standard Javascript comment signs, starting with /** and ending with */, such as:
/**
* My comment starts here.
* This is the second line prefixed with a `*`.
* ...
* ...
* All the follwing line will be prefixed with a `*` followed by a space.
* ...
* ...
*/
Comments line prefixed by // will be ignored. They are reserved for source code comments dedicated to developers.
Javscript functions docstring#
By default, the function directive describes a module-level function.
function directive#
Its purpose is to define the function prototype such as:
.. function:: loadxhtml(url, data, reqtype, mode)
If any namespace is used, we should add it in the prototype for now, until we define an appropriate directive:
.. function:: jQuery.fn.loadxhtml(url, data, reqtype, mode)
Function parameters#
We will define function parameters as a bulleted list, where the parameter name will be backquoted and followed by its description.
Example of a javascript function docstring:
.. function:: loadxhtml(url, data, reqtype, mode)
cubicweb loadxhtml plugin to make jquery handle xhtml response
fetches `url` and replaces this's content with the result
Its arguments are:
* `url`
* `mode`, how the replacement should be done (default is 'replace')
Possible values are :
- 'replace' to replace the node's content with the generated HTML
- 'swap' to replace the node itself with the generated HTML
- 'append' to append the generated HTML to the node's content
Optional parameter specification#
Javascript functions handle arguments not listed in the function signature. In the javascript code, they will be flagged using /* … */. In the docstring, we flag those optional arguments the same way we would define it in Python:
.. function:: asyncRemoteExec(fname, arg1=None, arg2=None)
Changelog#
3.38.7 (2023-03-07)#
👷 Bug fixes#
rdf: https instead of http for schema.org
sphinx-theme 1.0 breaks doc build
make sure we only install yapps2-logilab by updating depencies
tried to format a string while missing one formatting argument
3.38.6 (2023-02-13)#
👷 Bug fixes#
hooks: notification things are no more in “views” registry
3.38.5 (2023-01-31)#
👷 Bug fixes#
remove deprecated import to cubicweb.web
3.38.4 (2023-01-17)#
🎉 New features#
skeleton: remove format=pylint option from tox because it’s better without it
3.38.3 (2023-01-12)#
👷 Bug fixes#
avoid risking new cubes to install pre-release version of black
formrenderers: use UStringIO instead of list to keep the same api as self.w (https://forge.extranet.logilab.fr/cubicweb/cubicweb/-/issues/597)
schema_exporters: Add missing description field for relations (e.g in_state) to schema exporter
🤷 Various changes#
changelog/3.38: add instruction on how to use 3.38/cubicweb_web_imports.py
3.38.2 (2023-01-03)#
👷 Bug fixes#
sobjects/notifications: keep RecipientsFinder class and subclass in the components registry for retrocompatibility
3.38.1 (2022-12-05)#
🎉 New features#
schema: Export relations options on the schema (merge from 3.37)
👷 Bug fixes#
schema_exporters: Add missing description field for relations (e.g in_state) to schema exporter (merge from 3.37)
3.38.0 (2022-11-22)#
This is the last major release of the 3.* before the 4 branch.
In this release the whole cubicweb.web module and the cubicweb/view.py file have been extracted in the cubicweb_web cube which is a dependency of cubicweb now. Automatic backward compatibility is provided by imports so your projects should work with this new version without modifications.
A script to help migrating to this version is available in this repository in the 3.38 folder https://forge.extranet.logilab.fr/cubicweb/cw_versions_migration_tools This script will change all the imports to match the news one for CubicWeb 3.38 and the cube cubicweb_web. It will not change your dependencies in your setup.py or __pkginfo__.py, you have to do this yourself.
Its usage, once the dependencies has been installed (only RedBaron), is the following:
python 3.38/cubicweb_web_imports.py <path to my project>
It will hopefully save you quite some time.
🎉 New features#
the cubicweb_web cube is now a dependency of cubicweb
add adapter_regid as parameter on add_entity_to_graph (#535)
add relation constraints to schema export
cubicweb.web extraction: change all import of cubicweb.web to cubicweb_web
cubicweb.web removal: add deprecation warning in view module
cubicweb.web removal: add generic deprecation warning in all web modules
cwctl: don’t check if we need to upgrade anything when running cwctl versions (https://forge.extranet.logilab.fr/cubicweb/cubicweb/-/issues/563)
doc: clarify when rich had been removed
export relation options in schema options key
pkg: upgrade version of waitress to 2.1.1 or more, for security reason. (https://forge.extranet.logilab.fr/cubicweb/cubicweb/-/issues/543)
rdf: use entity.absolute_url instead of cwuri in RDF adapters (#534)
redirection: pyramid redirection now keep parameters by default (https://forge.extranet.logilab.fr/cubicweb/cubicweb/-/issues/566)
rql: Add “IRQLInterface” adapter to force defining a rql interface which is available on RQL projection varaibles
rql: Add entities function and attribute from RQL queries
serverctl: add a command to list all unused indexes
test/content-negociation: display rdf body on failing tests for easier debugging
test: use testing.cubicweb instead of testing.fr in test (https://forge.extranet.logilab.fr/cubicweb/cubicweb/-/issues/374) BREAKING CHANGE: use testing.cubicweb instead of testing.fr in test
👷 Bug fixes#
add retrocompatibility for anonymized_request that is now in cubicweb_web
base64.decodestring is deprecated and has been removed
cubicweb_web/deprecations: increase warning stack level to show correct line
cubicweb_web: change magic modules imports to uses cubicweb_web
cwconfig: Ensure the cube web is available with cubicweb-ctl commands
CWRelation.rtype api is different from CWRelation.relation_type api
ensure that the “web” cube is in the list of cubes dependencies
hook: Search the notification view from the good registry
htmlwidgets: BoxLink rendering is broken
make i18ncube load web cube’s appobjects
mod: Load sobjects.notification and sobjects.supervising even if no cubicweb_web
notification: Make NotificationView inherits from AppObject
pyramid: adapt TestApp.post_json method to CSRF
pyramid: adapt TestApp.put_json method to CSRF
pyramid: try to get “/login” if “/” is forbidden
req: add missing set_log_methods on CubicWebRequestBase
schema_exporters: handle symmetrical relation in schema export. (https://forge.extranet.logilab.fr/cubicweb/cubicweb/-/issues/568)
supervising: Adapt SupervisingView for the NotificationView API
supervising: Uses the NotificationView for the supervising instead of component
test-instance-creation: cubicweb now needs the web cube to be installed
🤖 Continuous integration#
Most python test have been splitted to speed up the CI speed.
.gitlab-ci.yml: refactoring py3 tests declaration using a base template
add check-dependencies-resolution job
add mypy job
add safety job
add twine-check job
fix: “base” in py3-server-base clashed with “py3-base”, use “core” instead
fix: py3-auto-test-views jobs wrongly launched py3-server-bases tests
migrate to v2 of templates
move to bullseye and pg13
split py3-misc into several different tests
split py3-server into several different tests
test-instance-creation: pip –use-feature=in-tree-build is deprecated, remove it
use .retry base template in (nearly) all jobs
🤷 Various changes#
[cubicweb 3.38] RequestSessionBase is deprecated, use RequestSessionAndConnectionBase instead
remove mailing-list from “how to contribute” since it’s no more used (https://forge.extranet.logilab.fr/cubicweb/cubicweb/-/issues/395)
supervising: Refactor to not using self.w from NotificationView
Unknown config option: log_print
3.37.14 (2023-03-07)#
👷 Bug fixes#
rdf: https instead of http for schema.org
sphinx-theme 1.0 breaks doc build
make sure we only install yapps2-logilab by updating depencies
tried to format a string while missing one formatting argument
3.37.13 (2023-01-27)#
🎉 New features#
misc: allow to disable or not constraint check on fast drop script
3.37.12 (2023-01-17)#
🎉 New features#
skeleton: remove format=pylint option from tox because it’s better without it
3.37.11 (2023-01-12)#
👷 Bug fixes#
avoid risking new cubes to install pre-release version of black
formrenderers: use UStringIO instead of list to keep the same api as self.w (https://forge.extranet.logilab.fr/cubicweb/cubicweb/-/issues/597)
schema_exporters: Add missing description field for relations (e.g in_state) to schema exporter
3.37.10 (2022-12-05)#
🎉 New features#
schema: Export relations options on the schema
👷 Bug fixes#
schema_exporters: Add missing description field for relations (e.g in_state) to schema exporter
3.37.9 (2022-11-15)#
👷 Bug fixes#
hook: correct a typo, self.warn doesn’t exist
3.37.8 (2022-10-04)#
👷 Bug fixes#
attr: when an entity is not existing always return None when fetching its attributes (#599)
web.views: escape text from the undohistory view (#598)
🤷 Various changes#
delete unused translations from *.po files (#600)
skeleton: add long_description_content_type in setup.py
3.37.7 (2022-09-22)#
👷 Bug fixes#
startup_views: raise AuthenticationError if anon access is disabled on StartupView (https://forge.extranet.logilab.fr/cubicweb/cubicweb/-/issues/595)
3.37.6 (2022-09-14)#
👷 Bug fixes#
bookmark: do not escape the xaddrelation view from ajaxedit module
3.37.5 (2022-08-30)#
👷 Bug fixes#
pyramid: Redirect to the wanted URL after a successfully loggedin (to #584)
xss: Ensure to use the xml_escape method on entity attributes
perf: Restore initial performances by removing the uneccessary join
3.37.4 (2022-07-21)#
👷 Bug fixes#
schema_exporters: handle symmetrical relation in schema export. (https://forge.extranet.logilab.fr/cubicweb/cubicweb/-/issues/568)
🤷 Various changes#
feat(markdown)!: update Mardown version to 3.4 and rewrite urlize extension (https://forge.extranet.logilab.fr/cubicweb/cubicweb/-/issues/569)
3.37.3 (2022-07-13)#
👷 Bug fixes#
htmlwidgets: BoxLink rendering is broken
3.37.2 (2022-06-03)#
👷 Bug fixes#
pyramid: adapt TestApp.put_json method to CSRF
3.37.1 (2022-06-01)#
🎉 New features#
pkg: upgrade version of waitress to 2.1.1 or more, for security reason. (https://forge.extranet.logilab.fr/cubicweb/cubicweb/-/issues/543)
👷 Bug fixes#
base64.decodestring is deprecated and has been removed
pyramid tests: adapt TestApp.post_json method to CSRF
pyramid tests: try to get “/login” if “/” is forbidden
3.37.0 (2022-03-31)#
Breaking changes#
cubicweb.web.BaseWebConfiguration
andcubicweb.web.WebConfigurationBase
have been merged into cubicweb.web.WebConfigurationcubicweb.web.CubicWebPyramidConfiguration
had been removedyou can nomore use -c option when creating a CW instance, since there is now only one kind of configuration: all-in-one.conf
🎉 New features#
add attributes constraints in exported schema
depends on yams 0.48
doc: mostly add links of issues
👷 Bug fixes#
unittest_devctl: give all debugging informations
🤖 Continuous integration#
use templates
🤷 Various changes#
refactor!: merge
BaseWebConfiguration
intoWebConfiguration
refactor!: remove
-c
option tocubicweb-ctl create
to only use all-in-onerefactor!: remove unused
CubicWebPyramidConfiguration
3.36.14 (2023-03-02)#
👷 Bug fixes#
sphinx-theme 1.0 breaks doc build
3.36.13 (2023-03-02)#
👷 Bug fixes#
make sure we only install yapps2-logilab by updating depencies
tried to format a string while missing one formatting argument
3.36.12 (2023-01-17)#
🎉 New features#
skeleton: remove format=pylint option from tox because it’s better without it
3.36.11 (2023-01-12)#
👷 Bug fixes#
avoid risking new cubes to install pre-release version of black
formrenderers: use UStringIO instead of list to keep the same api as self.w (https://forge.extranet.logilab.fr/cubicweb/cubicweb/-/issues/597)
schema_exporters: Add missing description field for relations (e.g in_state) to schema exporter
3.36.10 (2022-11-15)#
👷 Bug fixes#
hook: correct a typo, self.warn doesn’t exist
3.36.9 (2022-10-04)#
👷 Bug fixes#
attr: when an entity is not existing always return None when fetching its attributes (#599)
web.views: escape text from the undohistory view (#598)
🤷 Various changes#
delete unused translations from *.po files (#600)
skeleton: add long_description_content_type in setup.py
3.36.8 (2022-09-22)#
👷 Bug fixes#
startup_views: raise AuthenticationError if anon access is disabled on StartupView (https://forge.extranet.logilab.fr/cubicweb/cubicweb/-/issues/595)
3.36.7 (2022-09-14)#
👷 Bug fixes#
bookmark: do not escape the xaddrelation view from ajaxedit module
3.36.6 (2022-08-30)#
👷 Bug fixes#
pyramid: Redirect to the wanted URL after a successfully loggedin (to #584)
xss: Ensure to use the xml_escape method on entity attributes
perf: Restore initial performances by removing the uneccessary join
3.36.5 (2022-07-21)#
👷 Bug fixes#
schema_exporters: handle symmetrical relation in schema export. (https://forge.extranet.logilab.fr/cubicweb/cubicweb/-/issues/568)
🤷 Various changes#
feat(markdown)!: update Mardown version to 3.4 and rewrite urlize extension (https://forge.extranet.logilab.fr/cubicweb/cubicweb/-/issues/569)
3.36.4 (2022-07-13)#
merge 3.35.6 into 3.36
3.36.3 (2022-06-03)#
👷 Bug fixes#
pyramid: adapt TestApp.put_json method to CSRF
3.36.2 (2022-06-01)#
🎉 New features#
pkg: upgrade version of waitress to 2.1.1 or more, for security reason. (https://forge.extranet.logilab.fr/cubicweb/cubicweb/-/issues/543)
👷 Bug fixes#
base64.decodestring is deprecated and has been removed
pyramid: adapt TestApp.post_json method to CSRF
pyramid: try to get “/login” if “/” is forbidden
3.36.1 (2022-03-31)#
👷 Bug fixes#
rql2sql: upgrade RQL version to fix translation of
NOT EXISTS(X eid Y)
(#528)view: don’t escape html tags inside image previews
3.36.0 (2022-03-14)#
🎉 New features#
📝 Documentation#
fix sidebar table of content
improve basic tutorial
improve home and sidebar
improve setup instructions
improve skeleton readme
set version number
use relative links for static resources
use right number of characters for titles
use sphinx_book_theme
3.35.12 (2022-11-15)#
👷 Bug fixes#
hook: correct a typo, self.warn doesn’t exist
3.35.11 (2022-10-04)#
👷 Bug fixes#
attr: when an entity is not existing always return None when fetching its attributes (#599)
web.views: escape text from the undohistory view (#598)
🤷 Various changes#
delete unused translations from *.po files (#600)
skeleton: add long_description_content_type in setup.py
3.35.10 (2022-09-22)#
👷 Bug fixes#
startup_views: raise AuthenticationError if anon access is disabled on StartupView (https://forge.extranet.logilab.fr/cubicweb/cubicweb/-/issues/595)
3.35.9 (2022-09-14)#
👷 Bug fixes#
bookmark: do not escape the xaddrelation view from ajaxedit module
3.35.8 (2022-08-30)#
👷 Bug fixes#
pyramid: Redirect to the wanted URL after a successfully loggedin (to #584)
xss: Ensure to use the xml_escape method on entity attributes
perf: Restore initial performances by removing the uneccessary join
3.35.7 (2022-07-21)#
👷 Bug fixes#
schema_exporters: handle symmetrical relation in schema export. (https://forge.extranet.logilab.fr/cubicweb/cubicweb/-/issues/568)
🤷 Various changes#
feat(markdown)!: update Mardown version to 3.4 and rewrite urlize extension (https://forge.extranet.logilab.fr/cubicweb/cubicweb/-/issues/569)
3.35.6 (2022-07-13)#
🤷 Various changes#
fix warnings of yams 0.48+ (3.35 requires <0.48)
3.35.5 (2022-07-13)#
👷 Bug fixes#
basecontrollers: str object have no more “decode” method since py3
fix some warnings of yams 0.48+
htmlwidgets: BoxLink rendering is broken
server: remove a memory leak related to a file
test: improve one related to CSRF
3.35.4 (2022-06-03)#
👷 Bug fixes#
pyramid: adapt TestApp.put_json method to CSRF
3.35.3 (2022-06-01)#
🎉 New features#
pkg: upgrade version of waitress to 2.1.1 or more, for security reason. (https://forge.extranet.logilab.fr/cubicweb/cubicweb/-/issues/543)
👷 Bug fixes#
base64.decodestring is deprecated and has been removed
pyramid: adapt TestApp.post_json method to CSRF
pyramid: try to get “/login” if “/” is forbidden
3.35.2 (2022-03-31)#
👷 Bug fixes#
rql2sql: upgrade RQL version to fix translation of
NOT EXISTS(X eid Y)
(#528)view: don’t escape html tags inside image previews
3.35.1 (2022-03-09)#
3.35 (2022-02-02)#
Breaking changes#
deprecate RQLSuggestionsBuilder component ; users of this component should now use
rqlsuggestions.RQLSuggestionsBuilder
instead. RQL bar completion behaviour can be changed by replacing the “rql_suggest” ajax function. If this function isn’t registered, rql completion is disabled.remove RQLNoSuggestionsBuilder
disable login using GET requests for security reasons
web: remove support of old Internet Explorer versions:
add_css
no longer acceptsiespec
andieonly
arguments
🎉 New features#
add a Dockerfile in the skeleton
add a function for deleting entities faster
config: add help messages in configuration files (all-in-one and sources)
disable constraints checks on the DB upon deletion
show cube name when there is a version conflict
skeleton: add release-new in skeleton
upgrade to yams 0.47
content negociation: we now can use
/<etype>/<rest_attr>
route for content negociation, if rest_attr is defined, the route/<etype>/<rest_attr>
is disabled for content negociation in this situation
👷 Bug fixes#
relation_type not existing in some conditions on RelationDefinition (ionDefinition.rtype has been deprecated in yams in favor of relation_type)
🤖 Continuous integration#
only collect warnings when running tests on the default branch (#489)
3.34.3 (2022-03-31)#
👷 Bug fixes#
rql2sql: upgrade RQL version to fix translation of
NOT EXISTS(X eid Y)
(#528)view: don’t escape html tags inside image previews
3.34.2 (2022-03-09)#
👷 Bug fixes#
3.34.1 (2021-12-01)#
👷 Bug fixes#
server: correct RQL generation when we have function in
ORDERBY
(#466)
3.34.0 (2021-11-23)#
Breaking changes#
Python 3.7 is now the minimum supported version of Python;
test:
settings = {"cubicweb.bwcompat": true}
is now the default for test, please, check your test if they are failing because of this;test: the qunit test driver has been removed;
remove our deprecated and unused wsgi module;
fix!(handler): rediction to login on
cubicweb.AuthenticationError
Previously we were sending a forbidden response (403) with the login form as the html content, now we redirect (303) to the login form instead;rich <https://forge.extranet.logilab.fr/cubicweb/cubicweb/-/issues/348>, introduced in 3.33 for nice tracebacks, had been removed <https://forge.extranet.logilab.fr/cubicweb/cubicweb/-/issues/434#note_93131>.
Since we updated rdflib to version 6, some packages like rdflib_jsonld are no longuer needed. Please, check your dependencies if you have any issue.
🎉 New features#
add an export-schema command to cwctl
csrf: add debug login when creating a new csrf token
get_cleaned_form_data: add backward deprecated compatibility on req.form
pyramid/test/ux: better debugging information when failing to get CSRF token
security: implement inforcing form validation for POST arguments
store: allow stores to be used as context manager (#446)
👷 Bug fixes#
allow in Int as rest_attr
build_doc: docutils version 0.18.0 breaks doc building
doc8: indentation was using tabs in 3.32_reledit.rst
ldap: upgrade to ldap3 (datetime, encode fix)
p3-misc: missing fyzz modules for certains tests in spa2rql
pyramid/test: webapp handles cookies for us, we don’t need to manually set them
RDFLib: Remove rdflib-jsonld dependency and use RDFLib v6 jsonld builtin parser BREAKING CHANGE: The RDFLib v6 does not support python 3.6 anymore. With this dependency, CubicWeb neither.
reledit: Do not retrieve a list of schemata with _compute_ttypes
remove qunit test stuff (#447)
skeleton: use forge.extranet.logilab.fr as default web url for new cubes (#463)
startup: Fix RQL query to take advantage of caching (#384)
store csrf token during login
test: make anonymous user creation hook tests pass (#452)
utils: remove an useless space character
views: remove unneeded xml_escape for primary titles
🤖 Continuous integration#
🤷 Various changes#
instance-config: add attributes for authenticated smtp
pyramid.compat is deprecated and will be removed in Pyramid 2.0. The functionality is no longer necessary, as Pyramid 2.0 drops support for Python 2.
webconfig: remove an unused configuration option
3.33.13 (2022-03-09)#
👷 Bug fixes#
3.33.12 (2021-12-01)#
👷 Bug fixes#
server: correct RQL generation when we have function in
ORDERBY
(#466)
3.33.11 (2021-11-17)#
👷 Bug fixes#
pkg: pin Yams version < 0.46.0
3.33.10 (2021-11-17)#
Removed allowed-http-host-headers configuration (which was a breaking change), since we don’t have this vulnerability in CubicWeb.
Various changes#
depend on sphinx >= 4.3
3.33.9 (2021-11-08)#
👷 Bug fixes#
views: remove an abusive escape (#457)
3.33.8 (2021-11-02)#
👷 Bug fixes#
3.33.7 (2021-10-12)#
👷 Bug fixes#
ldap: upgrade to ldap3 (datetime, encode fix)
csrf: ensure that we have a csrf token returned on every requests
3.33.6 (2021-10-04)#
👷 Bug fixes#
facet: remove abusive escaping in facets views. (#394)
3.33.5 (2021-09-29)#
👷 Bug fixes#
backout “limit setuptools version to avoid issue with 2to3”
use our package rdflib-jsonld-without-2to3, this is a fork of rdflib-jsonld with 2to3 usage removed, but which still contains the whole package code unlike rdflib-jsonld 0.6.x.
reledit: Do not retrieve a list of schemata with _compute_ttypes
3.33.4 (2021-09-24)#
don’t escape value in navigation components (#389)
views: remove unneeded xml_escape for primary titles
setup: keep rdflib-jsonld at version < 0.6.0
setup: limit setuptools version to avoid issue with 2to3
3.33.3 (2021-09-14)#
upgrade rdflib-jsonld version to keep compatibility with setupools 58 and above
👷 Bug fixes#
startup: Fix RQL query to take advantage of caching (#384)
3.33.2 (2021-09-02)#
📝 Documentation#
tuto: Fix path
3.33.1 (2021-08-31)#
🎉 New features#
allowed-http-host-headers: automatically add default hostname to the allowed list on debug mode
req: Add a “limit” parameter to RequestSessionBase.find
req: Add exists for optimized search of at least one entity
ux: better error message when a controller can’t be select
👷 Bug fixes#
fyzz dep was missing for running certain tests
only fyzz 0.2.2 is compatible with python 3
typo: fix some mispellings
📝 Documentation#
fix allowed-http-host-header label and quote from Django’s doc
🤖 Continuous integration#
integrate can-i-merge
🤷 Various changes#
3.33: improve changelog quality
fix(bwcompat)!: return a 400 instead of a 401 when failed to select a controller
fix: allowed-http-host-headers has been released in 3.33 actually
misc: fix rst syntax
3.33.0 (2021-08-03)#
🎉 New features#
BREAKING security: introduce allowed-http-host-header against host attack (However, this is backed out in 3.33.10).
add postgresql extra requires
config: add ‘debug’ option in “[main]” of all-in-one.conf that does the same thing than “-D” in “cubicweb-ctl pyramid”
rich <https://github.com/willmcgugan/rich/>: to have nicer tracebacks, use rich.traceback <https://forge.extranet.logilab.fr/cubicweb/cubicweb/-/issues/348> (removed in 3.34 <https://forge.extranet.logilab.fr/cubicweb/cubicweb/-/issues/434#note_93131>)
👷 Bug fixes#
add default value for params argument in pyramid webtest post function (#350)
csrf: give CSRF token when using /ajax route
empty identification cookie on webapp.reset()
pin rdflib < 6.0.0 to avoid compatibility issues
rdf: graph.serialize needs to encode its content in utf-8
security: change configuration [WEB]interface default value to 127.0.0.1
views: Fix reledit errors when trying modify relation with multi subjects
🤖 Continuous integration#
use image from heptapod registry since r.intra was shut down
🤷 Various changes#
use open-source/gitlab-ci-templates in cube skeleton
drop mention of MySQL and SQLServer support
update cube installation procedure documentation
remove
*.spec
from skeleton
3.32.14 (2021-12-01)#
👷 Bug fixes#
server: correct RQL generation when we have function in
ORDERBY
(#466)
3.32.13 (2021-11-17)#
👷 Bug fixes#
pkg: pin Yams version < 0.46.0
3.32.12 (2021-11-17)#
Various changes#
depend on sphinx>=4.3
3.32.11 (2021-11-08)#
👷 Bug fixes#
views: remove an abusive escape (#457)
3.32.10 (2021-11-02)#
👷 Bug fixes#
3.32.9 (2021-10-12)#
👷 Bug fixes#
csrf: ensure that we have a csrf token returned on every requests
3.32.8 (2021-10-04)#
👷 Bug fixes#
facet: remove abusive escaping in facets views (#394)
3.32.7 (2021-09-29)#
👷 Bug fixes#
backout “limit setuptools version to avoid issue with 2to3”
use our package rdflib-jsonld-without-2to3, this is a fork of rdflib-jsonld with 2to3 usage removed, but which still contains the whole package code unlike rdflib-jsonld 0.6.x.
reledit: Do not retrieve a list of schemata with _compute_ttypes
3.32.6 (2021-09-24)#
👷 Bug fixes#
don’t escape value in navigation components (#398)
views: remove unneeded xml_escape for primary titles
setup: keep rdflib-jsonld at version < 0.6.0
setup: limit setuptools version to avoid issue with 2to3
3.32.5 (2021-09-14)#
upgrade rdflib-jsonld version to keep compatibility with setupools 58 and above
3.32.4 (2021-09-02)#
👷 Bug fixes#
do not use localhost.local has test domain, but keep the one already defined
3.32.3 (2021-08-31)#
🎉 New features#
migration: add a migration script to warn about incompatibility of cwtags. (#367)
👷 Bug fixes#
bringing back CubicWebServerTC and porting it to pyramid
fix bad escaped values in web views
pkg: since we added csrf mecanism, we need pyramid >= 1.9
test_newcube were broken because we removed cubicweb-*.spec file but didn’t updated the tests
3.32.2 (2021-07-30)#
🎉 New features#
use open-source/gitlab-ci-templates in cube skeleton
👷 Bug fixes#
add default value for params argument of PyramidCWTest.webapp.post (#350)
csrf: give CSRF token when using /ajax route
empty identification cookie on webapp.reset()
remove
*.spec
from skeletonviews: Fix reledit errors when trying modify relation with multi subjects
3.32.1 (2021-07-23)#
👷 Bug fixes#
pin rdflib < 6.0.0 to avoid compatibility issues
🤖 Continuous integration#
use image from heptapod registry since r.intra was shut down
3.32.0 (2021-07-13)#
🔒 Security, breaking changes#
Protection against XSS#
self.w
API has been changed to automatically escape arguments used to format the string to mitigate XSS attacks.
This means that instead of writing:
self.w("some %s string %s" % (a, b))
You need to write:
self.w("some %s string %s", a, b)
And CubicWeb will escape all arguments given to self.w
which are a
and b
here.
If for a specific reason (for example generating javascript) you don’t want to escape the arguments of self.w
you can use the escape
kwarg argument of self.w
like this:
self.w("some %s string %s", a, b, escape=False)
This is normally retrocompatible since self.w
old API with only one argument still works (but you shouldn’t use it anymore) but if you have been giving a custom function as self.w
you’ll need to adapt the API of this function to match self.w
new API which is:
def w(self, string, *args, **kwargs, escape=False): ...
Also note that UStringIO.write
function has also been modified to be compatible with self.w
new API (so if you are using it you won’t need to port this code).
CSRF protection#
A CSRF protection mechanism has been integrated in CubicWeb using Pyramid CSRF built-in protection. Regarding breaking changes:
Cubicweb now only works with pyramid
if you are only using cubicweb “web” without ajax and you have been doing advanced modification at the session management level this shouldn’t break anything for you
if you are doing POST/PUT/DELETE… requests using AJAX, you need to adapt your code to send the csrf_token otherwise all you requests will be denied. This is explained in the AJAX section of the documentation: CSRF protection in CubicWeb
The whole mechanism is explained in the documentation: CSRF protection in CubicWeb
🚧 Other breaking changes#
We decided to stop releasing cubicweb as debian packages that we used on multi-purpose servers in favor of docker images that we run with docker-compose or on kubernetes. Thanks for all the fishes.
🎉 New features#
add a component to disable RQL suggestions:
cubicweb.web.views.magicsearch.RQLNoSuggestionsBuilder
👷 Bug fixes#
[reledit] display reledit for a relation if some conditions are satisfied ([1] the relation don’t have rqlexpr permissions and can be deleted [2] at least one of related entites can be deleted)
pyramid/predicates: avoid to show an error without a session connection
be sure db-statement-timeout is not None
correctly transform cubicweb.web.RemoteCallFailed into pyramid corresponding exceptions, this allow to propagate the correct content type (for example for json exceptions)
“cubicweb-ctl list” now supports multiple dependencies constraints
🤷 Various changes#
fix error cases when internationalizable is not defined on rdef
improve docstring in web.views.basecontrollers
🤖 Continuous integration#
coverage: gitlab-ci is able to read the coverage report we produce
disable from-forge for now since we aren’t using them
fix path to coverage-*.xml for non-reports artifacts
flake8: integrate flake8-gl-codeclimate for QA reports
integrate junit reports style for tests errors in gitlab
optimisation: allow to interrupt started jobs that can be replaced
pytest-html: generate self contained html file for easier test repport browsing
trigger py3-* jobs on tox.ini/.gitlab-ci.yml/requirements modifications
use gitlab readthedocs integration
📋 Developer experience#
using black on the whole project o/ (thx for hg format-source)
debug/ux: display traceback of stderr when exception in addition of the html page
testing: activate debug mode during testing
ux: display on stdout the requests made to the server like nginx
ux: display traceback on stderr on request failure
ux: logger.info for selected view by ViewController
3.31.9 (2021-11-17)#
👷 Bug fixes#
pkg: pin Yams version < 0.46.0
3.31.8 (2021-11-17)#
🤷 Various changes#
depend on sphinx >= 4.3
3.31.7 (2021-11-02)#
👷 Bug fixes#
3.31.6 (2021-09-28)#
🤷 Various changes#
backout “limit setuptools version to avoid issue with 2to3”
use our package rdflib-jsonld-without-2to3, this is a fork of rdflib-jsonld with 2to3 usage removed, but which still contains the whole package code unlike rdflib-jsonld 0.6.x.
3.31.5 (2021-09-24)#
👷 Bug fixes#
setup: keep rdflib-jsonld at version < 0.6.0
setup: limit setuptools version to avoid issue with 2to3
3.31.4 (2021-09-14)#
upgrade rdflib-jsonld version to keep compatibility with setupools 58 and above
3.31.3 (2021-07-23)#
👷 Bug fixes#
pin rdflib < 6.0.0 to avoid compatibility issues
🤖 Continuous integration#
use image from heptapod registry since r.intra was shut down
3.31.2 (2021-07-19)#
👷 Bug fixes#
do not consume a lot of time to collect debug data if no one is listening on debug channels
fix incomplete merge of previous versions (brings back
write_front
)
3.31.1 (2021-05-18)#
Revert#
backed out changeset bcb633bd791d, don’t give event to notify_on_commit Notification are done using Operation, and Operation do not have a event attribute, because they can be used for several event. Moreover, this commit (bcb633bd791d) changed the prototype of the notify_on_commit without giving the right event to the Operation (which is a singleton).
3.31 (2021-05-04)#
🎉 New features#
handle same_site cookies configuration in pyramid.ini
order: add support for order by NULLS LAST and NULLS FIRST
improve default cubicweb skeleton
👷 Bug fixes#
create anonymous user at runtime if it doesn’t exist already.
dbcreate: don’t ask confirmation to create schema in automatic
hooks/notification: BREAKING CHANGE correctly initialize operation with event attribute
RQLExpression: performance issue on RQLExpressions using EXISTS() BREAKING CHANGE: explicitly use EXISTS in RQLExpression for permissions
fix some security issues
Documentation#
tuto: add structure of “enhance views” museum tutorial part.
tuto: redact “React in a CubicWeb view” museum tuto part.
tuto: rename cubicweb-tuto to tuto, avoiding confusion with cubicweb_tuto
🤖 Continuous integration#
gitlab-ci: set expiration delay to 2 weeks for artifacts
image is no longer a global keyword, use default
rename jobs names to match global conventions
test skeleton own tox in the CI
🤷 Various changes#
cleanup: Remove migrations for 3.21 and less
py3: Rename
raw_input
(that does not exist anymore) toinput
tests: create a .nobackup file in the data/database directory (#298)
3.30.1 (2021-07-23)#
👷 Bug fixes#
pin rdflib < 6.0.0 to avoid compatibility issues
🤖 Continuous integration#
use image from heptapod registry since r.intra was shut down
3.30.0 (2021-03-16)#
🎉 New features#
config: read required variables from environment (#85)
db-create: add drop option to control database deletion (#202) BREAKING CHANGE: cubicweb-ctl db-create no more drops the db in
massive store: add an option to allow stores not to drop constraints (#219)
pyramid-ctl: add “nb-threads” parameter to cubicweb-ctl pyramid (#119)
urlpublish: add empty_rset_raises_404 flag on rql rewrite urls (#199)
add script_attribute to add_js function (#210)
cubicweb/cwconfig: authenticated SMTP outgoing email
database/exception: include the query information in database error for better debuging
upgrade Logilab’s dependencies to last versions
web: only set “Vary: Accept-Language” when we translate something (#224)
👷 Bug fixes#
rql2sql: properly handle date and datetime operations with SQLite (#109)
rql: make the rql completion working again
rql: refactor GROUP_CONCAT so that it handles NULL values
catch authentication exception
ci: manually remove the .tox/doc directory (#206)
ci: use **/*.py to match all python files
ci: recreate doc environment from scratch (#206)
cwgettext: missing local module
db-create: don’t force to use –drop if there is no existing db.
deprecated: logilab.common.deprecated has been renamed to callable_deprecated
deps: we are not yet compatible with pyramid 2.0
migractions: don’t use notification hooks during postcreate
py3: we still have some unicode() arround
repo_cnx: Catch OperationalError during repo_cnx (#215)
skeleton: add gitlab-ci in skeleton manifest
skeleton: make the skeleton black compliant
skip a wdoc test when doctuils is not available
typo: drop_db instead of drop_pd
urlpublisher: raise a 404 when a URL rewrite with rql has no rset (#199)
UX when migractions failed to get its connection
migractions: don’t commit in the middle of drop_cube
views: fix possible UnboundLocalError in ErrorView
server: Set language of connection in all cases (#87)
Documentation#
deploy: add a Docker section in deployment
deploy: Update kubernetes deploy
deploy: Update uwsgi deployment
deploy: add section ref for kubernetes section
include api documentation
mention weekly meeting in matrix
Add link to migration and remove FIXME
add more links in the index and capitalize entries (#185)
all-in-one.conf: add link in index.rst
dataimport: remove SQLGenObjectStore description and add MassiveObjectStore.
index: remove “skeleton”, since it’s already explain in “layout”
licence: automatically set licence info in setup.py template (#94)
move (and fix) apache documentation to the deply section
Remove SQLSERVER
rql: replace COMMA_JOIN by GROUP_CONCAT (#259)
tutorials: correct a dead link.
tutorials: add a link to museum demo source code, and correct a typo.
tutorials: add introduction and structure of the museum tutorial.
tutorials: redaction of “data-management/import” part of the museum tuto.
tutorials: redaction of “getting started” part.
tutorials: reword
🤖 Continuous integration#
uses gitlab-ci ‘rules:’
integrate yamllint
simplify rule:changes
Use workflow to avoid duplicated pipelines (see https://docs.gitlab.com/ee/ci/yaml/#switch-between-branch-pipelines-and-merge-request-pipelines)
do not run sonaqube and deploy the doc when triggered by other project
fix: also monitore requirements/setup.py changes for triggering the pipelines updated
🤷 Various changes#
remove statsd (closes #39) BREAKING CHANGE
remove web.cors in favor of wsgicors with pyramid
server/migractions: simplify the Migration Handler entry point
server: replace utils.QueryCache with cachetools.LFUCache
Silent yams warning (first rdef selection from an ambiguous rtype)
Very minor improvements of cubicweb/server/repository.py
views: Make JsonMixIn.wdata method usable with non-web connections
3.29.6 (2021-10-07)#
🎉 New features#
Allow authentication on SMTP for outgoing emails. This feature was present in 3.28, but got lost during merge.
3.29#
🎉 New features#
we started to reorganize the documentation, in particular the index, this work will continue with the next releases. Thus, some chapters are still missing
ext/markdown: add urlize extension to auto link url in markdown documents
👷 Bug fixes#
.gitlab-ci.yaml.tmpl should be named .gitlab-ci.yml.tmpl for heptapod
Documentation#
Change data model link to use the definition from YAMS
reorganize the TOC
fix a few dead links
add explanation on site_cubicweb.py
update cube layout documentation
add more explanations to what is a cube
pooler: bad option name for the connections pooler
Change data model link to use the definition from YAMS
🤖 Continuous integration#
build the documentation on gitlab pages
🤷 Various changes#
DeprecatedWarning: [logilab.common.deprecation] moved has been renamed and is deprecated, uses callable_moved instead
add pipeline badge and shields with stats from pypi & docker
3.28.2#
Fixed#
re-introduce cubicweb.pyramid.resources.EntityResource/ETypeResource
re-introduce cubicweb.pyramid.predicates.MatchIsETypePredicate
both were still needed for several cubes
3.28.1#
Fixed#
python 3.8 compatibility: base64 encodestring has been removed, use encodebytes
3.28#
The big highlights of this release are:
CubicWeb now requires python >=3.6
a new dynamic database connections pooler to replace the old static one
a big upgrade in our CI workflow both for tests and documentations
RDF generations when rdf mimetype in Accept HTTP headers
rql resultset now stores selections variables names for RQL select queries, this will allow to build better tools
Added#
[pyramid]`has_cw_permission` pyramid predicates added for routes and view
The database pooler is now dynamic. New connections are opened when needed and closed after a configurable period of time. This can be configured through connections-pooler-max-size (default 0, unlimited), connections-pooler-min-size (default 0), and connections-pooler-idle-timeout (default 600 seconds). The old configuration connections-pooler-size has been dropped.
[pyramid-debugtoolbar] make SQL and RQL tables sortable
[RQL]Resulset now stores selected variables for RQL select queries
pyramid: add routes /{eid} and /{etype}/{eid} to return RDF when rdf mimetype in Accept HTTP headers
entities: simplify rdf generation and add a generic rdf adapter
web.views: add Link alternate in HTTP response header in HTML view
Black and Mypy config in tox.ini file in new cube skeleton
Gitlab CI config file in new cube skeleton
Changed#
CubicWeb now requires python >=3.6
CI now test Cubicweb against latest unreleased public commits of its dependencies
CI now rebuilds documentation after tests
Deprecated#
Class cubicweb.view.EntityAdapter was moved to cubicweb.entity.EntityAdapter, a deprecation warning is in place, but please update your source code accordingly
Removed#
Support for plpython has been dropped
RDFnquadsView (Breaking Change)
Fixed#
Fix various tests in the CI
Use SchemaLoader instead of pyfilereader
[pyramid-debugtoolbar] remove CW controller panel rendering when no controller got collected
[basecontroller] link tags in the header can only be added on entities
add a __contains__ method to dict_protocol_catcher to avoid breaking on “in”
Thanks to our contributors: Simon Chabot, Laurent Peuch, Nicolas Chauvat, Philippe Pepiot, Élodie Thieblin, François FERRY, Fabien Amarger, Laurent Wouters, Guillaume Vandevelde.
3.27 (31 January 2020)#
New features#
Tests can now be run concurrently across multiple processes. You can use pytest-xdist for that. For tests using PostgresApptestConfiguration you should be aware that startpgcluster() can’t run concurrently. Workaround is to call pytest with
--dist=loadfile
to use a single test process per test module or use an existing database cluster and setdb-host
anddb-port
ofdevtools.DEFAULT_PSQL_SOURCES['system']
accordingly.
on cubicweb-ctl create and cubicweb-ctl pyramid, if it doesn’t already exist in the instance directory, the pyramid.ini file will be generated with the needed secrets.
add a –pdb flag to all cubicweb-ctl command to launch (i)pdb if an exception occurs during a command execution.
the –loglevel and –dbglevel flags are available for all cubicweb-ctl instance commands (and not only the
pyramid
one)following “only in foreground” behavior all commands logs to stdout by default from now on. To still log to a file pass
log_to_file=True
toCubicWebConfiguration.config_for
add a new migration function update_bfss_path(old_path, new_path) to update the path in Bytes File-System Storage (bfss).
on every request display request path and selected controller in CLI
migration interactive mode improvements:
when an exception occurs, display the full traceback instead of only the exception
on migration p(db) choice, launch ipdb if it’s installed
on migration p(db) choice, give the traceback to pdb if it’s available, this mean that the (i)pdb interactive session will be on the stack of the exception instead of being on the stack where pdb is launched which will allow the user to access all the relevant context of the exception which otherwise is lost
on DBG_SQL and/or DBG_RQL, if pygments is installed, syntax highlight sql/rql debug output
allow to specify the instance id for any instance command using the CW_INSTANCE global variable instead of or giving it as a cli argument
when debugmode is activated (‘-D/–debug’ on the pyramid command for example), the HTML generated by CW will contains new tags that will indicate by which object in the code it has been generated and in which line of which source code. For example:
<div
cubicweb-generated-by="cubicweb.web.views.basetemplates.TheMainTemplate"
cubicweb-from-source="/home/user/code/logilab/cubicweb/cubicweb/web/views/basetemplates.py:161"
id="contentmain">
<h1
cubicweb-generated-by="cubicweb.web.views.basetemplates.TheMainTemplate"
cubicweb-from-source="/home/user/code/logilab/cubicweb/cubicweb/view.py:136">
unset title
</h1>
[...]
</div>
While this hasn’t been done yet, this feature is an open path for building dynamic tools that can help inspect the page.
a new debug channels mechanism has been added, you can subscribe to one of those channels in your python code to build debug tools for example (the pyramid custom panels are built using that) and you will receive a datastructure (a dict) containing related information. The available channels are: controller, rql, sql, vreg, registry_decisions
add a new ‘-t/–toolbar’ option the pyramid command to activate the pyramid debugtoolbar
a series of pyramid debugtoolbar panels specifically made for CW, see bellow
Pyramid debugtoolbar and custom panel#
The pyramid debugtoolbar is now integrated into CubicWeb during the development phase when you use the ‘pyramid’ command. To activate it you need to pass the ‘-t/–toolbar’ argument to the ‘pyramid’ command.
In addition, a series of custom panels specifically done for CW are now available, they display useful information for the development and the debugging of each page. The available panels are:
a general panel which contains the selected controller, the current settings and useful links screenshot
a panel listing all decisions taken in registry for building this page screenshot
a panel listing the content of the vreg registries screenshot
a panel listing all the RQL queries made during a request screenshot
a panel listing all the SQL queries made during a request screenshot
Furthermore, in all those panels, next to each object/class/function/method a link to display its source code is available (shown as ‘[source]’ screenshot) and also every file path shown is a traceback is also a link to display the corresponding file (screenshot). For example: screenshot.
Backwards incompatible changes#
Standardization on the way to launch a cubicweb instance, from now on the only way to do that will be the used the
pyramid
command. Therefore:cubicweb-ctl
commands “start”, “stop”, “restart”, “reload” and “status” have been removed because they relied on the Twisted web server backend that is no longer maintained nor working with Python 3.Twisted web server support has been removed.
cubicweb-ctl wsgi
has also been removed.
Support for legacy cubes (in the ‘cubes’ python namespace) has been dropped. Use of environment variables CW_CUBES_PATH and CUBES_DIR is removed.
Python 2 support has been dropped.
Exceptions in notification hooks aren’t catched-all anymore during tests so one can expect tests that seem to pass (but were actually silently failing) to fail now.
All “cubicweb-ctl” command only accept one instance argument from now one (instead of 0 to n)
‘pyramid’ command will always run in the foreground now, by consequence the option
--no-daemon
has been removed.DBG_MS flag has been removed since it is not used anymore
transactions db logs where displayed using the logging (debug/info/warning…) mechanism, now it is only displayed if the corresponding DBG_OPS flag is used
backward python 2 compatible code for
scheduler
class has been removed fromcubicweb.server.utils
. If you get an import error when doingfrom cubicweb.server.utils import scheduler
replace it withfrom sched import scheduler
.
Deprecated code drops#
Most code deprecated until version 3.25 has been dropped.
3.26 (1 February 2018)#
New features#
For
pyramid
instance configuration kind, logging is not handled anymore by CubicWeb but should be configured throughdevelopment.ini
file following https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html.
Backwards incompatible changes#
CubicWebConfiguration method ‘shared_dir’ got dropped.
3.25 (14 April 2017)#
New features#
A new option connections-pooler-enabled (default yes) has been added. This allow to switch off internal connection pooling for use with others poolers such as pgbouncer.
In deleteconf view (confirmation before deletion), the list of first-level composite objects that would be deleted along with the primary entity is displayed (01eeea97e549).
The
cubicweb.pyramid
module now provides a Paste application factory registered as an entry point namedpyramid_main
and that can be used to run a Pyramid WSGI application bound to a CubicWeb repository.A new configuration type
pyramid
has been added to create CubicWeb’s instances (throughcubicweb-ctl create -c pyramid <basecube> <appid>
). This configuration bootstraps a CubicWeb instance that is essentially a repository plus the minimal setup to run a Pyramid WSGI application on top of it. Noticeably, it does not ship all web configuration but rather relies on configuration declared in adevelopment.ini
file for any Pyramid application.A new way to declare workflows as simple data structure (dict/list) has been introduced. Respective utility functions live in
cubicweb.wfutils
module. This handles both the creation and migration of workflows.A new IDublinCore adapter has been introduced to control the generation of Dublin Core metadata that are used in several base views.
It is now possible to derive rtags using their
derive
method (0849a5eb57b8). Derived rtags keep a reference to the original rtag and only hold custom rules, allowing changes which are done in the original rtag after derivation to be still considered.A new
cubicweb-ctl scheduler <appid>
command has been introduced to run background and periodic tasks of the repository (previously called looping tasks). In a production environment, a process with this command should be run alongside with a WSGI server process (possibly running multiple processes itself).
Backwards incompatible changes#
As a consequence of the replacement of the old looping tasks manager by a scheduler, all cubicweb-ctl’s “start” commands (i.e.
start
,pyramid
,wsgi
) do not start repository looping tasks manager anymore, nor do they start the scheduler. Site administrators are thus expected to start this scheduler as a separate process. Also, registering looping tasks (i.e. callingrepo.looping_tasks()
) is a no-op when the repository has no scheduler set; a warning is issued in such cases. Application developers may rely on repository’shas_scheduler
method to determine if they should register a looping task or not.In
cubicweb.pyramid
, functionmake_cubicweb_application
got renamed intoconfig_from_cwconfig
(950ce7d9f642).Several cleanups in repository’s session management have been conducted resulting from changes introduced in 3.19 release. Among others, the
cubicweb.server.session.Session
class has been dropped, and requestsession
attribute is now tight to a web session whose implementation depends on the front-end used (twisted or pyramid). Hence this attribute should not be accessed from “repository side” code (e.g. hooks or operations) and has lost some of his former attributes likerepo
which used to reference the repository instance. Due to this, you don’t have anymore access to session’s data through the connection, which leds to deprecation of thedata
attribute and removal ofget_shared_data
andset_shared_data
methods which are deprecated since 3.19.Support for ‘https-url’ configuration option has been removed (4516c3956d46).
The next_tabindex method of request class has been removed (011730a4af73). This include the removal of settabindex from the FieldWidget class init method.
The cubicweb.hook.logstats.start hook was dropped because it’s looping task would not be run in a web instance (see first point about repository scheduler).
uicfg
rules to hide the opposite relation of inlined form are not anymore automatically added, because this was actually done randomly and so not reliable, so you’ll have to add them manually:autoform_section.tag_subject_of(('CWUser', 'use_email', 'EmailAddress'), 'main', 'inlined') autoform_section.tag_object_of(('CWUser', 'use_email', 'EmailAddress'), 'inlined', 'hidden')
3.24 (2 November 2016)#
New features#
Various bits of a CubicWeb application configuration can be now be overridden through environments variables matching configuration option names prefixed by
CW_
(for instanceCW_BASE_URL
).Cubes are now standard Python packages named as
cubicweb_<cubename>
. They are not anymore installed in<prefix>/share/cubicweb/cubes
. Their discovery by CubicWeb is handled by a new setuptools entry pointcubicweb.cubes
. A backward compatibility layer is kept for “legacy” cubes.Pyramid support made it into CubicWeb core. Applications that use it should now declare the cubicweb[pyramid] dependency instead of cubicweb-pyramid.
New NullStore class in
cubicweb.dataimport.stores
as new base class for every store, and allowing to test your dataimport chain without actually importing anything.
Major changes#
There has been several important changes to the core internals of CubicWeb:
Dropped asource and extid columns from the entities table as well as the index on the type column, for a sensible optimization on both massive data insertion and database size / index rebuilding.
Dropped the moved_entities table and related mecanism to remember that an entity has been moved from a source to the system database - this is now the responsability of source’s parser to detect this (usually by remembering its original external id as cwuri).
Dropped the original ‘give me an eid for this extid, but call me back on another line if it has to be created’ mecanism on which the
cwxmlparser
was relying, in favor of parsers using the dataimport API. This includes dropping thecwxmlparser
. If you’re using it, you’ll have to write a specific parser, examples to come.Dropped source mapping handling (schema, views, logic) with its client the
cwxmlparser
. This is not worth the burden, specific parsers should be preferred.
The above changes lead to the following API changes:
req.entity_metas(eid) doesn’t return anymore a ‘type’ nor ‘source’ keys, use req.entity_type(eid) instead or ‘cw_source’ relation to get those information,
deprecated entity.cw_metainformation(), which doesn’t return anymore it’s ‘source’ key,
dropped repository.type_and_source_from_eid(eid, cnx), repository.extid2eid(…) and source.eid_type_source(cnx, eid),
dropped source.support_entity(etype) and source.support_relation(rtype),
dropped ‘cw_source’ key from default JSON representation of an entity,
dropped source_uris() and handle_deletion(…) method from datafeed parser base class, deletion of entities is now the responsability of specific implementation (see
ldapparser
for example),entities from external source are always displayed in the UI with a link to the local entity, not the original one simplifying entity.absolute_url() implementation and allowing to drop use_ext_eid argument of entity.rest_path() (though it’s still supported for backward compat).
Changes to the massive store#
Several improvments have been done to cubicweb.dataimport.massive_store
,
with among the more important ones:
Extended store API to provide more control to end-users: fill_entities_table, fill_relation_table, fill_meta_relation_table.
Dropped on_commit / on_rollback arguments of the constructor.
Use a slave specific temporary table for entities insertion as for relations (should improve concurrency when using in master/slaves mode).
Delay dropping of constraint to the finish method, avoiding performance problem that was occuring because indexes were dropped at store creation time.
Consider the given metadata generator when looking for which metadata tables should have their constraints dropped.
Don’t drop index on entities.eid, it’s too costly to rebuild on database with some million of entities.
3.23 (24 June 2016)#
New features#
Python 3.x support in CubicWeb itself is now complete, except for the twisted package (since Twisted does not completely support Python 3.x itself). The skeleton for new cube should also be Python 3 compatible, in particular its setup.py got updated.
The source-sync command can now synchronize all sources in the database, if no <source> argument is provided.
Datafeed source synchronization is now asynchronous when requested from user interface.
Most indexes and constraints will be rebuilt during the migration, because they are now named after a md5 hash to control the name’s size.
Index are renamed upon renaming of an entity type, so they are still correctly tracked.
A new db-check-index command is added to cubicweb-ctl, to display the differences between the indexes in the database and those expected by the schema. It’s recommended to run this command after the migration to 3.23 and to adjust things manually for cases that are not easily handled by the migration script, such as indexes of entity types that have been renamed. It should be mostly about dropping extra indexes.
Deprecated MetaGenerator in favor of slightly adapted API in MetadataGenerator (more consistent, giving more control to sub-classes and suitable for usage with the MassiveObjectStore)
Major cleanups of the MassiveObjectStore and its PGHelper companion class:
dropped a bunch of unnecessary / unused attributes
refactored / renamed internal methods
added support for a metadata generator, the now recommended way to control metadata generation
Deprecated SQLGenObjectStore, MassiveObjectStore should be used instead.
Backwards-incompatible changes#
Generative tests à la logilab-common are not supported anymore in CubicWebTC. It is advised to use the subtests API (available on CubicWebTC either from the standard library as of Python 3.4 or through unittest2 package otherwise).
CubicWebTC’s set_description method (comming from logilab.common.testlib.TestCase) is no longer available.
Development#
When installed within a virtualenv, CubicWeb will look for instances data as
in user
mode by default, that is in $HOME/etc/cubicweb.d
, as opposed
to $VIRTUAL_ENV/etc/cubicweb.d
previously. To restore this behavior,
explicitly set CW_MODE
to system
. Alternatively (and preferably), the
CW_INSTANCES_DIR
environment variables may be used to specify instances
data location.
3.22 (4 January 2016)#
New features#
a huge amount of changes were done towards python 3.x support (as yet incomplete). This introduces a new dependency on six, to handle python2/python3 compatibility.
new cubicweb.dataimport.massive_store module, a postgresql-specific store using the COPY statement to accelerate massive data imports. This functionality was previously part of cubicweb-dataio (there are some API differences with that previous version, however).
cubes custom sql scripts are executed before creating tables. This allows them to create new types or extensions.
the
ejsonexport
view can be specialized using the newISerializable
entity adapter. By default, it will return an entity’s (non-Bytes and non-Password) attributes plus the specialcw_etype
andcw_source
keys.cubes that define custom final types are now handled by the
add_cube
migration command.synchronization of external sources can be triggered from the web interface by suitably privileged users with a new
cw.source-sync
action.
User-visible changes#
the ldapfeed source now depends on the ldap3 module instead of python-ldap.
replies don’t get an
Expires
header by default. However when they do, they also get a coherentCache-Control
.data files are regenerated at each request, they are no longer cached by
cubicweb.web.PropertySheet
. Requests for data files missing the instance hash are handled with a redirection instead of a direct reply, to allow correct cache-related reply headers.
API changes#
config.repository()
creates a new Repository object each time, instead of returning a cached object. WARNING: this may cause unexpected issues if several repositories end up being used.migration scripts, as well as other scripts executed by
cubicweb-ctl shell
, are loaded with the print_function flag enabled (for backwards compatibility, if that fails they are re-loaded without that flag)the
cw_fti_index_rql_queries
method on entity classes is replaced bycw_fti_index_rql_limit
, a generator which yieldsResultSet
objects containing entities to be indexed. By default, entities are returned 1000 at a time.IDownloadableAdapter
API is clarified:download_url
,download_content_type
anddownload_file_name
return unicode objects,download_data
returns bytes.the
Repository.extid2eid()
entry point for external sources is deprecated. Imports should use one of the stores from thecubicweb.dataimport
package instead.the
cubicweb.repoapi.get_repository()
function’suri
argument should no longer be used.the generic datafeed xml parser is deprecated in favor of the “store” API introduced in cubicweb 3.21.
the session manager lives in the
sessions
registry instead ofcomponents
.TZDatetime
attributes are returned as timezone-aware python datetime objects. WARNING: this will break client applications that compare or use arithmetic involving timezone-naive datetime objects.creation_date and modification_date attributes for all entities are now timezone-aware (
TZDatetime
) instead of localtime (Datetime
). More generally, theDatetime
type should be considered as deprecated.
Deprecated code drops#
the
cubicweb.server.hooksmanager
module was removedthe
Repository.pinfo()
method was removedthe
cubicweb.utils.SizeConstrainedList
class was removedthe ‘startorder’ file in configuration directory is no longer honored
3.21 (10 July 2015)#
New features#
the datadir-url configuration option lets one choose where static data files are served (instead of the default ${base-url}/data/)
some integrity checking that was previously implemented in Python was moved to the SQL backend. This includes some constraints, and referential integrity. Some consequences are that:
disabling integrity hooks no longer disables those checks
upgrades that modify constraints will fail when running on sqlite (but upgrades aren’t supported on sqlite anyway)
Note: as of 3.21.0, the upgrade script only works on PostgreSQL. The migration for SQLServer will be added in a future bugfix release.
for easier instance monitoring, cubicweb can regularly dump some statistics (basically those exposed by the ‘info’ and ‘gc’ views) in json format to a file
User-visible changes#
the use of fckeditor for text form fields is disabled by default, to re-enable it simply install the cubicweb-ckeditor cube (then add_cude(‘ckeditor’) in a migration or in the shell)
the ‘https-deny-anonymous’ configuration setting no longer exists
Code movement#
The cubicweb.web.views.timeline module (providing the timeline-json, timeline and static-timeline views) has moved to a standalone cube
API changes#
req.set_cookie’s “expires” argument, if not None, is expected to be a date or a datetime in UTC. It was previously interpreted as localtime with the UTC offset the server started in, which was inconsistent (we are not aware of any users of that API).
the way to run tests on a postgresql backend has changed slightly, use cubicweb.devtools.{start,stop}pgcluster in setUpModule and tearDownModule
the Connection and ClientConnection objects introduced in CubicWeb 3.19 have been unified. To connect to a repository, use:
session = repo.new_session(login, password=...) with session.new_cnx() as cnx: cnx.execute(...)
In tests, the ‘repo_cnx’ and ‘client_cnx’ methods of RepoAccess are now aliases to ‘cnx’.
Deprecated code drops#
the user_callback api has been removed; people should use plain ajax functions instead
the Pyro and Zmq-pickle remote repository access methods have been entirely removed (emerging alternatives such as rqlcontroller and cwclientlib should be used instead). Note that as a side effect, “repository-only” instances (i.e. without a http component) are no longer possible. If you have any such instances, you will need to rename the configuration file from repository.conf to all-in-one.conf and run
cubicweb-ctl upgrade
to update it. Likewise, remote cubicweb-ctl shell is no longer available.the old (deprecated since 3.19) DBAPI api is completely removed
cubicweb.toolsutils.config_connect() has been removed
3.20 (06/01/2015)#
New features#
virtual relations: a new ComputedRelation class can be used in schema.py; its rule attribute is an RQL snippet that defines the new relation.
computed attributes: an attribute can now be defined with a formula argument (also an RQL snippet); it will be read-only, and updated automatically.
Both of these features are described in CWEP-002, and the updated “Data model” chapter of the CubicWeb book.
cubicweb-ctl plugins can use the
cubicweb.utils.admincnx
function to get a Connection object from an instance name.new ‘tornado’ wsgi backend
session cookies have the HttpOnly flag, so they’re no longer exposed to javascript
rich text fields can be formatted as markdown
the edit controller detects concurrent editions, and raises a ValidationError if an entity was modified between form generation and submission
cubicweb can use a postgresql “schema” (namespace) for its tables
“cubicweb-ctl configure” can be used to set values of the admin user credentials in the sources configuration file
in debug mode, setting the _cwtracehtml parameter on a request allows tracing where each bit of output is produced
API Changes#
ucsvreader()
anducsvreader_pb()
from thedataimport
module have 2 new keyword argumentsdelimiter
andquotechar
to replace theseparator
andquote
arguments respectively. This makes the API match that of Python’scsv.reader()
. The old arguments are still supported though deprecated.the migration environment’s
remove_cube
function is now calleddrop_cube
.cubicweb.old.css is now cubicweb.css. The previous “new” cubicweb.css, along with its cubicweb.reset.css companion, have been removed.
the jquery-treeview plugin was updated to its latest version
Deprecated Code Drops#
most of 3.10 and 3.11 backward compat is gone; this includes:
CtxComponent.box_action() and CtxComponent.build_link()
cubicweb.devtools.htmlparser.XMLDemotingValidator
various methods and properties on Entities, replaced by cw_edited and cw_attr_cache
‘commit_event’ method on hooks, replaced by ‘postcommit_event’
server.hook.set_operation(), replaced by Operation.get_instance(…).add_data()
View.div_id(), View.div_class() and View.create_url()
*VComponent classes
in forms, Field.value() and Field.help() must take the form and the field itself as arguments
form.render() must get w as a named argument, and renderer.render() must take w as first argument
in breadcrumbs, the optional recurs argument must be a set, not False
cubicweb.web.views.idownloadable.{download_box,IDownloadableLineView}
primary views no longer have render_entity_summary and summary methods
WFHistoryVComponent’s cell_call method is replaced by render_body
cubicweb.dataimport.ObjectStore.add(), replaced by create_entity
ManageView.{folders,display_folders}
3.19 (28/04/2015)#
New functionalities#
implement Cross Origin Resource Sharing (CORS) (see #2491768)
system_source.create_eid can get a range of IDs, to reduce overhead of batch entity creation
Behaviour Changes#
The anonymous property of Session and Connection are now computed from the related user login. If it matches the
anonymous-user
in the config the connection is anonymous. Beware that theanonymous-user
config is web specific. Therefore, no session may be anonymous in a repository only setup.
New Repository Access API#
A new explicit Connection object replaces Session as the main repository entry
point. Connection holds all the necessary methods to be used server-side
(execute
, commit
, rollback
, call_service
, entity_from_eid
,
etc…). One obtains a new Connection object using session.new_cnx()
.
Connection objects need to have an explicit begin and end. Use them as a context
manager to never miss an end:
with session.new_cnx() as cnx:
cnx.execute('INSERT Elephant E, E name "Babar"')
cnx.commit()
cnx.execute('INSERT Elephant E, E name "Celeste"')
cnx.commit()
# Once you get out of the "with" clause, the connection is closed.
Using the same Connection object in multiple threads will give you access to the same Transaction. However, Connection objects are not thread safe (hence at your own risks).
repository.internal_session
is deprecated in favor of
repository.internal_cnx
. Note that internal connections are now safe by default,
i.e. the integrity hooks are enabled.
Backward compatibility is preserved on Session.
A new API has been introduced to replace the dbapi. It is called repoapi.
There are three relevant functions for now:
repoapi.get_repository
returns a Repository object either from an URI when used asrepoapi.get_repository(uri)
or from a config when used asrepoapi.get_repository(config=config)
.repoapi.connect(repo, login, **credentials)
returns a ClientConnection associated with the user identified by the credentials. The ClientConnection is associated with its own Session that is closed when the ClientConnection is closed. A ClientConnection is a Connection-like object to be used client side.repoapi.anonymous_cnx(repo)
returns a ClientConnection associated with the anonymous user if described in the config.
On the client/web side, the Request is now using a repoapi.ClientConnection
instead of a dbapi.connection
. The ClientConnection
has multiple backward
compatible methods to make it look like a dbapi.Cursor
and dbapi.Connection
.
Session used on the Web side are now the same than the one used Server side. Some backward compatibility methods have been installed on the server side Session to ease the transition.
The authentication stack has been altered to use the repoapi
instead of
the dbapi
. Cubes adding new element to this stack are likely to break.
Session data can be accessed using the cnx.data dictionary, while transaction data is available through cnx.transaction_data. These replace the [gs]et_shared_data methods with optional txid kwarg.
All current methods and attributes used to access the repo on CubicWebTC
are
deprecated. You may now use a RepoAccess
object. A RepoAccess
object is
linked to a new Session
for a specified user. It is able to create
Connection
, ClientConnection
and web side requests linked to this
session:
access = self.new_access('babar') # create a new RepoAccess for user babar
with access.repo_cnx() as cnx:
# some work with server side cnx
cnx.execute(...)
cnx.commit()
cnx.execute(...)
cnx.commit()
with access.client_cnx() as cnx:
# some work with client side cnx
cnx.execute(...)
cnx.commit()
with access.web_request(elephant='babar') as req:
# some work with client side cnx
elephant_name = req.form['elephant']
req.execute(...)
req.cnx.commit()
By default testcase.admin_access
contains a RepoAccess
object for the
default admin session.
API changes#
RepositorySessionManager.postlogin
is now called with two arguments, request and session. And this now happens before the session is linked to the request.SessionManager
andAuthenticationManager
now take a repo object at initialization time instead of a vreg.The
async
argument of_cw.call_service
has been dropped. All calls are now synchronous. The zmq notification bus looks like a good replacement for most async use cases.repo.stats()
is now deprecated. The same information is available through a service (_cw.call_service('repo_stats')
).repo.gc_stats()
is now deprecated. The same information is available through a service (_cw.call_service('repo_gc_stats')
).repo.register_user()
is now deprecated. The functionality is now available through a service (_cw.call_service('register_user')
).request.set_session
no longer takes an optionaluser
argument.CubicwebTC does not have repo and cnx as class attributes anymore. They are standard instance attributes.
set_cnx
and_init_repo
class methods become instance methods.set_cnxset
andfree_cnxset
are deprecated. cnxset are now automatically managed.The implementation of cascading deletion when deleting composite entities has changed. There comes a semantic change: merely deleting a composite relation does not entail any more the deletion of the component side of the relation.
_cw.user_callback
and_cw.user_rql_callback
are deprecated. Users are encouraged to write an actual controller (e.g. usingajaxfunc
) instead of storing a closure in the session data.A new
entity.cw_linkable_rql
method provides the rql to fetch all entities that are already or may be related to the current entity using the given relation.
Deprecated Code Drops#
session.hijack_user mechanism has been dropped.
EtypeRestrictionComponent has been removed, its functionality has been replaced by facets a while ago.
the old multi-source support has been removed. Only copy-based sources remain, such as datafeed or ldapfeed.
3.18 (10/01/2014)#
The migration script does not handle sqlite nor mysql instances.
New functionalities#
add a security debugging tool (see #2920304)
introduce an add permission on attributes, to be interpreted at entity creation time only and allow the implementation of complex update rules that don’t block entity creation (before that the update attribute permission was interpreted at entity creation and update time)
the primary view display controller (uicfg) now has a set_fields_order method similar to the one available for forms
new method ResultSet.one(col=0) to retrive a single entity and enforce the result has only one row (see #3352314)
new method RequestSessionBase.find to look for entities (see #3361290)
the embedded jQuery copy has been updated to version 1.10.2, and jQuery UI to version 1.10.3.
initial support for wsgi for the debug mode, available through the new
wsgi
cubicweb-ctl command, which can use either python’s builtin wsgi server or the werkzeug module if present.a
rql-table
directive is now available in ReST fieldscubicweb-ctl upgrade can now generate the static data resource directory directly, without a manual call to gen-static-datadir.
API changes#
not really an API change, but the entity permission checks are now systematically deferred to an operation, instead of a) trying in a hook and b) if it failed, retrying later in an operation
The default value storage for attributes is no longer String, but Bytes. This opens the road to storing arbitrary python objects, e.g. numpy arrays, and fixes a bug where default values whose truth value was False were not properly migrated.
symmetric relations are no more handled by an rql rewrite but are now handled with hooks (from the activeintegrity category); this may have some consequences for applications that do low-level database manipulations or at times disable (some) hooks.
unique together constraints (multi-columns unicity constraints) get a name attribute that maps the CubicWeb contraint entities to corresponding backend index.
BreadCrumbEntityVComponent’s open_breadcrumbs method now includes the first breadcrumbs separator
entities can be compared for equality and hashed
the
on_fire_transition
predicate accepts a sequence of possible transition namesthe GROUP_CONCAT rql aggregate function no longer repeats duplicate values, on the sqlite and postgresql backends
Deprecation#
pyrorql
sources have been deprecated. Multisource will be fully dropped in the next version. If you are still using pyrorql, switch todatafeed
NOW!the old multi-source system
find_one_entity and find_entities in favor of find (see #3361290)
the TmpFileViewMixin and TmpPngView classes (see #3400448)
Deprecated Code Drops#
ldapuser
have been dropped; useldapfeed
now (see #2936496)action
GotRhythm
was removed, make sure you do not import it in your cubes (even to unregister it) (see #3093362)all 3.8 backward compat is gone
all 3.9 backward compat (including the javascript side) is gone
the
twisted
(web-only) instance type has been removed
3.17 (02/05/2013)#
New functionalities#
API changes#
drop typed_eid() in favour of int() (see #2742462)
The SIOC views and adapters have been removed from CubicWeb and moved to the sioc cube.
The web page embedding views and adapters have been removed from CubicWeb and moved to the embed cube.
The email sending views and controllers have been removed from CubicWeb and moved to the massmailing cube.
RenderAndSendNotificationView
is deprecated in favor ofActualNotificationOp
the new operation use the more efficient data idiom.Looping task can now have a interval <=
0
. Negative interval disable the looping task entirely.We now serve html instead of xhtml. (see #2065651)
Deprecation#
ldapuser
have been deprecated. It’ll be fully dropped in the next version. If you are still using ldapuser switch toldapfeed
NOW!hijack_user
have been deprecated. It will be dropped soon.
Deprecated Code Drops#
The progress views and adapters have been removed from CubicWeb. These classes were deprecated since 3.14.0. They are still available in the iprogress cube.
API deprecated since 3.7 have been dropped.
3.16 (25/01/2013)#
New functionalities#
Add a new dataimport store (SQLGenObjectStore). This store enables a fast import of data (entity creation, link creation) in CubicWeb, by directly flushing information in SQL. This may only be used with PostgreSQL, as it requires the ‘COPY FROM’ command.
API changes#
Orm: set_attributes and set_relations are unified (and deprecated) in favor of cw_set that works in all cases.
db-api/configuration: all the external repository connection information is now in an URL (see #2521848), allowing to drop specific options of pyro nameserver host, group, etc and fix broken ZMQ source. Configuration related changes:
Dropped ‘pyro-ns-host’, ‘pyro-instance-id’, ‘pyro-ns-group’ from the client side configuration, in favor of ‘repository-uri’. NO MIGRATION IS DONE, supposing there is no web-only configuration in the wild.
Stop discovering the connection method through repo_method class attribute of the configuration, varying according to the configuration class. This is a first step on the way to a simpler configuration handling.
DB-API related changes:
Stop indicating the connection method using ConnectionProperties.
Drop _cnxtype attribute from Connection and cnxtype from Session. The former is replaced by a is_repo_in_memory property and the later is totaly useless.
Turn repo_connect into _repo_connect to mark it as a private function.
Deprecate in_memory_cnx which becomes useless, use _repo_connect instead if necessary.
the “tcp://” uri scheme used for ZMQ communications (in a way reminiscent of Pyro) is now named “zmqpickle-tcp://”, so as to make room for future zmq-based lightweight communications (without python objects pickling).
Request.base_url gets a secure=True optional parameter that yields an https url if possible, allowing hook-generated content to send secure urls (e.g. when sending mail notifications)
Dataimport ucsvreader gets a new boolean ignore_errors parameter.
Unintrusive API changes#
Drop of cubicweb.web.uicfg.AutoformSectionRelationTags.bw_tag_map, deprecated since 3.6.
User interface changes#
The RQL search bar has now some auto-completion support. It means relation types or entity types can be suggested while typing. It is an awesome improvement over the current behaviour !
The action box associated with table views (from tableview.py) has been transformed into a nice-looking series of small tabs; it means that the possible actions are immediately visible and need not be discovered by clicking on an almost invisible icon on the upper right.
The uicfg module has moved to web/views/ and ui configuration objects are now selectable. This will reduce the amount of subclassing and whole methods replacement usually needed to customize the ui behaviour in many cases.
Remove changelog view, as neither cubicweb nor known cubes/applications were properly feeding related files.
Other changes#
‘pyrorql’ sources will be automatically updated to use an URL to locate the source rather than configuration option. ‘zmqrql’ sources were broken before this change, so no upgrade is needed…
Debugging filters for Hooks and Operations have been added.
Some cubicweb-ctl commands used to show the output of msgcat and msgfmt; they don’t anymore.
3.15 (12/04/2012)#
New functionnalities#
Add Zmq server, based on the cutting edge ZMQ (http://www.zeromq.org/) socket library. This allows to access distant instance, in a similar way as Pyro.
Publish/subscribe mechanism using ZMQ for communication among cubicweb instances. The new zmq-address-sub and zmq-address-pub configuration variables define where this communication occurs. As of this release this mechanism is used for entity cache invalidation.
Improved WSGI support. While there is still some caveats, most of the code which was twisted only is now generic and allows related functionalities to work with a WSGI front-end.
Full undo/transaction support : undo of modification has eventually been implemented, and the configuration simplified (basically you activate it or not on an instance basis).
Controlling HTTP status code used is not much more easier :
WebRequest now has a status_out attribut to control the response status ;
most web-side exceptions take an optional
status
argument.
API changes#
The base registry implementation has been moved to a new logilab.common.registry module (see #1916014). This includes code from :
cubicweb.vreg (the whole things that was in there)
cw.appobject (base selectors and all).
In the process, some renaming was done:
the top level registry is now RegistryStore (was VRegistry), but that should not impact cubicweb client code ;
former selectors functions are now known as “predicate”, though you still use predicates to build an object’selector ;
for consistency, the objectify_selector decoraror has hence be renamed to objectify_predicate ;
on the CubicWeb side, the selectors module has been renamed to predicates.
Debugging refactoring dropped the more need for the lltrace decorator. There should be full backward compat with proper deprecation warnings. Notice the yes predicate and objectify_predicate decorator, as well as the traced_selection function should now be imported from the logilab.common.registry module.
All login forms are now submitted to <app_root>/login. Redirection to requested page is now handled by the login controller (it was previously handle by the session manager).
Publisher.publish has been renamed to Publisher.handle_request. This method now contains generic version of logic previously handled by Twisted. Controller.publish is not affected.
Unintrusive API changes#
New ‘ldapfeed’ source type, designed to replace ‘ldapuser’ source with data-feed (i.e. copy based) source ideas.
New ‘zmqrql’ source type, similar to ‘pyrorql’ but using ømq instead of Pyro.
A new registry called services has appeared, where you can register server-side cubicweb.server.Service child classes. Their call method can be invoked from a web-side AppObject instance using new self._cw.call_service method or a server-side one using self.session.call_service. This is a new way to call server-side methods, much cleaner than monkey patching the Repository class, which becomes a deprecated way to perform similar tasks.
a new ajax-func registry now hosts all remote functions (i.e. functions callable through the asyncRemoteExec JS api). A convenience ajaxfunc decorator will let you expose your python function easily without all the appobject standard boilerplate. Backward compatibility is preserved.
the ‘json’ controller is now deprecated in favor of the ‘ajax’ one.
WebRequest.build_url can now take a __secure__ argument. When True cubicweb try to generate an https url.
User interface changes#
A new ‘undohistory’ view expose the undoable transactions and give access to undo some of them.
3.14 (09/11/2011)#
First notice CW 3.14 depends on yams 0.34 (which is incompatible with prior cubicweb releases regarding instance re-creation).
API changes#
Entity.fetch_rql restriction argument has been deprecated and should be replaced with a call to the new Entity.fetch_rqlst method, get the returned value (a rql Select node) and use the RQL syntax tree API to include the above-mentionned restrictions.
Backward compat is kept with proper warning.
Entity.fetch_order and Entity.fetch_unrelated_order class methods have been replaced by Entity.cw_fetch_order and Entity.cw_fetch_unrelated_order with a different prototype:
instead of taking (attr, var) as two string argument, they now take (select, attr, var) where select is the rql syntax tree beinx constructed and var the variable node.
instead of returning some string to be inserted in the ORDERBY clause, it has to modify the syntax tree
Backward compat is kept with proper warning, BESIDE cases below:
custom order method return something else the a variable name with or without the sorting order (e.g. cases where you sort on the value of a registered procedure as it was done in the tracker for instance). In such case, an error is logged telling that this sorting is ignored until API upgrade.
client code use direct access to one of those methods on an entity (no code known to do that).
Entity._rest_attr_info class method has been renamed to Entity.cw_rest_attr_info
No backward compat yet since this is a protected method an no code is known to use it outside cubicweb itself.
AnyEntity.linked_to has been removed as part of a refactoring of this functionality (link a entity to another one at creation step). It was replaced by a EntityFieldsForm.linked_to property.
In the same refactoring, cubicweb.web.formfield.relvoc_linkedto, cubicweb.web.formfield.relvoc_init and cubicweb.web.formfield.relvoc_unrelated were removed and replaced by RelationField methods with the same names, that take a form as a parameter.
No backward compatibility yet. It’s still time to cry for it. Cubes known to be affected: tracker, vcsfile, vcreview.
CWPermission entity type and its associated require_permission relation type (abstract) and require_group relation definitions have been moved to a new localperms cube. With this have gone some functions from the cubicweb.schemas package as well as some views. This makes cubicweb itself smaller while you get all the local permissions stuff into a single, documented, place.
Backward compat is kept for existing instances, though you should have installed the localperms cubes. A proper error should be displayed when trying to migrate to 3.14 an instance the use CWPermission without the new cube installed. For new instances / test, you should add a dependancy on the new cube in cubes using this feature, along with a dependancy on cubicweb >= 3.14.
jQuery has been updated to 1.6.4 and jquery-tablesorter to 2.0.5. No backward compat issue known.
Table views refactoring : new RsetTableView and EntityTableView, as well as rewritten an enhanced version of PyValTableView on the same bases, with logic moved to some column renderers and a layout. Those should be well documented and deprecates former TableView, EntityAttributesTableView and CellView, which are however kept for backward compat, with some warnings that may not be very clear unfortunatly (you may see your own table view subclass name here, which doesn’t make the problem that clear). Notice that _cw.view(‘table’, rset, *kwargs) will be routed to the new RsetTableView or to the old TableView depending on given extra arguments. See #1986413.
display_name don’t call .lower() anymore. This may leads to changes in your user interface. Different msgid for upper/lower cases version of entity type names, as this is the only proper way to handle this with some languages.
IEditControlAdapter has been deprecated in favor of EditController overloading, which was made easier by adding dedicated selectors called match_edited_type and match_form_id.
Pre 3.6 API backward compat has been dropped, though data migration compatibility has been kept. You may have to fix errors due to old API usage for your instance before to be able to run migration, but then you should be able to upgrade even a pre 3.6 database.
Deprecated cubicweb.web.views.iprogress in favor of new iprogress cube.
Deprecated cubicweb.web.views.flot in favor of new jqplot cube.
Unintrusive API changes#
Refactored properties forms (eg user preferences and site wide properties) as well as pagination components to ease overridding.
New cubicweb.web.uihelper module with high-level helpers for uicfg.
New anonymized_request decorator to temporary run stuff as an anonymous user, whatever the currently logged in user.
New ‘verbatimattr’ attribute view.
New facet and form widget for Integer used to store binary mask.
New js_href function to generated proper javascript href.
match_kwargs and match_form_params selectors both accept a new once_is_enough argument.
- printable_value is now a method of request, and may be given dict of
formatters to use.
[Rset]TableView allows to set None in ‘headers’, meaning the label should be fetched from the result set as done by default.
Field vocabulary computation on entity creation now takes __linkto information into accounet.
Started a cubicweb.pylintext pylint plugin to help pylint analyzing cubes.
RQL#
Support for HAVING in ‘SET’ and ‘DELETE’ queries.
new AT_TZ function to get back a timestamp at a given time-zone.
new WEEKDAY date extraction function
User interface changes#
Datafeed source now present an history of the latest import’s log, including global status and debug/info/warning/error messages issued during imports. Import logs older than a configurable amount of time are automatically deleted.
Breadcrumbs component is properly kept when creating an entity with ‘__linkto’.
users and groups management now really lead to that (i.e. includes groups management).
New ‘jsonp’ controller with ‘jsonexport’ and ‘ejsonexport’ views.
Configuration#
Added option ‘resources-concat’ to make javascript/css files concatenation optional.
API#
cubicweb_web
#
CubicWeb web client core. You’ll need a apache-modpython or twisted publisher to get a full CubicWeb web application
Exceptions#
Base exceptions#
Repository exceptions#
Security Exceptions#
Source exceptions#
Registry exceptions#
Query exceptions#
Misc#
- exception cubicweb_web.ValidationError(entity, errors: Dict, msgargs: Optional[Dict] = None, i18nvalues: Optional[List] = None)[source]#
Bases:
yams._exceptions.SchemaError
Validation error details the reason(s) why the validation failed.
Arguments are:
entity: the entity that could not be validated; actual type depends on the client library
errors: errors dictionary, None key used for global error, other keys should be attribute/relation of the entity, qualified as subject/object using
yams.role_name()
. Values are the message associated to the keys, and may include interpolation string starting with ‘%(KEY-’ where ‘KEY’ will be replaced by the associated key once the message has been translated. This allows predictable/translatable message and avoid args conflict if used for several keys.msgargs: dictionary of substitutions to be inserted in error messages once translated (only if msgargs is given)
i18nvalues: list of keys in msgargs whose value should be translated
Translation will be done in-place by calling
translate()
.
Utilities#
cubicweb.dataimport
#
Package containing various utilities to import data into cubicweb.
Utilities#
- cubicweb.dataimport.count_lines(*args, **kwargs)#
- cubicweb.dataimport.ucsvreader_pb(*args, **kwargs)#
- cubicweb.dataimport.ucsvreader(*args, **kwargs)#
Object Stores#
- cubicweb.dataimport.RQLObjectStore#
alias of
logilab.common.deprecation.callable_moved.<locals>.callnew
- cubicweb.dataimport.NoHookRQLObjectStore#
alias of
logilab.common.deprecation.callable_moved.<locals>.callnew
cubicweb_web.predicates
#
- class cubicweb_web.predicates.paginated_rset(nbpages=1)[source]#
Return 1 or more for result set with more rows than one or more page size. You can specify expected number of pages to the initializer (default to one), and you’ll get that number of pages as score if the result set is big enough.
Page size is searched in (respecting order): * a page_size argument * a page_size form parameters * the navigation.page-size property (see Configuring persistent properties)
cubicweb.pyramid
#
cubicweb.pyramid.auth
#
CubicWeb AuthTkt authentication policy#
When using the cubicweb.pyramid.auth module, which is the default in most cases, you may have to configure the behaviour of these authentication policies using standard’s Pyramid configuration. You may want to configure in your pyramid configuration file:
- Session Authentication
This is a AuthTktAuthenticationPolicy so you may overwrite default configuration values by adding configuration entries using the prefix
cubicweb.auth.authtkt.session
. Default values are:cubicweb.auth.authtkt.session.hashalg = sha512 cubicweb.auth.authtkt.session.cookie_name = auth_tkt cubicweb.auth.authtkt.session.timeout = 1200 cubicweb.auth.authtkt.session.reissue_time = 120 cubicweb.auth.authtkt.session.http_only = True cubicweb.auth.authtkt.session.secure = True
- Persistent Authentication
This is also a AuthTktAuthenticationPolicy. It is used when persistent sessions are activated (typically when using the cubicweb-rememberme cube). You may overwrite default configuration values by adding configuration entries using the prefix
cubicweb.auth.authtkt.persistent
. Default values are:cubicweb.auth.authtkt.persistent.hashalg = sha512 cubicweb.auth.authtkt.persistent.cookie_name = pauth_tkt cubicweb.auth.authtkt.persistent.max_age = 3600*24*30 cubicweb.auth.authtkt.persistent.reissue_time = 3600*24 cubicweb.auth.authtkt.persistent.http_only = True cubicweb.auth.authtkt.persistent.secure = True
Warning
Legacy timeout values from the instance’s
all-in-one.conf
are not used at all (``
http-session-time`` and cleanup-session-time
)
Secrets#
There are a number of secrets to configure in pyramid.ini
. They
should be different one from each other, as explained in `Pyramid's
documentation`_.
For the record, regarding authentication:
- cubicweb.auth.authtkt.session.secret
This secret is used to encrypt the authentication cookie.
- cubicweb.auth.authtkt.persistent.secret
This secret is used to encrypt the persistent authentication cookie.
- cubicweb.pyramid.auth.includeme(config)[source]#
Activate the CubicWeb AuthTkt authentication policy.
Usually called via
config.include('cubicweb.pyramid.auth')
.See also cubicweb.pyramid.defaults
- class cubicweb.pyramid.auth.UpdateLoginTimeAuthenticationPolicy[source]#
Bases:
object
An authentication policy that update the user last_login_time.
The update is done in the ‘remember’ method, which is called by the login views login,
Usually used via
includeme()
.
cubicweb.pyramid.bwcompat
#
cubicweb.pyramid.core
#
Binding of CubicWeb connection to Pyramid request.
- cubicweb.pyramid.core.includeme(config)[source]#
Enables the core features of Pyramid CubicWeb.
Automatically called by the ‘pyramid’ command, or via
config.include('cubicweb.pyramid.code')
. In the later case, the following registry entries must be defined first:- ‘cubicweb.config’
A cubicweb ‘config’ instance.
- ‘cubicweb.repository’
The correponding cubicweb repository.
- ‘cubicweb.registry’
The vreg.
- cubicweb.pyramid.core.repo_connect(request, repo, eid)[source]#
A lightweight version of
cubicweb.server.repository.Repository.connect()
that does not keep track of opened sessions, removing the need of closing them
- cubicweb.pyramid.core.get_principals(login, request)[source]#
Returns the group names of the authenticated user.
This function is meant to be used as an authentication policy callback.
It also pre-open the cubicweb session and put it in request._cw_cached_session for later usage by
_cw_session()
.Note
If the default authentication policy is not used, make sure this function gets called by the active authentication policy.
- Parameters
login – A cubicweb user eid
request – A pyramid request
- Returns
A list of group names
- cubicweb.pyramid.core._cw_session(request)[source]#
Obtains a cw session from a pyramid request
- Parameters
request – A pyramid request
- Returns type
cubicweb.server.session.Session
Not meant for direct use, use
request.cw_session
instead.
- cubicweb.pyramid.core._cw_cnx(request)[source]#
Obtains a cw session from a pyramid request
The connection will be commited or rolled-back in a request finish callback (this is temporary, we should make use of the transaction manager in a later version).
Not meant for direct use, use
request.cw_cnx
instead.- Parameters
request – A pyramid request
- Returns type
cubicweb.pyramid.defaults
#
Defaults for a classical CubicWeb instance.
- cubicweb.pyramid.defaults.includeme(config)[source]#
Enable the defaults that make the application behave like a classical CubicWeb instance.
The following modules get included:
cubicweb.pyramid.login
It is automatically included by the configuration system, unless the following entry is added to the Pyramid Settings file:
cubicweb.defaults = no
cubicweb.pyramid.login
#
cubicweb.pyramid.profile
#
cubicweb.pyramid.session
#
Web session when using pyramid#
CubicWeb CWSession
entity type so that sessions can be
stored in the database, which allows to run a Cubicweb instance
without having to set up a session storage (like redis or memcache)
solution.
However, for production systems, it is greatly advised to use such a storage solution for the sessions.
The handling of the sessions is made by pyramid (see the `pyramid's documentation on sessions`_ for more details).
For example, to set up a redis based session storage, you need the `pyramid-redis-session`_ package, then you must configure pyramid to use this backend, by configuring the pyramid configuration file:
[main]
cubicweb.defaults = no # we do not want to load the default cw session handling
cubicweb.auth.authtkt.session.secret = <secret1>
cubicweb.auth.authtkt.persistent.secret = <secret2>
cubicweb.auth.authtkt.session.secure = yes
cubicweb.auth.authtkt.persistent.secure = yes
redis.sessions.secret = <secret3>
redis.sessions.prefix = <my-app>:
redis.sessions.url = redis://localhost:6379/0
pyramid.includes =
pyramid_redis_sessions
cubicweb.pyramid.auth
cubicweb.pyramid.login
Warning
If you want to be able to log in a CubicWeb application
served by pyramid on a unsecured stream (typically when
you start an instance in dev mode using a simple
cubicweb-ctl pyramid -D -linfo myinstance
), you
must set cubicweb.auth.authtkt.session.secure
to
no
.
Secrets#
There are a number of secrets to configure in pyramid.ini
. They
should be different one from each other, as explained in `Pyramid's
documentation`_.
For the record, regarding session handling:
- cubicweb.session.secret
This secret is used to encrypt the session’s data ID (data themselved are stored in the backend, database or redis) when using the integrated (
CWSession
based) session data storage.- redis.session.secret
This secret is used to encrypt the session’s data ID (data themselved are stored in the backend, database or redis) when using redis as backend.
- cubicweb.pyramid.session.includeme(config)[source]#
Activate the CubicWeb session factory.
Usually called via
config.include('cubicweb.pyramid.auth')
.See also cubicweb.pyramid.defaults
- cubicweb.pyramid.session.CWSessionFactory(secret, cookie_name='session', max_age=None, path='/', domain=None, secure=False, httponly=True, set_on_exception=True, timeout=1200, reissue_time=120, hashalg='sha512', salt='pyramid.session.', serializer=None)[source]#
A pyramid session factory that store session data in the CubicWeb database.
Storage is done with the ‘CWSession’ entity, which is provided by the ‘pyramid’ cube.
Warning
Although it provides a sane default behavior, this session storage has a serious overhead because it uses RQL to access the database.
Using pure SQL would improve a bit (it is roughly twice faster), but it is still pretty slow and thus not an immediate priority.
It is recommended to use faster session factory (pyramid_redis_sessions for example) if you need speed.
cubicweb.pyramid.url_redirection
#
cubicweb.web.views.urlrewrite
#
cubicweb_web - And how to use it with CubicWeb#
cubicweb_web is a cube which used to be part of CubicWeb. It provides a lot of generic HTML views and a controller mechanism.
First steps#
Routing#
CubicWeb offers two different ways of routing : one internal to CubicWeb and a one with the pyramid framework.
Principle:
cubicweb and pyramid
the CW request object
encapsulation of the CW request in the pyramid request
bw_compat and the options to use, fallback when CW doesn’t find anything
CubicWeb routing:
Pyramid routing:
general principles
predicates
tweens
content negociation
Front development#
With Javascript / Typescript (using React):
general principle
how to install and integrate js tooling into CW
cwelements
rql browser
With Pyramid:
general integration with CubicWeb
example of usages with CW
With CubicWeb Views:
Facets
How to use javascript inside CW views
Customize CSS
RDF:
the RDF adaptator
RDFLib integration into CW
Security#
Security:
Permissions management with Pyramid
Cubicweb configuration files#
Base configuration:
Advanced configuration:
Common Web application tools#
Test
Caching
Internationalization
Full text indexation