DataRequired validator is broken for wtforms.BooleanField - python

I'm using WTForms (together with Flask, flask-wtf, sqlalchemy) to validate incoming JSON for REST APIs. I realize that WTForms is aimed more towards HTML form rendering and validation, but I chose it because it can autogenerate forms out of my sqlalchemy models (thanks to wtforms.ext.sqlalchemy).
Anyway, here is the problem. One of my models includes boolean field which translates to wtforms.BooleanField with DataRequired validator. The issue is that validation fails with 'This field is required' error message even if I pass correct data. My Form:
class MyForm(Form):
name = TextField('name', validators=[DataRequired()])
disabled = BooleanField('disabled', validators=[DataRequired()])
JSON data is like this:
'{"name": "John", "disabled": "false"}'
What I'm expecting:
{"disabled": "false"} -> validates successful, coerced Python data: {'disabled': False}
{"disabled": "true"} -> validates successful, coerced Python data: {'disabled': True}
{"disabled": ""} or '{"disabled": "foo"}' -> fails validation
Currently in first case validation is failed with {'disabled': [u'This field is required.']}
I know there is a note in docs that says DataRequired validator "require coerced data, not input data", but 1) the form is autogenerated by wtforms.ext.sqlalchemy and 2) how it is supposed to behave if I use InputRequired validator? Check (via form.validate()) that some data exists and then check that this data is "true" or "false"?
To summarize, my question is:
What is the correct way of validating wtforms.BooleanField?
Maybe there is some other framework that can validate incoming JSON against given sqlalchemy models?
Thanks.

There are a number of ways of going about this. You could write your own converter to make of use a radiofield with true/false choices, you could use a data filter, you could set a default value, but I think the behavior you want will be possible with this:
MyForm = model_form(MyModel, db_session=db, field_args = {
'disabled' : {
'false_values': ['false'],
'validators' : [InputRequired()] }
})
EDIT: If you wanted a stricter handler you could do the following:
class BooleanRequired(object):
field_flags = ('required', )
def __init__(self, message=None):
self.message = message
def __call__(self, form, field):
if field.data is None:
if self.message is None:
message = field.gettext('This field is required.')
else:
message = self.message
field.errors[:] = []
raise StopValidation(message)
class StrictBooleanField(BooleanField):
def process_formdata(self, valuelist):
self.data = None
if valuelist:
if valuelist[0] == 'false':
self.data = False
elif valuelist[0] == 'true':
self.data = True
class StrictModelConverter(ModelConverter):
#converts('Boolean')
def conv_Boolean(self, field_args, **extra):
return StrictBooleanField(**field_args)
MyForm = model_form(MyModel, db_session=db, converter=StrictModelConverter(),
field_args = { 'disabled' : { 'validators': [BooleanRequired()] }
})

Related

Django: AuthStateMissing at /oauth/complete/google-oauth2/

I am using social-auth-app-django for GoogleOauth2 authentication. It works fine for all users but in case of django admin it gives me following error:
AuthStateMissing at /oauth/complete/google-oauth2/
Session value state missing.
I have tried all answers posted on stackoverflow but the error still persists. This is the result it returns.
The state value seems to be present there but either it gets null or overridden somehow.
This is my GoogleOAuth2 class, created by overriding social-auth-app-django's GoogleOAuth2 class. Though there is not much difference except for pipeline from base class. It works fine for non-admin user login.
class GoogleOAuth2(GoogleOAuth2):
"""Google OAuth2 authentication backend"""
name = 'google-oauth2'
REDIRECT_STATE = False
AUTHORIZATION_URL = 'https://accounts.google.com/o/oauth2/auth'
ACCESS_TOKEN_URL = 'https://accounts.google.com/o/oauth2/token'
ACCESS_TOKEN_METHOD = 'POST'
REVOKE_TOKEN_URL = 'https://accounts.google.com/o/oauth2/revoke'
REVOKE_TOKEN_METHOD = 'GET'
# The order of the default scope is important
DEFAULT_SCOPE = ['openid', 'email', 'profile']
EXTRA_DATA = [
('refresh_token', 'refresh_token', True),
('expires_in', 'expires'),
('token_type', 'token_type', True)
]
def pipeline(self, pipeline, pipeline_index=0, *args, **kwargs):
out = self.run_pipeline(pipeline, pipeline_index, *args, **kwargs)
user_ip = get_request_ip_address(self.strategy.request)
if not isinstance(out, dict):
return out
user = out.get('user')
if user:
user.social_user = out.get('social')
user.is_new = out.get('is_new')
if user.is_new:
logger.info(f'Register attempt', extra={"email": user.email, "remote_ip": user_ip, "status": "success", "user_id": user.pk, "oauth_backend": "google"})
else:
logger.info(f'Login attempt', extra={"email": user.email, "remote_ip": user_ip, "status": "success", "user_id": user.pk, "oauth_backend": "google"})
return user
I have tried following solutions, setting these values in settings.py file:
SOCIAL_AUTH_REDIRECT_IS_HTTPS = True
SESSION_COOKIE_SAMESITE = None
SESSION_COOKIE_HTTPONLY = False

How to pass arguments to HTML form using flask in case of redirection

I want to pass some arguments to my HTML form from flask when using redirect(url_for('Some End-Point')) instead of render_template(). I have already visited both of these questions
redirect while passing arguments
How can I pass arguments into redirect(url_for()) of Flask?
but neither of them had the answer I am looking for. After some surfing I do find out that I have to use session for this but the problem is I don't know actually how to use that either. When I use this:
return redirect(url_for('register', neg_resp="Username Already Taken"))
the problem was, my output message do generate but came with URL instead and thus my jinja template doesn't receive it. Link from 120.0.0.1:5000/register/ changed to 120.0.0.1:5000/register/?=Username Already Taken
And when I do this:
return redirect(url_for('register'), neg_resp="Username Already Taken")
An error gets generated, TypeError: redirect() got an unexpected keyword argument 'neg_resp'
Here's my Python Code
# Setting Up Route for Register Page
#app.route('/register/', methods=['GET', 'POST'])
def register():
# Fetching Form Data
user = {
"name": request.form.get('name'),
"email": request.form.get('email'),
"username": request.form.get('username'),
"password": request.form.get('password'),
"tasks":[]
}
# Inserting data to Database and Redirecting to Login Page after Successful Registration
if user['name'] != None:
user['password'] = pbkdf2_sha256.encrypt(user['password'])
if mongo.db.appname.find_one({"username": user["username"]}):
return redirect(url_for('register'), neg_resp="Username Already Taken")
else:
mongo.db.appname.insert(user)
return redirect(url_for('login', pos_resp="Registered Successfully"))
return render_template('register.html')
Error
TypeError: redirect() got an unexpected keyword argument 'neg_resp'
This won't work:
return redirect(url_for('register'), neg_resp="Username Already Taken")
because it passes neg_resp as a parameter of redirect instead of url_for.
Here's a basic example of how to pass parameters between routes and to a template:
#app.route('/first/')
def first():
return redirect(url_for('second', passed_value='string value'))
#app.route('/second/')
def second():
passed_value = request.args.get('passed_value')
return render_template('index.html', val_name=passed_value)
So here's what I tried, I made a global dictionary and keep updating it wherever needed, now whenever I want to access some values I directly access it from my dictionary and render it using jinja templating. Apart from this method there are other ways as well like storing data in flask.session also one can use flask.flash() to render messages and access them using messages.get() function in their jinja template, but the problem with it is, it only provides a limited amount of size, if you pass an object or string of beyond that size, the browser simply ignores it and your messages will not be displayed because the message passed is in the form of browser-cookies. So, storing them in my global dictionary works for me:
Here's a small snippet of my final code which similarizes the code I have posted as question:
# Globals
info = {
'logged_in' : False,
'user' : {},
'tasks' : [],
'status' : {
'value' : '',
'positive' : True
}
}
def reset():
'''
function to reset the values of info object
'''
global info
info['logged_in'] = False
info['user'] = {}
info['tasks'] = []
info['status'] = {
'value' : '',
'positive' : True
}
# Setting Up Route for Register Page
#app.route('/register/', methods=['GET', 'POST'])
def register():
'''
function to register an account into the server and database
'''
global info
# Fetching Form Data
user = {
"name": request.form.get('name'),
"email": request.form.get('email'),
"username": request.form.get('username'),
"password": request.form.get('password'),
"tasks":[]
}
# Inserting data to Database and Redirecting to Login Page after Successful Registration
if user['name'] != None:
user['password'] = pbkdf2_sha256.encrypt(user['password'])
if mongo.db.appname.find_one({"username": user["username"]}):
info['status']['value'] = 'Username Already Taken'
info['status']['positive'] = False
return redirect(url_for('register'))
else:
mongo.db.appname.insert(user)
info['status']['value'] = 'Registered Successfully'
info['status']['positive'] = True
return redirect(url_for('login'))
status_val = info['status']['value']
positive_status = info['status']['positive']
reset()
return render_template('register.html', value = status_val, positive = positive_status)

With Flask-Admin and Flask how can I submit a form\view based on ModelView from code?

With Flask-Admin and Flask how can I submit a form\view based on ModelView from code?
I'm trying to create a separate view\form that would allow user to add multiple entries with one form. Specifically allowing to upload multiple images with common prefix name and common parameters. I'd like to do it by submitting a single-image upload form from code, because it does some additional processing like resizing images and I'd like to let Flask-Admin handle connecting database entries and files.
Here's the form I'd like to submit from code:
class ImageView(ModelView):
def _list_thumbnail(view, context, model, name):
if not model.path:
return ''
return Markup('<img src="%s">' % url_for('media',
filename=form.thumbgen_filename(model.path)))
column_labels = dict(show_in_header="Show In Header?",
path="Image")
form_create_rules = ("name",
"tags",
rules.Text(
"Use this image as header. If more than one image is selected header image will be random each time the page is loaded."),
"show_in_header",
"path")
form_excluded_columns = ("timestamp")
column_formatters = {
'path': _list_thumbnail
}
thumbnail_size = config("media", "thumbnail_size")
form_extra_fields = {
'path': BroImageUploadField('Image',
base_path=IMAGES_FOLDER,
thumbnail_size=(thumbnail_size, thumbnail_size, True),
endpoint="media",
url_relative_path='media',
relative_path=None)
}
def is_accessible(self):
return current_user.is_authenticated
def inaccessible_callback(self, name, **kwargs):
# redirect to login page if user doesn't have access
return redirect(url_for('login', next=request.url))
And here I'm creating a form and I'm just not sure that .process() is the function to submit it? Is there one at all?
lass MultipleImagesUploadView(BaseView):
#expose("/", methods=["GET", "POST"])
def index(self):
if request.method == "POST":
a_form = MultipleImagesForm(request.form)
base_name = a_form.base_name.data
tags = a_form.tags.data
show_in_header = a_form.show_in_header.data
print (request.files.getlist(a_form.images.name))
uploaded_files = request.files.getlist(a_form.images.name)
for i, uf in enumerate(uploaded_files):
try:
name, ext = os.path.splitext(uf.filename)
if ext not in IMAGE_EXTENSIONS:
flash("Image file {} was skipped as it's extension is not supported ({}).".format(uf.filename, ext), category="warning")
continue
image_contents = uf.stream.read()
image_form = ImageView()
image_form.name.data = "{}_{}".format(base_name, i)
image_form.tags.data = tags
image_form.show_in_header.data = show_in_header
image_form.path.data = image_contents
image_form.process()
except Exception as e:
flash ("Unhandled exception: {}".format(e), category="warning")
flash("Images were added to the gallery.", category='success')
a_form = MultipleImagesForm()
print("############", a_form)
return self.render('/admin/multiple_images_upload.html', form=a_form)
I can't figure out a way to submit a form from code, been trying to find the answer in docs and google for hours now with no luck.
Found the issue. In my case I was missing the enctype="multipart/form-data". Without that files part was sent as empty.
Also changed to using from flask_wtf import FlaskForm and enabling it as {{ form.files(class="form-control", multiple="") }} in the template.
Files can then be accessed with uploaded_files = request.files.getlist("files") on POST request, it will hold array of file-objects.
I hope this helps someone. If any additional formatting is required I will add or expand the answer.

How to keep database data when run py.test?

I want to test my query db function.
import pytest
from account_system.action import my_action
from account_system.models import MyUser
#pytest.mark.django_db
def test_ok_profile():
req = FakeRequest()
email = 'tester#example.com'
MyUser.objects.create_user(
email=email,
password="example",
)
# query MyUser table and return result
result = my_action.get_profile(email)
assert result == 'success'
But it's fail.
> assert result == 'success'
E assert None == 'success'
The function doesn't get any result from DB.
I check the database data and doesn't see any record.
(ex. User tester#example.com)
How to rewrite my code for testing?
Or how to keep data in database?
Thank you,
This is the way of how databases tests works..
When you set a django_db, the pytest will rollback your data after use.
But if you want to use the same data in several tests, you should take a look on
pytest.fixtures and factory_boy like :
import factory
class UserFactory(factory.DjangoModelFactory):
class Meta:
model = User
#pytest.fixture
def user():
return UserFactory(email = "bla#bla.com", password = "blabla")
And now you apply this reference on your test code:
test_test_ok_profile(user):
assert user.email = "bla#bla.com"

Data is missing after using #validate with Schema

I'm working on validating my webapp, which is using Turbogears 2.3.3 and formencode 1.3
I want to validate a dynamic form that the user has created through a form creation process.
I'm sending the form fields from the client to the server using json to help organize things.
Here is how I'm sending the data:
var dataToSend = JSON.stringify({
'num_of_copies': num_of_copies.val(),
'amountAnswers' : amountAnswers,
'yesNoAnswers' : yesNoAnswers,
'selectAnswers' : selectAnswers,
'comments':comments.val()
})
$.ajax({
type: 'POST',
url: siteProxy+'orders/saveOrderItem',
data: {'data':dataToSend},
dataType: "json",
success: function (data, textStatus) {
if (textStatus == "success") {
if (data.errors){
console.log(data.errors)
}
}
},
error: function (data, textStatus) {
alert('error');
}
})
On The server I want to validate the data and then do some stuff
#expose('json')
#validate(validators=orderItemSchema(),error_handler=simpleErrorHandler)
def saveOrderItem(self,**kw):
answers = json.loads(kw['data'])
...... do stuff ...
Without the validations, my code works.
Here is my validation Schema:
class orderItemSchema(Schema):
def _convert_to_python(self, value_dict, state):
value_dict = json.loads(value_dict['data'])
super(orderItemSchema,self)._convert_to_python(value_dict, state)
num_of_copies = validators.Number(min=1)
comments = validators.UnicodeString()
amountAnswers = ForEach(AmountAnswerValidator())
yesNoAnswers = ForEach(YesNoAnswerValidator())
selectAnswers = ForEach(SelectAnswerValidator())
The validation works well.
My problem is this: after the validation, kw turns to none, and I can't do stuff in
def saveOrderItem(self,**kw):
I think the problem lies somewhere in this part of the code:
class orderItemSchema(Schema):
def _convert_to_python(self, value_dict, state):
value_dict = json.loads(value_dict['data'])
super(orderItemSchema,self)._convert_to_python(value_dict, state)
Thanks for the help
Probably orderItemSchema._convert_to_python is missing return value. Should be return super(orderItemSchema,self)._convert_to_python(value_dict, state) or you will be returning None as the converted value.
If you are using a recent tg version I suggest you also have a look at #decode_params decorator ( http://turbogears.readthedocs.org/en/latest/reference/classes.html#tg.decorators.decode_params ), it will extract the controller parameters from the json body and let validation flow as usual. It will avoid the two json.load in your code.

Categories