Warning: Creating default object from empty value in /membri/bancaldo/wp-content/plugins/paged-comments/paged-comments.php on line 31
bancaldo™ » python

Archivio

Posts Tagged ‘python’

django: standalone application

3 aprile 2017 Commenti chiusi

Supponiamo di dover creare una piccola applicazione (‘players’)
che inserisca in un database dei dati, ad esempio i nomi
dei calciatori della serie A.
Ci piace utilizzare django per la confidenza che abbiamo con il suo ORM,
ma non abbiamo voglia di sbatterci con tutto il meraviglioso e vastissimo
mondo di django, comprensivo di pattern Model-View-Template, Admin, browser…
Vogliamo invece usare le wx, ma non SQLAlchemy, o peewee.
Non resta che strutturare la nostra mini applicazione in questo modo:

C:\TMP\STANDALONEDJANGOEXAMPLE
|   main.py
|   manage.py
|   settings.py
|   __init__.py
|   
\---players
    |   controller.py
    |   model.py
    |   models.py
    |   view.py
    |   __init__.py
    |   
    \---migrations
            __init__.py

partendo dall’alto, analizziamo i singoli file.

‘main.py’ è il cuore della nostra applicazione django STAND-ALONE e contiene
i settaggi che ci permetteranno di utilizzare solo l’ORM di django:

import os

# qui importiamo il modulo settings
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")


import sys
# qui aggiungiamo il percorso della nostra app al path
sys.path.append('/tmp/standalonedjangoexample')
# qui aggiungiamo i virtualenv site-packages al sys.path
sys.path.append('/tmp/venv/Lib/site-packages')


from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()


if __name__ == '__main__':
    from players.controller import Controller
    c = Controller()

questo è il contenuto di ‘manage.py’, necessario per le migrations:

import os
import sys

sys.dont_write_bytecode = True

if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
    from django.core.management import execute_from_command_line
    execute_from_command_line(sys.argv)

poi abbiamo i ‘settings.py’:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
        'NAME': 'players.db',                      # Or path to database file if using sqlite3.
        'USER': '',                      # Not used with sqlite3.
        'PASSWORD': '',                  # Not used with sqlite3.
        'HOST': '',                      # Set to empty string for localhost. Not used with sqlite3.
        'PORT': '',                      # Set to empty string for default. Not used with sqlite3.
    }
}

INSTALLED_APPS = (
    'players',
    )

SECRET_KEY = 'mysecretkey'

Ora entriamo nel cuore della app, che prevede:
– i models che django utilizzerà con il suo ORM
– i file per uno spartano MVC

Questo spartano MVC prevede una file view.py che importa il modulo wx
per creare un’interfaccia di inserimento dati, un model.py per la gestione
dei dati e un controller che metta interagisca con essi tenendoli separati.

Il file models.py (django models)

from django.db import models


class Player(models.Model):
    name = models.CharField(max_length=32)

    def __unicode__(self):
        return self.name

il file view.py

import wx


class InfoMessage(wx.MessageDialog):
    """Simple message Dialog"""
    def __init__(self, parent, message):
        super(InfoMessage, self).__init__(parent, message, 'info', wx.OK |
                                          wx.ICON_EXCLAMATION)
        if self.ShowModal() == wx.ID_OK:
            self.Destroy()


class ViewNewPlayer(wx.Frame):
    def __init__(self, parent, controller, title):
        self.title = title
        super(ViewNewPlayer, self).__init__(parent=parent, title=title,)
                                         #style=STYLE)
        self.controller = controller
        self._build()
        self._bind_widgets()
        self.Centre()
        self.Show()

    def _build(self):
        self.panel = PanelNewPlayer(parent=self)
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.panel, 1, wx.EXPAND)
        self.SetSizer(sizer)
        self.SetSize((250, 100))

    def _bind_widgets(self):
        self.Bind(wx.EVT_BUTTON, self.on_quit, self.panel.btn_quit)
        self.Bind(wx.EVT_BUTTON, self.on_save, self.panel.btn_save)
        self.Bind(wx.EVT_TEXT, self.on_text_entry, self.panel.name)

    def show_message(self, message):
        InfoMessage(parent=self, message=message)

    def on_text_entry(self, event):
        entry = self.panel.name.GetValue()
        if len(entry) > 2:
            self.panel.btn_save.Enable()
        else:
            self.panel.btn_save.Disable()

    def on_save(self, event):
        name = self.panel.name.GetValue()
        self.controller.save_player(name)

    def on_quit(self, event):
        self.Destroy()


class PanelNewPlayer(wx.Panel):
    def __init__(self, parent):
        super(PanelNewPlayer, self).__init__(parent)
        # Attributes
        self.name = wx.TextCtrl(self)
        text_sizer = wx.FlexGridSizer(rows=2, cols=2, hgap=5, vgap=5)
        text_sizer.Add(wx.StaticText(self, label=''),
                       0, wx.ALIGN_CENTER_VERTICAL)
        text_sizer.AddGrowableCol(1)
        button_sizer = wx.StdDialogButtonSizer()
        self.btn_save = wx.Button(self, wx.ID_SAVE)
        self.btn_quit = wx.Button(self, wx.ID_CANCEL)
        # Buttons initialization
        self.btn_quit.SetDefault()
        self.btn_save.Disable()
        # Sizers
        button_sizer.AddButton(self.btn_save)
        button_sizer.AddButton(self.btn_quit)
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(text_sizer, 1, wx.EXPAND | wx.ALL, 5)
        sizer.Add(button_sizer, 0, wx.ALIGN_CENTER | wx.ALL, 5)
        button_sizer.Realize()
        self.SetBackgroundColour('LightGray')
        self.SetSizer(sizer)

il file model.py (MVC) che si interfaccerà a sua volta con i models di django:

from models import Player


class Model(object):

    @staticmethod
    def new_player(name):
        """new_player(name) -> player object"""
        player = Player.objects.create(name=name.upper())
        return player

    @staticmethod
    def get_player(name):
        """get_player(name) -> player object"""
        return Player.objects.filter(name=name.upper()).first()

    @staticmethod
    def get_players():
        """get_players() -> iterable"""
        return Player.objects.all()

il file controller.py

import wx
from model import Model
from view import ViewNewPlayer


class Controller(object):
    def __init__(self):
        app = wx.App()
        self.model = Model()
        self.view = ViewNewPlayer(parent=None, controller=self,
                                  title='New Player')
        app.MainLoop()

    def get_player(self, name):
        return self.model.get_player(name)

    def save_player(self, name):
        player_object = self.get_player(name)
        if player_object:
            self.view.show_message("Player already exist")
        else:
            new_player = self.model.new_player(name)
            self.view.show_message("New Player %s saved" % new_player.name)

Questo è quanto.
Ovviamente non esistono ancora le tabelle del database e provvediamo con i
comandi:

(venv) C:\tmp\standalonedjangoexample>python manage.py makemigrations
Migrations for 'players':
  players\migrations\0001_initial.py:
    - Create model Player

e successivamente:

(venv) C:\tmp\standalonedjangoexample>python manage.py migrate
Operations to perform:
  Apply all migrations: players
Running migrations:
  Applying players.0001_initial... OK

poi è possibile eseguire la nostra applicazione utilizzando il file
main.py.

Ovviamente la struttura dell’applicazione può essere fatta in mille modi
diversi, come pure il raggruppamento dei codici.
Lo stesso main.py può fungere da controller riducendo i livelli di
separazione, soprattutto qualora non si volesse utilizzare un
pattern simil-MVC.

Categorie:Django, python Tag: ,

django – RuntimeWarning: DateTimeField received a naive datetime

2 febbraio 2017 Commenti chiusi

Abbiamo un form

class ChangeForm(forms.Form):
    date = forms.DateField(label='date', initial=datetime.date.today)
    ...

e la view di competenza

def edit(request, item_id):
    myitem = get_object_or_404(AItem, pk=item_id)
    if request.method == "POST":
        form = ChangeForm(request.POST)
        if form.is_valid():
            myitem.date = form.cleaned_data['date']
            myitem.save()
            return redirect(...)
    else:
        form = ChangeForm(initial={'date': myitem.date})
    return render(request, 'atemplate.html',
                  {'form': form, 'myitem': myitem})

nel momento del salvataggio dei dati (POST) ottengo il warning in oggetto.

per risolvere il problema, tramite timezone di django, trasformiamo le date
da formato ‘naive’ a formato ‘aware’.
Nel mio caso la data è di tipo datetime.date per tento prima,
la trasformerò in formato datetime.datetime.

>>> import datetime
>>> from django.utils import timezone
>>> a_date = datetime.date.today()
>>> a_datetime = datetime.datetime.combine(a_date, datetime.time())
>>> a_datetime
datetime.datetime(2017, 2, 2, 0, 0)
>>> aware_date = timezone.make_aware(a_datetime, timezone.get_default_timezone())
>>> aware_date
datetime.datetime(2017, 2, 2, 0, 0, tzinfo=<UTC>)

oppure tramite il modulo pytz (installabile tramite pip):

>>> import pytz
>>> naive = datetime.datetime(2017, 2, 2, 12, 0, 0)
>>> aware = datetime.datetime(2017, 2, 2, 12, 0, 0, 0, pytz.UTC)
>>> aware == naive
Traceback (most recent call last):
  File "<console>", line 1, in <module>
TypeError: can't compare offset-naive and offset-aware datetimes
>>> aware == pytz.utc.localize(naive)
True

ora basta modificare la view con la gestione corretta delle date:

def edit(request, item_id):
    myitem = get_object_or_404(AItem, pk=item_id)
    if request.method == "POST":
        form = ChangeForm(request.POST)
        if form.is_valid():
            naive_date = form.cleaned_data['date']
            naive_datetime = datetime.datetime.combine(naive_date, datetime.time())
            myitem.date = timezone.make_aware(naive_datetime, 
                                              timezone.get_default_timezone())
            myitem.save()
            return redirect(...)
    else:
        form = ChangeForm(initial={'date': myitem.date})
    return render(request, 'atemplate.html',
                  {'form': form, 'myitem': myitem})
Categorie:Django, python Tag: ,

Django: in_memory_zip di più in_memory_files

22 novembre 2016 Commenti chiusi

Il concetto base è di non creare file su disco,
ma utilizzare file in_memory.
Dovranno essere “volanti” quindi, sia i file
da zippare, sia il file zip stesso, che verrà
in seguito servito da django.

Semplifichiamo il tutto:

ho una funzione che genera lo zip:

from zipfile import ZipFile
from StringIO import StringIO


def zip_files():
    zip_sio = StringIO()
    zip_file = ZipFile(zip_sio, 'w')

    for line in ['a', 'b', 'c', 'd']:
        name = 'file_{}.txt'.format(line)
        outfile = StringIO()
        outfile.write(line)
        zip_file.writestr(name, outfile.getvalue())
        outfile.close()

    zip_file.close()
    zip_sio_data = zip_sio.getvalue()
    zip_sio.close()
    return zip_file, zip_sio_data

chiaramente potrebbe essere più complesso, ad esempio una
funzione che prenda in ingresso un file, lo splitti in
più parti le quali vengano restituite sotto forma di zip…

from zipfile import ZipFile
from StringIO import StringIO


def zip_files(file_in):
    for line in file_in.readlines():
        ...

In pratica utilizziamo degli oggetti StringIO, ovvero oggetti File-like,
all’interno dei quali possiamo scrivere delle stringhe, con il metodo ‘write’,
proprio come gli oggetti file.
Con il metodo ‘getvalue’, otteniamo tutto il contenuto dell’oggetto, che una
volta chiuso con ‘close’, non è più accessibile. Per questo motivo lo inseriamo
all’interno del file-like object zip prima di chiuderlo.
Gli stessi dati del file zip, li memorizziamo prima di chiudere lo stesso flie-like
zip, in modo da poterli avere disponibili ad esempio nella view di django
prima di costruire il ‘response’…

comunque nel nostro esempio avremo:

>>> z, data = zip_files()
>>> [o.filename for o in z.filelist]
['file_a.txt', 'file_b.txt', 'file_c.txt', 'file_d.txt']
>>> len(data)
410

Come già detto, ‘data’ tornerà utile quando con django andremo a servire il file zip
per il download, ad es.:

...
def a_view(request):
    if request.method == "POST":
        form = a_form(request.POST, request.FILES)
        if form.is_valid():
            file_in = request.FILES['a_form_file_field']
            zip_file, data = zip_file()  # or zip_file(file_in) if you process a file
            response = HttpResponse(data,
                                    content_type="application/x-zip-compressed")
            response['Content-Disposition'] = 'attachment; filename=a_file_zip.zip'
            return response
    else:
        form = a_form()
    return render(request, 'a_template.html', {'form': form})

Con questo sistema, non scriveremo nessun file sul server.

Categorie:Django, python Tag: ,

Project Euler 87

8 settembre 2016 Commenti chiusi
The smallest number expressible as the sum of a prime square, prime cube, and prime fourth power is 28. In fact, there are exactly four numbers below fifty that can be expressed in such a way:

28 = 2^2 + 2^3 + 2^4
33 = 3^2 + 2^3 + 2^4
49 = 5^2 + 2^3 + 2^4
47 = 2^2 + 3^3 + 2^4

How many numbers below fifty million can be expressed as the sum of a prime square, prime cube, and prime fourth power?
import time


def is_prime(num):
    if num == 2:
        return True
    if num % 2 == 0 or num <= 1:
        return False
    sqr = int(num ** 0.5) + 1
    for divisor in xrange(3, sqr, 2):
        if num % divisor == 0:
            return False
    return True

def euler87(limit):
    """a**2 + b**3 + c**4"""
    results = set()
    primes = [x for x in xrange(2, int(limit ** 0.5))
              if is_prime(x)]
    for p4 in primes:
        for p3 in primes:
            partial = p4**4 + p3 ** 3
            if partial >= limit:
                break
            for p2 in primes:
                tot = p2 ** 2 + partial
                if tot >= limit:
                    break
                results.add(tot)
    return len(results)


if __name__ == "__main__":
    start_time = time.time()
    print "euler 87: {} \nelapsed time: {}s".format(
        euler87(50000000), time.time() - start_time)
Categorie:Project Euler, python Tag: ,

project euler 89

27 luglio 2016 Commenti chiusi

Qui il testo del problema 89

Si deve calcolare il totale dei caratteri risparmiati durante la semplificazione dei numeri romani.
Ci ho messo un po’ a capirlo e continuavo a calcolare il totale dei caratteri della lista corretta….

import time


def filter_romans(path):
    with open(path) as inputfile:
        romans = [num.strip() for num in inputfile]
    startchr = sum([len(r) for r in romans])

    for bad, correct in [("VIIII", "IX"), ("IIII", "IV"),
                         ("LXXXX", "XC"), ("XXXX", "XL"),
                         ("DCCCC", "CM"), ("CCCC", "CD"),]:
        for roman in romans:
            index = romans.index(roman)
            if bad in roman:
                romans[index] = roman.replace(bad, correct)
    return startchr - sum([len(r) for r in romans])


if __name__ == "__main__":
    start = time.time()
    result = filter_romans(r"p089_roman.txt")
    print "euler 89: %s\nElapsed time %ss." % (result, time.time() - start)
Categorie:Project Euler, python Tag: ,

Django: unknown command validate

1 giugno 2016 Commenti chiusi

per validare i models prima si utilizzava il comando:

python manage.py validate

nella versione attuale di django (1.9.x) questo solleva un errore:

(venv) C:\...\bancaldo\mysite>python manage.py validate
Unknown command: 'validate'
Type 'manage.py help' for usage.

il comando da utilizzare è “check”:

(venv) C:\...\bancaldo\mysite>python manage.py check
System check identified no issues (0 silenced).
Categorie:Django, python Tag: ,

Flask (tricks): download dati

12 maggio 2016 Commenti chiusi

Per poter aver disponibile un dato al download, utilizziamo

make_response

Per prima cosa creiamo una view chiamata download

from flask import flash, request, make_response

#.....

@main.route('/download/<id>')
def download(id):
    team = Team.query.get(id)
    players = team.players.all()
    data = """Team: %s\n""" % team.name
    for player in players:
        data += "\nplayer: %s - [%s]" % (player.name, player.value)
    response = make_response(data)
    response.headers["Content-Disposition"] = "attachment; filename=lineup.log"
    return response

nella template aggiungiamo un link (o un pulsante nel form) che richiami
la view corrispondente (download):

<a href="{{ url_for('main.download', id=team.id) }}">Download team lineup</a>

quando si clicchera’ sul link (o pulsante) si aprira’ il menu dove sara’ possibile
salvare il file, salvarlo con nome, ecc.

articoli successivi:

articoli precedenti:
Flask (parte 1): virtualenv
Flask (parte 2): struttura progetto complesso
Flask (parte 3): database
Flask (parte 4): views e templates
Flask (parte 5): password security
Flask (parte 6): authentication
Flask (parte 7): mail
Flask (parte 8): registrare un nuovo user

link utili:
“Flask Web Development” di Miguel Grinberg, fondamentale per calarsi nel mondo di Flask.
Altri link fondamentali:
Flask sito ufficiale
il blog dell’autore (Flask mega tutorial)

Categorie:flask, python Tag: ,

Flask (parte 8): registrare un nuovo user

1 settembre 2015 Commenti chiusi

Nella parte precedente abbiamo accennato alla authentication ed ai
forms di competenza, considerando utenti inseriti a database, tramite shell.
Vediamo come fare quando un nuovo utente decide di registrarsi in autonomia.
Il Blueprint per l’authentication è stato creato ed attivato in precedenza,
quello che dobbiamo fare è creare un form per la registrazione.
Editiamo quindi il file app/auth/forms.py
ed aggiungiamo il suddetto form:

from flask.ext.wtf import Form
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import DataRequired, Length, Email, Regexp, EqualTo
from wtforms import ValidationError
from ..models import User


class LoginForm(Form):
    ...

class RegistrationForm(Form):
    email = StringField('Email', 
                        validators=[DataRequired(), Length(1, 64), Email()])
    username = StringField('Username', 
                           validators=[DataRequired(), Length(1, 64),
                                    Regexp('^[A-Za-z][A-Za-z0-9_.]*$', 0,
                                          'Usernames must have only letters, '
                                          'numbers, dots or underscores')])
    password = PasswordField('Password', 
                             validators=[DataRequired(), EqualTo('password2',
                                            message='Passwords must match.')])
    password2 = PasswordField('Confirm password', validators=[DataRequired()])
    submit = SubmitField('Register')

    def validate_email(self, field):
        if User.query.filter_by(email=field.data).first():
            raise ValidationError('Email already registered.')

    def validate_username(self, field):
        if User.query.filter_by(username=field.data).first():
            raise ValidationError('Username already in use.')

Il validator usato nel campo “username”, si assicura che vengano utilizzati solo
lettere, numeri, punti e underscores altrimenti ritorna il messaggio di errore,
passato come argomento a Regexp.
Nel campo password abbiamo un altro validator (EqualTo) che si assicura che le
password immesse (password e conferma della stessa) siano uguali.
I due metodi validate_email() e validate_username() si assicurano che i valori
immessi nei campi non siano già presenti a database, sollevando in caso affermativo,
l’eccezione di competenza.

La template che rappresenterà il form di registrazione sarà /templates/auth/register.html:

{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}

{% block title %} Registration {% endblock %}

{% block page_content %}
    <div class="page-header">
        <h1>New User Registration</h1>
    </div>

    <div class="col-md-4">
        {{ wtf.quick_form(form) }}
    </div>
{% endblock %}

Solitamente la pagina di registrazione, va linkata da quella di Login, in modo che un
nuovo utente, trovandosi di fornte il form di accesso, possa facilmente raggiungere
quello di registrazione.

Apriamo quindi la template /templates/auth/login.html e aggiungiamo il link:

{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}

{% block title %}Login{% endblock %}
{% block page_content %}
    <div class="page-header">
        <h1>Login</h1>
    </div>

    <div class="col-md-4">
        {{ wtf.quick_form(form) }}
    </div>

<p>
New user?
    <a href="{{ url_for('auth.register') }}">
        Click here to register
    </a>
</p>
{% endblock %}

La funzione (view) che gestisce l’operazione di registrazione va inserita in
app/auth/views.py:

from flask import render_template, redirect, request, url_for, flash
from flask.ext.login import login_user
from flask.ext.login import logout_user, login_required
from . import auth
from ..models import User
from .forms import LoginForm, RegistrationForm
from .. import db

...

@auth.route('/register', methods=['GET', 'POST'])
def register():
    form = RegistrationForm()
    if form.validate_on_submit():
        user = User(email=form.email.data,
                    username=form.username.data,
                    password=form.password.data)
        db.session.add(user)
        flash('You can now login.')
        return redirect(url_for('auth.login'))
    return render_template('auth/register.html', form=form)

molto semplice in fase di richiesta visualizzazione form di registrazione (GET),
viene visualizzata la template register.html, in fase di immissione dati (POST),
viene eseguita la parte di codice dentro l’if e, registrato il nuovo user, si
viene reindirizzati alla pagina di login.

Conferma di registrazione – Token

Con la gestione delle registrazioni appena accennata, potremmo però registrare decine
di nuovi user senza problemi. Dobbiamo quindi verificare la mail immessa, mandando ad essa,
una email di conferma, in modo che il nuovo utente possa confermare la propria registrazione.
Per fare questo e per motivi di sicurezza, utilizziamo i token.
Per gestirli si utilizza il pacchetto itsdangerous.

come funziona?

brevemente…apriamo una shell:

python manage.py shell
>>> from manage import app
>>> from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
>>> s = Serializer(app.config['SECRET_KEY'], expires_in = 3600)
>>> token = s.dumps({ 'confirm': 10 })
>>> token
'eyJhbGciOiJIUzI1NiIsImV4cCI6MTQ0MDQxMzkyNCwiaWF0IjoxNDQwNDEwMzI0fQ.eyJjb25maXJtIjoxMH0.Bzoa8GFakRN3WkfvSt5Vkk9RjPC2ffa6Il1aEhFa8tc'
>>> data = s.loads(token)
>>> data
{u'confirm': 10}

L’oggetto Serialiezer fornisce due metodi:
dumps() crea una signature crittografata ed expires_in indicano i secondi di validità della signature.
load() decodifica la stessa.

Cominciamo a gestire la cosa a livello di user e modifichiamo il file app/models.py:

...
from flask import current_app
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer

...

class User(UserMixin, db.Model):
    ...
    confirmed = db.Column(db.Boolean, default=False)

    ...
    def generate_confirmation_token(self, expiration=3600):
        s = Serializer(current_app.config['SECRET_KEY'], expiration)
        return s.dumps({'confirm': self.id})

    def confirm(self, token):
        s = Serializer(current_app.config['SECRET_KEY'])
        try:
            data = s.loads(token)
        except:
            return False
        if data.get('confirm') != self.id:
            return False
        self.confirmed = True
        db.session.add(self)
        return True

Siccome abbiamo aggiunto il campo “confirmed” nella tabella “users”, dobbiamo
allineare il database con una migration:

(venv) >python manage.py db migrate -m "confirmed field added to User class"
INFO  [alembic.migration] Context impl SQLiteImpl.
INFO  [alembic.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added column 'users.confirmed'
Generating ...\migrations\versions\59952a5eeded_confirmed_field_added_to_user_class.py ... done

(venv) >python manage.py db upgrade
INFO  [alembic.migration] Context impl SQLiteImpl.
INFO  [alembic.migration] Will assume non-transactional DDL.
INFO  [alembic.migration] Running upgrade ea1a33541f6 -> 59952a5eeded, confirmed field added to User class

Ora modifichiamo la view che gestisce la registrazione (app/auth/views.py), in modo da mandare la mail di
conferma, prima che l’utente venga redirezionato.

@auth.route('/register', methods=['GET', 'POST'])
def register():
    form = RegistrationForm()
    if form.validate_on_submit():
        user = User(email=form.email.data,
                    username=form.username.data,
                    password=form.password.data)
        db.session.add(user)
        db.session.commit()
        token = user.generate_confirmation_token()
        send_email(user.email, 'Confirm Your Account', 'auth/email/confirm',
                   user=user, token=token)
        flash('A confirmation email has been sent to you by email.')
        return redirect(url_for('main.index'))
    return render_template('auth/register.html', form=form)

Nota:
nel caso nelle impostazioni del database avessimo settato il commit automatico, è necessario
fare il commit manuale, una volta creato il nuovo user, in modo da avere disponibile subito
l’id, necessario nella conferma del token.
La template che si occupa della mail risiede in app/auth/templates/auth/email/confirm.txt

Dear {{ user.username }},
Welcome to Fantamanager!

To confirm your account please click on the following link:
{{ url_for('auth.confirm', token=token, _external=True) }}

Sincerely,
The Fantamanager Admin
Note: replies to this email address are not monitored.

mentre il file html sarà:

<p>Dear {{ user.username }},</p>
<p>Welcome to <b>Fantamanager</b>!</p>

<p>To confirm your account please <a href="{{ url_for('auth.confirm', token=token, _external=True) }}">click here</a>.</p>
<p>Alternatively, you can paste the following link in your browser's address bar:</p>
<p>{{ url_for('auth.confirm', token=token, _external=True) }}</p>
<p>Sincerely,</p>
<p>The Fantamanager Team</p>

<p><small>Note: replies to this email address are not monitored.</small></p>

ora vediamo la view che si occupa della conferma (app/auth/views.py):

from flask.ext.login import current_user

...

@auth.route('/confirm/<token>')
@login_required
def confirm(token):
    if current_user.confirmed:
        return redirect(url_for('main.index'))
    if current_user.confirm(token):
        flash('You have confirmed your account. Thanks!')
    else:
        flash('The confirmation link is invalid or has expired.')
        return redirect(url_for('main.index'))

articoli successivi:
Flask (tricks): download dati

articoli precedenti:
Flask (parte 1): virtualenv
Flask (parte 2): struttura progetto complesso
Flask (parte 3): database
Flask (parte 4): views e templates
Flask (parte 5): password security
Flask (parte 6): authentication
Flask (parte 7): mail

link utili:
“Flask Web Development” di Miguel Grinberg, fondamentale per calarsi nel mondo di Flask.
Altri link fondamentali:
Flask sito ufficiale
il blog dell’autore (Flask mega tutorial)

Categorie:flask, python Tag: ,

Flask (parte 7): mail

26 agosto 2015 Commenti chiusi

Per notificare un evento ad uno User, utilizzeremo il pacchetto Flask-Mail,
che integra in maniera efficiente smtplib con flask.
Per installarlo:

(venv) >pip install flask-mail

flask-mail consente di connetterci ad un SMTP server in modo da passargli
le mail e far sì che vengano spedite.
Con la configurazione di default, Flask-Mail si connette tramite la porta
25 a localhost e spedisce mail senza bisogno di autenticazione.
Le chiavi che permettono di configurare Flask-Mail sono:

MAIL_SERVER : default “localhost”
MAIL_PORT : default 25
MAIL_USE_TLS : default False
MAIL_USE_SSL : default False
MAIL_DEBUG : default app.debug
MAIL_USERNAME : default None
MAIL_PASSWORD : default None
MAIL_DEFAULT_SENDER : default None
MAIL_MAX_EMAILS : default None
MAIL_SUPPRESS_SEND : default app.testing
MAIL_ASCII_ATTACHMENTS : default False

Nel caso specifico della nostra applicazione, dovremo inizializzare Flask-Mail
in app.__init__.py, creando una istanza di Mail, che gestirà le emails:

from flask.ext.mail import Mail

...
mail = Mail()

def create_app(config_name):
    ...
    mail.init_app(app)
    ...

I valori di configurazione sono già stati settati nel file config.py e
prevedono di spedire mail tramite account di posta gmail:

...

class DevelopmentConfig(Config):
    DEBUG = True
    MAIL_SERVER = 'smtp.googlemail.com'
    MAIL_PORT = 587
    MAIL_USE_TLS = True
    MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
    MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
    ...

i valori “MAIL_USERNAME” e “MAIL_PASSWORD” vengono settati per motivi di privacy
nel seguente modo per linux:

(venv) $ export MAIL_USERNAME=<Gmail username>
(venv) $ export MAIL_PASSWORD=<Gmail password>

e nel seguente, per windows:

(venv) $ set MAIL_USERNAME=<Gmail username>
(venv) $ set MAIL_PASSWORD=<Gmail password>

In questo modo, le nostre credenziali non appariranno nel codice, ma verranno prelevate dal dizionario
os.environ direttamente:

    MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
    MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
(venv) >set [email protected]
(venv) >set MAIL_PASSWORD=pippo

(venv) >python manage.py shell

>>> import os
>>> os.environ.get("MAIL_USERNAME")
[email protected]'
>>> os.environ.get("MAIL_PASSWORD")
'pippo'

ovviamente username e pw vanno sostituiti con qualcosa di esistente…
vediamo come mandare una mail dalla shell:

>>> import os
>>> from flask import Flask
>>> from flask_mail import Message
>>> from flask_mail import Mail
>>> app = Flask("test")
>>> mail = Mail(app)
>>> sender = os.environ.get("MAIL_USERNAME")
>>> sender
[email protected]'
>>> recipients = [sender] # mi auto spedisco una mail!
>>> msg = Message("Test", sender=sender, recipients=recipients)
>>> msg.recipients
[[email protected]']
>>> msg.add_recipient("[email protected]") # per aggiungere destinatari...
>>> msg.recipients
[[email protected]', [email protected]']
>>> msg.body = "text body"
>>> msg.html = "<b>HTML</b> body"
>>> with app.app_context():
...     mail.send(msg)
...

nota: il messaggio può avere un text body e/o uno html, pertanto li abbiamo aggiunti
entrambi.
Questo modo di mandare mail, non è comodissimo pertanto automatizziamo l’operazione di
spedizione mail, creando una funzione nell’apposito file app/email.py:

from threading import Thread
from flask import current_app, render_template
from flask.ext.mail import Message
from . import mail


def send_async_email(app, msg):
    with app.app_context():
        mail.send(msg)


def send_email(to, subject, template, **kwargs):
    app = current_app._get_current_object()
    msg = Message(app.config['FANTAMANAGER_MAIL_SUBJECT_PREFIX'] + ' ' + subject,
                  sender=app.config['FANTAMANAGER_MAIL_SENDER'], recipients=[to])
    msg.body = render_template(template + '.txt', **kwargs)
    msg.html = render_template(template + '.html', **kwargs)
    thr = Thread(target=send_async_email, args=[app, msg])
    thr.start()
    return thr

La funzione mail.send() blocca l’applicazione durante l’operazione di spedizione.
Per questo l’operazione stessa viene delegata ad un thread che lavora in background,
evitando che il browser e tutta l’applicazione rimangano in attesa.

La template che si occupa della mail risiede in app/auth/templates/auth/email/confirm.txt

Dear {{ user.username }},
Welcome to Fantamanager!

To confirm your account please click on the following link:
{{ url_for('auth.confirm', token=token, _external=True) }}

Sincerely,
The Fantamanager Admin
Note: replies to this email address are not monitored.

mentre il file html sarà:

<p>Dear {{ user.username }},</p>
<p>Welcome to <b>Fantamanager</b>!</p>

<p>To confirm your account please <a href=
    "{{ url_for('auth.confirm', token=token, _external=True) }}">click here</a>.</p>
<p>Alternatively, you can paste the following link in your browser's address bar:</p>
<p>{{ url_for('auth.confirm', token=token, _external=True) }}</p>

<p>Sincerely,</p>
<p>The Fantamanager Team</p>

<p><small>Note: replies to this email address are not monitored.</small></p>

articoli successivi:
Flask (parte 8): registrare un nuovo user

articoli precedenti:
Flask (parte 1): virtualenv
Flask (parte 2): struttura progetto complesso
Flask (parte 3): database
Flask (parte 4): views e templates
Flask (parte 5): password security
Flask (parte 6): authentication

link utili:
“Flask Web Development” di Miguel Grinberg, fondamentale per calarsi nel mondo di Flask.
Altri link fondamentali:
Flask sito ufficiale
il blog dell’autore (Flask mega tutorial)

Categorie:flask, python Tag: ,

Flask (parte 6): authentication

9 luglio 2015 Commenti chiusi

Nella parte precedente abbiamo accennato alla gestione delle password utente.
Ora vediamo come gestire l’autenticazione sul sito di uno User e la User-session.
Dobbiamo installare per prima cosa l’estensione Flask-Login:

(venv) >pip install flask-login

Poi, siccome l’inserimento delle proprie credenziali da parte dello User che si connette o
si vuole registrare, necessita di Forms, installiamo anche l’estensione WTF:

(venv) >pip install flask-wtf

Ricordiamoci, dopo un’installazione, di rinfrescare i requirements con il comando:

(venv) >pip freeze > requirements.txt

Siccome abbiamo già accennato ai Blueprints e siccome vogliamo tenere i vari codici separati,
creeremo un blueprint specifico che chiameremo “auth”, che gestirà tutto quello
che concerne l’autenticazione dello User, nell afattispecie forms e views.
“auth” avrà di diritto le proprie route specifiche e verrà inizializzato e gestito,
come abbiamo fatto per il blueprint “main”.

Creiamo la dir app/auth ed il file costruttore app/auth/__init__.py

from flask import Blueprint

auth = Blueprint('auth', __name__)

from . import views

ora nella factory function dell’applicazione (file app/__init__.py)
aggiungiamo il blueprint ed inizializziamo le estensioni precedentemente installate:

from flask import Flask, render_template
from flask.ext.bootstrap import Bootstrap
from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext.login import LoginManager
from config import config


bootstrap = Bootstrap()
db = SQLAlchemy()

login_manager = LoginManager() # inizializzo l'estensione
login_manager.session_protection = 'strong'
login_manager.login_view = 'auth.login'


def create_app(config_name):
    app = Flask(__name__)
    app.config.from_object(config[config_name])
    config[config_name].init_app(app)
    bootstrap.init_app(app)
    db.init_app(app)
    login_manager.init_app(app)  

    from main import main as main_blueprint
    app.register_blueprint(main_blueprint)

    from auth import auth as auth_blueprint # importo e registro...
    app.register_blueprint(auth_blueprint)  # ...il blueprint auth

    return app

In fase di inizializzazione dell’estensione che gestisce il login, setto
il livello di protezione della user-session a “strong”, ma è possibile
utilizzare anche i valori None e “basic”.

#...
login_manager = LoginManager()
login_manager.session_protection = 'strong'
#...

Come già accennato, utilizzando un blueprint dedicato, tutte le views e tutti i forms relativi
all’autenticazione di uno User, risiederanno nella dir app/auth, dobbiamo quindi esplicitamente
settare l’endpoint della view di login in fase di inizializzazione della estensione,
tramite l’attributo login_view.

#...
login_manager.login_view = 'auth.login'
#...

Per prima cosa creiamo il form che si occuperà del login nel file app/auth/forms.py:

from flask.ext.wtf import Form
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import Required, Email


class LoginForm(Form):
    email = StringField('Email', validators=[Required(), Length(1, 64),
                                             Email()])
    password = PasswordField('Password', validators=[Required()])
    remember_me = BooleanField('Keep me logged in')
    submit = SubmitField('Log In')

Come si nota, ci viene fornito tutto dall’estensione wtf precedentemente installata.
Abbiamo 2 field dove inseriamo email e pw, un booleanfield (casellina di spunta) e
il bottone submit. I primi due campi vengono validati tramite l’argomento validators.

Ora creiamo la view con la route dedicata ed importiamo in essa la form appena creata.
Il file app/auth/views.py sarà:

from flask import render_template, redirect, request, url_for, flash
from flask.ext.login import login_user
from . import auth
from ..models import User
from .forms import LoginForm  # qui importo la form di login!


@auth.route('/login', methods=['GET', 'POST'])  # route dedicata al login
def login():
    form = LoginForm()
    if form.validate_on_submit():
        user = User.query.filter_by(email=form.email.data).first()
        if user is not None and user.verify_password(form.password.data):
            login_user(user, form.remember_me.data)
            return redirect(request.args.get('next') or url_for('main.index'))
        flash('Invalid username or password.')
    return render_template('auth/login.html', form=form)

Il funzionamento è semplicissimo:
accedendo all’url auth/login, abbiamo una richiesta (request) di tipo GET, quindi
passiamo direttamente alla rappresentazione della template auth/login.html, saltando
tutto il blocco if, che tratta solo una richiesta di tipo POST grazie al metodo
validate_on_submit() che restituisce True solo alla pressione del tasto Log-in.
In questo caso viene cercato lo user tramite query in base alle credenziali inserite e,
se trovato (not None), lo user e la sessione di competenza, vengono memorizzati e
reindirizzati nella pagina index, altrimenti protetta.
Se lo User non viene trovato appare un messaggio sulla pagina di Login grazie a flash.

La funzione login_user() mantiene l’utente loggato per tutta la sessione. Il booleanfield
remember_me mantiene l’utente loggato anche in caso di chiusura della finestra browser, in caso
di valore True, altrimenti “muore” in caso di chiusura del browser ed è necessario un nuovo login.

Per gestire le templates del blueprint auth, creiamo una sottodirectory auth in templates e creiamo
il file login.html (app/templates/auth/login.html):

{% extends "base.html" %}

{% import "bootstrap/wtf.html" as wtf %}

{% block title %}Fantamanager - Login{% endblock %}

{% block page_content %}
    <div class="page-header">
        <h1>Login</h1>
    </div>

    <div class="col-md-4">
        {{ wtf.quick_form(form) }}
    </div>
{% endblock %}

Facciamo anche un’utile modifica al file app/templates/base.html
aggiungendo i links sign-in e sign-out nella navigation bar:

{% extends "bootstrap/base.html" %}


{% block title %}Fantamanager{% endblock %}

{% block head %}

  {{ super() }}
  <link rel="shortcut icon" href="{{ url_for('static', filename = 'favicon.ico') }}"

  type="image/x-icon">
  <link rel="icon" href="{{ url_for('static', filename = 'favicon.ico') }}"

  type="image/x-icon">
  <link rel="stylesheet" 
  type="text/css" href="{{ url_for('static', filename='styles.css') }}">

{% endblock %}



{% block navbar %}

<div class="navbar navbar-inverse" role="navigation">

  <div class="container">

    <div class="navbar-header">
                 <button type="button" class="navbar-toggle"

        data-toggle="collapse" data-target=".navbar-collapse">

        <span class="sr-only">Toggle navigation</span>

        <span class="icon-bar"></span>

        <span class="icon-bar"></span>

        <span class="icon-bar"></span>
            
      </button>

      <a class="navbar-brand" href="/users">Users</a>
            
    </div>
        
    
    <div class="navbar-collapse collapse">
            
      <ul class="nav navbar-nav">

      <li><a href="/">Home</a></li>
             {% if current_user.is_authenticated() %}

        <li><a href="{{ url_for('main.user',
 username=current_user.username) }}">Profile</a></li>

      {% endif %}

      </ul>


      <ul class="nav navbar-nav navbar-right">

        {% if current_user.is_authenticated() %}
                
          <li><a href="{{ url_for('auth.logout') }}">Sign Out</a></li>

        {% else %}

          <li><a href="{{ url_for('auth.login') }}">Sign In</a></li>
                
        {% endif %}

      </ul>
        
    </div>

  </div>

</div>

{% endblock %}





{% block content %}
  
<div class="container">
  
  {% for message in get_flashed_messages() %}
    
    <div class="alert alert-warning">
      
      <button type="button" class="close" data-dismiss="alert"> &times; </button>
      
      {{ message }}
    
    </div>
  
  {% endfor %}

  
{% block page_content %}{% endblock %}
  
</div>

{% endblock %}


La variabile current_user è fornita e gestita direttamente da Flask-Login ed è pertanto
disponibile nelle views e nelle templates.
Le template specifiche poi, potranno estendere direttamente base.html invece di bootstrap/base.html
Per ottenere vantaggio da questo dobbiamo però ereditare la nostra classe User,
da quella predefinita di Flask-Login.

La classe User del file app/models.py sarà pertanto:

# ...
from flask.ext.login import UserMixin

class User(UserMixin, db.Model):
    # ...

Perchè tutto funzioni, Flask-Login richiede di settare una callback che,
dato un ID, ritorni lo User corrispondente (app/models.py):

from . import login_manager
#...

@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))

Ora non resta che proteggere una route. Per farlo basta utilizzare il decoratore
@login_required ad es. (app/main/views.py)

#...
from flask.ext.login import login_required

@main.route('/', methods=['GET'])
@login_required
def index():
    return render_template('index.html')

@main.route('/users', methods=['GET'])
@login_required
def users():
   #...

Abbiamo fatto tutto per gestire il login, ma manca ancora il logout.
Apriamo il file app/auth/views.py ed aggiungiamo la view logout

#...
from flask.ext.login import logout_user, login_required

#...
@auth.route('/logout')
@login_required
def logout():
    logout_user()
    flash('You have been logged out.')
    return redirect(url_for('main.index'))

la funzione logout_user, serve per resettare e rimuovere la user-session corrente.
Fino ad ora, abbiamo aggiunto Users tramite shell, quindi il prossimo passo è la
registrazione di un nuovo utente tramite form, con conseguente invio di richiesta
conferma tramite email.

articolo successivo:
Flask (parte 7): mail

articoli precedenti:
Flask (parte 1): virtualenv
Flask (parte 2): struttura progetto complesso
Flask (parte 3): database
Flask (parte 4): views e templates
Flask (parte 5): password security

link utili:
“Flask Web Development” di Miguel Grinberg, fondamentale per calarsi nel mondo di Flask.
Altri link fondamentali:
Flask sito ufficiale
il blog dell’autore (Flask mega tutorial)

Categorie:flask, python Tag: ,