Flask-Pydantic disable validation on get request - flask

I have a flask view that accepts a get and post request and I use Pydantic for request body validation using flask-pydantic. It works fine for post requests but on get requests it returns a 415 error with this message -
{"detail":"Unsupported media type '' in request. 'application/json' is required."}
#bp.route('/login', methods=('GET', 'POST',))
#validate()
def login(body: UserLoginSchema):
if request.method == 'POST':
existing_user = UserListSchema.from_orm(
db_session.query(UserModel.id, UserModel.email, UserModel.first_name, UserModel.last_name,
UserModel.is_admin, UserModel.is_verified, UserModel.password)
.filter_by(email=body.email, is_active=True).first()
)
if existing_user:
if check_password_hash(existing_user.password, body.password):
session.clear()
session['user'] = str(existing_user.json())
return redirect(url_for('index'))
flash('Invalid username or password')
return render_template('auth/login.html')
I have tried setting the query parameter in the function to an empty string or None and it doesn't help.

I removed the flask-pydantic package and initialised the pydantic models manually because the validate decorator from flask-pydantic requires the content-type header to be set to application/json.
#bp.route('/login', methods=('GET', 'POST',))
def login():
if request.method == 'POST':
body = auth_schemas.UserLoginSchema(**request.form)
existing_user = auth_schemas.UserListSchema.from_orm(
db_session.query(UserModel.id, UserModel.email, UserModel.first_name, UserModel.last_name,
UserModel.is_admin, UserModel.is_verified, UserModel.password)
.filter_by(email=body.email, is_active=True).first()
)
if existing_user:
if check_password_hash(existing_user.password, body.password):
session.clear()
session['user'] = str(existing_user.json())
return redirect(url_for('index'))
flash('Invalid username or password')
return render_template('auth/login.html')
Then I created a ValidationError handler to catch validation errors while initializing the pydantic model classs.
from pydantic import ValidationError
#app.errorhandler(ValidationError)
def handle_pydantic_validation_errors(e):
return jsonify(e.errors())

Related

How to convert json to form in Django?

I'm developing a Django project, and the team want to seperate the front-end and the back-ed, so I'm using Django to develop api. The format of the data transmitting is json. However, I want to use the defalt user package (django.contrib.auth), and I have to use Form. How could I convert the json received from the frontend to the form that I'm going to use in backend? thanks!
I have tried the silly code as below and it does not work.
#require_http_methods(["POST"])
def register(request):
form = CustomUserCreationForm(data=request.POST)
response = {"status": "success"}
if form.is_valid():
new_user = form.save()
print("valid")
return JsonResponse(response)
This is how your view must look if you want to register new user with custom form :
def register(request):
if request.method == 'POST':
form = CustomUserCreationForm(request.POST)
response = {}
if form.is_valid():
# The form is valid, now we can save the user in the db
new_user = form.save()
response = {"status": "success", "message": "User created"}
return JsonResponse(response)
else:
# Invalid form
response = {"status": "error", "message": "Form is invalid"}
else:
# It's not a post request ( GET or others)
# Instantiate an empty form
form = CustomUserCreationForm()
context = {
'form': form,
}
return render(request, 'register_template.html', context=context)

redirect to created object after form submission

Goal: To redirect to the 'build log' after the form is submitted
view:
#login_required
def buildLogCreate(request, pk):
post = Post.objects.get(pk=pk)
if request.user.id != post.author_id:
raise PermissionDenied()
currentUser = request.user
form = BuildLogForm()
if request.method == 'POST':
form = BuildLogForm(request.POST, request.FILES)
if form.is_valid():
formID = form.save(commit=False)
formID.author = request.user
formID.post_id = pk
formID.save()
return redirect('build-log-view', formID.pk)
context = { 'form':form, }
return render(request, 'blog/buildlog_form.html', context)
The url structure setup is post->buildlog(subpost) ie: /post/118/build-log/69/
The form gets successfully submitted but I get the error NoReverseMatch at /post/118/build-log-form/ Reverse for 'build-log-view' with arguments '(69,)' What confuses me is the url is still on the post form and not build log view but the argument 69 is the correct build log id so it should be working.
url to be redirected to
path('post/<int:pk>/build-log/<int:pkz>/', views.BuildLogDisplay, name='build-log-view'),
The URL pattern has two parameters: the primary key of the post object, and the primary key of the build log.
You can thus pass this with:
return redirect('build-log-view', pk, formID.pk)
or with named parameters:
return redirect('build-log-view', pk=pk, pkz=formID.pk)

Django returning None instead of a HTTP response

OK im probably doing this all wrong!
I am trying to run a function in a view which calls another view.
This seems to pass my request into the next function as a POST method before loading the form from the second function.
my views.py
''' This section of hte code seems to function correctly '''
#login_required()
def joinLeague(request):
if request.method == 'POST':
league = JoinLeagueQueue(user=request.user)
form = JoinLeagueForm(instance=league, data=request.POST)
if form.is_valid():
form.save()
context = int(league.id) # displays id of model, JoinLeagueQueue
return HttpResponseRedirect(confirmLeague(request, context))
else:
form = JoinLeagueForm()
context = {'form':form}
return render(request, 'userteams/joinleagueform.html', context)
This section of the views file is not working correctly.
it seems to run the POST request without displaying the GET request with the form first.
#login_required()
def confirmLeague(request, league):
# gets ID of application to join league
joinleagueid=JoinLeagueQueue.objects.get(id=league)
pin = joinleagueid.pin # gets pin from application
user = joinleagueid.user # get user from application
leagueinquestion=League.objects.get(leaguePin=pin) # gets the league record for applied league
manager=leagueinquestion.leagueManager # Gets the league manager for league applied for
leaguename=leagueinquestion.leagueName # Gets the league name for league applied for
if request.method == 'POST':
if 'accept' in request.POST:
LeaguesJoinedTo.objects.create(
leaguePin = pin,
playerjoined = user,
)
return redirect('punterDashboard')# user homepage
else:
print("Error in POST request")
else:
context = {'leaguename':leaguename, 'pin':pin, 'manager':manager}
return render(request, 'userteams/confirmleague.html', context)
I now get an error saying Page not found (404)
Request Method: GET
Request URL: http://127.0.0.1:8000/userteams/None
Using the URLconf defined in fanfoo_proj.urls, Django tried these URL patterns, in this order:
... im skipping a list of the patterns.
10. userteams/ confirmLeague [name='confirmLeague']
Ok i think the better way would be a HttpRedirect to the second view:
return confirmLeague(request, context)
should change to something like:
return redirect(confirmLeague,args=league)
django doc to urlresolvers: https://docs.djangoproject.com/en/3.0/topics/http/shortcuts/#redirect
def joinLeague(request):
if request.method == 'POST':
league = JoinLeagueQueue(user=request.user)
form = JoinLeagueForm(instance=league, data=request.POST)
if form.is_valid():
form.save()
context = league.id
return HttpResponseRedirect( reverse("your_confirmLeague_url",kwargs={'league':context}) )
else:
form = JoinLeagueForm()
context = {'form':form}
return render(request, 'userteams/joinleagueform.html', context)
def confirmLeague(request, league):
league = get_object_or_404(JoinLeagueQueue, pk=league)
pin = league.pin
if request.method == 'POST':
if 'accept' in request.POST: # This refers to the action from my form which is waiting for a button press in a html form.
LeaguesJoinedTo.objects.create(
leaguePin = pin,
playerjoined = request.user.id,
)
return redirect('punterDashboard')
else:
context = {'league':league}
return render(request, 'userteams/confirmleague.html', context)

Ho to test a create function in Django?

# my .views
def post_create_view(request):
form = PostModelForm(request.POST or None)
if form.is_valid():
obj = form.save(commit=False)
obj.user = request.user
obj.save()
form = PostModelForm()
context = {'form': form}
return render(request, 'form.html', context)
# my .urls
path('create/', post_create_view, name='create')
# my .test
class TestViews(TestCase):
def setUp(self):
self.client = Client()
self.create_url = reverse('posts:create')
self.user = User.objects.create_user('john', 'lennon#thebeatles.com', 'johnpassword')
self.post1 = Post.objects.create(user=self.user, title='testpost', content='fgh')
self.query = Post.objects.all()
def test_blog_create_POST(self):
# this is where the error surely is
response = self.client.post(self.create_url, {'title': 'testpost2', 'content': 'asd'})
self.assertEqual(response.status_code, 302)
# this is where the test fail
self.assertEqual(self.query.last().title, "testpost2")
'testpost2' doesn't get created but my form works fine. How can I simulate the inputs in my testcase?
self.create_url redirect to the correct url, the one with the form in the template, such form works great, but when I write the response in my test the dictionary I pass as second arguments seems not to go through.
In case of invalid form it's just re-render form on page - that's produces status 200, not 302. Also, it's not obvious why you want to send redirect status in case of invalid form.
If this endpoint only used for POST request, then you need display form errors in your template. Or, if you think redirect is a better option in your case, if is_valid() returns false just perform redirect.

Save and retrieve a form with session?

I have a context_processor.py file with the following function
def include_user_create(request):
if 'form' not in request.session:
form = CreateUserForm()
else:
form = request.session['form']
return { 'create_user_form' : form }
I use this to display my register in my base.html template, so that I may reuse it for all pages. A function create_user handles the form submit
def create_user(request):
form = CreateUserForm(request.POST or None, request.FILES or None)
if request.method == 'POST':
if form.is_valid():
user = form.save(commit=False)
user.save()
user = authenticate(username=user.email, password=user.password)
else:
request.session['form'] = form #<--- save
next = request.POST.get('next', '/')
return HttpResponseRedirect(next)
If the form is invalid I'd like to save the form so that the context_processor may reuse the form, for the purpose of saving the errors so they may be displayed in the template.
Doing this gives me a error:
TypeError: <CreateUserForm bound=True, valid=False, fields=(email;password;confirm_password)> is not JSON serializable
Is it possible to get this to work somehow?
You have this error because a form object is not JSON serializable, and the default session serializer is serializers.JSONSerializer.
Try to change it to a pickle serializer in your settings.py:
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'
EDIT:
With this setting, you don't have to care about pickle serialization, you just have to write:
request.session['form'] = form