Chapter 3. Tutorial

First of all, you need to be familiar with the Python language and it is highly recommended to be also familiar with Qt usage withing Python. If it is not the case, here is some materials that can help you :

Start by creating a PyDADL project using the pydadl_new_project command :

/usr/local/pydadl/bin/pydadl_new_project

Answer to the questions asked, the default values are presented between brackets.

We'll create a project named myapp and we'll assume that the project will be stored in the directory /home/joe/myapp. Your screen should looks like the following :

$ /usr/local/pydadl/bin/pydadl_new_project
Project name : myapp
Project directory (/home/joe/myapp) :
SQL host (127.0.0.1) :
SQL port (3306) :
SQL user (joe) : root
SQL password :

PyDADL projet 'myapp' successfully created

The command creates the project directory that will store your project files. In this directory you'll also find some initial files, folders and scripts.

Start the PyDADL server :

/home/joe/myapp/server

The server listens to a random port chosen during the creation process, you can modify it by editing the etc/server.conf file located in the project directory, you must restart the server if you modify this file. See Section 4.36, “Server's configuration file” for more details. You have also to restart the server when you modify an application name or add/delete an application to refresh the applications list sent to clients.

In a production environment you would use PyDADL in conjunction with apache HTTP server with the mod_python module. See Section 4.38, “Apache integration via mod_python” for more details.

Start the PyDADL compiler :

/home/joe/myapp/compile -w

PyDADL uses XML files for describing the UI, those files are read by the compiler and stored in a dedicated database created automatically. the -w switch instruct the compiler to look for changes in the ui_xml directory. It's in that directory where you will create your UI XML files.

Start the PyDADL client :

/usr/local/pydadl/bin/pydadl_client

A logon screen will popup, enter user as the username and pass as the password. PyDADL client uses a discovery protocol to locate IP and port of the PyDADL server running on your local network. You can have multiple servers offering multiple applications on your network, click on the Options button to select your application or to connect to a server beyond your local network.

Ok, at this point your application is up and running but as you can see, it's only an empty useless window. We'll start by changing the application title and the famous "Hello world". Edit the application.xml located in ui_xml so it looks like the following :

<application name='myapp' title='My first PyDADL application'>

    <data_server type='mysql' host='127.0.0.1' port='3306' user='root' passwd='' db='myapp_data'>
    </data_server>

    <user login='user' passwd='pass'>
    </user>

<post_show>
MsgBox("Hello world")
</post_show>

</application>

After saving, the compiler will automatically process the changes and you must restart the PyDADL client.

[Note]Note

You have to restart the PyDADL client only when you modify application.xml file. It is not necessary when working with other files.

Now we'll do more usefull things, we will create an application menu. Edit application.xml again :

<application name='myapp' title='My first PyDADL application'>

    <data_server type='mysql' host='127.0.0.1' port='3306' user='root' passwd='' db='myapp_data'>
    </data_server>

    <user login='user' passwd='pass'>
    </user>

    <menu label='Menu A'>
        <item label='Dialog 1'>dialog:d1</item>
        <item label='Dialog 2'>dialog:d2</item>
        <item label='|'></item>
        <item label='Quit'>callback:quit</item>
    </menu>

<callback name='quit'>
Quit()
</callback>

</application>

There are many more things you can do, like adding toolbars and docks or creating more users. See Section 4.1, “Application XML” for more information on what you can put in application.xml.

Now when you click on "Dialog 1" or "Dialog 2" menu items, you get an error message telling you that the dialog does not exist. The next step we will do is to create a dialog named d1. Create a new file with the following content :

<dialog application='myapp' name='d1' title='Dialog 1'>	
</dialog>

And save it under the ui_xml directory. The name of the file is not important, choose whatever name you want. ex: d1.xml. Click now on "Dialog 1", and empty dialog popup.

Let's transform this empty dialog into a simple contact list. We start by creating a database table to store our contacts. Execute the following query in the myapp_data database :

CREATE TABLE `contacts` (
  `id` bigint(20) unsigned NOT NULL auto_increment,
  `first_name` varchar(100) collate utf8_unicode_ci NOT NULL,
  `last_name` varchar(100) collate utf8_unicode_ci NOT NULL,
  `address` text collate utf8_unicode_ci NOT NULL,
  `phone` varchar(100) collate utf8_unicode_ci NOT NULL,
  `email` varchar(100) collate utf8_unicode_ci NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

Edit d1.xml and set the following content :

<dialog application='myapp' name='d1' title='Contacts' buttons='2' values_call='get_contacts'> 

    <widget type='table' row='0' col='0' name='contacts' headers='top' grid='none' ro='1' selection='rows' del_item_msg='Delete this item ?'></widget>
    <widget type='grid' row='0' col='1' h_layout='max'>
        <widget type='button' name='bt_new'>New...</widget>
        <widget type='button' name='bt_edit'>Edit...</widget>
        <widget type='button' name='bt_del'>Delete</widget>
        <widget type='spacer'></widget>
    </widget>

<init>
d.setCancelText('Close')
</init>

</dialog>

Create a file named for example contacts.py in the xmlrpc_functions directory with the following content :

def get_contacts():
    ret = {}

    ret['contacts'] = '<table>'
    ret['contacts'] += '<header>'
    ret['contacts'] += "<column>First name</column>"
    ret['contacts'] += "<column>Last name</column>"
    ret['contacts'] += '</header>'

    dao = Data.getDAO('contacts')
    for i in dao.select():
        ret['contacts'] += "<row id='%s'>" % i.id
        ret['contacts'] += "<column>%s</column>" % XmlEscape(i.first_name)
        ret['contacts'] += "<column>%s</column>" % XmlEscape(i.last_name)
        ret['contacts'] += "</row>"

    ret['contacts'] += '</table>'

    return ret

Click again on "Dialog 1", you should see an empty contact list with three nonfunctional buttons. You can manually enter some data into the table contacts and reload the dialog.

Now let's add support for creating, editing and deleting contacts. Create another dialog XML file named add_edit_contact.xml with the following content :

<dialog application='myapp' name='add_edit_contact' modal='1' width='500' height='300'>
			
    <widget type='label' row='0' col='0'>First name</widget>
    <widget type='line' row='=' col='1' name='first_name'></widget>

    <widget type='label' row='+' col='0'>Last name</widget>
    <widget type='line' row='=' col='1' name='last_name'></widget>

    <widget type='label' row='+' col='0'>Address</widget>
    <widget type='text' row='=' col='1' name='address'></widget>

    <widget type='label' row='+' col='0'>Phone</widget>
    <widget type='line' row='=' col='1' name='phone'></widget>

    <widget type='label' row='+' col='0'>Email</widget>
    <widget type='line' row='=' col='1' name='email'></widget>

<init>
if DialogData:
    values = Rpc.get_contact(DialogData)
    d.setTitle("Edit contact '%s %s'" % (values['first_name'], values['last_name'],))
    d.setValues(values)
else:
    d.setTitle('New contact')
</init>
			
<callback>
if DialogData:
    return Rpc.edit_contact(DialogData, Values)
else:
    return Rpc.add_contact(Values)
</callback>

</dialog>

This dialog is used to enter a new contact's information and to edit old contacts information.

Add the following sections to d1.xml :

<connection widget='bt_new' signal='clicked()'>
ret = ShowDialog('add_edit_contact')
if ret:
    row = "&lt;row id='%s'>&lt;column>%s&lt;/column>&lt;column>%s&lt;/column>&lt;/row>" % \
            (ret['id'], ret['first_name'], ret['last_name'])
    d.contacts.addRows(row)
</connection>

<connection widget='bt_edit' signal='clicked()' widget1='contacts' signal1='doubleClicked'>
d.contacts.setSingleSelection()
current = d.contacts.getCurrentValue(True)
if current:
    ret = ShowDialog('add_edit_contact', int(current['__id__']))
    if ret:
        d.contacts.editCurrentRow((ret['first_name'], ret['last_name']))
</connection>

<connection widget='bt_del' signal='clicked()'>
d.contacts.delCurrentRows()
</connection>
			
<connection widget='contacts' signal='rowsDeleted'>
Rpc.del_contacts([int(i) for i in Args[0].keys()])
</connection>

</dialog>

And finally add those functions to contacts.py :

def get_contact(id):
    dao = Data.getDAO('contacts')
    return dao.selectRow(id=id).getRow()

def add_contact(values):
    dao = Data.getDAO('contacts')
    for k, v in values.items(): dao[k] = v
    return {'id': dao.insert()}

def edit_contact(id, values):
    dao = Data.getDAO('contacts')
    dao.setFilter(id=id)
    for k, v in values.items(): dao[k] = v
    return dao.update()

def del_contacts(ids):
    dao = Data.getDAO('contacts')
    dao.setFilter('id', ids, 'IN')
    dao.delete()

At this point, the contact list is ready to use. Bellow is the full content of these files. There are some validation code added (bold code) :

Here is the full content of d1.xml :

<dialog application='myapp' name='d1' title='Contacts' buttons='2' values_call='get_contacts'> 

    <widget type='table' row='0' col='0' name='contacts' headers='top' grid='none' ro='1' selection='rows' del_item_msg='Delete this item ?'></widget>
    <widget type='grid' row='0' col='1' h_layout='max'>
        <widget type='button' name='bt_new'>New...</widget>
        <widget type='button' name='bt_edit'>Edit...</widget>
        <widget type='button' name='bt_del'>Delete</widget>
        <widget type='spacer'></widget>
    </widget>


<init>
d.setCancelText('Close')
</init>

<connection widget='bt_new' signal='clicked()'>
ret = ShowDialog('add_edit_contact')
if ret:
    row = "&lt;row id='%s'>&lt;column>%s&lt;/column>&lt;column>%s&lt;/column>&lt;/row>" % \
            (ret['id'], ret['first_name'], ret['last_name'])
    d.contacts.addRows(row)
</connection>

<connection widget='bt_edit' signal='clicked()' widget1='contacts' signal1='doubleClicked'>
d.contacts.setSingleSelection()
current = d.contacts.getCurrentValue(True)
if current:
    ret = ShowDialog('add_edit_contact', int(current['__id__']))
    if ret:
        d.contacts.editCurrentRow((ret['first_name'], ret['last_name']))
</connection>

<connection widget='bt_del' signal='clicked()'>
d.contacts.delCurrentRows()
</connection>
			
<connection widget='contacts' signal='rowsDeleted'>
Rpc.del_contacts([int(i) for i in Args[0].keys()])
</connection>

</dialog>

Here is the full content of add_edit_contact.xml :

<dialog application='myapp' name='add_edit_contact' modal='1' width='500' height='300'>
			
    <widget type='label' row='0' col='0'>First name</widget>
    <widget type='line' row='=' col='1' name='first_name'></widget>

    <widget type='label' row='+' col='0'>Last name</widget>
    <widget type='line' row='=' col='1' name='last_name'></widget>

    <widget type='label' row='+' col='0'>Address</widget>
    <widget type='text' row='=' col='1' name='address'></widget>

    <widget type='label' row='+' col='0'>Phone</widget>
    <widget type='line' row='=' col='1' name='phone'></widget>

    <widget type='label' row='+' col='0'>Email</widget>
    <widget type='line' row='=' col='1' name='email'></widget>

<init>
if DialogData:
    values = Rpc.get_contact(DialogData)
    d.setTitle("Edit contact '%s %s'" % (values['first_name'], values['last_name'],))
    d.setValues(values)
else:
    d.setTitle('New contact')
</init>

<post_show>
d.first_name.setFocus()
</post_show>
			
<callback>
if not Values['first_name'] and not Values['last_name']:
    Error('Please enter a name !')
if DialogData:
    return Rpc.edit_contact(DialogData, Values)
else:
    return Rpc.add_contact(Values)
</callback>

</dialog>

Here is the full content of contacts.py :

def get_contacts():
    ret = {}

    ret['contacts'] = '<table>'
    ret['contacts'] += '<header>'
    ret['contacts'] += "<column>First name</column>"
    ret['contacts'] += "<column>Last name</column>"
    ret['contacts'] += '</header>'

    dao = Data.getDAO('contacts')
    for i in dao.select():
        ret['contacts'] += "<row id='%s'>" % i.id
        ret['contacts'] += "<column>%s</column>" % XmlEscape(i.first_name)
        ret['contacts'] += "<column>%s</column>" % XmlEscape(i.last_name)
        ret['contacts'] += "</row>"

    ret['contacts'] += '</table>'

    return ret

def get_contact(id):
    dao = Data.getDAO('contacts')
    return dao.selectRow(id=id).getRow()

def add_contact(values):
    dao = Data.getDAO('contacts')

    if dao.select(first_name=values['first_name'], last_name=values['last_name']):
        Error("Contact exists: %s %s" % (values['first_name'], values['last_name']))

    for k, v in values.items(): dao[k] = v
    return {'id': dao.insert()}

def edit_contact(id, values):
    dao = Data.getDAO('contacts')

    dao.setFilter('id', id, '<>')
    if dao.select(first_name=values['first_name'], last_name=values['last_name']):
        Error("Contact exists: %s %s" % (values['first_name'], values['last_name']))

    dao.reset()
    dao.setFilter('id', id)
    for k, v in values.items(): dao[k] = v
    return dao.update()

def del_contacts(ids):
    dao = Data.getDAO('contacts')
    dao.setFilter('id', ids, 'IN')
    dao.delete()

Last but not least, the PyDADL.ui module can be used to create UI without the client/server part. See the examples directory in the PyDADL sources. PyDADL.ui can also be used from a PyDADL application to generate dynamic UI.