RockMy Events

How to manage Tags and Tag clouds with AppEngine and Django

posted by: stefano on: December 14, 2008 18:38
tags: AppEngine django tag

Hi.

Today I would like to show you how to model a tag cloud with AppEngine and Django.

Suppose that we have a relationship n-n between a Post model and a Tag model and we would like to add a list of tags when creating a Post and to be able to modify that list when updating the Post.

Finally, we would like to display a tag cloud in the homepage by highlighting the most used tags.

Let’s start from the Tag model:

# myapp/models.py

class Tag(db.Model):
    """
    A descriptive tag to be applied to a Post.
    
    label:: 
        the Tag label
    slug:: 
        the Tag slug to be used in urls
    post_count:: 
        the sum of all post that have been tagged with this tag
    """
    label = db.StringProperty(required=True)
    slug = db.StringProperty()
    post_count = db.IntegerProperty(default=1)

    def put(self):
        try:
            self.key()
        except NotSavedError:
            self.slug = slugify(self.label)
        super(Tag, self).put()

    @permalink
    def get_absolute_url(self):
        return ('myapp.views.posts.posts_by_tag', (), {'tag_label': self.slug})

    def __str__(self):
        return self.label

and then the Post model:

# myapp/models.py

class Post(db.Model):
    """
    A post of code in some Language.
    
    title:: 
        the title of the Post
    slug:: 
        the post slug to be used in urls
    body:: 
        the body of the Post
    tags:: 
        the list of tag labels associated to this Post. Useful to show post's tags without loading them from DataStore. 
    """

    title = db.StringProperty(required=True)
    slug = db.StringProperty(required=False)
    body = db.TextProperty()
    tags = db.StringListProperty()

    def put(self):
        try:
            self.key()
        except NotSavedError:
            self.slug = slugify(self.title)
            
        super(Post, self).put()

    def __str__(self):
        return self.title

    @permalink
    def get_absolute_url(self):
        return ('myapp.views.posts.post_detail', (), { 'post_id': str(self.key().id()) + "-" + self.slug})

    def set_tags_from_list(self, new_tags):
        """        
        deletes the current tag list from the Post and creates a new one.
        creates a new Tag if not yet in DataStore or increase/decrease it's counter if necessary. 
        """
        old_tags = self.tags[:] # copy the list of tag
        self.tags = [] # empty the list of tag associated to current post
        tags_to_set = [] # the list of tag to associate to current post

        same_tag_list = [item for item in old_tags if item in new_tags] # the list of tags that are the same after editing the post
        removed_tag_list = [item for item in old_tags if item not in new_tags] # the list of tags that have been removed after editing the post
        for new_tag in new_tags:
            new_tag = new_tag.strip()
            tag = Tag.get_by_key_name(new_tag)
            if tag is None:
                tags_to_set = Tag(key_name=new_tag, label=new_tag)
                tags_to_set.put()
                self.tags.append(new_tag)
            else:
                self.tags.append(new_tag)
                self.put()
                if new_tag not in same_tag_list:
                    tag.post_count += 1
                tag.put()

        for tag_to_remove in removed_tag_list:
            tag = Tag.get_by_key_name(tag_to_remove.strip())
            tag.post_count -= 1
            tag.put()


    def set_tags_from_string(self, tag_labels):
        """
        gets the list of tags (comma separated) to be associated to current post
        """
        new_tags = string.split(tag_labels, ',')
        self.set_tags_from_list(new_tags)

As you can see, each post is able to manage by itself its associated tags, by creating a new one if necessary or updating the counter of an existent one.

Let’s write views code for creating/editing a post.

# myapp/views/posts.py

@login_required
def add_post(request):
    """
    Allows a user to add a Post to the database.

    Context::
        form
            The form to add the Post.

    Template::
        myapp/posts/add_post_form.html

    """
    if request.method == 'POST':
        form = forms.AddPostForm(request.POST)
        if form.is_valid():

            new_post = Post(title=form.cleaned_data['title'],
                                  body=form.cleaned_data['body'])
            new_post.set_tags_from_string(form.cleaned_data['tags'])
            new_post.put()
            return HttpResponseRedirect(new_post.get_absolute_url())
    else:
        form = forms.AddPostForm()
    return render_to_response('myapp/posts/add_post_form.html',
                              { 'form': form },
                              context_instance=RequestContext(request))
                              
                              
@login_required
def edit_post(request, post_id):
    """
    Allows a user to edit an existing Post.

    Context::
        form
            The form to add the Post.

    Template::
        myapp/posts/edit_post_form.html

    """
    post = get_object_or_404(Post, id=int(post_id.split('-')[0]))
    if request.method == 'POST':
        form = forms.EditPostForm(data=request.POST)
        if form.is_valid():
            for field in ['body']:
                setattr(post, field, form.cleaned_data[field])
            post.set_tags_from_string(form.cleaned_data['tags'])
            post.put()
            return HttpResponseRedirect(post.get_absolute_url())
    else:
        form = forms.EditPostForm(instance=post)
    return render_to_response('myapp/posts/edit_post_form.html',
                              { 'form': form,
                                'original': post },
                              context_instance=RequestContext(request))

As you can see, both in add and edit post we call the set_tags_from_string method by passing the tag string that comes from the Form. Infact, in the AddPostForm or EditPostForm the tags field is defined as tags = forms.CharField(max_length=255, widget=forms.TextInput()).

Now we have all required code to handle taggings in our application.

We can easly show post’s tag list in our template by calling:

<p>
{{post.title}} is tagged with: {% for tag in post.tags %}{{ tag }} &nbsp;{% endfor %}
</p>

Notice that we don’t make any DataStore lookup because we have stored all post’s tag labels in the tags = db.StringListProperty() property.

Let’s create a tag cloud that takes as input the total number of tags that we want to display in the page:

# myapp/templatetags/tags.py

from django.template import Library, Node
from myapp.models import Tag
from google.appengine.ext import db

register = Library()

class TagsCloudNode(Node):

    def __init__(self, num, context_var):
        self.context_var = context_var
        self.num = int(num)
        
    def sort_by_attr(self,seq,attr):
        intermed = [ (getattr(seq[i],attr), i, seq[i]) for i in xrange(len(seq)) ]
        intermed.sort()
        return [ tup[-1] for tup in intermed ]

    def gen_cloud(self, num):
        query=Tag.all().filter("post_count >", 0).order('-post_count') # only retrieve tags that have at least one post associated
        p = query.fetch(num)
        if p:
            max1=max([int(p_item.post_count) for p_item in p])

        for i in range(len(p)):
            size =int(round(int(p[i].post_count)*maxsize/max1))
            if size<minsize:
                size=minsize
            cloudsize =str(size) +"%"
            p[i].cloudsize=cloudsize
        return self.sort_by_attr(p, "label")

    def render(self, context):
        context[self.context_var] = self.gen_cloud(self.num)
        return ''


def get_tags_cloud(parser, token):

    """
    Returns the tag cloud.

    Example::
        {% get_tags_cloud 5 as tag_cloud %}

    """
    bits = token.contents.split()
    if len(bits) != 4:
        raise template.TemplateSyntaxError("'%s' tag takes exactly three arguments" % bits[0])
    if bits[2] != 'as':
        raise template.TemplateSyntaxError("second argument to '%s' tag must be 'as'" % bits[0])

    return TagsCloudNode(bits[1], bits[3])
get_tags_cloud= register.tag(get_tags_cloud)

And in our template we can call this templatetag using this code:

<div>
<h2>
  Tag Cloud
</h2>
{% load tags %}
{% get_tags_cloud 20 as tag_cloud %}
{% for tag in tag_cloud %}
<a href="{{ tag.get_absolute_url }}" style="font-size:{{ tag.cloudsize }}; text-align:left;"> {{ tag.label }}</a>
{% endfor %}
</div>

I hope you will find this tutorial useful for managing Tags in your AppEngine application.

I don’t know if this is the best way to follow, but it works for me.

Please send me feedbacks about this tutorial.

comments: 0

Django tag cloud

posted by: stefano on: November 25, 2008 09:40
tags: python django tag

Ciao a tutti.

In questo post vorrei mostrare uno snippet veramente utile per visualizzare una tag cloud in una applicazione Django.

Supponiamo di avere nella nostra applicazione una relazione n-n tra un modello Post e un modello Tag.

Vogliamio dunque visualizzare nel nostro layout tutta la lista dei tag associati ai vari post, evidenziando quelli maggiormente utilizzati.

Per prima cosa e’ necessario registrare un nuovo templatetag:

from django.template import Library, Node
from myapp.models import Tag, Post
import random


register = Library()

maxsize =180 # maximum size of the most popular tag
minsize = 75 # minimum size of the least popular tag


class LatestTagsNode(Node):
    def gen_clouds(self):
        tag_list=Tag.objects.all()
        if tag_list:
            max1=max([int(tag_item.posts.count()) for tag_item in tag_list])

        for i in range(tag_list.count()):
            size =int(round(int(tag_list[i].posts.count())*maxsize/max1))
            if size<minsize:
                size=minsize
            cloudsize =str(size) +"%"
            tag_list[i].cloudsize=cloudsize
        return tag_list

    def render(self, context):
        self.__init__()
        context['content_tagclouds'] = self.gen_clouds()
        return ''

def get_latest_cloudtag(parser, token):

    return LatestTagsNode()
get_latest_cloudtag= register.tag(get_latest_cloudtag)

A questo punto, nel nostro template, bastera’ richiamare il templatetag in questo modo:

<div>
  {% load tags %}
  {% get_latest_cloudtag %}
  {% for tag in content_tagclouds %}
    <  a href="{{ tag.get_absolute_url }}" 
         style="font-size:{{ tag.cloudsize }}; 
                text-align:left;"> {{ tag.name }}</a>
  {% endfor %}
</div>
comments: 1

RockMy Events e' online

posted by: stefano on: September 15, 2008 16:45
tags: RockMy Events web2.0

Ciao a tutti.

Finalmente e’ online la versione 1.0 di quello che fino a qualche tempo fa era conosciuto come Tonight Events.

Dalla settimana scorsa RockMy Events ha iniziato a fare parte delle applicazioni web 2.0 dedicate al mondo degli eventi e degli artisti.

Questa applicazione si configura come un’evoluzione del precedente Tonight Events: tutta l’interfaccia grafica e’ stata infatti ripresa, snellita e modernizzata; inoltre il workflow dell’applicazione e’ stato notevolmente semplificato.

Inoltre, sono stati implementati tutti i piu’ importanti miglioramenti presentati nello scorso post Storia di un rilascio 2, tra cui la localizzazione in inglese e italiano.

Una delle nuove features apportate riguarda inoltre l’integrazione con last.fm: ogni giorno il database di eventi viene aggiornato con eventi provenienti da last.fm e localizzati in tutto il mondo.

A questo punto non mi resta che invitarvi nuovamente a visitare RockMy Events e a contribuire alla sua crescita.

comments: 0

Storia di un rilascio 2

posted by: stefano on: July 25, 2008 19:13
tags: TonightEvents web2.0

E’ ormai passato un mese dal rilascio della versione beta di Tonight Events ed e’ tempo dei primi bilanci.

Dal punto di vista tecnico sono molto soddisfatto, sia per quanto riguarda l’hosting che per l’applicazione in se.
Il sito ha risposto pienamente alle mie aspettative e direi che tutte le funzionalita’ implementate hanno funzionato egregiamente senza dare errori.

Le stesse funzionalita’ messe a disposizione dal sito sono addirittura aumentate in questo mese: rispondendo ad alcune richieste degli utenti, ora e’ possibile notificare eventi in quattro modi diversi:

* un evento puo’ essere organizzato tra un artista ed un club esistenti nell’applicazione (funzionalita’ originale) * un evento puo’ essere creato da un artista (non e’ necessaria la presenza di un locale registrato nel sito) * un evento puo’ essere creato da un locale (non e’ necessaria la presenza di un artista registrato nel sito) * un evento puo’ essere creato da un utente registrato

In questo modo, penso di aver garantito agli utenti una vasta gamma di opzioni per segnalare eventi.

Una considerazione ora riguardante l’usabilita’ del sito: a breve penso migliorare ulteriormente questo aspetto rendendo piu’ semplici tutte le funzioni utente. Infatti, analizzando i feedback degli utenti, alcune parti non sono ancora molto chiare o comunque troppo articolate:

* la necessita’ di confermare l’evento per renderlo disponibile alle funzioni di ricerca atraverso la mail inviata dal sistema all’utente * chiarire che prima e’ necessario iscriversi come utente, e solo in seguito sara’ possibile creare un artista o live club.

Queste le principali critiche ricevute. Per far fronte alle segnalazioni ho intenzione di implementare le seguenti funzionalita’:

* meccanismo di inbox locale, utile per evitare l’invio di mail ad account esterni e usabile anche per un’eventuale messaggistica tra utenti. * durante la signup dare la possibilita’ all’utente di creare on fly il proprio artista o club * permettere anche ai guest di segnalare eventi e poi richiedere la login solo al momento del submit dell’evento * guidare l’utente nelle operazioni basilari mediante popup di help * implementare un meccanismo di localizzazione (inglese e italiano tanto per cominciare)

Una volta completati questi punti, penso che il sito potra’ essere pronto per essere rilasciato con versione 1.0…

comments: 0

Tonight Events

posted by: stefano on: June 15, 2008 15:08
tags: TonightEvents web2.0

Finalmente, dopo mesi di duro lavoro serale e festivo, sono felice di annunciare il rilascio di Tonight Events .

Questo piccolo social network e’ nato per dare la possibilita’ a tutti coloro che appartengono ad un gruppo (musicale, teatrale ecc..) di pubblicizzare la propria attivita’ ed i propri eventi. E’ infatti possibile iscrivere il proprio artista o essere aggiunto come membro di un artista gia’ creato da qualche amico.

Il sito e’ inoltre rivolto anche a tutti i gestori di locali: e’ infatti possibile registrare il proprio locale e concordare eventi con tutti gli artisti in modo semplice e sicuro..

Infine, Tonight Events e’ rivolto a tutti noi che vogliamo organizzare le nostre serate: da oggi sara’ possibile cercare tutti gli eventi che si svolgeranno in una determinata data e localita’ geografica. La novita’ significativa di questo sito e’ infatti l’utilizzo di google maps per localizzare artisti, locali ed eventi sul territorio.
Una semplice mascherina di ricerca permette infatti di trovare tutti gli eventi in una determinata localita’ e data, dando cosi’ all’utente la possibilita’ di fruttare tutta la potenza delle API di google maps per capire esattamente dove si svolgera’ l’evento.

Spero che questo sito possa essere apprezzato da tutti voi e che venga utilizzato e seguito da sempre piu’ persone e artisti.
Purtroppo io non ho le possibilita’ economiche per una pubblicita’ massiccia, pertanto mi affido al passaparola e a qualche post sui vari forum.

Detto questo, non mi resta che invitare tutti quanti a visitare Tonight Events e a contribuire fin da subito alla sua crescita.

nota tecnica:

* Tonight Events e’ stato realizzato interamente da me, utilizzando Rails 2.0.2 come web framework; jQuery e Prototype per la parte javascript; PostgreSQL come database relazionale; webfaction per lo shared hosting. * tutto lo sviluppo e’ stato affiancato da una massiccia attivita’ di testing unitario e funzionale, ottenendo un code coverage di oltre il 90%.
comments: 0

Rails performances

posted by: stefano on: May 29, 2008 11:36
tags: rails cache performace

Uno degli aspetti piu’ importanti riguardanti lo sviluppo di un’applicazione web riguarda sicuramente l’ottimizzazione delle sue prestazioni.

Ottimizzare le prestazioni significa rendere disponibili i risultati della richiesta nel minore tempo possibile.

Due sono gli aspetti cruciali che entrano in gioco ad ogni richiesta dell’utente:

* la computazione della richiesta lato backend, con accesso al database ed il recupero dei dati * il rendering della pagina di risposta

In questi giorni ho iniziato a tenere sotto controllo le performance della mia applicazione in via di sviluppo: non disponendo del grande budget necessario a garantirle un server dedicato, ho bisogno di ridurre al minimo le latenze, in modo da garantire dei tempi di risposta dignitosi in uno shared hosting con 120 MB di memoria.

Una delle prime azioni e’ stata ottimizzare le query necessarie a recuperare i dati da mostrare in pagina.
Molto spesso infatti capitava di eseguire nella action una find del tipo

@artist = Artist.find(params[:id])

e nella view ciclare

<% @artist.shows.each do |show| %>

per visualizzarne i concerti.

Tutto questo al costo di n+1 accessi al database.

La soluzione adottata consiste nell’*eager loading*, cioe’ recuperare con un’unica query sia l’artista che i suoi concerti:

@artist = Artist.find(params[:id], :include => [:shows])

Una seconda ottimizzazione e’ stata sostituire nelle pagine le occorrenze di

<%= @artist.shows.count %>

con

<%= @artist.shows.length %>

evitando cosi’ un’ulteriore accesso al database.

Con queste ottmizzazioni, sono riuscito a garantire una riduzione dei tempi di risposta del 40% circa.

Tutto questo non e’ pero’ abbastanza per rendere la mia applicazione performante e scalabile.
Nonostante i miglioramenti apportati, nel caso in cui piu’ utenti richiedono la stessa pagina, per ciascuno l’azione accedera’ al database per calcolare i medesimi risultati.
Per evitare questo spreco di risorse, e’ necessario utilizzare dei meccanismi di caching.

Rails mette a disposizione dello sviluppatore tre meccanismi di caching:

* page caching * action caching * fragment caching

Non mi dilungo con la descizione dettagliata di questi meccanismi, in quanto gia’ trattata da innumerevoli altri articoli, come:

Riassumendo la loro utilita’, si puo’ affermare che il page caching e’ sicuramente il piu’ performante, in quanto viene servita all’utente una pagina html statica senza dover passare attraverso il controller di Rails.
Questo meccanismo e’ indicato per servire pagine che rimangono prevalentemente invariate nel corso del tempo e servibili a tutti gli utenti indifferentemente.

L’ action caching e’ meno veloce della precedente, in quanto il risultato dell’azione viene cacheato, ma tutti i before e after filters vengono comunque invocati.
Questo tipo di caching e’ invitante quando si devono servire pagine ristrette ad utenti loggati o con un particolare ruolo.

Il terzo tipo di caching fornisce invece allo sviluppatore un grado di liberta’ maggiore, dandogli la possibilita’ di cacheare piccoli frammenti di view, a scapito pero’ delle prestazioni.

La mia applicazione, come ogni social network, presenta un layout personalizzato in base alla presenza di un utente autenticato o meno.

Questo aspetto in un primo momento mi ha fatto scartare il page caching (ovviamente) e l’action caching, facendomi concentrare sul fragment caching.
Purtroppo, il guadagno in prestazione del fragment caching e’ veramente irrisorio se la action deve comunque computare dei risultati necessari per la personalizzazione della pagina.

La soluzione adottata infine e’ stata l’utilizzo della conditional cache

Questo plugin permette di appendere al frammento di cache una stringa custom:


caches_action :show, :tag => :cache_tag

def cache_tag
if logged_in?
return ‘-’ + current_user.to_param
else
return ‘-guest’
end
end

#User.rb
def to_param
“#{id}-#{login.gsub(/[^a-z1-9]+/i, ‘-’)}”
end

In questo modo, sono riuscito a produrre frammenti di cache personalizzati per ogni utente e per visitatori guest.

Per intenderci, la action show di un artista (artistxx, id 1) produrra’ questi frammenti:

1-artistxx-guest.cache per tutti i visitatori non autenticati

1-artistxx-3-stefano.cache per lo user stefano con id 3

1-artistxx-4-pippo.cache per lo user pippo con id 4

Quindi, la prima volta che un utente visualizzara’ la pagina show dell’artista artistxx la action verra’ computata interamente, per tutte le successive verra’ usata la cache.

Un approccio di questo tipo ha comportato un grande lavoro per gestire lo sweeping (a suon di espressioni regolari expire_fragment(%r{})) in seguito a create, update o destroy dei vari modelli.

L’utilizzo di una cache personalizzata di queto tipo mi ha permesso di abbattere i costi di computazione delle action.

Il mio unico dubbio deriva dalla proliferazione di frammenti di cache sul filesystem: non vorrei che a lungo andare l’accesso al filesystem potesse diventare un collo di bottiglia.
Purtroppo, per il momento non posso dare una risposta valida a questo interrogativo, dal momento che l’applicazione e’ ancora in sviluppo.

Dal mio punto di vista, mi pare di aver comunque scelto l’approccio migliore…

comments: 0

Rails e transazioni

posted by: stefano on: April 13, 2008 12:52
tags: rails

In questi giorni ho toccato con mano la gestione delle transazioni in Rails.

Il mio problema consisteva nel gestire in un’unica transazione la crezione di due modelli diversi ed indipendenti l’uno dall’altro.

Ogni oggetto ActiveRecord e’ provvisto di un metodo transaction, in grado di gestire in blocco operazioni di scrittura sullo stesso o su oggetti collegati da relazioni tipo has_one, has_many ecc. Quindi, nel mio caso, creare due oggetti non relazionati significava aprire due transazioni separate.

Questo aspetto di Rails mi ha molto stupito e sinceramente non capisco la scelta di THH di gestire le transazioni in questo modo.
A mio parere, una scelta piu’ felice e’ stata presa dal team di Django, dando la possibilita’ di gestire le transazioni alla Rails o relative all’intera richiesta HTTP, attraverso la libreria TransactionMiddleware. Questa libreria permette infatti di aprire una transazione con l’inizio di una richiesta HTTP e di committare o di eseguire il rollback in base al successo o no della response

Tornando al mio problema, a nulla sono valsi i tentativi di risolvere la transazione usando:

begin 
  ActiveRecord::Base.transaction do
    @first_model = FirstModel.new(params[:first_model])
    @first_model.save!
    @second_model = SecondModel.new(:user => current_user, :first_model => @first_model)
    @second_model.save!  
  end
rescue Exception => @ex
 # handle the exception
end

oppure usando transazioni innestate:

begin
  FirstModel.transaction do
    SecondModel.transaction do
      @first_model = FirstModel.new(params[:first_model])
      @first_model.save!
      @second_model = SecondModel.new(:user => current_user, :first_model => @first_model)
      @second_model.save!  
    end
  end
rescue Exception => @ex
 # handle the exception
end

In entrambi i casi, se la creazione di SecondModel falliva, per la prima transazione relativa al FirstModel non veniva eseguito il rollback, e i dati venivano salvati comunque sul database.

La soluzione al problema e’ stata incapsulare l’intero motodo del controller in un around filter, aprendo una transazione prima dell’esecuzione del metodo e chiudendola all’uscita dello stesso.

Questo il codice:

#application.rb
prepend_around_filter :transaction_filter

def transaction_filter
  ActiveRecord::Base.transaction { |*block_args| yield(*block_args) if block_given? }
end

Un’alternativa potrebbe essere l’utilizzo del plugin Super Transaction

comments: 0

Frameworks Javascript

posted by: stefano on: March 31, 2008 12:19
tags: javascript

Quanto un framework javascript contribuisce al successo di una applicazione web?

Mi sono posto questa domanda qualche giorno fa, mentre stavo scribacchiando un po’ di codice per un social network Rails che ho iniziato a sviluppare.

Il problema si e’ posto non appena ho iniziato ad abbozzare lo studio dell’interfaccia. Per dare un aspetto moderno all’applicazione, il design della pagina doveva contenere:

* tab panels * modal windows * splash screens * resize dinamico di elementi DOM

( Come e’ noto, Rails fornisce prototype come framework javascript. Questa libreria puo’ essere o non essere utilizzata nel proprio progetto, a patto di rinunciare alle magie degli helpers come form_remote_tag o dei file rjs.
Sinceramente non mi sento di approvare la scelta del core team di Rails di inculdere una libreria javascript di default. Penso che un framework dovrebbe essere javascript agnostic, in modo da garantire allo sviluppatore la scelta della libreria preferita. )

Questa digressione per giungere infine alla domanda di partenza: puo’ un framework javascript determinare il successo di un’applicazione web?

Secondo me si.

Le moderne applicazioni web 2.0 necessitano di interfacce sempre piu’ avanzate, intuitive, veloci e usabili.
Non e’ piu’ possibile al giorno d’oggi pensare di basare la propria interfaccia solo utilizzando css.
Come detto sopra, diventa necessario introdurre tab panels, splash screens, gallerie dinamice di immagini ecc. L’uso di AJAX poi diventa la discriminante per l’interazione veloce dell’utente.

Ecco dunque l’utilita’ dei framework javascript: fornire allo sviluppatore tutti gli strumenti necessari e le API per manipolare e attraversare il DOM della pagina, gestire eventi, realizzare effetti ecc.

La scelta della libreria da utilizzare diventa un fattore importante ed elemento chiave da valutare gia’ durante la pianificazione del progetto.
Il design dell’interfaccia a questo punto puo’ essere infatti definito anche in base a cio’ che il framework javascript fornisce.

comments: 2

Storia di un rilascio

posted by: stefano on: March 17, 2008 21:23
tags: PHP CodeIgniter

Venerdi finalmente sono riuscito a mettere online il sito bruttiserramenti.it .

Le sorprese sono arrivate subito: il provider non supporta le pretty url di CodeIgniter (siteurl/index.php/controller/method).

Le conseguenze:

* tornare alla confusa sintassi siteurl/index.php?c=controller&m=method (per fortuna configurabile in CodeIgniter settando un flag nel file config) * rinunciare a tutti gli url helper, tipo echo site_url("controller/method/id"); * evitare di usare redirect('/controller/method/', 'refresh'); nei controller. * eliminare l’interfaccia di amministrazione JTabyBackend e riscrivere tutto a mano.

Per quanto riguarda la parte di amministrazione, il primo elemento da sviluppare era una libreria per una semplice autenticazione e gestione della sessione utente.
Spulciando un po’ su internet, ecco il risultato:


<?php  if (!defined('BASEPATH')) exit('No direct script access allowed');
 
 class SimpleAuth {
 
  var $CI;
  
  function SimpleAuth() {
    $this->CI =& get_instance();
    $this->CI->load->database();
    $this->CI->load->library('session');
  }
  
  /**
   * Attempt to login using the given condition
   *
   * Accepts an associative array as input, containing login condition
   * Example: $this->simpleauth->try_login(array('username'=>$username, 'password'=>dohash($password)))
   *
   * @access  public
   * @param array login conditions
   * @return  boolean
   */ 
  function try_login($condition = array()) {
    $this->CI->db->select('id');
    $query = $this->CI->db->getwhere('users', $condition, 1, 0);
    if ($query->num_rows != 1) {
      return FALSE;
    } else {
      $row = $query->row();
      $this->CI->session->set_userdata(array('user_id'=>$row->id));
      return TRUE;
    }
  }
  
  
  /**
   * Attempt to login using session stored information
   *
   * Example: $this->simpleauth->try_session_login()
   *
   * @access  public
   * @return  boolean
   */
  function try_session_login() {
    if ($this->CI->session->userdata('user_id')) {
      $query = $this->CI->db->query('SELECT COUNT(*) AS total FROM users WHERE id = ' . $this->CI->session->userdata('user_id'));
      $row = $query->row();
      if ($row->total != 1) {
        // Bad session - kill it
        $this->logout();
        return FALSE;
      } else {
        return TRUE;
      }
    } else {
      return FALSE;
    }
  }
  
  /**
   * Logs a user out
   *
   * Example: $this->simpleauth->logout()
   *
   * @access  public
   * @return  void
   */
  function logout() {
    $this->CI->session->set_userdata(array('user_id'=>FALSE));
  }
  
?>

Una volta implementato il meccanismo di login / logout, mettere in piedi un controller ad uso esclusivo di utenti loggati è stato abbastanza semplice.

Infine, per implementare il CRUD delle news è bastato sfruttare la struttura Model di CodeIgniter.

<?php

class News extends Model {

    var $id = '';
    var $title  = '';
    var $body = '';
    var $created  = '';

    function News() {
      // Call the Model constructor
      parent::Model();
    }
    
    function get($id) { 
  	  $query = $this->db->getwhere('news',array('id'=>$id)); 
      return $query->row_array();          
    } 

     
    function insert_entry() {     
      $data = array( 
             'title'=>$this->input->post('title'), 
             'body'=>$this->input->post('body'), 
             'created'=>date("Y-m-d H:m:s"), 
             ); 

      $this->db->insert('news', $data);
    }

    function update_entry() {
      $data = array( 
             'title'=>$this->input->post('title'), 
             'body'=>$this->input->post('body'), 
              );
      $this->db->where('id',$this->input->post('id')); 
      $this->db->update('news', $data);
    }
    
    function delete_entry() {
      $data = array('id'=>$this->input->post('id')); 
      $this->db->delete('news', $data);
    }
    
}

?>

comments: 0

Rails Plugins

posted by: stefano on: March 10, 2008 18:29
tags: rails plugin

Una delle feature piu’ interessanti di Rails e’ senza dubbio il meccanismo dei plugin.

L’utilizzo di plugin permette infatti di estendere la propria applicazione aggiungendo in modo quasi trasparente funzionalita’ nuove sviluppate dalla comunita’ intorno a Rails.

Ecco una lista dei plugin indispensabili che ogni web application dovrebbe avere:

GENERALE:

* RESTful authentication * Acts As Taggable On Steroids * Acts as Ferret * ACL System2 Ownership

BLOGGING:

* Acts As Blog * acts_as_textiled * WhiteList * acts_as_commentable

GEOCODING:

* GeoKit * YM4R/GM

ECOMMERCE:

* Active Merchant

TEMPLATING:

* Liquid
comments: 0
1 2