Archivos y correo
Traducción automática
Esta página se tradujo automáticamente del inglés y todavía no la revisó un hablante nativo; las correcciones son bienvenidas en GitHub. Si algo no coincide, vale el original en inglés.
Un formulario puede recibir un archivo, una página puede devolver uno, y una acción puede enviar correo. projects/helpdesk usa las tres cosas: tickets con un adjunto, un correo al equipo de soporte y otro a quien hizo el pedido.
Recibir un archivo
Declara el archivo como un parámetro de la acción. accept y maxsize son reglas como minlength: un archivo que no las cumple nunca llega a la acción, y el formulario muestra el motivo junto al campo.
<q:action name="open" method="POST">
<q:param name="title" required="true" minlength="5" />
<q:param name="attachment" type="file" maxsize="5MB" accept=".png,.jpg,.pdf" />
<q:if condition="attachment">
<q:file action="upload" file="{attachment}" result="saved" />
<!-- saved.filename, saved.original_filename, saved.size, saved.mimetype -->
</q:if>
<q:redirect url="/" flash="Ticket opened: {title}" />
</q:action>
<ui:form on-submit="open" submit="Open ticket">
<ui:input bind="title" />
<ui:input bind="attachment" />
</ui:form>El formulario sabe que la acción recibe un archivo: envía multipart/form-data, y attachment es un campo de archivo que ofrece .png,.jpg,.pdf. No escribes ninguna de las dos cosas.
El archivo se guarda en paths.uploads (./uploads salvo que la configuración diga otra cosa), con un nombre seguro; destination="invoices" lo pone en una carpeta dentro de ella. Guarda saved.filename — es la forma de volver a encontrar el archivo.
q:file action es upload, delete o send (más abajo); cualquier otra cosa no pasa el análisis:
<q:file action="copy" file="{attachment}" />Error: <q:file action="copy">: use upload, delete or send
Un campo de varias líneas es <ui:input bind="description" rows="6" />.
Devolver un archivo
Los archivos subidos no se sirven como archivos estáticos: cualquiera con la URL podría leerlos. Una página envía uno, y decide quién puede tenerlo:
<!-- components/attachment/[id].q -->
<q:component name="Attachment">
<q:query name="ticket" datasource="db">
SELECT attachment, attachment_name FROM tickets WHERE id = :id AND attachment IS NOT NULL
<q:param name="id" value="{id}" type="integer" />
</q:query>
<q:if condition="ticket_result.recordCount == 1">
<q:file action="send" file="{ticket[0].attachment}" name="{ticket[0].attachment_name}" />
</q:if>
<p>There is no attachment for ticket #{id}.</p>
</q:component>q:file action="send" termina la página con el archivo como descarga. El nombre guardado viene de la base de datos, nunca de la URL; una ruta fuera de paths.uploads se rechaza, y un archivo que no existe responde 404. Pon una guarda en la página (require_auth, o un q:if con q:redirect) y solo lo recibe quien corresponde.
Enviar correo
Una acción envía un mensaje con q:mail; el servidor es la sección mail: de quantum.config.yaml. De la receta Correo en desarrollo, probada tal como se muestra:
<q:component name="Contact">
<q:action name="send" method="POST">
<q:param name="email" type="email" required="true" />
<q:param name="message" required="true" minlength="5" />
<q:mail to="support@example.com" reply_to="{email}" subject="Contact from {email}" type="text">{message}</q:mail>
<q:redirect url="/" flash="Thanks! We will answer {email}." />
</q:action>
<h1>Contact us</h1>
<q:if condition="flash"><p class="flash">{flash}</p></q:if>
<form method="POST" action="/?action=send">
<input name="email" type="email" />
<textarea name="message"></textarea>
<button>Send</button>
</form>
</q:component># Recipe: mail in development. `host: log` writes each message to the log
# instead of sending it; set SMTP_HOST in production and the same page sends.
paths:
components: ./components
mail:
host: ${SMTP_HOST:-log}
from: support@example.comhost: log escribe cada mensaje en el log en lugar de enviarlo — úsalo en desarrollo. Sin una sección mail:, q:mail es un error que lo dice; nunca finge que envía:
<q:mail to="ana@example.com" subject="Welcome">Hello, Ana!</q:mail>Error: q:mail needs a mail server
Cuando el servidor dice que no
Un mensaje que el servidor no acepta detiene la acción con el motivo del servidor. Cuando el resto de la acción debe ocurrir igual, pon onerror="continue" y verifica <name>_result.success — de la receta Cuando el servidor de correo dice que no, donde el pedido se guarda aunque no se pueda enviar su confirmación:
<q:component name="Order">
<q:action name="order" method="POST">
<q:param name="email" type="email" required="true" />
<q:param name="item" required="true" />
<q:query name="saved" datasource="db">
INSERT INTO orders (email, item) VALUES (:email, :item)
<q:param name="email" value="{email}" type="string" />
<q:param name="item" value="{item}" type="string" />
</q:query>
<!-- onerror="continue": a refused message does not undo the order -->
<q:mail name="confirmation" to="{email}" subject="Your order: {item}"
type="text" onerror="continue">We received your order for {item}.</q:mail>
<q:if condition="confirmation_result.success">
<q:redirect url="/" flash="Ordered {item}. A confirmation is on its way." />
</q:if>
<q:redirect url="/" flash="Ordered {item}. We could not send the confirmation e-mail." />
</q:action>
<h1>Order</h1>
<q:if condition="flash"><p class="flash">{flash}</p></q:if>
<form method="POST" action="/?action=order">
<input name="email" type="email" />
<input name="item" />
<button>Order</button>
</form>
</q:component>El helpdesk (projects/helpdesk) hace lo mismo: guarda primero el ticket y dice, en su mensaje, cuándo no se pudo enviar un correo.
Reglas
FILE-1, FILE-2, MAIL-1, MAIL-2 y UI-14 en la SPEC.