def reserve(email,roombc,datetime_start,datetime_end):
    ret = {
        'success': False,
        'error': '',
    }
    
    if len(email) == 0 or len(roombc) == 0 or datetime_start is None or datetime_end is None:
        ret['error'] = 'INTERNAL ERROR: reserve function was not given valid values.'
        #print "ERROR: {%s} {%s} {%s} {%s}" % (email,roombc,datetime_start,datetime_end)
        return ret
    
    try:
        user = Patron.objects.get(email=email)
    except:
        user = Patron()
        user.email = email
        user.date_last_booking = datetime.now() # mostly for daily quotas
        
    try:
        roomkey = RoomKey.objects.get(barcode=roombc)
        room = roomkey.room
    except:
        ret['error'] = 'That room doesn\'t appear to exist. Please select another room.'
        return ret

    # don't 'try-except' this if it errors because that means a core problem exists
    # that should be fixed.    
    user.save()
    
    res = Reservation()
    res.requested_user = user
    res.room = room
    res.datetime_start = datetime_start
    res.datetime_end = datetime_end
    
    # Allow early returns of keys and semi-correction of reservation end datetimes.
    _ = return_key(roombc,False)
    
    try:
        res.clean() # not called automatically without a form
    except ValidationError as ve:
        # failure to clean, only validation errors are raised
        if len(ve.messages) > 1:
            for msg in ve.messages:
                ret['error'] += msg + '<br />'
        else:
            ret['error'] = ve.messages[0]
            
        return ret
    
    # again, if an exception occurs by now, it's likely a bug.        
    res.save()
    
    ret['success'] = True
    return ret