Re: [flask] Flask, WTForms, and duplicate IDs
- From:
- Alex
- Date:
- 2011-06-17 @ 18:48
On Fri, Jun 17, 2011 at 5:55 PM, Daniele Nicolodi <daniele@grinta.net> wrote:
> What's the preferred pattern to transform a duplicate key error into a
> validation error?
Here how I do it:
First I create a custum WTForm validator
class Unique(object):
""" Validator that checks field uniqueness """
def __init__(self, model, field, message=None):
self.model = model
self.field = field
if not message:
message = u'This element already exists'
self.message = message
def __call__(self, form, field):
check = self.model.query.filter(self.field == field.data).first()
if check:
raise ValidationError(self.message)
Then in the form class:
class MyForm(Form):
"""Form that manage what you like"""
my_field = TextField('URL', [validators.Required(),
validators.URL(), Unique(Site, Site.URL)])
Re: [flask] Flask, WTForms, and duplicate IDs
- From:
- Daniele Nicolodi
- Date:
- 2011-06-17 @ 17:01
Hello,
Thank you all for the answers to my question. I reply to my own mail
just because all the answers are many and I would have to answer almost
all of them in the same way.
I'm well aware of WTForms customs validators, and I use them in my
application, but they do not solve my problem in the way I would like to
solve it.
My point is that I would like the validation of key uniqueness and
creation of new object to be a single atomic operation, to catch the
rare case when an object with the same id is created between validation
and actual creation of the object (and to spare an SQL query, but this
is definitely a minor concern).
Cheers,
--
Daniele
Re: [flask] Flask, WTForms, and duplicate IDs
- From:
- Sean Chittenden
- Date:
- 2011-06-17 @ 18:02
> My point is that I would like the validation of key uniqueness and
> creation of new object to be a single atomic operation, to catch the
> rare case when an object with the same id is created between validation
> and actual creation of the object (and to spare an SQL query, but this
> is definitely a minor concern).
Or you can use PostgreSQL and a stored procedure to handle this logic. In SQL:
> SELECT result, "column", message FROM aaa.register(email :=
'user@example.com', password := 'myfailpw', ip_address := '11.22.33.44')
AS (result BOOL, "column" TEXT, message TEXT);
Backed by the function:
https://github.com/sean-/flask-skeleton/blob/master/sql/initialize/300_funcs.sql.in#L273
PostgreSQL will return a tuple (success BOOL, column TEXT, message TEXT)
which makes it easy to set very tailored error messages:
https://github.com/sean-/flask-skeleton/blob/master/skeleton/modules/aaa/views.py#L203
Here's the complete code used to call this:
https://github.com/sean-/flask-skeleton/blob/master/skeleton/modules/aaa/views.py#L184
I mixed and matched a PL function for handling the creation of a user for
the sake of demoing, but normally I'd handle all of this in a single PL
call to the database. -sc
--
Sean Chittenden
sean@chittenden.org
Re: [flask] Flask, WTForms, and duplicate IDs
- From:
- Adam Oakman
- Date:
- 2011-06-17 @ 17:19
Hmm I work with MSSQL a lot more than MySQL and I don't know if this
is even possible in MySQL. At the db level the only authoritative way
of doing that would be to perform everything in a database transaction
that kept a table write lock on your usernames. But that could be an
horribly expensive blocking operation depending on your load.
On Jun 17, 2011, at 12:01 PM, Daniele Nicolodi wrote:
> Hello,
>
> Thank you all for the answers to my question. I reply to my own mail
> just because all the answers are many and I would have to answer
> almost
> all of them in the same way.
>
> I'm well aware of WTForms customs validators, and I use them in my
> application, but they do not solve my problem in the way I would
> like to
> solve it.
>
> My point is that I would like the validation of key uniqueness and
> creation of new object to be a single atomic operation, to catch the
> rare case when an object with the same id is created between
> validation
> and actual creation of the object (and to spare an SQL query, but this
> is definitely a minor concern).
>
> Cheers,
> --
> Daniele
Re: [flask] Flask, WTForms, and duplicate IDs
- From:
- Daniele Nicolodi
- Date:
- 2011-06-17 @ 17:25
On 17/06/11 19:19, Adam Oakman wrote:
> Hmm I work with MSSQL a lot more than MySQL and I don't know if this
> is even possible in MySQL. At the db level the only authoritative way
> of doing that would be to perform everything in a database transaction
> that kept a table write lock on your usernames. But that could be an
> horribly expensive blocking operation depending on your load.
That's definitely possible at the database level: just try the insert
and catch any integrity check error. That's what the code in my original
email does. The only drawback is that you have some kind of validation
code outside the form definition. It looks a bit ugly (but not much
uglier that having to specify different validators for the upgrade and
create cases).
Cheers,
--
Daniele
Re: [flask] Flask, WTForms, and duplicate IDs
- From:
- Adam Oakman
- Date:
- 2011-06-17 @ 18:49
Yeah I agree its a bit ugly.
Just thinking out loud. I assume username is your primary key on the
table. You might add a datetime column to the table named something
like pending_expiry. When you want to check if the username is in use
you just insert into users (username, now + 30 mins). If the username
is already in use it will throw an error and you can report back. If
its not you have effectively reserved the username from other signups,
without any lengthy locks. When ready to save the object for real you
just remove that expiry. You would need to have a clean up method to
remove the accounts that no one finished the signup process for, but
that should be reasonably trivial.
On Jun 17, 2011, at 12:25 PM, Daniele Nicolodi wrote:
> On 17/06/11 19:19, Adam Oakman wrote:
>> Hmm I work with MSSQL a lot more than MySQL and I don't know if this
>> is even possible in MySQL. At the db level the only authoritative way
>> of doing that would be to perform everything in a database
>> transaction
>> that kept a table write lock on your usernames. But that could be an
>> horribly expensive blocking operation depending on your load.
>
> That's definitely possible at the database level: just try the insert
> and catch any integrity check error. That's what the code in my
> original
> email does. The only drawback is that you have some kind of validation
> code outside the form definition. It looks a bit ugly (but not much
> uglier that having to specify different validators for the upgrade and
> create cases).
>
> Cheers,
> --
> Daniele
Re: [flask] Flask, WTForms, and duplicate IDs
- From:
- Sean Chittenden
- Date:
- 2011-06-17 @ 19:03
> Just thinking out loud. I assume username is your primary key on the
> table. You might add a datetime column to the table named something
> like pending_expiry. When you want to check if the username is in use
> you just insert into users (username, now + 30 mins). If the username
> is already in use it will throw an error and you can report back. If
> its not you have effectively reserved the username from other signups,
> without any lengthy locks. When ready to save the object for real you
> just remove that expiry. You would need to have a clean up method to
> remove the accounts that no one finished the signup process for, but
> that should be reasonably trivial.
Asynchronous operations need to have their state maintained someplace....
databases are fantastic at doing this very easily.
CREATE TABLE "user" (
id SERIAL NOT NULL,
username TEXT NOT NULL,
active BOOL NOT NULL DEFAULT TRUE,
PRIMARY KEY(id), -- This really should be "(username) WHERE active =
TRUE" and id is the surrogate key that gets used throughout the database
);
-- User a WHERE clause on your UNIQUE INDEX
CREATE UNIQUE INDEX user_username_lower_udx ON "user"(LOWER(username))
WHERE active = TRUE;
CREATE TABLE user_confirmation (
user_id INT NOT NULL,
confirmation_sent_utc TIMESTAMP WITH TIME ZONE NOT NULL,
confirmed_utc TIMESTAMP WITH TIME ZONE,
confirmed BOOL,
FOREIGN KEY(user_id) REFERENCES "user"(id)
);
-- Create an INDEX that you can index_scan easily to determine what
usernames need to be expired
CREATE INDEX user_confirmation_sent_idx ON
user_confirmation(confirmation_sent_utc) WHERE confirmed IS NULL OR
confirmed IS FALSE;
Then run a cron job that does:
UPDATE "user" SET active = FALSE WHERE id IN(SELECT uc.user_id FROM
user_confirmation AS uc WHERE uc.confirmed IS NULL OR uc.confirmed = FALSE
OR uc.confirmation_sent_utc + '30 minutes'::INTERVAL < NOW());
Easy mode. -sc
--
Sean Chittenden
sean@chittenden.org
Re: [flask] Flask, WTForms, and duplicate IDs
- From:
- Adam Oakman
- Date:
- 2011-06-17 @ 16:11
I do it all with WTForms and a custom validator, I'm using GAE for my
data store. Probably not perfect but it works and is quite simple.
def unique_user_name(form, field):
users = db.Query(Users).filter('user_name =', field.data)
if users.count() == 1:
raise validators.ValidationError('That user name is already
in use, please select another.')
class RegisterForm(Form):
user_name = TextField(u'Username*',
validators=[validators.Required(), validators.length(max=200),
unique_user_name])
password = PasswordField(u'Password*',
validators=[validators.Required(), validators.EqualTo('confirm',
message='Passwords must match')])
confirm = PasswordField(u'Confirm Password*')
On Jun 17, 2011, at 10:55 AM, Daniele Nicolodi wrote:
> Hello,
>
> I'm sorry, the subject of this mail is kind of vague, but I'm unable
> to
> summarize my problem any better. In my application I use WTForms and I
> would like to validate user chosen object names for being unique.
>
> Storing the data into an relation database it is easy to add such
> constrain at the storage level, the creation of the new object with a
> duplicate ID would thus fail at insert time, and this is the only form
> of validation I would trust.
>
> What's the preferred pattern to transform a duplicate key error into a
> validation error?
>
> Currently I do the following (I hope the code is clear enough)::
>
>
> @app.route('/create', methods=('GET', 'POST'))
> @require('admin')
> def create():
> form = IUser()
> if request.method == 'POST' and form.validate():
> user = User()
> form.update(user)
> try:
> user.create()
> except DuplicateKeyError: # I wish MySQLdb would have
> # something like this
> from.username.errors.append('Duplicated user name.')
> else:
> flash('User "%s" created.' % form.data['username'])
> return redirect(url_for('manage.users.index'))
> return render_template('users/create.html', form=form)
>
>
> but it feels suboptimal. Suggestions?
>
> Thank you. Cheers,
> --
> Daniele
Re: [flask] Flask, WTForms, and duplicate IDs
- From:
- Jesse
- Date:
- 2011-06-17 @ 16:24
Here's my version, just for comparison's sake :)
class UniqueValidator(object):
def __init__(self, attribute, message=None):
self.attribute = attribute
if not message:
message = u'This field\'s value must be unique.'
self.message = message
def __call__(self, form, field):
if
self.attribute.parententity.class_.query.filter(self.attribute==field.data).count()
> 0:
raise wtf.ValidationError(self.message)
class RegisterForm(wtf.Form):
username = wtf.TextField('Username', validators=[
wtf.Required(),
UniqueValidator(User.username),
...
])
On Fri, Jun 17, 2011 at 9:11 AM, Adam Oakman <adam.oakman@gmail.com> wrote:
> I do it all with WTForms and a custom validator, I'm using GAE for my
> data store. Probably not perfect but it works and is quite simple.
>
> def unique_user_name(form, field):
> users = db.Query(Users).filter('user_name =', field.data)
> if users.count() == 1:
> raise validators.ValidationError('That user name is already
> in use, please select another.')
>
> class RegisterForm(Form):
> user_name = TextField(u'Username*',
> validators=[validators.Required(), validators.length(max=200),
> unique_user_name])
> password = PasswordField(u'Password*',
> validators=[validators.Required(), validators.EqualTo('confirm',
> message='Passwords must match')])
> confirm = PasswordField(u'Confirm Password*')
>
> On Jun 17, 2011, at 10:55 AM, Daniele Nicolodi wrote:
>
>> Hello,
>>
>> I'm sorry, the subject of this mail is kind of vague, but I'm unable
>> to
>> summarize my problem any better. In my application I use WTForms and I
>> would like to validate user chosen object names for being unique.
>>
>> Storing the data into an relation database it is easy to add such
>> constrain at the storage level, the creation of the new object with a
>> duplicate ID would thus fail at insert time, and this is the only form
>> of validation I would trust.
>>
>> What's the preferred pattern to transform a duplicate key error into a
>> validation error?
>>
>> Currently I do the following (I hope the code is clear enough)::
>>
>>
>> @app.route('/create', methods=('GET', 'POST'))
>> @require('admin')
>> def create():
>> form = IUser()
>> if request.method == 'POST' and form.validate():
>> user = User()
>> form.update(user)
>> try:
>> user.create()
>> except DuplicateKeyError: # I wish MySQLdb would have
>> # something like this
>> from.username.errors.append('Duplicated user name.')
>> else:
>> flash('User "%s" created.' % form.data['username'])
>> return redirect(url_for('manage.users.index'))
>> return render_template('users/create.html', form=form)
>>
>>
>> but it feels suboptimal. Suggestions?
>>
>> Thank you. Cheers,
>> --
>> Daniele
>
>
Re: [flask] Flask, WTForms, and duplicate IDs
- From:
- Amirouche Boubekki
- Date:
- 2011-06-17 @ 16:05
Héllo Daniele,
If I understand well you want username to be unique in the application,
check out this code for an example of this feature (with mongoalchemy tough)
``unique_login`` and ``unique_email`` [1]. I think the code is explicit
enough but I may add more explanation if you want.
[1]
https://bitbucket.org/abki/andalucia/src/72f835438458/andalucia/forms.py#cl-42
2011/6/17 Daniele Nicolodi <daniele@grinta.net>
> Hello,
>
> I'm sorry, the subject of this mail is kind of vague, but I'm unable to
> summarize my problem any better. In my application I use WTForms and I
> would like to validate user chosen object names for being unique.
>
> Storing the data into an relation database it is easy to add such
> constrain at the storage level, the creation of the new object with a
> duplicate ID would thus fail at insert time, and this is the only form
> of validation I would trust.
>
> What's the preferred pattern to transform a duplicate key error into a
> validation error?
>
> Currently I do the following (I hope the code is clear enough)::
>
>
> @app.route('/create', methods=('GET', 'POST'))
> @require('admin')
> def create():
> form = IUser()
> if request.method == 'POST' and form.validate():
> user = User()
> form.update(user)
> try:
> user.create()
> except DuplicateKeyError: # I wish MySQLdb would have
> # something like this
> from.username.errors.append('Duplicated user name.')
> else:
> flash('User "%s" created.' % form.data['username'])
> return redirect(url_for('manage.users.index'))
> return render_template('users/create.html', form=form)
>
>
> but it feels suboptimal. Suggestions?
>
> Thank you. Cheers,
> --
> Daniele
>
Re: [flask] Flask, WTForms, and duplicate IDs
- From:
- Alfred Hall
- Date:
- 2011-06-17 @ 16:05
Here is how I do it:
class ProjectForm(Form):
id = IntegerField("Id")
name = TextField("Name", validators=[
required(message="You must provide project name")
])
description = TextAreaField("Description", validators=[
required(message="You must provide project name")
])
def validate_name(form, field):
# If we're editing and not changing the name we don't need to validate
# any further.
if form.id.data:
obj = Project.query.get(form.id.data)
if obj.name == field.data:
return
count = Project.query.filter_by(name=field.data).count()
if count > 0:
raise ValidationError(u'Name must be unique')
@project.route('/edit/<int:id>/', methods=['POST'])
@admin_permission.require(401)
def edit(id):
project = Project.query.get_or_404(id)
# Do validation of new data.
form = ProjectForm(id=id)
if not form.validate_on_submit():
return jsonify(success=False, errors=form.errors,
csrf=form.csrf.data,
message="Validation failed")
# Do the update of the object.
form.populate_obj(project)
db.session.add(project)
db.session.commit()
return jsonify(success=True, message="Project updated",
csrf=form.csrf.data)
@project.route('/create/', methods=['POST'])
@admin_permission.require(401)
def create():
form = ProjectForm()
if not form.validate_on_submit():
return jsonify(success=False, errors=form.errors,
csrf=form.csrf.data,
message="Validation failed")
project = Project()
form.populate_obj(project)
db.session.add(project)
db.session.commit()
return jsonify(success=True, message="Project created",
csrf=form.csrf.data)
Then will catch 99% and then if someone changes it between the checks
you will get a 500 this is how you tackle that in an errorhandler:
@app.errorhandler(500)
def server_error(error):
if request.is_xhr:
return jsonify(error='Sorry, an error has occurred')
return render_template("errors/500.html", error=error)
I want all my validation logic in the forms and avoid all try/except
in the views if necessary.
On Fri, Jun 17, 2011 at 4:55 PM, Daniele Nicolodi <daniele@grinta.net> wrote:
> Hello,
>
> I'm sorry, the subject of this mail is kind of vague, but I'm unable to
> summarize my problem any better. In my application I use WTForms and I
> would like to validate user chosen object names for being unique.
>
> Storing the data into an relation database it is easy to add such
> constrain at the storage level, the creation of the new object with a
> duplicate ID would thus fail at insert time, and this is the only form
> of validation I would trust.
>
> What's the preferred pattern to transform a duplicate key error into a
> validation error?
>
> Currently I do the following (I hope the code is clear enough)::
>
>
> @app.route('/create', methods=('GET', 'POST'))
> @require('admin')
> def create():
> form = IUser()
> if request.method == 'POST' and form.validate():
> user = User()
> form.update(user)
> try:
> user.create()
> except DuplicateKeyError: # I wish MySQLdb would have
> # something like this
> from.username.errors.append('Duplicated user name.')
> else:
> flash('User "%s" created.' % form.data['username'])
> return redirect(url_for('manage.users.index'))
> return render_template('users/create.html', form=form)
>
>
> but it feels suboptimal. Suggestions?
>
> Thank you. Cheers,
> --
> Daniele
>
Re: [flask] Flask, WTForms, and duplicate IDs
- From:
- Daniele Nicolodi
- Date:
- 2011-06-17 @ 16:54
On 17/06/11 18:05, Alfred Hall wrote:
> Then will catch 99% and then if someone changes it between the checks
> you will get a 500 this is how you tackle that in an errorhandler:
> I want all my validation logic in the forms and avoid all try/except
> in the views if necessary.
Hello Alfred,
thank you for sharing for your code, this is close to my initial
solution, but I'm worried exactly about the 1% cases that this solution
misses. I do not like my solution exactly for the points you raise.
Cheers,
--
Daniele
Re: [flask] Flask, WTForms, and duplicate IDs
- From:
- Ron DuPlain
- Date:
- 2011-06-17 @ 16:03
Hi Daniele,
On Fri, Jun 17, 2011 at 11:55 AM, Daniele Nicolodi <daniele@grinta.net> wrote:
> I'm sorry, the subject of this mail is kind of vague, but I'm unable to
> summarize my problem any better. In my application I use WTForms and I
> would like to validate user chosen object names for being unique.
>
> Storing the data into an relation database it is easy to add such
> constrain at the storage level, the creation of the new object with a
> duplicate ID would thus fail at insert time, and this is the only form
> of validation I would trust.
>
> What's the preferred pattern to transform a duplicate key error into a
> validation error?
Check WTForms custom validators:
http://wtforms.simplecodes.com/docs/0.6.1/validators.html#custom-validators
You can perform a query that raises ValidationError on duplicate keys.
-Ron