Fixures

Um fixture é definido como “uma peça de equipamento ou de mobiliário, que é fixa em posição num edifício ou veículo”. No nosso caso, um dispositivo elétrico é algo ligado à ação que processa um pedido HTTP, a fim de produzir uma resposta.

Ao processar qualquer solicitações HTTP existem algumas operações opcionais que pode desejar executar. Por exemplo parse o cookie para procurar informações sessão, confirmar uma transação de banco de dados, determinar o idioma preferido do cabeçalho HTTP e lookup internacionalização adequada, etc. Essas operações são opcionais. Algumas ações precisam deles e algumas ações não. Eles também podem depender um do outro. Por exemplo, se as sessões são armazenadas no banco de dados e nossa ação precisa dele, talvez seja necessário analisar o cookie de sessão a partir do cabeçalho, pegar uma conexão do pool de conexão do banco de dados, e - após a ação foi executada - salvar a sessão de volta no banco de dados se os dados foram alterados.

Fixtures PY4WEB fornecem um mecanismo para especificar o que uma ação necessidades para que py4web pode realizar as tarefas necessárias (e ignorar os não necessários) da maneira mais eficiente. Fixures tornar o código eficiente e reduzir a necessidade de código clichê.

Fixtures PY4WEB são semelhantes aos middleware WSGI e BottlePy plug-in, exceto que eles se aplicam a ações individuais, não para todos eles, e pode dependem uns dos outros.

PY4WEB vem com alguns jogos pré-definidos para as ações que precisam sessões, conexões de banco de dados, internacionalização, autenticação e templates. A sua utilização será explicado neste capítulo. O desenvolvedor também é livre para adicionar Fixtures, por exemplo, para lidar com uma terceira língua template do partido ou a lógica sessão de terceiros.

Importante sobre Fixures

Nos exemplos a seguir vamos explicar como aplicar fixtures individuais. Na prática fixtures podem ser aplicadas em grupos. Por exemplo:

preferred = action.uses(Session, Auth, T, Flash)

Então você pode aplicar todo o ao mesmo tempo com:

@action('index.html')
@preferred
def index():
    return dict()

Templates Py4web

PY4WEB, por padrão utiliza a linguagem de template yatl e fornece um dispositivo elétrico para ele.

from py4web import action
from py4web.core import Template

@action('index')
@action.uses(Template('index.html', delimiters='[[ ]]'))
def index():
    return dict(message="Hello world")

Nota: Este exemplo assume que você criou o aplicativo da App andaimes, para que o index.html template já é criado para você.

O objeto template é um dispositivo elétrico. Ele transforma o `` dict () “ retornado pela ação em uma string usando o arquivo template index.html`. Em um capítulo posterior iremos fornecer um exemplo de como definir um fixture personalizado para usar uma linguagem de template diferente, por exemplo Jinja2.

Tenha em conta que uma vez que o uso de templates é muito comum e uma vez que, muito provavelmente, cada ação usa um template diferente, nós fornecemos um pouco de açúcar sintático, e as duas linhas a seguir são equivalentes:

@action.uses('index.html')
@action.uses(Template('index.html', delimiters='[[ ]]')

Observe que arquivos de template py4web são armazenados em cache na memória RAM. O objecto py4web cache é descrito mais tarde.

Sessões

O objeto da sessão é também um dispositivo elétrico. Aqui está um exemplo típico de uso para implementar um contador.

from py4web import Session, action
session = Session(secret='my secret key')

@action('index')
@action.uses(session)
def index():
    counter = session.get('counter', -1)
    counter += 1
    session['counter'] = counter
    return "counter = %i" % counter

Observe que o objeto da sessão tem a mesma interface como um dicionário Python.

Por padrão, o objeto da sessão é armazenado em um cookie chamado, assinadas e criptografadas, utilizando o segredo fornecido. Se as mudanças secretas as sessões existentes são invalidados. Se o usuário muda de HTTP para HTTPS ou vice-versa, a sessão do usuário é invalidado. Sessão em cookies tem um pequeno limite de tamanho (4Kbytes depois de ser serializados e criptografados) para não colocar demasiada para eles.

Em py4web sessões são dicionários, mas eles são armazenados usando JSON (JWT especificamente), portanto, você só deve armazenar objetos que são JSON serializado. Se o objeto não é JSON serializado, ele vai ser serializado usando o `` operador __str__`` e algumas informações podem ser perdidas.

Por padrão sessões py4web nunca expiram (a menos que contenham informações de login, mas isso é outra história), mesmo se uma expiração pode ser definido. Outros parâmetros podem ser especificados, bem como:

session = Session(secret='my secret key',
                  expiration=3600,
                  algorithm='HS256',
                  storage=None,
                  same_site='Lax')
  • Aqui `` algorithm`` é o algoritmo a ser usado para a assinatura de token JWT.

  • `` Storage`` é um parâmetro que permite especificar um método alternativo de armazenamento de sessão (por exemplo, redis, ou base de dados).

  • `` Same_site`` é uma opção que impede CSRF ataques e é ativado por padrão. Você pode ler mais sobre ele aqui <https://www.owasp.org/index.php/SameSite> __.

Sessão em memcache

import memcache, time
conn = memcache.Client(['127.0.0.1:11211'], debug=0)
session = Session(storage=conn)

Observe que um segredo não é necessária quando o armazenamento de cookies no memcache porque neste caso o cookie contém apenas o UUID da sessão.

Sessão em Redis

import redis
conn = redis.Redis(host='localhost', port=6379)
conn.set = lambda k, v, e, cs=conn.set, ct=conn.ttl: (cs(k, v), e and ct(e))
session = Session(storage=conn)

Aviso: um objecto de armazenamento deve ter `` `` GET`` e métodos set`` e do método set` deve permitir especificar uma expiração. O objecto de ligação redis tem um método ttl` para especificar a expiração, remendo, portanto, que o macaco` método set` ter a assinatura esperada e funcionalidade.

Sessão no banco de dados

from py4web import Session, DAL
from py4web.utils.dbstore import DBStore
db = DAL('sqlite:memory')
session =  Session(storage=DBStore(db))

Um segredo não é necessária quando o armazenamento de cookies no banco de dados porque, neste caso, o cookie contém apenas o UUID da sessão.

Além disso, este é um caso em que o fixture uma (sessão) requer um outro suporte (db). Isso é feito automaticamente pelo py4web e os seguintes são equivalentes:

@action.uses(session)
@action.uses(db, session)

Sessão em qualquer lugar

Você pode facilmente armazenar sessões em qualquer lugar que você quer. Tudo que você precisa fazer é fornecer ao objeto `` Session`` um objeto `` storage`` com ambos os `` GET`` e `` métodos set``. Por exemplo, imagine que você deseja armazenar sessões no seu sistema de arquivos local:

import os
import json

class FSStorage:
   def __init__(self, folder):
       self.folder = folder
   def get(self, key):
       filename = os.path.join(self.folder, key)
       if os.path.exists(filename):
           with open(filename) as fp:
              return json.load(fp)
       return None
   def set(self, key, value, expiration=None):
       filename = os.path.join(self.folder, key)
       with open(filename, 'w') as fp:
           json.dump(value, fp)

session = Session(storage=FSStorage('/tmp/sessions'))

Deixamos-lhe como um exercício para implementar validade, limitar o número de arquivos por pasta usando subpastas, e implementar o bloqueio de arquivos. No entanto, nós não recomment armazenar sessões no sistema de arquivos: é ineficiente e não escala bem.

Tradutor

Aqui está um exemplo de uso:

from py4web import action, Translator
import os

T_FOLDER = os.path.join(os.path.dirname(__file__), 'translations')
T = Translator(T_FOLDER)

@action('index')
@action.uses(T)
def index(): return str(T('Hello world'))

A string ‘Olá mundo ` será traduzido com base no arquivo de internacionalização em ‘traduções’ especificados pasta que melhor corresponde ao HTTP `` aceitar-language`` cabeçalho.

Aqui `` Translator`` é uma classe py4web que se estende `` pluralize.Translator`` e também implementa a interface de `` Fixture``.

Podemos facilmente combinar vários Fixtures. Aqui, como exemplo, podemos tornar a acção com um contador que conta “visitas”.

from py4web import action, Session, Translator, DAL
from py4web.utils.dbstore import DBStore
import os
db = DAL('sqlite:memory')
session =  Session(storage=DBStore(db))
T_FOLDER = os.path.join(os.path.dirname(__file__), 'translations')
T = Translator(T_FOLDER)

@action('index')
@action.uses(session, T)
def index():
    counter = session.get('counter', -1)
    counter += 1
    session['counter'] = counter
    return str(T("You have been here {n} times").format(n=counter))

Agora crie o seguinte arquivo de tradução `` traduções / en.json``:

{"You have been here {n} times":
  {
    "0": "This your first time here",
    "1": "You have been here once before",
    "2": "You have been here twice before",
    "3": "You have been here {n} times",
    "6": "You have been here more than 5 times"
  }
}

Ao visitar este site com o navegador preferência de idioma definida para Inglês e recarregar várias vezes você receberá as seguintes mensagens:

This your first time here
You have been here once before
You have been here twice before
You have been here 3 times
You have been here 4 times
You have been here 5 times
You have been here more than 5 times

Agora tente criar um arquivo chamado `` traduções / it.json`` que contém:

{"You have been here {n} times":
  {
    "0": "Non ti ho mai visto prima",
    "1": "Ti ho gia' visto",
    "2": "Ti ho gia' visto 2 volte",
    "3": "Ti ho visto {n} volte",
    "6": "Ti ho visto piu' di 5 volte"
  }
}

e definir preferência seu navegador para Italiano.

O fixture flash

É comum querer exibir “alertas” para os suers. Aqui nos referimos a eles como messeges de flash. Há um pouco mais do que apenas exibir uma mensagem para o ponto de vista porque as mensagens flash podem ter estado que deve ser preservado após o redirecionamento. Também podem ser gerados lado servidor e do lado do cliente, pode haver apenas um no momento, eles podem ter um tipo, e eles devem ser descartável.

O auxiliar o Flash lida com o lado do servidor deles. Aqui está um exemplo:

from py4web import Flash

flash = Flash()

@action('index')
@action.uses(Flash)
def index():
    flash.set("Hello World", _class="info", sanitize=True)
    return dict()

e no template:

...
<div id="py4web-flash"></div>
...
<script src="js/utils.js"></script>
[[if globals().get('flash'):]]<script>utils.flash([[=XML(flash)]]);</script>[[pass]]

Ao definir o valor da mensagem do ajudante flash, uma variável flash é retornado pela ação e o gatilho os JS no template para injetar a mensagem na `` DIV # py4web-flash`` que você pode posicionar a sua conveniência . Também a classe opcional é aplicada ao HTML injetado.

Se uma página é redirecionada depois de um flash está definido, o flash é lembrado. Isto é conseguido por pedir o navegador para manter a mensagem temporariamente em um cookie de uma só vez. Depois de redirecionamento a mensagem é enviada de volta pelo navegador para o servidor e os conjuntos de servidor ele novamente automaticamente antes de retornar o conteúdo, a menos que seja substituído por um outro conjunto.

O cliente também pode definir / adicionar mensagens flash chamando:

utils.flash({'message': 'hello world', 'class': 'info'});

defaults py4web para uma classe de alerta chamado `` default`` ea maioria dos quadros CSS definir classes para alertas chamado `` success``, `` error``, `` warning``, `` default``, e `` info` `. No entanto, não há nada em py4web que hardcodes esses nomes. Você pode usar seus próprios nomes de classe.

O fixture DAL

Nós já usou o `` dispositivo elétrico DAL`` no contexto das sessões, mas talvez você queira ter acesso direto ao objeto DAL com a finalidade de acessar o banco de dados, e não apenas sessões.

PY4WEB, por padrão, usa o PyDAL (Python abstração de dados Layer) que está documentado em um capítulo posterior. Aqui está um exemplo, lembre-se de criar o `` databases`` pasta em seu projeto caso ele não existe:

from datetime import datetime
from py4web import action, request, DAL, Field
import os

DB_FOLDER = os.path.join(os.path.dirname(__file__), 'databases')
db = DAL('sqlite://storage.db', folder=DB_FOLDER, pool_size=1)
db.define_table('visit_log', Field('client_ip'), Field('timestamp', 'datetime'))
db.commit()

@action('index')
@action.uses(db)
def index():
    client_ip = request.environ.get('REMOTE_ADDR')
    db.visit_log.insert(client_ip=client_ip, timestamp=datetime.utcnow())
    return "Your visit was stored in database"

Observe que as define fixação de banco de dados (cria / recria tabelas) automaticamente quando começa py4web (e cada vez que recarrega este app) e pega uma conexão do pool de conexão em cada solicitação HTTP. Além disso, cada chamada para o índice `` () `` acção é envolvido numa transacção e que compromete `` on_success`` e reverte `` on_error``.

Advertências sobre Fixures

Desde fixtures são compartilhados por várias ações que você não tem permissão para alterar seu estado, porque não seria seguro para threads. Há uma exceção a esta regra. As ações podem alterar alguns atributos de campos de banco de dados:

from py4web import Field, action, request, DAL, Field
from py4web.utils.form import Form
import os

DB_FOLDER = os.path.join(os.path.dirname(__file__), 'databases')
db = DAL('sqlite://storage.db', folder=DB_FOLDER, pool_size=1)
db.define_table('thing', Field('name', writable=False))

@action('index')
@action.uses(db, 'generic.html')
def index():
    db.thing.name.writable = True
    form = Form(db.thing)
    return dict(form=form)
)

Nota thas este código só será capaz de exibir um formulário, para processá-lo depois de apresentar, necessidades código adicional para ser adicionado, como veremos mais tarde. Este exemplo supõe que você criou o aplicativo da App andaimes, de modo que um generic.html já é criado para você.

A `` readable``, `` writable``, `` default``, `` update``, e atributos `` `` require`` de db. {Tabela}. {Campo} `` são objectos especiais de classe `` ThreadSafeVariable`` definido a `` threadsafevariable`` módulo. Esses objetos são muito parecidos com Python rosca objetos locais, mas eles estão em todos os pedidos utilizando o valor fora da ação especificada inicializado-re. Isto significa que as ações podem mudar com segurança os valores desses atributos.

Fixtures personalizados

Um fixture é um objecto com a seguinte estrutura mínima:

from py4web import Fixture

class MyFixture(Fixture):
    def on_request(self): pass
    def on_success(self): pass
    def on_error(self): pass
    def transform(self, data): return data

se uma ação usa este equipamento:

@action('index')
@action.uses(MyFixture())
def index(): return 'hello world'

Em seguida, `` on_request () `` é garantida para ser chamado antes do índice `` () `` função é chamada. A `` on_success () `` é garantida para ser accionado se os `` índice () `` função retorna com sucesso ou aumentos `` HTTP`` ou executa um `` redirect``. A `` on_error () `` é garantida para ser chamado quando o índice `` () `` função levanta qualquer outra excepção para além `` HTTP``. A função `` transform`` é chamado para executar qualquer transformação desejada do valor devolvido pelo `` índice) `` função (.

Auth e Auth.user

`` Auth`` e `` auth.user`` são ambos os jogos. Eles dependem de `` session``. O papel do acesso é fornecer a ação com informações de autenticação. Ele é utilizado como segue:

from py4web import action, redirect, Session, DAL, URL
from py4web.utils.auth import Auth
import os

session = Session(secret='my secret key')
DB_FOLDER = os.path.join(os.path.dirname(__file__), 'databases')
db = DAL('sqlite://storage.db', folder=DB_FOLDER, pool_size=1)
auth = Auth(session, db)
auth.enable()

@action('index')
@action.uses(auth)
def index():
    user = auth.get_user() or redirect(URL('auth/login'))
    return 'Welcome %s' % user.get('first_name')

O construtor do objeto `` Auth`` define a tabela `` auth_user`` com os seguintes campos: nome de usuário, e-mail, senha, first_name, last_name, sso_id e action_token (os dois últimos são principalmente para uso interno).

`` Auth.enable () `` registadoras múltiplas acções incluindo `` {nomeaplic} / auth / login`` e que exige a presença de a `` auth.html`` molde e a `` auth`` componente valor fornecido pela o `` aplicativo _scaffold``.

O objeto `` auth`` é a fixure. Ele gerencia as informações do usuário. Ela expõe um único método:

auth.get_user()

que retorna um dicionário pitão contendo a informação do utilizador actualmente em sessão. Se o usuário não está conectado, ele retorna `` None``. O código do exemplo redireciona para a “auth / login A página se não houver usuário.

Desde essa verificação é muito comum, py4web fornece um fixture adicional `` auth.user``:

@action('index')
@action.uses(auth.user)
def index():
    user = auth.get_user()
    return 'Welcome %s' % user.get('first_name')

Este fixture redireciona automaticamente para a página login`` `` auth / se o usuário não está conectado. Depende de `` auth``, que depende `` db`` e `` session``.

A `` fixação Auth`` é baseado plug-in e suporta vários métodos de plug-in. Eles incluem oauth2 (Google, Facebook, Twitter), PAM, LDAP, e SMAL2.

Aqui está um exemplo de como usar o plugin Google oauth2:

from py4web.utils.auth_plugins.oauth2google import OAuth2Google
auth.register_plugin(OAuth2Google(
    client_id='...',
    client_secret='...',
    callback_url='auth/plugin/oauth2google/callback'))

A `` `` client_id`` e client_secret`` são fornecidos pelo Google. O URL de retorno é a opção padrão para py4web e deve ser na lista de autorizações com o Google. Todos os `` plugins Auth`` são objetos. Diferentes plugins são configurados de diferentes maneiras, mas eles são registrados usando `` auth.register_plugin (…) . Os exemplos são fornecidos no `` _scaffold / common.py.

Caching e Memoize

py4web fornece um cache no objeto ram que implementa a Última Recently Used (LRU) Algoritmo. Ele pode ser usado para armazenar em cache qualquer função através de um decorador:

import uuid
from py4web import Cache, action
cache = Cache(size=1000)

@action('hello/<name>')
@cache.memoize(expiration=60)
def hello(name):
    return "Hello %s your code is %s" % (name, uuid.uuid4())

Ele irá armazenar em cache (memoize) o valor de retorno da função `` hello``, como função da entrada `` name``, durante até 60 segundos. Ele irá armazenar em cache de 1000 valores mais usados ​​recentemente. Os dados são sempre armazenados na memória RAM.

O objeto de cache não é um dispositivo elétrico e não deve e não pode ser registrado usando o `` @ action.uses`` objeto, mas nós mencioná-lo aqui, porque alguns dos dispositivos elétricos de usar esse objeto internamente. Por exemplo, arquivos de template são armazenadas em cache na memória RAM para evitar acessar o sistema de arquivo de cada vez que um template precisa ser renderizado.

Decoradores de conveniência

A `` aplicação _scaffold``, no `` define common.py`` dois decoradores especiais conveniennce:

@unauthenticated
def index():
    return dict()

e

` `@Authenticated def index (): retorno Dict () `

Aplicam-se todos os decoradores abaixo, utilizar um template com o mesmo nome que a função (.html), e também registar-se uma via com o nome de acção seguido o número de argumentos da acção separadas por uma barra (/).

@unauthenticated não requer que o usuário seja identificado. @authenticated necessário que o usuário estar logado.

Se pode ser combinado com (e preceder) outro `` @ action.uses (…) ``, mas eles não devem ser combinados com `` @Action (…) ``, porque eles executar essa função automaticamente.