0

I'm having trouble creating admin pages on my Python Google App Engine site. I think the answer should be pretty straightforward, but honestly, I've been trying to understand how classes inheriting from other classes, or using functions to wrap other functions, and I just can't seem to get a good understanding of it.

Basically, my site has two kinds of pages: the main page, and then some pages that allow the user to perform admin actions. The main page can be seen by anyone without signing in. The other pages are for admins. The only users with accounts are admins, so I've set up webapp2 sessions, and as long as

self.sessions.get('username')

returns something that's enough to be allowed access to the other pages.

Here are my handlers:

class BaseHandler(webapp2.RequestHandler):
    def write(self, *a, **kw):
        self.response.out.write(*a, **kw)

    def render(self, template, **kw):
        self.response.out.write(render_str(template, **kw))

    def dispatch(self):
        # Get a session store for this request.
        self.session_store = sessions.get_store(request=self.request)

        try:
            # Dispatch the request.
            webapp2.RequestHandler.dispatch(self)
        finally:
            # Save all sessions.
            self.session_store.save_sessions(self.response)

    @webapp2.cached_property
    def session(self):
        # Returns a session using the default cookie key.
        return self.session_store.get_session()

class MainHandler(BaseHandler):
    def get(self):
        animals = Animal.query().fetch(100)
        self.render('index.html',animals=animals)

class AdminHandler(BaseHandler):
    def get(self):
        if self.session.get('username'):
            self.render('admin.html')
        else:
            self.render('signin.html')

class ReorderHandler(BaseHandler):
    def get(self):
        self.render('reorder.html')
    def post(self):
        #Change order of item display
        self.write('OK')

class DeleteHandler(BaseHandler):
    def get(self):
        self.render('delete.html')
    def post(self):
        #Delete entry from db
        self.write('OK')

class AddHandler(BaseHandler):
    def get(self):
        self.render('add.html')
    def post(self):
        #add entry to db
        self.write('OK')

class SigninHandler(BaseHandler):
    def post(self):
        #Check username and password
        if valid:
            self.session['username'] = username
            self.redirect('/admin')
        else:
            self.write('Not valid')

The AdminHandler lays out the basic logic of what these Admin pages should do. If someone is trying to access an admin pages, the handler should checks to see if user is signed in, and if so, allow access to the page. If not, it renders the sign-in page.

Reorder, Delete, and Add are all actions I want admins to be able to do, but there might be more in the future. I could add the AdminHandler logic to all the GETs and POSTs of those other handlers, but that is extremely repetitive and therefore I am sure that it is the wrong thing to do.

Looking for some guidance on how to get the logic of AdminHandler incorporated into all of the other Handlers that cover "administrative" tasks.

Update: Brent Washburne pointed me in the right direction enough to get the thing working, although I still don't feel like I understand what the decorator function actually does. Anyway, the code seems to be working, and now looks like this:

def require_user(old_func):
    def new_function(self):
        if not self.session.get('username'):
            self.redirect('/signin')
        old_func(self)
    return new_function

class AdminHandler(BaseHandler):
    @require_user
    def get(self):
        self.render('admin.html')

class AddHandler(BaseHandler):
    @require_user
    def get(self):
        self.render('add.html')
    @require_user
    def post(self):
        name = self.request.get('name')
        qry = Animal.query(Animal.name == name).get()
        if not qry:
            new_animal = Animal(name=name)
            new_animal.put()
        self.write('OK')

And so on for all the other "admin" Handlers.

wbruntra
  • 1,021
  • 1
  • 10
  • 18
  • I'm not sure what you're asking here. Does this code not work? What happens when you use it? – Daniel Roseman Feb 08 '16 at 20:39
  • As written it **only** checks for admin status on the AdminHandler page. I want it to check for admin status on all the Admin pages (Admin, Add, Delete, Reorder), but I think I am making a mistake if I directly duplicate the logic in AdminHandler. I feel like somehow the other Handlers can "inherit" that from AdminHandler, or that I can wrap their GETs and POSTs in some other function. But I am not sure how to do it. – wbruntra Feb 08 '16 at 20:43
  • But those classes *do* inherit from BaseHander, and you *do* override the wrapper method (dispatch), so I can't understand what's not working. – Daniel Roseman Feb 08 '16 at 20:45
  • Obviously I am confused about something. It seems to me that someone who tries to visit '/admin' will be checked for admin status, but someone who tries to GET or POST to '/add', '/delete', or '/reorder' will be allowed to do so without any status check. – wbruntra Feb 08 '16 at 20:49
  • Is this what you are looking for? https://github.com/coto/gae-boilerplate/blob/master/bp_includes/handlers.py#L977 It uses a decorator for any route that requires logged in user without repeating the code each time. – Jeff Deskins Feb 08 '16 at 21:08

1 Answers1

0

Here's a brute-force way to ensure a user is logged in for every page (except the login page), or it redirects them to the login page:

def dispatch(self):
    # Get a session store for this request.
    self.session_store = sessions.get_store(request=self.request)

    if not self.session['username'] and self.request.get('path') != '/login':
        return redirect('/login')

A better way is to add this code to the top of every get() and put() routine:

def get(self):
    if not self.session['username']:
        return redirect('/login')

An even better way is to turn that code into a decorator so all you need to add is one line:

@require_login
def get(self):
    ....
Brent Washburne
  • 12,904
  • 4
  • 60
  • 82