tl;dr: I need to change the order in which WTForms validators validate the user input. How do I do that?
Details:
Flask code:
class SampleForm(Form):
user_email = user_email_field
...
#api.route('/sample-route')
class ClassName(Resource):
#api.expect(sample_payload)
#api.marshal_with(sample_response)
def post(self):
form = SampleForm(formdata=MultiDict(api.payload))
if not form.validate():
return {"form_errors": form.errors}, 400
...
WTForms validation field:
user_email_field = EmailField('Email Address',[
validators.InputRequired(Errors.REQUIRED_FIELD),
validators.Length(min=5, max=256),
validators.Email(Errors.INVALID_EMAIL_ADDRESS),
])
Problem is, user_email is checked by validators in the wrong order. I send a request with the following body:
{
"user_email": ""
}
I get this response:
{
"form_errors": {
"user_email": [
"'' is too short"
]
}
}
As you see, despite being 2nd in the list of validators, validators.Length() kicks in before everything else.
If I comment it out in the validation field like that:
user_email_field = EmailField('Email Address',[
validators.InputRequired(Errors.REQUIRED_FIELD),
# validators.Length(min=5, max=256),
validators.Email(Errors.INVALID_EMAIL_ADDRESS),
])
then the exact same request will yield a desired response:
{
"errors": null,
"success": null,
"form_errors": {
"user_email": [
"REQUIRED_FIELD"
]
}
}
However, this is not a working solution because, of course, then eMail won't be checked for its length.
Question:
How do I change the priority of these validators? How do I make WTForms always check the user input with validators.InputRequired() FIRST and with validators.Length() SECOND and not the other way around?
As per your question:
You can use custom validators for that purpose. Then you can change the order of validation by if, elif, else logic within.
class Test(Form):
test_field = StringField('Test_Field')
submit = SubmitField()
def validate_test_field(self, test_field):
try:
if len(test_field.data) == 0:
raise wtforms.validators.ValidationError('Input is required for this field!')
elif len(test_field.data) not in range(5, 257):
raise wtforms.validators.ValidationError(f'Error in input length.')
except TypeError as e:
raise wtforms.validators.ValidationError('Input is required for this field!')
Check the documentation for custom validators here
Related
Using Mocha and Chai, I am trying to check whether JSON array contains a specific text. I tried multiple things suggested on this site but none worked.
await validatePropertyIncludes(JSON.parse(response.body), 'scriptPrivacy');
async validatePropertyIncludes(item, propertyValue) {
expect(item).to.contain(propertyValue);
}
Error that I getting:
AssertionError: expected [ Array(9) ] to include 'scriptPrivacy'
My response from API:
[
{
"scriptPrivacy": {
"settings": "settings=\"foobar\";",
"id": "foobar-notice-script",
"src": "https://foobar.com/foobar-privacy-notice-scripts.js",
}
You can check if the field is undefined.
If field exists in the JSON object, then won't be undefined, otherwise yes.
Using filter() expresion you can get how many documents don't get undefined.
var filter = object.filter(item => item.scriptPrivacy != undefined).length
If attribute exists into JSON file, then, variable filter should be > 0.
var filter = object.filter(item => item.scriptPrivacy != undefined).length
//Comparsion you want: equal(1) , above(0) ...
expect(filter).to.equal(1)
Edit:
To use this method from a method where you pass attribute name by parameter you can use item[propertyName] because properties into objects in node can be accessed as an array.
So the code could be:
//Call function
validatePropertyIncludes(object, 'scriptPrivacy')
function validatePropertyIncludes(object, propertyValue){
var filter = object.filter(item => item[propertyValue] != undefined).length
//Comparsion you want: equal(1) , above(0) ...
expect(filter).to.equal(1)
}
I am trying to create an input that takes a string that will be used as the href value for a tag. The href can be a url OR an email (for mailto:).
It works if I just check for email, or if I just check for URL. However, I want to check for one or the other. I am looking through yup documentation but I can't find a way to do an OR.
I noticed that there is a when to test for another field but I'm not checking if another field is true or not, or use test but I also can't seem to get it to work.
const vSchema = yup.object().shape({
text: yup.string().required(),
href: yup
.string()
.email('Link must be a URL or email')
.url('Link must be a URL or email')
.required('Link is a required field'),
});
test this
yup.addMethod(yup.string, "or", function(schemas, msg) {
return this.test({
name: "or",
message: "Please enter valid url or email." || msg,
test: value => {
if (Array.isArray(schemas) && schemas.length > 1) {
const resee = schemas.map(schema => schema.isValidSync(value));
return resee.some(res => res);
} else {
throw new TypeError("Schemas is not correct array schema");
}
},
exclusive: false
});
});
I want to add status field to error response, so instead of this:
{
"errors": [
{
"message": "Authentication credentials were not provided",
"locations": [
{
"line": 2,
"column": 3
}
]
}
],
"data": {
"viewer": null
}
}
It should be like this:
{
"errors": [
{
"status": 401, # or 400 or 403 or whatever error status suits
"message": "Authentication credentials were not provided",
"locations": [
{
"line": 2,
"column": 3
}
]
}
],
"data": {
"viewer": null
}
}
I found out that I only can change message by raising Exception inside resolver: raise Error('custom error message'), but how to add field?
Code example:
class Query(UsersQuery, graphene.ObjectType):
me = graphene.Field(SelfUserNode)
def resolve_me(self, info: ResolveInfo):
user = info.context.user
if not user.is_authenticated:
# but status attr doesn't exist...
raise GraphQLError('Authentication credentials were not provided', status=401)
return user
Update the default GraphQLView with the following:
from graphene_django.views import GraphQLView as BaseGraphQLView
class GraphQLView(BaseGraphQLView):
#staticmethod
def format_error(error):
formatted_error = super(GraphQLView, GraphQLView).format_error(error)
try:
formatted_error['context'] = error.original_error.context
except AttributeError:
pass
return formatted_error
urlpatterns = [
path('api', GraphQLView.as_view()),
]
This will look for the context attribute in any exceptions raised. If it exists, it'll populate the error with this data.
Now you can create exceptions for different use cases that populate the context attribute. In this case you want to add the status code to errors, here's an example of how you'd do that:
class APIException(Exception):
def __init__(self, message, status=None):
self.context = {}
if status:
self.context['status'] = status
super().__init__(message)
You'd use it like this:
raise APIException('Something went wrong', status=400)
I didn't found a way to solve your problem int the way that you propose, otherwise i extend the LoginRequiredMixin class like this:
class LoginRequiredMixin:
def dispatch(self, info, *args, **kwargs):
if not info.user.is_authenticated:
e = HttpError(HttpResponse(status=401, content_type='application/json'), 'Please log in first')
response = e.response
response.content = self.json_encode(info, [{'errors': [self.format_error(e)]}])
return response
return super().dispatch(info, *args, **kwargs)
class PrivateGraphQLView(LoginRequiredMixin, GraphQLView):
schema=schema
and in your url:
from django.views.decorators.csrf import csrf_exempt
from educor.schema import PrivateGraphQLView
url(r'^graphql', csrf_exempt(PrivateGraphQLView.as_view(batch=True)))
you can't see the status with the graphiql but in your client you can get it in the headers or you could modify this line to add in the response response.content = self.json_encode(info, [{'errors': [self.format_error(e)]}])
. Hope it helps anyway i'll leave you another possible solution https://github.com/graphql-python/graphene-django/issues/252
Im using stream-django with django REST framework and the enriched activities are throwing "not JSON serializable" on the objects returned from enrichment, which is as expected as they have not gone through any serializing.
How do i customize the enrichment process so that it returns a serialized object from my drf serializer and not the object itself?
Some example data, not enriched:
"is_seen": false,
"is_read": false,
"group": "19931_2016-04-04",
"created_at": "2016-04-04T08:53:42.601",
"updated_at": "2016-04-04T11:33:26.140",
"id": "0bc8c85a-fa59-11e5-8080-800005683205",
"verb": "message",
"activities": [
{
"origin": null,
"verb": "message",
"time": "2016-04-04T11:33:26.140",
"id": "0bc8c85a-fa59-11e5-8080-800005683205",
"foreign_id": "chat.Message:6",
"target": null,
"to": [
"notification:1"
],
"actor": "auth.User:1",
"object": "chat.Message:6"
}
The view:
def get(self, request, format=None):
user = request.user
enricher = Enrich()
feed = feed_manager.get_notification_feed(user.id)
notifications = feed.get(limit=5)['results']
enriched_activities=enricher.enrich_aggregated_activities(notifications)
return Response(enriched_activities)
I solved it by doing the following:
property tag on the model that returns the serializer class
#property
def activity_object_serializer_class(self):
from .serializers import FooSerializer
return FooSerializer
Then used this to serialize the enriched activities. Supports nesting.
#staticmethod
def get_serialized_object_or_str(obj):
if hasattr(obj, 'activity_object_serializer_class'):
obj = obj.activity_object_serializer_class(obj).data
else:
obj = str(obj) # Could also raise exception here
return obj
def serialize_activities(self, activities):
for activity in activities:
for a in activity['activities']:
a['object'] = self.get_serialized_object_or_str(a['object'])
# The actor is always a auth.User in our case
a['actor'] = UserSerializer(a['actor']).data
return activities
and the view:
def get(self, request, format=None):
user = request.user
enricher = Enrich()
feed = feed_manager.get_notification_feed(user.id)
notifications = feed.get(limit=5)['results']
enriched_activities = enricher.enrich_aggregated_activities(notifications)
serialized_activities = self.serialize_activities(enriched_activities)
return Response(serialized_activities)
The enrich step replaces string references into full Django model instances.
For example: the string "chat.Message:6" is replaced with an instance of chat.models.Message (same as Message.objects.get(pk=6)).
By default DRF does not know how to serialize Django models and fails with a serialization error. Luckily serializing models is a very simple task when using DRF. There is a built-in serializer class that is specific to Django models (serializers.ModelSerializer).
The documentation of DRF explains this process in detail here: http://www.django-rest-framework.org/api-guide/serializers/#modelserializer.
In your case you probably need to use nested serialization and make the serialization of the object field smart (that field can contain references to different kind of objects).
There is an open issue about this on Github: https://github.com/GetStream/stream-django/issues/38. Ideally this is something the library will provide as helper/example, so any code contribution / input will help making that happen.
Is there a nice and clean way to use custom error codes while form validation? Eg:
{
"name": {
"code": 121,
"message": "This field can't be blank."
}
}
instead of:
{
"name": ["This field can't be blank."]
}
Thank you.
You can overwrite the error message in the __init__ method of your serializer:
self.fields['field_name'].error_messages['error_message_key'] = your_custom_error
You can find all error_messages keys related to each Field in the documentation. For instance, CharFields error message keys are required, max_length, min_length.
If you're using custom field from DRF:
name = serializers.CharField(
...,
error_messages={error_message_key: your_custom_error}
)
If you want to raise more specific errors, I encourage you to take a look at Raising ValidationError from the official documentation and ValidationError from DRF documentation.