Skip to content

Commit

Permalink
Merge pull request #1368 from Scifabric/issue-1344-account-update
Browse files Browse the repository at this point in the history
Issue 1344 account update. This PR closes #1344
  • Loading branch information
teleyinex committed Jan 9, 2017
2 parents 51e5743 + 3298fb0 commit 550d601
Show file tree
Hide file tree
Showing 8 changed files with 603 additions and 103 deletions.
138 changes: 138 additions & 0 deletions doc/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -694,3 +694,141 @@ If there's an error in the form fields, you will get them in the **form.errors**
},
"template": "/account/password_forgot.html"
}
Account update profile
~~~~~~~~~~~~~~~~~~~~~~
**Endpoint: /account/<name>/update**

*Allowed methods*: **GET/POST**

**GET**

It returns a JSON object with the following information:

* **form**: the form fields that need to be sent for updating account. It contains the csrf token for validating the post, as well as an errors field in case that something is wrong.
* **password_form**: the form fields that need to be sent for updating the account's password. It contains the csrf token for validating the post, as well as an errors field in case that something is wrong.
* **upload_form**: the form fields that need to be sent for updating the account's avatar. It contains the csrf token for validating the post, as well as an errors field in case that something is wrong.
* **template**: The Jinja2 template that could be rendered.
* **title**: The title for the view.

**Example output**

.. code-block:: python
{
"flash": null,
"form": {
"ckan_api": null,
"csrf": "token",
"email_addr": "email@emai.com",
"errors": {},
"fullname": "John Doe",
"id": 0,
"locale": "en",
"name": "johndoe",
"privacy_mode": true,
"subscribed": true
},
"password_form": {
"confirm": null,
"csrf": "token",
"current_password": null,
"errors": {},
"new_password": null
},
"show_passwd_form": true,
"template": "/account/update.html",
"title": "Update your profile: John Doe",
"upload_form": {
"avatar": null,
"csrf": "token",
"errors": {},
"id": null,
"x1": 0,
"x2": 0,
"y1": 0,
"y2": 0
}
}
**POST**

To send a valid POST request you need to pass the *csrf token* in the headers. Use
the following header: "X-CSRFToken".

As this endpoint supports **three** different forms, you must specify which form are
you targetting adding an extra key: **btn**. The options for this key are:

* **Profile**: to update the **form**.
**Upload**: to update the **upload_form**.
**Password**: to update the **password_form**.
**External**: to update the **form** but only the external services.

.. note::
Be sure to respect the Uppercase in the first letter, otherwise it will fail.

It returns a JSON object with the following information:

* **flash**: A success message, or error indicating if the request was succesful.
* **form**: the form fields with the sent information. It contains the csrf token for validating the post, as well as an errors field in case that something is wrong.

**Example output**

.. code-block:: python
{
"flash": "Your profile has been updated!",
"next": "/account/pruebaadfadfa/update",
"status": "success"
}
If there's an error in the form fields, you will get them in the **form.errors** key:

.. code-block:: python
{
"flash": "Please correct the errors",
"form": {
"ckan_api": null,
"csrf": "token",
"email_addr": "pruebaprueba.com",
"errors": {
"email_addr": [
"Invalid email address."
]
},
"fullname": "prueba de json",
"id": 0,
"locale": "es",
"name": "pruebaadfadfa",
"privacy_mode": true,
"subscribed": true
},
"password_form": {
"confirm": "",
"csrf": "token",
"current_password": "",
"errors": {},
"new_password": ""
},
"show_passwd_form": true,
"template": "/account/update.html",
"title": "Update your profile: John Doe",
"upload_form": {
"avatar": "",
"csrf": "token",
"errors": {},
"id": 0,
"x1": 0,
"x2": 0,
"y1": 0,
"y2": 0
}
}
.. note::
For updating the avatar is very important to not set the *Content-Type*. If you
are using jQuery, set it to False, so the file is handled properly.

The (x1,x2,y1,y2) are the coordinates for cutting the image and create the avatar.

(x1,y1) are the offset left of the cropped area and the offset top of the cropped
area respectively; and (x2,y2) are the width and height of the crop.
13 changes: 6 additions & 7 deletions pybossa/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@
import os
import logging
import humanize
from werkzeug.exceptions import Forbidden, Unauthorized, InternalServerError
from werkzeug.exceptions import NotFound, BadRequest
from flask import Flask, url_for, request, render_template, \
flash, _app_ctx_stack
from flask.ext.login import current_user
Expand All @@ -34,6 +32,7 @@
from pybossa.util import pretty_date, handle_content_type
from pybossa.news import FEED_KEY as NEWS_FEED_KEY
from pybossa.news import get_news
from pybossa.messages import *


def create_app(run_as_server=True):
Expand Down Expand Up @@ -450,31 +449,31 @@ def setup_error_handlers(app):
@app.errorhandler(400)
def _bad_request(e):
response = dict(template='400.html', code=400,
description=BadRequest.description)
description=BADREQUEST)
return handle_content_type(response)

@app.errorhandler(404)
def _page_not_found(e):
response = dict(template='404.html', code=404,
description=NotFound.description)
description=NOTFOUND)
return handle_content_type(response)

@app.errorhandler(500)
def _server_error(e): # pragma: no cover
response = dict(template='500.html', code=500,
description=InternalServerError.description)
description=INTERNALSERVERERROR)
return handle_content_type(response)

@app.errorhandler(403)
def _forbidden(e):
response = dict(template='403.html', code=403,
description=Forbidden.description)
description=FORBIDDEN)
return handle_content_type(response)

@app.errorhandler(401)
def _unauthorized(e):
response = dict(template='401.html', code=401,
description=Unauthorized.description)
description=UNAUTHORIZED)
return handle_content_type(response)


Expand Down
62 changes: 62 additions & 0 deletions pybossa/messages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# -*- coding: utf8 -*-
# This file is part of PYBOSSA.
#
# Copyright (C) 2017 Scifabric LTD.
#
# PYBOSSA is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# PYBOSSA is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with PYBOSSA. If not, see <http://www.gnu.org/licenses/>.
"""
PYBOSSA messages.
This module exports the following variables:
* SUCCESS
* ERROR
* WARNING
* FORBIDDEN
* NOTFOUND
* BADREQUEST
* INTERNALSERVERERROR
* NOTFOUND
* UNAUTHORIZED
"""
from werkzeug.exceptions import Forbidden, Unauthorized, InternalServerError
from werkzeug.exceptions import NotFound, BadRequest

__all__ = ['SUCCESS', 'ERROR', 'WARNING', 'FORBIDDEN', 'NOTFOUND', 'BADREQUEST',
'NOTFOUND', 'INTERNALSERVERERROR', 'UNAUTHORIZED']

SUCCESS = "success"

ERROR = "error"

WARNING = "warning"

FORBIDDEN = Forbidden.description

NOTFOUND = NotFound.description

BADREQUEST = BadRequest.description

INTERNALSERVERERROR = InternalServerError.description

UNAUTHORIZED = Unauthorized.description

assert SUCCESS
assert ERROR
assert WARNING
assert FORBIDDEN
assert NOTFOUND
assert BADREQUEST
assert INTERNALSERVERERROR
assert UNAUTHORIZED
35 changes: 22 additions & 13 deletions pybossa/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"""Module with PYBOSSA utils."""
from datetime import timedelta, datetime
from functools import update_wrapper
from flask_wtf import Form
import csv
import codecs
import cStringIO
Expand All @@ -32,25 +33,33 @@

def last_flashed_message():
"""Return last flashed message by flask."""
messages = get_flashed_messages()
messages = get_flashed_messages(with_categories=True)
if len(messages) > 0:
return messages[-1]
else:
return None


def form_to_json(form):
"""Return a form in JSON format."""
tmp = form.data
tmp['errors'] = form.errors
tmp['csrf'] = generate_csrf()
return tmp


def handle_content_type(data):
"""Return HTML or JSON based on request type."""
if request.headers['Content-Type'] == 'application/json':
data['flash'] = last_flashed_message()
if 'form' in data.keys():
tmp = data['form']
data['form'] = tmp.data
data['form']['csrf'] = generate_csrf()
data['form']['errors'] = tmp.errors
if 'pagination' in data.keys():
pagination = data['pagination'].to_json()
data['pagination'] = pagination
message_and_status = last_flashed_message()
if message_and_status:
data['flash'] = message_and_status[1]
data['status'] = message_and_status[0]
for item in data.keys():
if isinstance(data[item], Form):
data[item] = form_to_json(data[item])
if isinstance(data[item], Pagination):
data[item] = data[item].to_json()
if 'code' in data.keys():
return jsonify(data), data['code']
else:
Expand All @@ -65,10 +74,10 @@ def handle_content_type(data):
else:
return render_template(template, **data)

def redirect_content_type(url, message=None):
def redirect_content_type(url, status=None):
data = dict(next=url)
if message is not None:
data['message'] = message
if status is not None:
data['status'] = status
if request.headers['Content-Type'] == 'application/json':
return handle_content_type(data)
else:
Expand Down
Loading

0 comments on commit 550d601

Please sign in to comment.