From 88cd2f86751621d73574ac56a9d6c4bedcbdd3d5 Mon Sep 17 00:00:00 2001 From: Arnaud Bos Date: Sun, 11 Sep 2011 05:25:42 +0200 Subject: [PATCH] Fix #24 on Authentication and New project forms usability. - Do not display anymore the identifier field in home. - Let the user enter the id if the slug generated from project name already exists as a project id. - Moved get_billform_for from 'utils' to 'forms', to avoid issue (was 'from forms import ...' into utils, and 'from utils import ...' into forms, which causeed an error). --- budget/forms.py | 35 +++++++++++++++++++++++++++++++---- budget/run.py | 2 +- budget/templates/forms.html | 11 ++++++++--- budget/utils.py | 25 ++++++++++--------------- budget/web.py | 18 ++++++++++++------ 5 files changed, 62 insertions(+), 29 deletions(-) diff --git a/budget/forms.py b/budget/forms.py index 7ac48cc..3ab5f45 100644 --- a/budget/forms.py +++ b/budget/forms.py @@ -2,6 +2,8 @@ from flaskext.wtf import * from wtforms.widgets import html_params from models import Project, Person, Bill from datetime import datetime +from jinja2 import Markup +from utils import slugify def select_multi_checkbox(field, ul_class='', **kwargs): @@ -18,16 +20,41 @@ def select_multi_checkbox(field, ul_class='', **kwargs): return u''.join(html) +def get_billform_for(request, project, set_default=True): + """Return an instance of BillForm configured for a particular project. + + :set_default: if set to True, on GET methods (usually when we want to + display the default form, it will call set_default on it. + + """ + form = BillForm() + form.payed_for.choices = form.payer.choices = [(str(m.id), m.name) for m in project.active_members] + form.payed_for.default = [str(m.id) for m in project.active_members] + + if set_default and request.method == "GET": + form.set_default() + return form + + class ProjectForm(Form): name = TextField("Project name", validators=[Required()]) id = TextField("Project identifier", validators=[Required()]) - password = PasswordField("Password", validators=[Required()]) + password = PasswordField("Private code", validators=[Required()]) contact_email = TextField("Email", validators=[Required(), Email()]) submit = SubmitField("Create the project") def validate_id(form, field): - if Project.query.get(field.data): - raise ValidationError("This project id is already used") + form.id.data = slugify(field.data) + if Project.query.get(form.id.data): + raise ValidationError(Markup("""The project identifier is used + to log in and for the URL of the project. +
+ We tried to generate an identifier for you but + a projet with this identifier already exists. +
+ Please create a new identifier you will be able + to remember. + """)) def save(self): """Create a new project with the information given by this form. @@ -42,7 +69,7 @@ class ProjectForm(Form): class AuthenticationForm(Form): id = TextField("Project identifier", validators=[Required()]) - password = PasswordField("Password", validators=[Required()]) + password = PasswordField("Private code", validators=[Required()]) submit = SubmitField("Get in") diff --git a/budget/run.py b/budget/run.py index b1fad19..c01dcdb 100644 --- a/budget/run.py +++ b/budget/run.py @@ -1,5 +1,5 @@ from web import main, db, mail -import api +#import api from flask import * diff --git a/budget/templates/forms.html b/budget/templates/forms.html index b027763..ba915e8 100644 --- a/budget/templates/forms.html +++ b/budget/templates/forms.html @@ -17,8 +17,11 @@ {% endmacro %} -{% macro submit(field, cancel=False) -%} +{% macro submit(field, cancel=False, home=False) -%}
+ {% if home %} + Back Home + {% endif %} {% if cancel %} @@ -42,12 +45,14 @@ {% include "display_errors.html" %} {{ form.hidden_tag() }} - {{ input(form.name) }} + {% if not home %} {{ input(form.id) }} + {% endif %} + {{ input(form.name) }} {{ input(form.password) }} {{ input(form.contact_email) }} {% if not home %} - {{ submit(form.submit) }} + {{ submit(form.submit, home=True) }} {% endif %} {% endmacro %} diff --git a/budget/utils.py b/budget/utils.py index 262ebfe..f4003eb 100644 --- a/budget/utils.py +++ b/budget/utils.py @@ -1,24 +1,19 @@ +import re from functools import wraps from flask import redirect, url_for, session, request from werkzeug.routing import HTTPException, RoutingException -from models import Bill, Project -from forms import BillForm +def slugify(value): + """Normalizes string, converts to lowercase, removes non-alpha characters, + and converts spaces to hyphens. -def get_billform_for(project, set_default=True): - """Return an instance of BillForm configured for a particular project. - - :set_default: if set to True, on GET methods (usually when we want to - display the default form, it will call set_default on it. - + Copy/Pasted from ametaireau/pelican/utils itself took from django sources. """ - form = BillForm() - form.payed_for.choices = form.payer.choices = [(str(m.id), m.name) for m in project.active_members] - form.payed_for.default = [str(m.id) for m in project.active_members] - - if set_default and request.method == "GET": - form.set_default() - return form + if type(value) == unicode: + import unicodedata + value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore') + value = unicode(re.sub('[^\w\s-]', '', value).strip().lower()) + return re.sub('[-\s]+', '-', value) class Redirect303(HTTPException, RoutingException): """Raise if the map requests a redirect. This is for example the case if diff --git a/budget/web.py b/budget/web.py index f72a686..cb8c4ac 100644 --- a/budget/web.py +++ b/budget/web.py @@ -5,9 +5,9 @@ from flaskext.mail import Mail, Message # local modules from models import db, Project, Person, Bill -from forms import (ProjectForm, AuthenticationForm, BillForm, MemberForm, - InviteForm, CreateArchiveForm) -from utils import get_billform_for, Redirect303 +from forms import (get_billform_for, ProjectForm, AuthenticationForm, BillForm, + MemberForm, InviteForm, CreateArchiveForm) +from utils import Redirect303 """ The blueprint for the web interface. @@ -111,6 +111,12 @@ def create_project(): form.name.data = request.values['project_id'] if request.method == "POST": + # At first, we don't want the user to bother with the identifier + # so it will automatically be missing because not displayed into the form + # Thus we fill it with the same value as the filled name, the validation will + # take care of the slug + if not form.id.data: + form.id.data = form.name.data if form.validate(): # save the object in the db project = form.save() @@ -178,7 +184,7 @@ def list_bills(): bills = g.project.get_bills() return render_template("list_bills.html", bills=bills, member_form=MemberForm(g.project), - bill_form=get_billform_for(g.project) + bill_form=get_billform_for(request, g.project) ) @main.route("//members/add", methods=["GET", "POST"]) @@ -224,7 +230,7 @@ def remove_member(member_id): @main.route("//add", methods=["GET", "POST"]) def add_bill(): - form = get_billform_for(g.project) + form = get_billform_for(request, g.project) if request.method == 'POST': if form.validate(): bill = Bill() @@ -250,7 +256,7 @@ def delete_bill(bill_id): @main.route("//edit/", methods=["GET", "POST"]) def edit_bill(bill_id): bill = Bill.query.get_or_404(bill_id) - form = get_billform_for(g.project, set_default=False) + form = get_billform_for(request, g.project, set_default=False) if request.method == 'POST' and form.validate(): form.save(bill) db.session.commit()