From 91afa01b35cd78adc6197e1df9f369a09cea6108 Mon Sep 17 00:00:00 2001 From: Joseph Mancuso Date: Sat, 26 Jan 2019 20:03:15 -0500 Subject: [PATCH] Next Minor (#550) * added new config helper function * flake8 * added docstrings * flake8 * removed need for config prefix * Add in container (#520) * added contains method to container * formatted and added tests * bumped version * Add ability to login using multiple columns (#521) * add list to auth class * adds ability to set a password column * formatted * fixed space in exception * Adds route redirection (#545) * adds a new redirect route class * flake8 * fixes issue where a json null could throw an exception (#544) * added ability to set status code in controller (#540) * added ability to set status code in controller * flake8 * added better exception when passing incorrect parameter type (#539) * added better exception when passing incorrect parameter type * fixed view test * fix delete method when body length is 0 (#529) * fix docstring for secure headers middleware (#548) * bumped version --- app/http/controllers/TestController.py | 4 +++ masonite/auth/Auth.py | 12 ++++---- masonite/exceptions.py | 4 +++ masonite/info.py | 2 +- masonite/request.py | 3 ++ masonite/response.py | 3 +- masonite/routes.py | 26 ++++++++++++++++- masonite/view.py | 4 ++- routes/web.py | 3 +- .../test_secure_headers_middleware.py | 2 +- tests/test_auth.py | 14 --------- tests/test_requests.py | 16 ++++++++++ tests/test_response.py | 6 ++++ tests/test_routes.py | 29 ++++++++++++++++++- tests/test_view.py | 7 ++++- 15 files changed, 107 insertions(+), 28 deletions(-) diff --git a/app/http/controllers/TestController.py b/app/http/controllers/TestController.py index 7b306e359..229e6f609 100644 --- a/app/http/controllers/TestController.py +++ b/app/http/controllers/TestController.py @@ -16,6 +16,10 @@ def change_header(self, request: Request): request.header('Content-Type', 'application/xml') return 'test' + def change_status(self, request: Request): + request.status(203) + return 'test' + def testing(self): return 'test' diff --git a/masonite/auth/Auth.py b/masonite/auth/Auth.py index 38af7147c..d39c47cc4 100644 --- a/masonite/auth/Auth.py +++ b/masonite/auth/Auth.py @@ -63,6 +63,11 @@ def login(self, name, password): """ auth_column = self.auth_model.__auth__ + try: + password_column = self.auth_model.password if hasattr(self.auth_model, 'password') else self.auth_model.__password__ + except AttributeError as e: + raise AttributeError('Your model does not have a password column or a designated __password__ attribute. Set the __password__ attribute to the name of your password column.') from e + try: # Try to login multiple or statements if given an auth list if isinstance(auth_column, list): @@ -75,12 +80,7 @@ def login(self, name, password): else: model = self.auth_model.where(auth_column, name).first() - # try: - # password_column = self._get_password_value(model) - # except AttributeError as e: - # raise AttributeError('Your model does not have a password column or a designated __password__ attribute. Set the __password__ attribute to the name of your password column.') from e - - if model and bcrypt.checkpw(bytes(password, 'utf-8'), bytes(model.password, 'utf-8')): + if model and bcrypt.checkpw(bytes(password, 'utf-8'), bytes(password_column, 'utf-8')): if not self._once: remember_token = str(uuid.uuid4()) model.remember_token = remember_token diff --git a/masonite/exceptions.py b/masonite/exceptions.py index 41571f7fe..707c552e0 100644 --- a/masonite/exceptions.py +++ b/masonite/exceptions.py @@ -72,3 +72,7 @@ class DebugException(Exception): class DumpException(Exception): pass + + +class ViewException(Exception): + pass diff --git a/masonite/info.py b/masonite/info.py index 3cc32b809..5574817f2 100644 --- a/masonite/info.py +++ b/masonite/info.py @@ -1,3 +1,3 @@ """Module for specifying the Masonite version in a central location.""" -VERSION = '2.1.12' +VERSION = '2.1.13' diff --git a/masonite/request.py b/masonite/request.py index a20501fa3..49d29c609 100644 --- a/masonite/request.py +++ b/masonite/request.py @@ -227,6 +227,9 @@ def _get_standardized_value(self, value): Returns: string|bool """ + if value is None: + return None + if isinstance(value, list): # If the list contains MiniFieldStorage objects then loop through and get the values. diff --git a/masonite/response.py b/masonite/response.py index 0d8b59cf8..878ba56fc 100644 --- a/masonite/response.py +++ b/masonite/response.py @@ -82,7 +82,8 @@ def view(self, view, status=200): Returns: string|dict|list -- Returns the data to be returned. """ - self.request.status(status) + if self.request.get_status() in (404,): + self.request.status(status) if isinstance(view, dict) or isinstance(view, list): return self.json(view) diff --git a/masonite/routes.py b/masonite/routes.py index f13bc81e1..5fd91d63d 100644 --- a/masonite/routes.py +++ b/masonite/routes.py @@ -86,7 +86,7 @@ def set_post_params(self): if isinstance(request_body, bytes): request_body = request_body.decode('utf-8') - return json.loads(request_body) + return json.loads(request_body or '{}') else: fields = cgi.FieldStorage( fp=self.environ['wsgi.input'], environ=self.environ, keep_blank_values=1) @@ -439,6 +439,30 @@ def get_response(self): return self.request.app().make('ViewClass').render(self.template, self.dictionary).rendered_template +class Redirect(BaseHttpRoute): + + def __init__(self, current_route, future_route, status=302, methods=['GET']): + """Class used for view routes. + + This class should be returned when a view is called on an HTTP route. + This is useful when returning a view that doesn't need any special logic and only needs a dictionary. + + Arguments: + method_type {string} -- The method type (GET, POST, PUT etc) + route {string} -- The current route (/test/url) + template {string} -- The template to use (dashboard/user) + dictionary {dict} -- The dictionary to use to render the template. + """ + self.list_middleware = [] + self.method_type = methods + self.route_url = current_route + self.status = status + self.future_route = future_route + + def get_response(self): + return self.request.redirect(self.future_route, status=self.status) + + class RouteGroup(): """Class for specifying Route Groups.""" diff --git a/masonite/view.py b/masonite/view.py index cd5cf1abc..ec43d30bd 100644 --- a/masonite/view.py +++ b/masonite/view.py @@ -4,7 +4,7 @@ from jinja2 import ChoiceLoader, Environment, PackageLoader, select_autoescape from jinja2.exceptions import TemplateNotFound -from masonite.exceptions import RequiredContainerBindingNotFound +from masonite.exceptions import RequiredContainerBindingNotFound, ViewException class View: @@ -49,6 +49,8 @@ def render(self, template, dictionary={}): Returns: self """ + if not isinstance(dictionary, dict): + raise ViewException('Second parameter to render method needs to be a dictionary, {} passed.'.format(type(dictionary).__name__)) self.__load_environment(template) self.dictionary = {} diff --git a/routes/web.py b/routes/web.py index 3bc1ee4e2..e63bc5b65 100644 --- a/routes/web.py +++ b/routes/web.py @@ -1,10 +1,11 @@ """ Web Routes """ from masonite.helpers.routes import group -from masonite.routes import Get, Post +from masonite.routes import Get, Post, Redirect ROUTES = [ Get().route('/test', None).middleware('auth'), + Redirect('/redirect', 'test'), Get().domain('test').route('/test', None).middleware('auth'), Get().domain('test').route('/unit/test', 'TestController@testing').middleware('auth'), Get().domain('test').route('/test/route', 'TestController@testing'), diff --git a/tests/middleware/test_secure_headers_middleware.py b/tests/middleware/test_secure_headers_middleware.py index 16c5d2408..140b073b0 100644 --- a/tests/middleware/test_secure_headers_middleware.py +++ b/tests/middleware/test_secure_headers_middleware.py @@ -1,4 +1,4 @@ -""" Test Maintenance Mode Midddleware """ +""" Test Secure Headers Midddleware """ import os from masonite.app import App diff --git a/tests/test_auth.py b/tests/test_auth.py index f06c43084..937af9113 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -77,20 +77,6 @@ def test_login_user_with_list_auth_column(self): assert isinstance(self.auth.login('testuser123', 'secret'), user) assert self.request.get_cookie('token') - def test_auth_gets_user_login_attribute(self): - auth = Auth(self.request, ListUser()) - assert auth._get_password_value(ListUser()) == 'pass123' - - auth = Auth(self.request, MockUser()) - assert auth._get_password_value(MockUser()) == '$2a$04$SXAMKoNuuiv7iO4g4U3ZOemyJJiKAHomUIFfGyH4hyo4LrLjcMqvS' - - def test_auth_gets_user_login_attribute_column(self): - auth = Auth(self.request, ListUser()) - assert auth._get_password_column(ListUser()) == 'users_password' - - auth = Auth(self.request, MockUser()) - assert auth._get_password_column(MockUser()) == 'password' - def test_get_user(self): assert self.auth.login_by_id(1) assert isinstance(self.auth.user(), MockUser) diff --git a/tests/test_requests.py b/tests/test_requests.py index 76e62fe38..d1aaaccf3 100644 --- a/tests/test_requests.py +++ b/tests/test_requests.py @@ -358,6 +358,22 @@ def test_redirect_compiles_url_with_http(self): route = "http://google.com" assert request.compile_route_to_url(route) == 'http://google.com' + + def test_can_get_nully_value(self): + app = App() + app.bind('Request', self.request) + request = app.make('Request').load_app(app) + + request._set_standardized_request_variables({ + "gateway": "RENDIMENTO", + "request": { + "user": "data" + }, + "response": None, + "description": "test only" + }) + + assert request.input('response') == None def test_request_gets_correct_header(self): app = App() diff --git a/tests/test_response.py b/tests/test_response.py index 486ba0a51..84b9bb038 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -11,6 +11,7 @@ def setup_method(self): self.app = App() self.request = Request(generate_wsgi()).load_app(self.app) self.app.bind('Request', self.request) + self.app.bind('StatusCode', '404 Not Found') self.response = Response(self.app) def test_can_set_json(self): @@ -48,3 +49,8 @@ def test_view_can_return_integer_as_string(self): assert self.app.make('Response') == '1' assert self.request.is_status(200) + + def test_view_can_set_own_status_code(self): + + self.response.view(self.app.resolve(ControllerTest().change_status)) + assert self.request.is_status(203) diff --git a/tests/test_routes.py b/tests/test_routes.py index f52601d06..05df4f871 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -1,6 +1,7 @@ from masonite.routes import Route from masonite.request import Request -from masonite.routes import Get, Post, Put, Patch, Delete, RouteGroup, Match +from masonite.app import App +from masonite.routes import Get, Post, Put, Patch, Delete, RouteGroup, Match, Redirect from masonite.helpers.routes import group, flatten_routes from masonite.testsuite.TestSuite import generate_wsgi from masonite.exceptions import InvalidRouteCompileException, RouteException @@ -188,6 +189,32 @@ def test_correctly_parses_json_with_list(self): assert route.environ['QUERY_STRING'] == { "options": ["foo", "bar"] } + + def test_redirect_route(self): + route = Redirect('/test1', '/test2') + request = Request(generate_wsgi()) + route.load_request(request) + request.load_app(App()) + + route.get_response() + assert request.is_status(302) + assert request.redirect_url == '/test2' + + def test_redirect_can_use_301(self): + request = Request(generate_wsgi()) + route = Redirect('/test1', '/test3', status=301) + + route.load_request(request) + request.load_app(App()) + route.get_response() + assert request.is_status(301) + assert request.redirect_url == '/test3' + + def test_redirect_can_change_method_type(self): + request = Request(generate_wsgi()) + route = Redirect('/test1', '/test3', methods=['POST', 'PUT']) + assert route.method_type == ['POST', 'PUT'] + class WsgiInputTestClass: diff --git a/tests/test_view.py b/tests/test_view.py index 8740528e9..34377dca0 100644 --- a/tests/test_view.py +++ b/tests/test_view.py @@ -7,7 +7,7 @@ from config import cache from masonite.app import App from masonite.drivers.CacheDiskDriver import CacheDiskDriver -from masonite.exceptions import RequiredContainerBindingNotFound +from masonite.exceptions import RequiredContainerBindingNotFound, ViewException from masonite.managers.CacheManager import CacheManager from masonite.view import View @@ -283,6 +283,11 @@ def test_can_render_pubjs(self): assert view.render( 'pug/hello.pug', {'name': 'Joe'}).rendered_template == '

hello Joe

' + def test_throws_exception_on_incorrect_type(self): + view = self.container.make('ViewClass') + with pytest.raises(ViewException): + assert view.render('test', {'', ''}) + class MockAdminUser: admin = 1