Categorías
Python

El uso de las pandas para leer archivos grandes de Excel en Python

 

Tabla de Contenidos

  • HousekeepingFrom solo a multi-paquete
  • Single a múltiples paquete de gestión
  • UsersEnlisting helpModelsIntegrating matraz de LoginViewsFighting duplicationTemplatesLayoutForm
  • Alistarse ayuda
  • Modelos
  • Integración Frasco de sesión
  • Vistas
  • la duplicación de lucha gestión
  • plantillas

  • Disposición
  • Formulario

  • Refactoring los datos de seguimiento ApplicationFiltering sitesDeriving de visitorsSeeing la visitsProviding un medio de seguimiento de visitantes sitios
  • filtrado de datos
  • derivados de visitantes
  • al ver las visitas
  • proporcionando un medio de seguimiento de los visitantes
  • embalaje para arriba de
  • Single a múltiples paquetes
  • Alistarse ayuda
  • Modelos
  • Integración Frasco de sesión
  • Vistas
  • lucha contra la duplicación
  • gestión de plantillas
  • Disposición
  • Formulario

  • Filtrado de sitios de datos
  • derivados de visitantes
  • Al ver las visitas
  • proporcionando un medio de seguimiento de visitantes

Por favor nota: Esta es una pieza colaboración entre Michael Herman, desde real Python, y Sean Vieira, un desarrollador de Python de Deo diseños.

los artículos de esta serie:

Bienvenido de nuevo a la serie de desarrollo matraz de seguimiento! Para aquellos de ustedes que están sólo nos une, estamos implementando una red de aplicaciones analíticas que se ajuste a esta especificación servilleta. Para todos aquellos que siguiendo a lo largo en el hogar, es posible que echa un vistazo a el código de hoy con:

$ git checkout v0.2

O bien, puede descargarlo de la página de comunicados en Github. Aquellos de ustedes que se nos acaba de unión puede desear leer una nota en la estructura del repositorio también.

limpieza

Para rápidamente opinión, en nuestro último artículo hemos creado una aplicación escueto que permitió a los sitios que desea agregar y visitas registradas contra ellos a través de una simple interfaz web o a través de HTTP.

Hoy vamos a añadir usuarios, control de acceso, y permitir a los usuarios añadir las visitas de sus propios sitios web que utilizan balizas de seguimiento. También vamos a bucear en algunas de las mejores prácticas para la escritura de plantillas, manteniendo nuestros modelos y formas en sincronía, y el manejo de archivos estáticos.

Single a múltiples paquetes

La última vez que dejó nuestra aplicación, la estructura de directorios parecía algo como esto:

flask-tracking/
flask_tracking/
templates/ # Holds Jinja templates
__init__.py # General application setup
forms.py # User data to domain data mappers and validators
models.py # Domain models
views.py # well ... controllers, really.
config.py # Configuration, just like it says on the cover
README.md
requirements.txt
run.py # `python run.py` to bring the application up locally.

Para mantener las cosas clara, vamos a pasar los formularios existentes, modelos y vistas en un sub-paquete de seguimiento y crear otro sub-paquete para nuestra funcionalidad específica del usuario que llamaremos usuarios:

flask_tracking/
templates/
tracking/ # This is the code from Part 1
__init__.py # Create this file - it should be empty.
forms.py
models.py
views.py
users/ # Where we are working today
__init__.py
__init__.py # This is also code from Part 1

Esto significa que tendremos que cambiar nuestra importación en flask_tracking / __ init__.py a partir de .views importar a partir de seguimiento a partir de .tracking.views rastreo de las importaciones .

Luego está la configuración de base de datos en tracking.models. Esto nos trasladaremos hacia el paquete principal (flask_tracking) ya que el gestor de bases será compartida entre los paquetes. la llamada de Let que los datos del módulo:

# flask_tracking/data.py
from flask.ext.sqlalchemy import SQLAlchemy

db = SQLAlchemy()

def query_to_list(query, include_field_names=True):
"""Turns a SQLAlchemy query into a list of data values."""
column_names = []
for i, obj in enumerate(query.all()):
if i == 0:
column_names = [c.name for c in obj.__table__.columns]
if include_field_names:
yield column_names
yield obj_to_list(obj, column_names)

def obj_to_list(sa_obj, field_order):
"""Takes a SQLAlchemy object - returns a list of all its data"""
return [getattr(sa_obj, field_name, None) for field_name in field_order]

entonces podemos actualizar tracking.models a usar desde db importación flask_tracking.data y tracking.views al uso de flask_tracking.data db importación, query_to_list y ahora deben tener un multi-paquete de trabajo solicitud. Los usuarios

Ahora que hemos dividido nuestra aplicación en paquetes separados de funcionalidad relacionada, vamos a empezar a trabajar en el paquete de usuarios. Los usuarios deben ser capaces de inscribirse para una cuenta, gestionar su cuenta y entrar y salir. Podría haber más funcionalidad relacionada con los usuarios (especialmente alrededor de permisos), sino para mantener las cosas claras vamos a seguir con estos elementos básicos.

Alistarse ayuda

tenemos una regla para tomar, en las dependencias – cada dependencia añadimos debe resolver también al menos un problema difícil. El mantenimiento de las sesiones de usuario tiene varias de borde casos interesantes que hace que sea un excelente candidato para una dependencia. Afortunadamente, hay una disponible para este caso de uso – Frasco de inicio de sesión. Sin embargo, hay una cosa que matraz de sesión no se ocupa en absoluto – autenticación. Podemos utilizar cualquier esquema de autenticación que queremos – desde “sólo un nombre de usuario” a los esquemas de autenticación distribuidos como Persona. Vamos a mantenerlo simple y van con nombre de usuario y contraseña. Esto significa que tenemos que almacenar la contraseña de un usuario, lo que vamos a querer hash. Desde hash adecuadamente las contraseñas es también un problema difícil que se enfrentará a otra dependencia, backports.pbkdf2 para asegurar nuestras contraseñas son ordenadas de forma segura. (Escogimos pbdkdf2 ya que se considera segura partir de este escrito y se incluye en Python 3.3+ – sólo lo necesitamos, mientras que se están ejecutando en Python 2.) Ir de

Let adelante y añadir:

Flask-Login==0.2.7
backports.pbkdf2==0.1

a nuestra requirements.txt archivo y luego (asegurándose de que se activa nuestro entorno virtual) podemos ejecutar pip instalar -r requirements.txt de nuevo para instalarlos. (Es posible obtener algunos errores de compilación las aceleraciones C para PBKDF2 – se puede ignorarlas). Vamos a integrarlo con nuestra aplicación en un momento – en primer lugar tenemos que configurar nuestros usuarios de manera matraz de inicio de sesión tiene algo con qué trabajar. Modelos

vamos a configurar nuestra clase SQLAlchemy usuario en users.models. Sólo se almacene un nombre de usuario, dirección de correo electrónico y la contraseña (salada y hash):

from random import SystemRandom

from backports.pbkdf2 import pbkdf2_hmac, compare_digest
from flask.ext.login import UserMixin
from sqlalchemy.ext.hybrid import hybrid_property

from flask_tracking.data import db

class User(UserMixin, db.Model):
__tablename__ = 'users_user'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
email = db.Column(db.String(120), unique=True)
_password = db.Column(db.LargeBinary(120))
_salt = db.Column(db.String(120))
sites = db.relationship('Site', backref='owner', lazy='dynamic')

@hybrid_property
def password(self):
return self._password

# In order to ensure that passwords are always stored
# hashed and salted in our database we use a descriptor
# here which will automatically hash our password
# when we provide it (i. e. user.password = "12345")
@password.setter
def password(self, value):
# When a user is first created, give them a salt
if self._salt is None:
self._salt = bytes(SystemRandom().getrandbits(128))
self._password = self._hash_password(value)

def is_valid_password(self, password):
"""Ensure that the provided password is valid.

We are using this instead of a ``sqlalchemy.types.TypeDecorator``
(which would let us write ``User.password == password`` and have the incoming
``password`` be automatically hashed in a SQLAlchemy query)
because ``compare_digest`` properly compares **all***
the characters of the hash even when they do not match in order to
avoid timing oracle side-channel attacks."""
new_hash = self._hash_password(password)
return compare_digest(new_hash, self._password)

def _hash_password(self, password):
pwd = password.encode("utf-8")
salt = bytes(self._salt)
buff = pbkdf2_hmac("sha512", pwd, salt, iterations=100000)
return bytes(buff)

def __repr__(self):
return "".format(self.id)

Uf – casi la mitad de este código es para la contraseña! Lo que es peor, en el momento en que usted está leyendo esta nuestra aplicación de _hash_password es probable que sea considerada imperfecta (tal es la naturaleza siempre cambiante de la criptografía) pero cubre todas las mejores prácticas básicas:

  • Siempre use una sal única a cada usuario.
  • uso de un algoritmo de estiramiento de llave con una unidad sintonizable de trabajo.
  • Comparar hash utilizando un algoritmo de tiempo constante.

en las notas no relacionadas con la contraseña, que están haciendo una relación de uno a muchos entre usuarios y sitios (sitios db.relationship = ( ‘Sitio’, backref = ‘propietario’, = vagos ‘dinámica’)) para que podemos tener los usuarios que gestionan múltiples sitios.

Además, estamos de subclases de clase UserMixin del matraz de inicio de sesión. Frasco de sesión requiere que la clase User implementar ciertos métodos (get_id, is_authenticated, etc.) para que pueda hacer su trabajo. UserMixin ofrece versiones predeterminadas de los métodos que funcionan bastante bien para nuestros propósitos.

Integración Frasco de sesión

Ahora que tenemos un usuario podemos integrar con el frasco de inicio de sesión. Con el fin de evitar las importaciones circulares vamos a configurar la extensión de su propio módulo de alto nivel de autenticación llamado:

# flask_tracking/auth.py
from flask.ext.login import LoginManager

from flask_tracking.users.models import User

login_manager = LoginManager()

login_manager.login_view = "users.login"
# We have not created the users.login view yet
# but that is the name that we will use for our
# login view, so we will set it now.

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

@ registros login_manager.user_loader nuestra función load_user con el frasco de inicio de sesión para que cuando un usuario vuelve después de entrar en el frasco de inicio de sesión puede cargar al usuario de la user_id que almacena en la sesión del frasco.

Finalmente, importar login_manager en flask_tracking / __ init__.py y registrarlo con nuestro objeto de aplicación:

from .auth import login_manager

# ...

login_manager.init_app(app)

Vistas

A continuación vamos a configurar nuestro punto de vista y el controlador de funciones para los usuarios habilitar registro / log / Cerrar sesión funcionalidad. En primer lugar, vamos a configurar nuestras formas:

# flask_tracking/users/forms.py
from flask.ext.wtf import Form
from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound
from wtforms import fields
from wtforms.validators import Email, InputRequired, ValidationError

from .models import User

class LoginForm(Form):
email = fields.StringField(validators=[InputRequired(), Email()])
password = fields.StringField(validators=[InputRequired()])

# WTForms supports "inline" validators
# which are methods of our `Form` subclass
# with names in the form `validate_[fieldname]`.
# This validator will run after all the
# other validators have passed.
def validate_password(form, field):
try:
user = User.query.filter(User.email == form.email.data).one()
except (MultipleResultsFound, NoResultFound):
raise ValidationError("Invalid user")
if user is None:
raise ValidationError("Invalid user")
if not user.is_valid_password(form.password.data):
raise ValidationError("Invalid password")

# Make the current user available
# to calling code.
form.user = user

class RegistrationForm(Form):
name = fields.StringField("Display Name")
email = fields.StringField(validators=[InputRequired(), Email()])
password = fields.StringField(validators=[InputRequired()])

def validate_email(form, field):
user = User.query.filter(User.email == field.data).first()
if user is not None:
raise ValidationError("A user with that email already exists")

Una vez más, una buena cantidad de código, esta vez principalmente en torno a la validación de entrada del usuario. Una cosa a destacar es que para nuestro formulario de acceso, cuando el usuario es autenticado, que exponga la instancia de usuario en el formulario como form.user (por lo que no tenemos que hacer la misma consulta en dos lugares – a pesar de que va a hacer el SQLAlchemy cosa aquí y sólo golpeó la base de datos una vez).

Por último, podemos configurar nuestros puntos de vista:

# flask_tracking/users/views.py
from flask import Blueprint, flash, redirect, render_template, request, url_for
from flask.ext.login import login_required, login_user, logout_user

from flask_tracking.data import db
from .forms import LoginForm, RegistrationForm
from .models import User

users = Blueprint('users', __name__)

@users.route('/login/', methods=('GET', 'POST'))
def login():
form = LoginForm()
if form.validate_on_submit():
# Let Flask-Login know that this user
# has been authenticated and should be
# associated with the current session.
login_user(form.user)
flash("Logged in successfully.")
return redirect(request.args.get("next") or url_for("tracking.index"))
return render_template('users/login.html', form=form)

@users.route('/register/', methods=('GET', 'POST'))
def register():
form = RegistrationForm()
if form.validate_on_submit():
user = User()
form.populate_obj(user)
db.session.add(user)
db.session.commit()
login_user(user)
return redirect(url_for('tracking.index'))
return render_template('users/register.html', form=form)

@users.route('/logout/')
@login_required
def logout():
# Tell Flask-Login to destroy the
# session->User connection for this session.
logout_user()
return redirect(url_for('tracking.index'))

y la importación y registrarlos con nuestro objeto de aplicación:

# flask_tracking/__init__.py
from .users.views import users

# ...

app.register_blueprint(users)

Aviso a la llamada dentro load_user de nuestro punto de vista de inicio de sesión. Frasco de sesión nos obliga a llamar a esta función con el fin de activar la sesión de nuestro usuario (que se gestionará por nosotros).

Una última cosa a tener en cuenta es nuestros usuarios / login.html plantilla:

{% extends "layout.html" %}
{% import "helpers/forms.html" as forms %}
{% block title %}Log into Flask Tracking!{% endblock %}
{% block content %}
{{super()}}

{{ forms.render(form) }}

{% endblock content %}

vamos a cubrir las macros layout.html y se forma en un momento – la clave a destacar es que la acción de nuestro formulario estamos pasando de forma explícita en el valor del parámetro siguiente:

url_for('users.login', next=request.args.get('next', ''))

esto asegura que cuando los usuarios somete el formulario para users.login el siguiente parámetro está disponible para nuestro código de redirección:

login_user(form.user)
flash("Logged in successfully.")
return redirect(request.args.get("next") or url_for("tracking.index"))

hay un agujero de seguridad sutil en este código, que seremos fijar en nuestro próximo artículo (pero señala que si ya han visto que).

La lucha contra la duplicación

Pero espera! ¿Has visto el patrón que acaba de repetir por tercera vez? (En realidad, repetimos al menos dos patrones , pero sólo vamos a quitar la duplicación de uno de ellos hoy en día). Esta parte del código de registro:

user = User()
form.populate_obj(user)
db.session.add(user)
db.session.commit()

también se repite varias veces en el código de seguimiento. Vamos a tirar de que el comportamiento sesión de base de datos utilizando un mixin costumbre que podemos tomar prestado de frasco-Kit. flask_tracking / datos abiertos y agregue el código siguiente:

class CRUDMixin(object):
__table_args__ = {'extend_existing': True}

id = db.Column(db.Integer, primary_key=True)

@classmethod
def create(cls, commit=True, **kwargs):
instance = cls(**kwargs)
return instance.save(commit=commit)

@classmethod
def get(cls, id):
return cls.query.get(id)

# We will also proxy Flask-SqlAlchemy's get_or_44
# for symmetry
@classmethod
def get_or_404(cls, id):
return cls.query.get_or_404(id)

def update(self, commit=True, **kwargs):
for attr, value in kwargs.iteritems():
setattr(self, attr, value)
return commit and self.save() or self

def save(self, commit=True):
db.session.add(self)
if commit:
db.session.commit()
return self

def delete(self, commit=True):
db.session.delete(self)
return commit and db.session.commit()

CRUDMixin nos proporciona una manera más fácil de manejar las cuatro mayoría de las operaciones modelo común (crear, leer, actualizar y eliminar):

def create(cls, commit=True, **kwargs):
pass

def get(cls, id):
pass

def update(self, commit=True, **kwargs):
pass

def delete(self, commit=True):
pass

Ahora, si actualizamos nuestra clase Usuario a CRUDMixin también subclase:

from flask_tracking.data import CRUDMixin, db

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

que luego puede utilizar el mucho más claro: llamada

user = User.create(**form.data)

en nuestros puntos de vista. Esto hace que sea más fácil de razonar acerca de lo que nuestro código está haciendo y lo hace mucho más fácil de refactor (ya que cada pieza de ofertas de código con menos preocupaciones). También podemos actualizar el código de nuestro paquete de seguimiento para hacer uso de los mismos métodos.

plantillas

En la primera parte, nos saltamos la revisión de nuestras plantillas para ahorrar tiempo. Tomemos un par de minutos ahora y revisar las partes más interesantes de lo que estamos usando para prestar nuestro HTML.

Más adelante, podría romper todos éstos para arriba en una interfaz REST. En lugar de tener Python / frasco / Jinja servir a una página con formato previo que podríamos utilizar un marco de JavaScript MVC para manejar los front-end y hacer peticiones al backend para obtener los datos necesarios. El cliente entonces envía peticiones al servidor para hacer / registrar nuevos sitios y se encargará de la actualización de los puntos de vista cuando se crean nuevos sitios y visitas. Los puntos de vista sería entonces responsable de la interfaz REST.

Dicho esto, ya que nos estamos centrando en el frasco, utilizaremos Jinja para servir a la página por ahora.

Disposición

En primer lugar, vistazo a layout.html (estoy dejando la mayor parte del código de este artículo para ahorrar espacio, pero estoy proporcionando enlaces al código completo):

{% block title %}{{ title }}{% endblock %}<itle><br /> <!-- ... snip ... --></p> <h1>{{ self.title() }}</h1> <p></code> </p> <p> este fragmento vitrinas dos de mis favoritos trucos – en primer lugar, que tienen un bloque (título) que contiene una variable para que podamos establecer este valor de nuestras llamadas render_template (por lo que no necesitamos para crear una plantilla totalmente nueva sólo para cambiar un título). En segundo lugar, estamos <em> re-uso de </em> los contenidos del bloque para nuestra cabecera con la variable especial auto. Este medio, cuando fijamos título (ya sea en una plantilla de niño o a través de un argumento de palabra clave a render_template) el texto que proporcionamos se mostrarán <em> tanto </em> en la barra de título del navegador y en la etiqueta h1. Forma de administración </p> <h3> </h3> <p> La otra pieza de nuestra estructura de plantillas que merece una mirada es nuestros macros. Para aquellos de ustedes que viene de un fondo de Django, macros de Jinja son etiquetas de Django en esteroides. Nuestra macro form.render, por ejemplo, hace que sea muy fácil de agregar un formulario a una de nuestras plantillas: </p> <p> <code>{% macro render(form) %}</p> <dl> {% for field in form if field.type not in ["HiddenField", "CSRFTokenField"] %}</p> <dt>{{ field.label }}</dt> <dd>{{ field }}<br /> {% if field.errors %}</p> <ul class="errors"> {% for error in field.errors %}</p> <li>{{error}}</li> <p>{% endfor %} </ul> <p>{% endif %}</dd> <p>{% endfor %} </dl> <p>{{ form.hidden_tag() }}<br /> {% endmacro %}<br /> </code> </p> <p> uso es tan simple como: </p> <p> <code>{% import "helpers/forms.html" as forms %}<br /> <!-- ... snip ... --></p> <form action="{{url_for('users.register')}}" method="POST"> {{ forms.render(form) }}</p> <p><input type="Submit" value="Learn more about your visitors"></p> </form> <p></code> </p> <p> En lugar de escribir el mismo formulario HTML una y otra vez que sólo puede form.render utilizar para generar automáticamente el texto modelo HTML para cada campo en nuestros formularios. De esta manera todas nuestras formas se ven y funcionan de la misma manera y si alguna vez tenemos que cambiarlas, sólo hay que hacerlo una vez en su lugar. <strong> No te repitas </strong> marcas de código muy limpio. </p> <h2> Refactoring la aplicación de seguimiento de </h2> <p> Ahora que tenemos todo lo configurado correctamente, vamos a ir hacia atrás y refactorizar la carne de la aplicación: <em> la solicitud seguimiento </em>. </p> <p> En la Parte I, construimos el esqueleto de una solicitud de seguimiento. Los sitios fueron creados en la página de índice y cualquiera podía ver todo <em> </em> los sitios disponibles. Siempre y cuando el usuario final envía <em> toda la información </em> sí mismos, Frasco-Tracking almacenaría felizmente. Ahora, nosotros tenemos usuarios, por lo que queremos filtrar la lista de sitios. Además <em> todo </em> y, sería bueno si nuestra aplicación podría derivar algunos de los datos por parte del visitante, en lugar de pedir al usuario final de nuestra aplicación para derivarla <em> todo </em> por sí mismos. inicio sitios </p> <h3> Filtrado </h3> <p> Vamos con la lista de sitios: </p> <p> <code># flask_trackingracking/views.py<br /> @tracking.route("/sites", methods=("GET", "POST"))<br /> @login_required<br /> def view_sites():<br /> form = SiteForm()</p> <p> if form.validate_on_submit():<br /> Site.create(owner=current_user, **form.data)<br /> flash("Added site")<br /> return redirect(url_for(".view_sites"))</p> <p> query = Site.query.filter(Site.user_id == current_user.id)<br /> data = query_to_list(query)<br /> results = []</p> <p> try:<br /> # The header row should not be linked<br /> results = [next(data)]<br /> for row in data:<br /> row = [_make_link(cell) if i == 0 else cell<br /> for i, cell in enumerate(row)]<br /> results.append(row)<br /> except StopIteration:<br /> # This happens when a user has no sites registered yet<br /> # Since it is expected, we ignore it and carry on.<br /> pass</p> <p> return render_template("tracking/sites.html", sites=results, form=form)</p> <p>_LINK = Markup('<a href="{url}">{name}</a>')</p> <p>def _make_link(site_id):<br /> url = url_for(".view_site_visits", site_id=site_id)<br /> return _LINK.format(url=url, name=site_id)<br /> </code> </p> <p> A partir de la parte superior, el decorador @login_required es proporcionada por matraz de inicio de sesión. Cualquier persona que no ha iniciado la sesión que trata de ir a / sites / será redirigido a la página de inicio de sesión. A continuación, estamos comprobando para ver si el usuario está añadiendo un nuevo sitio (cheques form.validate_on_submit para ver si request.method es POST y valida la forma – si alguna de las condiciones previas falla, el método retorna falso, de lo contrario, devuelve True ). Si el usuario está creando un nuevo sitio, creamos un nuevo sitio (usando el método definido por nuestro CRUDMixin, por lo que si usted está haciendo cambios en el código usted mismo, tendrá que asegurarse de que el sitio y visitar tanto Heredar del CRUDMixin) y volver redirección a la misma página. Redirigimos de nuevo a nosotros mismos después de guardar el nuevo sitio para evitar que una actualización de la página haciendo que el usuario intente agregar el sitio dos veces. (Esto se llama el patrón posterior a la redirección-GET). </p> <p> Si no está seguro de lo que quiero decir con esto, tratar comentando la redirección de retorno (url_for ( «view_sites»)), a continuación, enviar el formulario ‘Añadir un sitio’ y cuando la página se recarga empujan F5 para actualizar su navegador. Trate de ese mismo ejercicio después de la restauración de la redirección. (Cuando se elimina la redirección del navegador le preguntará si realmente desea enviar los datos del formulario de nuevo – la última petición de que el navegador hecho es el post que ha creado el nuevo sitio con la redirección, la última petición de que el navegador hecho es el. petición GET que vuelve a cargar la página view_sites). </p> <p> Continuando, si el usuario no está creando un nuevo sitio (o si los datos proporcionados tiene errores) estamos consultando la base de datos para buscar todos los sitios que fueron creados por el usuario actualmente conectado. a continuación, transformamos un poco nuestra lista, convirtiendo el ID de base de datos en un enlace HTML para cada una de nuestras filas no de cabecera. Este uso de una plantilla “en línea” es bueno para el prototipado rápido, cuando todavía no tiene una buena idea de si el patrón de plantilla vale “macro-izing”. En nuestro caso, esta es la única visión que se tiene con una mesa con un enlace de acción, por lo que utilizar la técnica de la plantilla en línea para demostrar otra forma de hacer las cosas. </p> <p> Vale la pena señalar que hemos elegido para su uso sites_view para ambos sitios que exhiben y sus visitas y para los sitios que registran. Es realmente depende de ti cómo desea romper su aplicación. Tener un view_sites y una vista add_site, donde el primero es sólo accesible a las peticiones GET y el segundo a POST es también una técnica válida. Independientemente de la técnica se siente más clara de que es el que se debe preferir – sólo asegúrese de que sean compatibles. datos </p> <h3> derivados de visitantes </h3> <p> add_visit, por su parte, es ahora un poco más complejo (código a pesar de que es en su mayoría Mapping): </p> <p> <code>from flask import request</p> <p>from .geodata import get_geodata</p> <p># ... snip ...</p> <p>@tracking.route("/sites/<int_site_id>/visit", methods=("GET", "POST"))<br /> def add_visit(site_id=None):<br /> site = Site.get_or_404(site_id)</p> <p> browser = request.headers.get("User-Agent")<br /> url = request.values.get("url") or request.headers.get("Referer")<br /> event = request.values.get("event")<br /> ip_address = request.access_route[0] or request.remote_addr<br /> geodata = get_geodata(ip_address)<br /> location = "{}, {}".format(geodata.get("city"),<br /> geodata.get("zipcode"))</p> <p> # WTForms does not coerce obj or keyword arguments<br /> # (otherwise, we could just pass in `site=site_id`)<br /> # CSRF is disabled in this case because we will *want*<br /> # users to be able to hit the /sites/{id} endpoint from other sites.<br /> form = VisitForm(csrf_enabled=False,<br /> site=site,<br /> browser=browser,<br /> url=url,<br /> ip_address=ip_address,<br /> latitude=geodata.get("latitude"),<br /> longitude=geodata.get("longitude"),<br /> location=location,<br /> event=event)</p> <p> if form.validate():<br /> Visit.create(**form.data)<br /> # No need to send anything back to the client<br /> # Just indicate success with the response code<br /> # (204 is "Your request succeeded; I have nothing else to say.")<br /> return '', 204</p> <p> return jsonify(errors=form.errors), 400<br /> </code> </p> <p> Hemos eliminado la capacidad de los usuarios para añadir manualmente las visitas de nuestro sitio web a través de un formulario (y por lo también hemos eliminado la segunda ruta en add_visit). Ahora hacemos asignación explícita de datos que podemos derivar en el servidor (el navegador, la dirección IP) y luego construimos nuestra VisitForm que pasa en esos valores corresponden directamente. La dirección IP que tire de access_route en caso de que están detrás de un proxy desde entonces remote_addr contendrá la dirección IP de la última proxy, que no es lo que queremos <em> </em> en absoluto. Nos protección CSRF desactivar porque en realidad queremos <em> </em> usuarios puedan hacer peticiones a este punto final de otra parte. Por último, sabemos qué sitio esta solicitud es para debido a la <int: site_id> parámetro que hemos establecido en la URL. </p> <p> Esto no es una aplicación perfecta de esta idea. No tenemos ninguna manera de verificar que la petición es una petición lícita de nuestros balizas de seguimiento. Alguien podría modificar el código JavaScript o presentar solicitudes modificadas de otro servidor por completo y nos volveríamos a guardarlo. Esto es simple y fácil de implementar. Pero es probable que no debe usar este código en un entorno de producción. </p> <p> get_geodata (dirección_ip) http://freegeoip.net/ consultas para que podamos tener una idea aproximada de dónde las solicitudes provienen de: </p> <p> <code>from json import loads<br /> from re import compile, VERBOSE<br /> from urllib import urlopen</p> <p>FREE_GEOIP_URL = "http://freegeoip.net/json/{}"<br /> VALID_IP = compile(r"""<br /> \b<br /> (25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)<br /> \.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)<br /> \.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)<br /> \.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)<br /> \b<br /> """, VERBOSE)</p> <p>def get_geodata(ip):<br /> """<br /> Search for geolocation information using http://freegeoip.net/<br /> """<br /> if not VALID_IP.match(ip):<br /> raise ValueError('Invalid IPv4 format')</p> <p> url = FREE_GEOIP_URL.format(ip)<br /> data = {}</p> <p> try:<br /> response = urlopen(url).read()<br /> data = loads(response)<br /> except Exception:<br /> pass</p> <p> return data<br /> </code> </p> <p> Guardar esto como geodata.py en el directorio de seguimiento. </p> <p> Volver a la vista, toda esta visión está haciendo es copiar información de la solicitud de abajo y almacenarla en la base de datos. Se responde a la solicitud con un 204 (Contenido No) de respuesta HTTP. Esto indica al navegador que la solicitud tuvo éxito, pero no tiene que gastar tiempo extra de generación de contenidos que el usuario final no quiere ver. </p> <h3> Al ver las visitas </h3> <p> También añadir autenticación para las Visitas vista para cada sitio individual: </p> <p> <code>@tracking.route("/sites/<int_site_id>")<br /> @login_required<br /> def view_site_visits(site_id=None):<br /> site = Site.get_or_404(site_id)<br /> if not site.user_id == current_user.id:<br /> abort(401)</p> <p> query = Visit.query.filter(Visit.site_id == site_id)<br /> data = query_to_list(query)<br /> return render_template("tracking/site.html", visits=data, site=site)<br /> </code> </p> <p> El único cambio real aquí es que si el usuario está conectado, pero no es dueño del sitio, verán un error de autorización página, en lugar de ser capaz de ver las visitas para el sitio. </p> <h3> proporcionando un medio de seguimiento de visitantes </h3> <p> Por último, queremos proporcionar a los usuarios un fragmento de código que se puede colocar en su sitio web que visitas que grabe automáticamente: </p> <p> <code>{# flask_trackingemplatesracking/site.html #}<br /> {% block content %}<br /> {{ super() }}</p> <p>To track visits to this site, simple add the following snippet to the pages that you wish to track:</p> <p><code></p> <pre> <script> (function() { var img = new Image(); img.src = "{{ url_for('tracking.add_visit', site_id=site.id, event='PageLoad', _external=true) }}"; })(); </script> <noscript> <img src="{{ url_for('tracking.add_visit', site_id=site.id, event='PageLoad', _external=true) }}" width="1" height="1" /> <<br />oscript> </pre> <p></code></p> <h2>Visits for {{ site.base_url }}</h2> <table> {{ tables.render(visits) }}<br /> <able><br /> {% endblock content %}<br /> </code> </p> <p> Nuestro fragmento es muy simple – cuando se carga la página que crean una nueva imagen y ajuste su fuente a ser nuestra URL de seguimiento. El navegador <em> inmediatamente </em> carga de la imagen especificada (que será nada en absoluto) y vamos a grabar un golpe de seguimiento en nuestra aplicación. También tenemos un <noscript> bloque para aquellas personas que nos visitan sin JavaScript habilitado. (Si realmente quisiéramos para mantenerse al día con los tiempos, también podríamos actualizar nuestro código del lado del servidor para comprobar si la cabecera no siguen la pista y sólo grabar la visita si el usuario ha optado por la de seguimiento.) </p> <h2> Embalaje para arriba de </h2> <p> Que de que para este post. Ahora tenemos las cuentas de usuario, y el inicio de un fácil utilizar el API de cliente para el seguimiento. Todavía tenemos que finalizar nuestro API de cliente, el estilo de la aplicación y agregar informes. </p> <p> El código de la aplicación se puede encontrar aquí. </p> <p> Su aplicación debe tener este aspecto: </p> </p> <p> <img src="https://eltecnofilo.es/wp-content/uploads/2020/03/flask-tracking-3.20b14871c9a4.png"> </p> <p> <strong> De cara al futuro: </strong> </p> <ul> <li> En la Parte III, exploraremos escribir pruebas para nuestra aplicación, registro y depuración de errores. </li> <li> En la Parte IV vamos a hacer un poco de Test Driven Desarrollo para permitir que nuestra aplicación para aceptar pagos y visualizar informes sencillos. </li> <li> en la Parte V que va a escribir una API REST JSON para el consumo de otros. </li> <li> En la Parte VI que cubrirá las implementaciones de Automatización (en Heroku) con Tela y básica A / B función de prueba. </li> <li> Por último, en la Parte VII que cubrirá la preservación de su aplicación para el futuro con la documentación, la cobertura de código y herramientas de métricas de calidad. </li> </ul> </div><!-- .entry-content --> </div><!-- .post-inner --> <div class="section-inner"> </div><!-- .section-inner --> <nav class="pagination-single section-inner" aria-label="Entrada" role="navigation"> <hr class="styled-separator is-style-wide" aria-hidden="true" /> <div class="pagination-single-inner"> <a class="previous-post" href="https://eltecnofilo.es/cartilla-en-python-decoradores/"> <span class="arrow" aria-hidden="true">←</span> <span class="title"><span class="title-inner">Cartilla en Python decoradores</span></span> </a> <a class="next-post" href="https://eltecnofilo.es/python-api-rest-con-el-frasco-connexion-y-sqlalchemy-parte-4/"> <span class="arrow" aria-hidden="true">→</span> <span class="title"><span class="title-inner">Python API REST con el frasco, Connexion, y SQLAlchemy – Parte 4</span></span> </a> </div><!-- .pagination-single-inner --> <hr class="styled-separator is-style-wide" aria-hidden="true" /> </nav><!-- .pagination-single --> <div class="comments-wrapper section-inner"> <div id="respond" class="comment-respond"> <h2 id="reply-title" class="comment-reply-title">Deja un comentario <small><a rel="nofollow" id="cancel-comment-reply-link" href="/el-uso-de-las-pandas-para-leer-archivos-grandes-de-excel-en-python/#respond" style="display:none;">Cancelar la respuesta</a></small></h2><form action="https://eltecnofilo.es/wp-comments-post.php" method="post" id="commentform" class="section-inner thin max-percentage" novalidate><p class="comment-notes"><span id="email-notes">Tu dirección de correo electrónico no será publicada.</span> Los campos obligatorios están marcados con <span class="required">*</span></p><p class="comment-form-comment"><label for="comment">Comentario</label> <textarea id="comment" name="comment" cols="45" rows="8" maxlength="65525" required="required"></textarea></p><p class="comment-form-author"><label for="author">Nombre <span class="required">*</span></label> <input id="author" name="author" type="text" value="" size="30" maxlength="245" required='required' /></p> <p class="comment-form-email"><label for="email">Correo electrónico <span class="required">*</span></label> <input id="email" name="email" type="email" value="" size="30" maxlength="100" aria-describedby="email-notes" required='required' /></p> <p class="comment-form-url"><label for="url">Web</label> <input id="url" name="url" type="url" value="" size="30" maxlength="200" /></p> <p class="comment-form-cookies-consent"><input id="wp-comment-cookies-consent" name="wp-comment-cookies-consent" type="checkbox" value="yes" /> <label for="wp-comment-cookies-consent">Guardar mi nombre, correo electrónico y sitio web en este navegador para la próxima vez que haga un comentario.</label></p> <p class="form-submit"><input name="submit" type="submit" id="submit" class="submit" value="Publicar el comentario" /> <input type='hidden' name='comment_post_ID' value='6658' id='comment_post_ID' /> <input type='hidden' name='comment_parent' id='comment_parent' value='0' /> </p></form> </div><!-- #respond --> </div><!-- .comments-wrapper --> </article><!-- .post --> </main><!-- #site-content --> <div class="footer-nav-widgets-wrapper header-footer-group"> <div class="footer-inner section-inner"> <aside class="footer-widgets-outer-wrapper" role="complementary"> <div class="footer-widgets-wrapper"> <div class="footer-widgets column-one grid-item"> <div class="widget widget_search"><div class="widget-content"><form role="search" method="get" class="search-form" action="https://eltecnofilo.es/"> <label for="search-form-2"> <span class="screen-reader-text">Buscar:</span> <input type="search" id="search-form-2" class="search-field" placeholder="Buscar …" value="" name="s" /> </label> <input type="submit" class="search-submit" value="Buscar" /> </form> </div></div> <div class="widget widget_recent_entries"><div class="widget-content"> <h2 class="widget-title subheading heading-size-3">Entradas recientes</h2> <ul> <li> <a href="https://eltecnofilo.es/vps-cloud-hosting/">VPS cloud hosting</a> </li> <li> <a href="https://eltecnofilo.es/versiones-de-ejecucion-de-python-en-acoplable-como-probar-la-ultima-release-python/">Versiones de ejecución de Python en acoplable: Cómo probar la última Release Python</a> </li> <li> <a href="https://eltecnofilo.es/leer-y-escribir-archivos-csv/">Leer y escribir archivos CSV</a> </li> <li> <a href="https://eltecnofilo.es/python-puro-vs-vs-numpy-tensorflow-comparacion-de-rendimiento/">Python puro vs vs NumPy TensorFlow Comparación de Rendimiento</a> </li> <li> <a href="https://eltecnofilo.es/estructura-python-programa-lexico/">Estructura Python Programa léxico</a> </li> </ul> </div></div><div class="widget widget_recent_comments"><div class="widget-content"><h2 class="widget-title subheading heading-size-3">Comentarios recientes</h2><ul id="recentcomments"><li class="recentcomments"><span class="comment-author-link"><a href="https://wordpress.org/" rel="external nofollow ugc" class="url">A WordPress Commenter</a></span> en <a href="https://eltecnofilo.es/hello-world/#comment-1">Hello world!</a></li></ul></div></div> </div> <div class="footer-widgets column-two grid-item"> <div class="widget widget_archive"><div class="widget-content"><h2 class="widget-title subheading heading-size-3">Archivos</h2> <ul> <li><a href='https://eltecnofilo.es/2020/04/'>abril 2020</a></li> <li><a href='https://eltecnofilo.es/2020/03/'>marzo 2020</a></li> <li><a href='https://eltecnofilo.es/2019/12/'>diciembre 2019</a></li> </ul> </div></div><div class="widget widget_categories"><div class="widget-content"><h2 class="widget-title subheading heading-size-3">Categorías</h2> <ul> <li class="cat-item cat-item-22"><a href="https://eltecnofilo.es/category/python/">Python</a> </li> <li class="cat-item cat-item-1"><a href="https://eltecnofilo.es/category/uncategorized/">Uncategorized</a> </li> </ul> </div></div><div class="widget widget_meta"><div class="widget-content"><h2 class="widget-title subheading heading-size-3">Meta</h2> <ul> <li><a rel="nofollow" href="https://eltecnofilo.es/wp-login.php">Acceder</a></li> <li><a href="https://eltecnofilo.es/feed/">Feed de entradas</a></li> <li><a href="https://eltecnofilo.es/comments/feed/">Feed de comentarios</a></li> <li><a href="https://es.wordpress.org/">WordPress.org</a></li> </ul> </div></div> </div> </div><!-- .footer-widgets-wrapper --> </aside><!-- .footer-widgets-outer-wrapper --> </div><!-- .footer-inner --> </div><!-- .footer-nav-widgets-wrapper --> <footer id="site-footer" role="contentinfo" class="header-footer-group"> <div class="section-inner"> <div class="footer-credits"> <p class="footer-copyright">© 2020 <a href="https://eltecnofilo.es/">My Blog</a> </p><!-- .footer-copyright --> <p class="powered-by-wordpress"> <a href="https://es.wordpress.org/"> Funciona gracias a WordPress </a> </p><!-- .powered-by-wordpress --> </div><!-- .footer-credits --> <a class="to-the-top" href="#site-header"> <span class="to-the-top-long"> Ir arriba <span class="arrow" aria-hidden="true">↑</span> </span><!-- .to-the-top-long --> <span class="to-the-top-short"> Subir <span class="arrow" aria-hidden="true">↑</span> </span><!-- .to-the-top-short --> </a><!-- .to-the-top --> </div><!-- .section-inner --> </footer><!-- #site-footer --> <script src='https://eltecnofilo.es/wp-includes/js/comment-reply.min.js?ver=5.3.4'></script> <script src='https://eltecnofilo.es/wp-includes/js/wp-embed.min.js?ver=5.3.4'></script> <script> /(trident|msie)/i.test(navigator.userAgent)&&document.getElementById&&window.addEventListener&&window.addEventListener("hashchange",function(){var t,e=location.hash.substring(1);/^[A-z0-9_-]+$/.test(e)&&(t=document.getElementById(e))&&(/^(?:a|select|input|button|textarea)$/i.test(t.tagName)||(t.tabIndex=-1),t.focus())},!1); </script> </body> </html>