Python para Desenvolvedores

2ª edição, revisada e ampliada

Capítulo 31: MVC


Model-view-controller (MVC) é uma arquitetura de software que divide a aplicação em três partes distintas: o modelo de dados da aplicação, a interface com o usuário e a lógica de controle.

O objetivo é obter um baixo acoplamento entre as três partes de forma que uma alteração em uma parte tenha pouco (ou nenhum) impacto nas outras partes.

Model View Controller

A criação da aplicação depende da definição de três componentes:

  • Modelo (model): encapsula os dados da aplicação e a lógica de domínio.
  • Visão (view): recupera dados do modelo e apresenta ao usuário.
  • Controlador (controller): recebe e reage a possíveis eventos, como interações com o usuário e requisita alterações no modelo e na visão.

Embora a arquitetura não determine formalmente a presença de um componente de persistência, fica implícito que este faz parte do componente modelo.

O uso mais comum para o modelo MVC é em aplicações Web baseadas em bancos de dados, que implementam as operações básicas chamadas CRUD (Create, Read, Update and Delete).

Existem vários frameworks para aumentar a produtividade na criação de aplicativos seguindo o MVC, com recursos como:

  • Scripts que automatizam as tarefas mais comuns de desenvolvimento.
  • Geração automática de código.
  • Uso de ORM.
  • Uso de CSS (Cascade Style Sheets).
  • Uso de AJAX (Asynchronous Javascript And XML).
  • Modelos de aplicações.
  • Uso de introspecção para obter informações sobre as estruturas de dados e gerar formulários com campos com as características correspondentes.
  • Diversas opções pré-configuradas com defaults adequados para a maioria das aplicações.

Uma das maiores vantagens oferecidas pelo MVC é que, ao separar a apresentação da lógica de aplicação, se torna mais fácil dividir as tarefas de desenvolvimento e de design da interface em uma equipe.

Exemplo:

In [ ]:
"""
Web com operações CRUD
"""

# CherryPy
import cherrypy

# CherryTemplate
import cherrytemplate

# SQLAlchemy
import sqlalchemy as sql

# Conecta ao bando
db = sql.create_engine('sqlite:///zoo.db')

# Acesso aos metadados
metadata = sql.MetaData(db)

try:
    # Carrega metadados da tabela
    zoo = sql.Table('zoo', metadata, autoload=True)

except:
    # Define a estrutura da tabela zoo
    zoo = sql.Table('zoo', metadata,
        sql.Column('id', sql.Integer, primary_key=True),
        sql.Column('nome', sql.String(100),
            unique=True, nullable=False),
        sql.Column('quantidade', sql.Integer, default=1),
        sql.Column('obs', sql.String(200), default='')
    )

    # Cria a tabela
    zoo.create()

# Os nomes das colunas
colunas = [col for col in zoo.columns.keys()]
colunas.remove('id')


class Root(object):
    """Raiz do site"""

    @cherrypy.expose
    def index(self, **args):
        """
        Lista os registros
        """

        msg = ''
        op = args.get('op')
        ident = int(args.get('ident', 0))
        novo = {}

        for coluna in colunas:
            novo[coluna] = args.get(coluna)

        if op == 'rem':

            # Remove dados
            rem = zoo.delete(zoo.c.id==ident)
            rem.execute()
            msg = 'registro removido.'

        elif op == 'add':

            novo = {}

            for coluna in colunas:
                novo[coluna] = args[coluna]

            try:
                # Insere dados
                ins = zoo.insert()
                ins.execute(novo)
                msg = 'registro adicionado.'

            except sql.exceptions.IntegrityError:
                msg = 'registro existe.'

        elif op == 'mod':

            novo = {}

            for coluna in colunas:
                novo[coluna] = args[coluna]

            try:
                # Modifica dados
                mod = zoo.update(zoo.c.id==ident)
                mod.execute(novo)
                msg = 'registro modificado.'

            except sql.exceptions.IntegrityError:
                msg = 'registro existe.'

        # Seleciona dados
        sel = zoo.select(order_by=zoo.c.nome)
        rec = sel.execute()

        # Gera a página principal a partir do modelo "index.html"
        return cherrytemplate.renderTemplate(file='index.html',
            outputEncoding='utf-8')

    @cherrypy.expose
    def add(self):
        """
        Cadastra novos registros
        """

        # Gera a página de registro novo a partir do modelo "add.html"
        return cherrytemplate.renderTemplate(file='add.html',
            outputEncoding='utf-8')

    @cherrypy.expose
    def rem(self, ident):
        """
        Confirma a remoção de registros
        """

        # Seleciona o registro
        sel = zoo.select(zoo.c.id==ident)
        rec = sel.execute()
        res = rec.fetchone()

        # Gera a página de confirmar exclusão a partir do modelo "rem.html"
        return cherrytemplate.renderTemplate(file='rem.html',
            outputEncoding='utf-8')

    @cherrypy.expose
    def mod(self, ident):
        """
        Modifica registros
        """

        # Seleciona o registro
        sel = zoo.select(zoo.c.id==ident)
        rec = sel.execute()
        res = rec.fetchone()

        # Gera a página de alteração de registro a partir do modelo "mod.html"
        return cherrytemplate.renderTemplate(file='mod.html',
            outputEncoding='utf-8')


# Inicia o servidor na porta 8080
cherrypy.quickstart(Root())

Modelo index.html (página principal):

In [ ]:
<py-include="header.html">
<table>
<tr>
<th></th>
<py-for="coluna in colunas">
    <th><py-eval="coluna"></th>
</py-for>
<th></th>
<th></th>
</tr>
<py-for="i, campos in enumerate(rec.fetchall())">
    <tr>
    <th><py-eval="unicode(i + 1)"></th>
    <py-for="coluna in colunas">
        <td><py-eval="unicode(campos[coluna])"></td>
    </py-for>
    <td>
    <a href="/mod?ident=<py-eval="unicode(campos['id'])">">modificar</a>
    </td><td>
    <a href="/rem?ident=<py-eval="unicode(campos['id'])">">remover</a>
    </td>
    </tr>
</py-for>
</table>
<br />
<form action="/add" method="post">
    <input type="submit" value=" adicionar " />
</form>
<p>
<py-eval="msg">
</p>
<py-include="footer.html">

Modelo add.html (página de formulário para novos registros):

In [ ]:
<py-include="header.html">
<form action="/?op=add" method="post">
    <table>
    <py-for="coluna in colunas">
        <tr><td>
        <py-eval="coluna">
        </td><td>
        <input type="text" size="30" name="<py-eval="coluna">" />
        </td></tr>
    </py-for>
    </table>
<br />
<input type="submit" value=" salvar " />
</form>
<br />
[ <a href="/">voltar</a> ]
<py-include="footer.html">

Modelo mod.html (página de formulário para alteração de registros):

In [ ]:
<py-include="header.html">
<form action="/?op=mod&ident=<py-eval="unicode(res['id'])">" method="post">
    <table border="0">
    <py-for="coluna in colunas">
        <tr><th>
        <py-eval="coluna">
        </th><td>
        <input type="text" size="30" name="<py-eval="coluna">"
        value="<py-eval="unicode(res[coluna])">" />
        </td></tr>
    </py-for>
    </table>
<br />
<input type="submit" value=" salvar " />
</form>
<br />
[ <a href="/">voltar</a> ]
<py-include="footer.html">

Modelo rem.html (página que pede confirmação para remoção de registros):

In [ ]:
<py-include="header.html">
<table border="1">
<tr>
<py-for="coluna in colunas">
    <th><py-eval="coluna"></th>
</py-for>
</tr>
<tr>
<py-for="coluna in colunas">
    <td><py-eval="unicode(res[coluna])"></td>
</py-for>
</tr>
</table>
<br />
<form action="/?op=rem&ident=<py-eval="unicode(res['id'])">" method="post">
    <input type="submit" value=" remover " />
</form>
<br />
[ <a href="/">voltar</a> ]
<py-include="footer.html">

Modelo header.html (cabeçalho comum a todos os modelos):

In [ ]:
<html>
<head>
 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<title>Zoo</title>
<style type="text/css">
<!--
body {
    margin: 10;
    padding: 10;
    font: 80% Verdana, Lucida, sans-serif;
    color: #333366;
}
h1 {
    margin: 0;
    padding: 0;
    font: 200% Lucida, Verdana, sans-serif;
}
a {
    color: #436976;
    text-decoration: none;
}
a:hover {
    background: #c4cded;
    text-decoration: underline;
}
table {
    margin: 1em 0em 1em 0em;
    border-collapse: collapse;
    border-left: 1px solid #858ba1;
    border-bottom: 1px solid #858ba1;
    font: 90% Verdana, Lucida, sans-serif;
}
table th {
    padding: 0em 1em 0em 1em;
    border-top: 1px solid #858ba1;
    border-bottom: 1px solid #858ba1;
    border-right: 1px solid #858ba1;
    background: #c4cded;
    font-weight: normal;
}
table td {
    padding: 0em 1em 0em 1em;
    border-top: 1px solid #858ba1;
    border-right: 1px solid #858ba1;
    text-align: center;
}
form {
    margin: 0;
    border: none;
}
input {
    border: 1px solid #858ba1;
    background-color: #c4cded;
    vertical-align: middle;
}
-->
</style>
</head>
<body>
<h1>Zoo</h1>
<br />

Modelo footer.html (rodapé comum a todos os modelos):

In [ ]:
</body>
</html>

Página principal:

Página principal de Zoo

O framework MVC mais conhecido é o Ruby On Rails, que ajudou a popularizar o MVC entre os desenvolvedores.

Especificamente desenvolvidos em Python, existem os frameworks Django, TurboGears e web2py, entre outros.

In [1]:
 
Out[1]: