Search code examples
pythonvalidationsqlalchemypyramid

Server-Side PreValidation of User Form and Conditionals (Design/Best Practice)


Issue: Create a method that verifies if username exists when Creating an Account, deny account if username exists, and advises the user to create a new username and repeat the process should need be.

I have some idea of how to do this based on some STACKS questions (1, 2) that I have read on here. Such as something like:

  1. Send the username to the server.
  2. Check for the existence of the username in the Database.
  3. Respond to the client with true or false depending on the presence of the username.
  4. Based on the response, send the user a client side alert!

I am uncertain how to properly execute the process in the Sign Up Page (also known as the Create an Account page) using Pyramid and SQLAlchemy. Since I am newbie, I want to make sure I am creating code that is fast, efficient and with smart design. I want to ensure I am staying within best practices.

Right now in the User database, Username is UNIQUE; this causes the system to crash when a user tries to create a username that exists in the db. My code is missing something as there is a traceback that indicates DETAIL: Key (username)=(baseball) already exists. Any help or suggestions is truly appreciated! If I have a poor method, suggestions of a better method is highly welcomed!

Software: Python 2.7, Pyramid 1.5.7, SQLAlchemy 1.0.9


views.py
(code: to the create a user page and save new user)

@view_config(route_name='create_user', request_method='GET', renderer='templates/create_account.jinja2')
def user_form_view(request):
    return {}

@view_config(route_name='save_new_user')
def save_new_user(request):
    with transaction.manager:

        username = request.params['username']

        check_username = api.retrieve_user(username) #retrieves one_user
        #check_users = api.retrieve_users() #this retrieves ALL the users

        taken = False
        for user in check_username: #prints out all user info
            if username == user.username:
                taken = True
                break
        if taken:
            username = request.params['username']

        password = request.params['password']
        firstname = request.params['firstname']
        lastname = request.params['lastname']
        email = request.params['email']

        new_user = api.create_user(username, password, firstname, lastname, email)
        new_account = api.update_group_add_user('Registered User', new_user)

        transaction.commit()
        return HTTPSeeOther(location=request.route_url('login'))

Traceback:

IntegrityError: (raised as a result of Query-invoked autoflush; consider using a session.no_autoflush block if this flush is occurring prematurely) (psycopg2.IntegrityError) duplicate key value violates unique constraint "users_username_key"
DETAIL:  Key (username)=(baseball) already exists.
 [SQL: 'INSERT INTO users (username, firstname, lastname, email, password, institution, created_on) VALUES (%(username)s, %(firstname)s, %(lastname)s, %(email)s, %(password)s, %(institution)s, %(created_on)s) RETURNING users.id'] [parameters: {'username': u'baseball', 'firstname': u'jlo', 'lastname': u'lo', 'institution': None, 'created_on': datetime.datetime(2015, 11, 24, 22, 27, 20, 286260), 'password': '78d8045d684abd2eece923758f3cd781489df3a48e1278982466017f', 'email': u'j'}]

Updates based on suggestion below

Question:

Where should I create the function validate_registration -- outside of the registration_view function? Should this be a Boolean statement? Is this the best method? Where would transaction.commit() exist?

View Code with GET and POST:

def validate_registration_form(request):
    with transaction.manager:
        username = request.params['username']
        check_username = api.retrieve_user(username)

        password = request.params['password']
        firstname = request.params['firstname']
        lastname = request.params['lastname']
        email = request.params['email']

        if check_username is not None:
            return False
        else:
            return True

@view_config(route_name='registration', renderer='templates/create_account.jinja2')
@view_config(route_name='save_registration',  renderer='templates/create_account.jinja2')
def registration_view(request):
    if request.method == 'GET':
        return {} # render the empty form
    elif request.method == 'POST':
        if validate_registration_form(request): #save new_user and redirect
            new_user = api.create_user(username, password, firstname, lastname, email)
            new_account = api.update_group_add_user('Registered User', new_user)
            transaction.commit()
            raise HTTPSeeOther(location=request.route_url('login'))
        else:
            # form is not valid, re-render the form
            # with the data user entered and an error message
            return {
                'error_message': 'username already taken',
                'username': request.POST.get('username', ''),
                'password': request.POST.get('password', ''),
                'firstname': request.POST.get('firstname', ''),
                'lastname': request.POST.get('lastname', ''),
                'email': request.POST.get('email', '')
                }

form:

    <form action="/save_registration" method="POST">
        <div class="form-group">
            <dl>
                <dt><label for = 'username'> Username: <em>single word--no spaces</em> </label></dt>
      #more ....

Solution

  • Well, the classic approach to server-side form validation on submit is something like this (in pseudocode):

    @view_config(route_name='registration', renderer='my_rego_form.jinja2')  # handles both GET and POST
    def rego_form(request):
        if request.metod == 'GET':
            return {}  # render the empty form
        elif request.method == 'POST':
            if validate_rego_form(request):  
                # create a new user and redirect elsewhere
                create_user(request)
                raise HTTPFound('/post_registration_page') 
            else: 
                # form is not valid, re-render the form 
                # with the data user entered and an error message
                return {
                    'error_message': 'Error creating user',
                    'username': request.POST.get('username', '')
                    'email': request.POST.get('email', '')
                    'phone_num': request.POST.get('phone_num', '')
                }
            else:
                # some other HTTP method, not sure how to deal
    

    So, basically, the form needs to be able to re-render itself with the data submitted by the client.

    The validation method itself can be trivial and just check for the user with the given email in the db. Or, as you tried to do, instead of pre-validation you can try to just create a record and handle the exception if one occurs.