A simple FormAlchemy tutorial

December 4th, 2009

Here’s a quick tutorial on the usage of FormAlchemy with Elixir and Pylons.

The model

To illustrate how things work, we will take a very simple example.

Let’s say you have a website where people have to sign in.

Here’s a very simple User class

# Entity binds a class to a table
from elixir import Entity
# Field represents bounds an attribute to a column in your table
from elixir import Field
# Unicode, Integer, DateTime etc. are column types. Pay attention to encoding when you’re using Unicode.
from elixir import Unicode
# This will allow us to represent relations between classes. Elixir will figure out how to represent them
# in the database, between the tables
from elixir import ManyToOne, OneToMany

import datetime # we will use this to put default values in DateTime columns

# Elixir will create a table for each class inherting form Entity
class User(Entity):
    # By default, your table names will look like "ModuleName_ClassName".
    # To change this, you can either pass in a shortnames=True argument to
    # the using_options function, which will tell elixir to create your tables
    # based on ClassName only, or a tablename=’My_Table_Name’ argument.
    # I’m used to have plural names for tables, and single names for classes,
    # so I’ll pass in a tablename arguemnt
   
    # Next thing is the inhertince argument. This will tell Elixir
    # that User can be subclassed.
    using_options (tablename=“Users”,inheritance=“multi”)
   
    # I like powers of 2
    username      = Field(Unicode(32))
    email         = Field(Unicode(64))
    password      = Field(Unicode(32))
    phone         = Field(Unicode(16))
    address       = Field(Unicode(64))
    # Many users may live in One City, so in this case it’s Many(users)ToOne(City)
    city          = ManyToOne(“City”)
    creation_date = Field(DateTime,default=datetime.datetime.now)

# Let’s make a distinction between Persons and Companies. Both are users, wich means they share a certain amount of
# properties, but they can also have different additional attribtues of their own.

class Person(User):
    using_options (tablename=“Persons”,inheritance=“multi”)
    first_name = Field(Unicode(32))
    last_name  = Field(Unicode(32))   

    def __str__(self):
        “”” This method is called when you try to print a user.”“”
        return “%s %s (%s)” % (self.first.encode(“utf-8″), self.last.encode(“utf-8″), self.username.encode(“utf-8″))
   
    def __repr__(self):
        “”” This method is called when you try to print a representation of a user.”“”
        return “” % str(self)   

class Company(User):
    using_options (tablename=“Users”,inheritance=“multi”)   
    logo    = Field(String(32)) # we store only the filename here.
    name    = Field(Unicode(64))
    website = Field(Unicode(64))

Things are looking pretty good so far ! Now let’s write a simple registration form.

The mako template

Let’s begin with the mako template :

<h2>Register to our great website !</h2>
<form action="${h.url_for(action=’processRegistrationForm’,controller=’registration’)}">
${c.form}
<input type="submit" value="go" />
</form>

Very simple code. c.form will contain the html code of the form, generated by formalchemy.

On to the controller action.

The controller

We’ll create a controller named registration for this purpose. Don’t forget to activate your virutal environement.

$(PYLONSENV) paster controller registration

Now let’s edit controller/registration.py

import logging

from pylons import request, response, session, tmpl_context as c
from pylons.controllers.util import abort, redirect_to
from myapp.lib.base import BaseController, render
# These imports up there are generated for you by paster
# Now you’ll need two other imports to generate the form : FieldSet and your model’s Person

# This is a customized FieldSet that will render itself following the template that you’ll find
# in templates/forms/fieldset.mako.
# I have edited that template to use semantic html elements like dl, dt and dd instead of bare div elements.
from myapp.forms import FieldSet

# This represents a form field, we will use this to create a password check form field.
from formalchemy import Field

from myapp.model import Person

log = logging.getLogger(__name__)

class RegistrationController(BaseController):

    def personRegistrationForm(self):
        “”
        Call this action in your mako templates to render the registration form
        “
“”
        fs = self.getPersonFieldSet()       
        c.form = fs.render()
        return render(“/pages/registration.mako”)

    def processRegistrationForm(self):
        “”
        Put this method in your registration’s form’s action attribute.
        <form action=”
/registration/processRegistrationForm“>
        or use
        <form action=”
${h.url_for(controller=‘registration’,action=‘processRegistrationForm’)}“>
        “
“”
        fs      = self.getPersonFieldSet()
        # This will populate the FieldSet with values found in request.params
        fs.data = request.params
        # This will populate the Person instance that FieldSet stores as an attribute named "model"
        fs.sync()
        # Here’s your object :) !
        user = fs.model
        # now do whatever you want with it. For this example, we’ll just return a simple message
        # saying that everything was ok.
        return “User (%s) successfully created” % str(user) # This will call user.__str__

    def getPersonFieldSet(self):
        “”
        Return the fieldset corresponding to a Person
        “
“”
        # Just pass in the class, not a specific instance
        fs = FieldSet(Person)
        # Now let’s add a password check field.
        # name is the attribute name that will be generated
        #
        # By default, formalchemy will append the class name as a prefix to every field name.
        # This is to prevent confusion when two classes may have the same name for one of their attribute.
        pwdcheck= Field(name=“pwdcheck”,type=Unicode)
        # We’ll add it to the FieldSet
        fs.append(pwdcheck)
        # Now we’ll customize the generated HTML with the conigure method.
        # the include argument tells FieldSet objects what fields you actually want to render.
        # A FieldSet consists of a group of Fields. Each field is accessed as an attribute of the FieldSet
        # it’s contained within. The attribute name of that field is the same as the Field name you used
        # in your class declaration.
        fs.configure(include=[fs.first,fs.last,fs.username,fs.city,fs.adresse,fs.email,
                              # Now here we’ll tell fs.password to render itself as a password field
                              # instead of a usual text field. This will return the modified field.
                              fs.password.password(),
                              # Now we’ll configure the pwdcheck field to have a meaningful and helpful label
                              fs.pwdcheck.password().label(“Confirm your password”)])
        return fs

That’s it :) you’re ready to go ! This is actually not too much code, let’s strip the comments to see how much code we needed.

Let’s recap’

the model code, model/__init__.py

from elixir import Entity, Field, Unicode,ManyToOne,OneToMany
import datetime

class User(Entity):
    using_options (tablename=“Users”,inheritance=“multi”)
    # I like powers of 2
    username      = Field(Unicode(32))
    email         = Field(Unicode(64))
    password      = Field(Unicode(32))
    phone         = Field(Unicode(16))
    address       = Field(Unicode(64))
    city          = ManyToOne(“City”)
    creation_date = Field(DateTime,default=datetime.datetime.now)

class Person(User):
    using_options (tablename=“Persons”,inheritance=“multi”)
    first_name = Field(Unicode(32))
    last_name  = Field(Unicode(32))   

    def __str__(self):
        “”” This method is called when you try to print a user.”“”
        return “%s %s (%s)” % (self.first.encode(“utf-8″), self.last.encode(“utf-8″), self.username.encode(“utf-8″))
   
    def __repr__(self):
        “”” This method is called when you try to print a representation of a user.”“”
        return “” % str(self)   

class Company(User):
    using_options (tablename=“Users”,inheritance=“multi”)   
    logo    = Field(String(32)) # we store only the filename here.
    name    = Field(Unicode(64))
    website = Field(Unicode(64))

the mako template

<h2>Register to our great website !</h2>
<form action="${h.url_for(action=’processRegistrationForm’,controller=’registration’)}">
${c.form}
<input type="submit" value="go" />
</form>

The Fieldset.mako template

The interresting part of the mako template for the form generation, templates/forms/fieldset.mako.
Here, I consider the form as a definition list, every label is a definition term, and the inputs are data definitions.

% for field in fieldset.render_fields.itervalues():
% if field.requires_label:
<dl>
<dt>
<label class="${field.is_required() and ‘field_req’ or ‘field_opt’}" for="${field.renderer.name}">${[field.label_text, fieldset.prettify(field.key)][int(field.label_text is None)]|h}</label>
</dt>
<dd>
${field.render()|n}
</dd>
% if ‘instructions’ in field.metadata:
<span class="instructions">${field.metadata[’instructions’]}</span>
% endif
% for error in field.errors:
<span class="field_error">${_(error)}</span>
% endfor
</dl>

The controller

The controller’s code, controller/registration.py. This is where formalchemy comes into play.

import logging
from pylons import request, response, session, tmpl_context as c
from pylons.controllers.util import abort, redirect_to
from formalchemy import Field
from myapp.model import Person
from myapp.forms import FieldSet
log = logging.getLogger(__name__)

class RegistrationController(BaseController):

    def personRegistrationForm(self):
        “”
        Call this action in your mako templates to render the registration form
        “
“”
        fs = self.getPersonFieldSet()       
        c.form = fs.render()
        return render(“/pages/registration.mako”)

    def processRegistrationForm(self):
        “”
        Put this method in your registration’s form’s action attribute.
        “
“”
        fs      = self.getPersonFieldSet()
        fs.data = request.params
        fs.sync()
        user = fs.model
        return “User (%s) successfully created” % str(user) # This will call user.__str__

    def getPersonFieldSet(self):
        “”
        Return the fieldset corresponding to a Person
        “
“”
        fs = FieldSet(Person)
        pwdcheck= Field(name=“pwdcheck”,type=Unicode)
        fs.append(pwdcheck)
        fs.configure(include=[fs.first,fs.last,fs.username,fs.city,fs.adresse,fs.email,
                              fs.password.password(),
                              fs.pwdcheck.password().label(“Confirm your password”)])
        return fs

12 Responses to “A simple FormAlchemy tutorial”

  1. The Matrix Says:

    Je voulais me mettre à Python depuis un moment, mais faute de temps j’ai pas pu avancé !!! as tu une bonne recette ou un “très” bon site pour ça ? il faut savoir que j’ai commencé avec l’excellent livre “Apprendre à programmer avec Python” de Gérard Swinnen. Je pense ce qu’il me faut c’est des tutoriaux pour les nulssss coz je ne suis pas informaticien (rappel toi je suis une matrice) :-(

    Merci pour tes visites sur mon blog

    The Matrix

  2. yassinechaouche Says:

    Hello,

    J’ai aussi commencer avec Swinnen, qui est pour moi un des meilleurs bouquins sur python jamais écrit. Next step, le tutoriel en ligne python sur le site de la documentation officielle http://docs.python.org/tutorial/index.html. Hasard ou pas, le document viens d’être mis à jour aujourd’hui même. Avec ces deux éléments tu es déjà bien parti. Si tu veux aller plus loin, je te conseil le classique dive into python de Pilgrim http://diveintopython.org/, je crois qu’il y a une traduction française pour ce bouquin (ici http://diveintopython.adrahon.org/).

    A partir de là tu peux jouer comme tu veux. D’ailleurs, le jour où tu te sentiras prêt, tu pourras défier le python challenge (http://pythonchallenge.com). Je pense que je suis bloqué au niveau 4. Attention ça demande beaucoup plus que des aptitudes en python, il faut être capable de résoudre des puzzles pas évidents.

    Je maintiens une liste de pointeurs python sur mon xmarks que je partage à cette adresse : http://share.xmarks.com/folder/bookmarks/kHC3RMAOQD

    Voici le flux RSS pour être au courant des nouveaux ajouts : http://share.xmarks.com/folder/rss/kHC3RMAOQD

    Cheers.

  3. The Matrix Says:

    merci pour ta réponse ainsi que pour les liens. Néanmoins ce qui me manque le plus c’est le temps :-(( aussi car je fais tout autre chose que l’informatique ou la programmation !!!

    un très bon site http://pythonchallenge.com à l’image de euleur project.

    Ah, oui je suis bloqué au niv 2, je t’ai dis je ne suis pas très doué

    Take care (et’hala)

  4. yassinechaouche Says:

    Si tu n’as pas beaucoup de temps n’en perds pas avec les langages de programmation génériques, prends plutôt des environnement de programmation spécialisés de haut niveau. Si tu veux bien t’amuser très rapidement regardes du coté de NetLogo que j’ai personnellement essayé (voir http://sites.google.com/site/yacinechaouche/works), ou encore E-Toys dont j’ai vu des vidéos assez marantes.

    Ça me fait rappeler qu’il y a un article de Kindi à ce sujet, il est à cette adresse si tu veux le consulter : http://elkindi.wordpress.com/2009/02/20/open-source-programming-languages-for-kids/. Ces environnements, à la base destinés aux enfants, sont un très bon point de départ pour les non informaticiens.

    Mais il y en a encore pleins d’autres comme Alice, Agent Sheet etc.

    Cheers

  5. The Matrix Says:

    Merci pour tes conseils sauf que j’ai fais un peu (très peu) de Fortran, Delphi, LabView mais très très peu càd j’ai juste touché à ces langages bien longtemps et depuis quelques mois j’ai découvert Python et là c’est le coud de foudre ne me demande pas pour quoi même moi je ne sais pas! J’aimerai bien pouvoir le maitriser, notamment pour l’utiliser dans mon domaine scientifique (Za3ma). Mais bon jusqu’à maintenant je l’avait utilisé que pour résoudre de petites énigmes mathématiques en voici une problème 28 Euler project:

    “”"”"”"”"”"”"”"”"”"”"”"”"”"”"”"”"”"”"”"”"”"”"”"”"”"”"”
    Starting with the number 1 and moving to the right in a clockwise direction a 5 by 5 spiral is formed as follows:

    21 22 23 24 25
    20 7 8 9 10
    19 6 1 2 11
    18 5 4 3 12
    17 16 15 14 13

    It can be verified that the sum of the numbers on the diagonals is 101.

    What is the sum of the numbers on the diagonals in a 1001 by 1001 spiral formed in the same way?
    “”"”"”"”"”"”"”"”"”"”"”"”"”"”"”"”"”"”"”"”"”"”"”"”"
    j’ai passé plus d’une journée pour extraire une lois et après la programmer, c’était assez stimulant pour mais neurones gelés depuis longtemps, la surprise c’est que après résolution du problème j’ai vus les solutions des autres et ben “une seule ligne dans l’algo” .’-(( .

    Merci encore

  6. yassinechaouche Says:

    Moi aussi ça m’a amusé pendant 3 jours, je crois que j’y ai passé en tout et pour tout 6H à 7H sur 3 journées, dont 1h30 à peu près à chercher et tester des packages python pour la programmation cellulaire…

    Bon voici un brouillon, je ferais peut être un post distinct pour décrire un peu plus ma démarche.

    En attendant, moi j’ai comme résultat: 669171001
    et la formule du calcul : 2(d-1)² + d + 1, où d représente la dimension de la grille.
    Ensuite, il faut faire une somme de cette formule f:
    sum(f) pour f allant de 3 à d, en sautant un chiffre à chaque fois (3,5,7,9…d)

    Voici le code python : http://pastebin.com/aukJ6Y4A

    Et voici mon brouillon : http://pastebin.com/SubKXK8b

  7. The Matrix Says:

    Ah cela remonte à quelques mois, et en fait j’ai essayé d’extraire l’algo la nuit…sans réussite. Le lendemain matin dans une Kahwa j’ai demandé un café et avant qu’il arrive j’ai sorti le papier de la nuit et j’ai recommencé à gribouiller, surprise avant que le café arrive ….la solution est là ;-) c’est magique non. J’essayerai de retrouver les photos des mais brouillons car à cette époque je voulais écrire un billet sur çà mais bon.
    Je te mettrai ma solution si je la trouve ce soir

    PS: J’ai utilisé Python juste pour exécuter l’algo, je ne suis pas encore très pro comme toi

  8. The Matrix Says:

    Voilà ma solution, mon utilisation de python est très basique

    http://pastebin.com/zJbJUCcQ

  9. yassinechaouche Says:

    Je décris la résolution du problème ici :

    http://readwritecode.com/blog/2010/03/17/eulers-project-problem-28

  10. The Matrix Says:

    Ohh j’ai rien compris à tes codes ;-). En fait moi mon but c’était d’extraire une loi mathématique, autrement dis une suite (sur le papier), après Python c’est juste un outil de programmation.
    Je vois que tu es un expert en python ;-D

  11. yassinechaouche Says:

    J’ai utilisé python pour dessiner automatiquement des grilles spirales, au lieu de les faire à la main.

    A partir de là, j’ai observé ces grilles et j’ai essayé de voir si les chiffres sur les diagonales suivent une règle donnée (ou pattern), et j’en ai trouvé une pour chaque diagonale :

    d² pour la diagonal partant du centre vers le coin Haut Droit
    (d-1)²+1 pour la diagonal partant du centre vers le coin Bas Gauche
    d*(d-1)+1 sur la diagonal centre -> Haut Gauche
    (d-1)*(d-2)+1 sur la diagonale centre -> Bas Droit.

    J’ai aussi remarqué que la somme des deux coins à droite est égale à la somme des deux coins à gauche.

    Ensuite, il fallait écrire une petite fonction qui calcule la somme de ces coins pour n’importe quelle dimension en utilisant ces règles empiriques, vérifiées pour d = 5, et voilà.

    La formule de calcule que j’ai utilisée a une forme plutôt sympa, c’est : 2(x-1)² + x + 1. Il faut faire une somme de cette formule pour x allant de 3 à d, mais en sautant un chiffre à chaque fois.(3,5,7,9…d)

    Je n’ai pas encore regardé les autres solutions mais apparemment il y en a beaucoup. C’est plus intéressant de voir comment les autres se sont pris pour résoudre ce problème que de simplement trouver la solution et s’arrêter là.

  12. Proactol For Easy Weight Loss Says:

    Proactol For Easy Weight Loss…

    Proactol like a healthy way of making it easier to take charge of your easy weight loss and eating habits. …

Leave a Reply