Python GAE Cloud Platform存储桶TypeError:无法连接'str'和'NoneType'对象

时间:2016-03-29 08:56:57

标签: python-2.7 google-app-engine webapp2

非编码员。我正在为初学者编辑一项活动。它是一年前由其他人创建的。该项目是为学生预先创建的。他们应该部署它,创建一个存储桶,并从应用程序中将一些文件上传到存储桶。当我尝试它时,我收到此错误:

追踪(最近一次呼叫最后一次):

  File "/base/data/home/runtimes/python27/python27_lib/versions/third_party/webapp2-2.5.2/webapp2.py", line 1535, in __call__
    rv = self.handle_exception(request, response, e)
  File "/base/data/home/runtimes/python27/python27_lib/versions/third_party/webapp2-2.5.2/webapp2.py", line 1529, in __call__
    rv = self.router.dispatch(request, response)
  File "/base/data/home/runtimes/python27/python27_lib/versions/third_party/webapp2-2.5.2/webapp2.py", line 1278, in default_dispatcher
    return route.handler_adapter(request, response)
  File "/base/data/home/runtimes/python27/python27_lib/versions/third_party/webapp2-2.5.2/webapp2.py", line 1102, in __call__
    return handler.dispatch()
  File "/base/data/home/runtimes/python27/python27_lib/versions/third_party/webapp2-2.5.2/webapp2.py", line 572, in dispatch
    return self.handle_exception(e, self.app.debug)
  File "/base/data/home/runtimes/python27/python27_lib/versions/third_party/webapp2-2.5.2/webapp2.py", line 570, in dispatch
    return method(*args, **kwargs)
  File "/base/data/home/apps/s~lively-armor-126415/1.391710117126333360/main.py", line 205, in get
    for imagefile in gcs.listbucket(bucket_path(), delimiter='/'):
  File "/base/data/home/apps/s~lively-armor-126415/1.391710117126333360/main.py", line 114, in bucket_path
    return '/' + bucket_name + '/'
TypeError: cannot concatenate 'str' and 'NoneType' objects

这是(部分)main.py:

#!/usr/bin/env python
import webapp2
import sys
import os
import logging
import urllib
import zipfile
import StringIO
import jinja2
import datetime
import mimetypes
import json

from google.appengine.api import users
from google.appengine.api import mail
from google.appengine.api import xmpp
from google.appengine.api import channel
from google.appengine.api import app_identity
from google.appengine.api import images
from google.appengine.api import memcache
from google.appengine.api import taskqueue
from google.appengine.api import search
from google.appengine.ext import ndb
from google.appengine.datastore.datastore_query import Cursor

sys.path.insert(0, 'libs')
libpath = os.path.join(os.path.dirname(__file__), 'lib')
sys.path.append(libpath)

from wtforms import Form
from wtforms import StringField,TextAreaField,SelectField,DecimalField
from wtforms import FileField
from wtforms import SubmitField

from wtforms import validators
import cloudstorage as gcs

from datamodels import Product, ProductCategory

JINJA_ENV = jinja2.Environment(
    loader=jinja2.FileSystemLoader(os.path.dirname(__file__)),
    extensions=['jinja2.ext.autoescape'],
    autoescape=True
)

# Add custom filter for currency output in JINJA2
def currencyformat(value):
    template = "${:.2f}"
    currency_string = template.format(value)
    return currency_string
JINJA_ENV.filters['currencyformat'] = currencyformat

PRODUCT_GROUPS = [
    ('1','Bathroom'),
    ('2','Decor'),
    ('3','Lumber'),
    ('4','Materials'),
    ('5','Outdoors'),
    ('6','Tools')]

def login_html():

    # Load differently based on whether logged in to Google account
    user = users.get_current_user()
    if user:
        url = users.create_logout_url('/')
        username = user.nickname()
    else:
        url = users.create_login_url('/')
        username = ''

    template_values = {
        'url': url,
        'username': username
    }

    greeting_template = JINJA_ENV.get_template('html/greeting.htm')
    greeting_html = greeting_template.render(template_values)
    return greeting_html

def store_product(prodcode, title, price, category, description):
    logging.info('Add product %s to category %s in database', title, category)
    category_key = ndb.Key(ProductCategory, category)
    product = Product(
        parent=category_key,
        id=prodcode,
        title=title,
        price=price,
        category=category,
        desc=description
    )
    product.put()

    try:
        # Create a searchable document to use with Search API
        document = search.Document(
        doc_id = prodcode,
        fields=[
           search.TextField(name='title', value=title),
           search.TextField(name='category', value=category),
           search.HtmlField(name='desc', value=description),
           search.NumberField(name='price', value=float(price)),
           ])
        index = search.Index(name="ProductIndex")
        index.put(document)
    except:
        logging.exception("Unable to store search document for " + prodcode)

def file_extension(filename):
    return os.path.splitext(filename)[-1]

def bucket_path():
    bucket_name = app_identity.get_default_gcs_bucket_name()
    return '/' + bucket_name + '/'

class EditProductForm(Form):

    # Test and message for currency format
    cur_regex = '^\s*(?=.*[1-9])\d*(?:\.\d{1,2})?\s*$'
    pricemsg = 'Enter a price with up to two decimal places (no dollar symbol)'

    prodcode = StringField(
        '* Product Code:',
        [validators.Length(min=1, max=10)])
    price = StringField(
        '* Product Price:',
        [validators.Regexp(cur_regex, message=pricemsg)])
    title = StringField(
        '* Product Title:',
        [validators.Length(min=1, max=500)])
    category = SelectField(
        '* Product Group:',
        choices=PRODUCT_GROUPS,
        default='Hardware')
    description = TextAreaField(
        '* Product Description:',
        [validators.Required()])
    submitbtn = SubmitField('Save Product')

class EditImagesForm(Form):
    image = FileField('File to Upload:')
    submitbtn = SubmitField('Upload')

class BucketImageHandler(webapp2.RequestHandler):

    # Return image from cloud storage
    def get(self, image_file):

        self.response.headers['Content-Type'] = 'image/png'

        # Get complete file name
        filename = bucket_path() + image_file
        cache_name = 'productimages:{}'.format(image_file)

        # Get image data from memcache
        filedata = memcache.get(cache_name)

        if filedata is None:
            try:
                # Get image from cloud storage
                gcs_file = gcs.open(filename)
                filedata = gcs_file.read()
                memcache.add(cache_name, filedata, 3600)
            except:
                # Get placeholder image from static images
                self.redirect('/images/image_placeholder.png')

        self.response.out.write(filedata)

class UploadHandler(webapp2.RequestHandler):

    # Display upload page
    def get(self):

        # Allow only for admin users
        if users.is_current_user_admin():

            # Delete image if one is passed in
            # (in finished site, add a prompt to confirm)
            image_filename = self.request.get('del')
            if image_filename != '':
                datastore_filename = bucket_path() + image_filename
                logging.info('>>> DELETED FILE %s', image_filename)
                try:
                    gcs.delete(datastore_filename)
                except:
                    pass

            # Gather image data to pass in to HTML template
            MAX_IMAGES = 10
            image_count = 0
            reached_end = True
            last_image = 1

            start = self.request.get('s')
            if start is '':
                first_image = 1
            else:
                first_image = int(start)
                if first_image < 1:
                    first_image = 1

            # Get images from Cloud Storage
            image_gallery = []
            for imagefile in gcs.listbucket(bucket_path(), delimiter='/'):

                image_count += 1
                reached_first_image = (image_count >= first_image)
                reached_last_image = (image_count >= first_image + MAX_IMAGES)

                if reached_first_image and not reached_last_image:
                    # Files to show for this page
                    filename = imagefile.filename.split('/')[-1]
                    if file_extension(filename) == '.png':
                        this_image = dict(
                            name=filename,
                            size=imagefile.st_size,
                            safename=urllib.quote_plus(filename)
                        )
                        image_gallery.append(this_image)
                        last_image = image_count

            back_start_index = first_image - MAX_IMAGES
            next_start_index = last_image + 1           

            # Prepare image edit form for HTML template
            new_images_form = EditImagesForm()

            # Populate batch upload page
            template_values = {
                'admin_mode': users.is_current_user_admin(),
                'greeting_html': login_html(),
                'editform': new_images_form,
                'gallery': image_gallery,
                'start_image_index': first_image,
                'end_image_index': last_image,
                'image_count': image_count,
                'back_start_index': back_start_index,
                'next_start_index': next_start_index
            }

            image_mgr_template = JINJA_ENV.get_template('html/uploadmgr.htm')
            image_mgr_html = image_mgr_template.render(template_values)

            self.response.write(image_mgr_html)

        else:
            # Unauthorized user - raise an error
            self.abort(401)

    # Post new image or batch update to the gallery
    def post(self):

        # Allow batch upload only for admin users
        if users.is_current_user_admin():

            file_data = self.request.get('image')
            upload_filename = ''
            try:
                upload_filename = os.path.basename(self.request.POST['image'].filename)
            except:
                logging.info('NO FILE SPECIFIED')
                self.redirect('/upload')

            upload_file_extension = file_extension(upload_filename)
            datastore_filename = bucket_path() + upload_filename
            logging.info('Store file to %s', datastore_filename)

            if upload_file_extension == '.png':

                # Write image to cloud storage
                if len(file_data) > 0:

                    gcs_file = gcs.open(
                        datastore_filename,
                        'w',content_type='image/png')

                    file_data = images.resize(file_data, 400, 400)

                    gcs_file.write(file_data)
                    gcs_file.close()

                # Upload done -- return to gallery    
                self.redirect('/upload')

            elif upload_file_extension == '.zip':

                # Save uploaded Zip file to Google Cloud Storage
                gcs_file = gcs.open(
                    datastore_filename,
                    'w',content_type='application/zip')             
                gcs_file.write(file_data)
                gcs_file.close()
                logging.info('>>> STORED ZIP FILE %s', datastore_filename)

                # Start background task to extract the Zip file
                client_id = 'bgmsg-' + users.get_current_user().user_id()
                email_address = users.get_current_user().email()
                taskqueue.add(
                    url='/processuploads',
                    method="POST",
                    params={'zipfile': datastore_filename,
                            'address': email_address,
                            'clientid': client_id,
                            'starttime': datetime.datetime.now() }
                    )

                # Upload done -- return to gallery 
                self.redirect('/upload')

            else:

                # Ignore other file types
                self.redirect('/upload')

        else:
            # Unauthorized user - raise an error
            self.abort(401)

class BatchProcessBackgroundHandler(webapp2.RequestHandler):

    def post(self):

        # Task queue handler - Extract and process uploaded Zip file

        # Check header to ensure request came from inside App Engine platform
        if 'X-AppEngine-TaskName' in self.request.headers:

            zip_file_name = self.request.get('zipfile')
            address = self.request.get('address')
            client_id = self.request.get('clientid')
            start_time = self.request.get('starttime')

            # logging.info('>>> EXTRACTING ZIP FILE %s', zip_file_name)

            # Get zip data from cloud storage
            gcs_file = gcs.open(zip_file_name)
            gcs_data = gcs_file.read()
            zip_data = StringIO.StringIO(gcs_data)

            # Open the archive for reading
            zip_file = zipfile.ZipFile(zip_data, 'r')

            # Extract each file in the archive and process based on extension
            for extracted_file_name in zip_file.namelist():

                extracted_file_extension = file_extension(extracted_file_name)
                if extracted_file_extension == '.png':

                    # Read Zip file data as StringIO
                    extracted_image_data = zip_file.read(extracted_file_name)

                    # Resize images no wider or taller than 400 pixels
                    extracted_image_data = images.resize(
                        extracted_image_data,
                        400,
                        400)

                    datastore_filename = bucket_path() + extracted_file_name
                    gcs_file = gcs.open(
                        datastore_filename,
                        'w',
                        content_type='image/png')
                    gcs_file.write(extracted_image_data)
                    gcs_file.close()

                elif extracted_file_extension == '.txt':

                    extracted_data = zip_file.read(extracted_file_name)
                    lines = extracted_data.split('\r\n')
                    for line in lines:

                        if line:

                            line_values = line.split('\t')
                            category = line_values[0]
                            prodcode = line_values[1]
                            title = line_values[2]
                            price = line_values[3]
                            description = line_values[4]
                            store_product(
                                prodcode,
                                title,
                                price,
                                category,
                                description)

            # Close the Zip file
            zip_file.close()

            # Delete the Zip file when done
            gcs.delete(zip_file_name)

            # Compose success message
            notify_title = 'Batch Update Successfully Completed'
            message_body = 'Batch file ' + zip_file_name + '\n'
            message_body += 'Started at ' + start_time + '\n'
            message_body += 'Finished at ' + str(datetime.datetime.now()) + '\n'
            message_body += 'Refresh your browser to see the product updates.\n'

            # Send message by email
            mail.send_mail(
                sender = 'WW Admin <admin@wwheelhouse.com>',
                to = address,
                subject = notify_title,
                body = message_body
            )

            # Send message by XMPP
            user_address = address
            chat_message_sent = False
            msg = message_body
            status_code = xmpp.send_message(user_address, msg)
            chat_message_sent = (status_code == xmpp.NO_ERROR)

            # Send message to web client via channel API
            channel.send_message(
                client_id,
                msg
            )

        else:
            # Report forbidden operation
            self.error(403)

class DeleteProductHandler(webapp2.RequestHandler):

    def get(self):

        if users.is_current_user_admin():
            # Get product code from query string passed in to page
            prodcode = self.request.get('edit')
            category = self.request.get('cat')
            logging.info('>>> GET prodcode=%s and cat=%s', prodcode, category)

            try:
                # Get product from the datastore
                parent_key = ndb.Key('ProductCategory', category)
                product = Product.get_by_id(prodcode, parent=parent_key)

                # Delete the entity
                product.key.delete()
            except:
                pass

            # Redirect back to main product view
            self.redirect('/?cat=' + category)

        else:
            # Report forbidden operation
            self.error(403)

class SearchHandler(webapp2.RequestHandler):

    def post(self):

        # Process search
        search_text = self.request.get('q')
        search_category = self.request.get('scat')

        query_string = "title:" + search_text
        query_string += " OR desc:" + search_text
        if search_category != '' and search_category != '0':
            query_string += " AND category=" + search_category

        found_products = ''
        num_found = 0
        if search_text != '':
            index = search.Index(name="ProductIndex")
            found_products = index.search(query_string)
            num_found = found_products.number_found

        # Populate search results page
        template_values = {
            'admin_mode': users.is_current_user_admin(),
            'greeting_html': login_html(),
            'prod_categories': PRODUCT_GROUPS,
            'selected': search_category,
            'search_text': search_text,
            'product_list': found_products,
            'num_found': num_found
            }
        results_template = JINJA_ENV.get_template('html/searchresults.htm')
        search_html = results_template.render(template_values)
        self.response.write(search_html)

class MainHandler(webapp2.RequestHandler):
    def get(self):

        editcode = self.request.get('edit')
        prod_category = self.request.get('cat',default_value='0')
        in_edit = (prod_category and editcode)

        if in_edit:
            product = Product.query_get_product(prod_category, editcode)
            new_product_form = EditProductForm(
                prodcode=editcode,
                title=product.title,
                price=product.price,
                category=prod_category,
                description=product.desc
            )
        else:
            # Produce empty product editing form
            new_product_form = EditProductForm()

        self.response.write(self.catalog_html(new_product_form))
        # logging.info("ENVIRONMENT: %s", os.environ)

    def post(self):

        if users.is_current_user_admin():

            # Get data submitted in form and validate user input
            prodcode = self.request.get('prodcode')
            title = self.request.get('title')
            price = self.request.get('price')
            category = self.request.get('category')
            description = self.request.get('description')

            new_product_form = EditProductForm(
                prodcode=prodcode,
                title=title,
                price=price,
                category=category,
                description=description
            )

            if new_product_form.validate():
                store_product(prodcode, title, price, category, description)        
                self.redirect('/?cat='+category+'&viewproduct='+prodcode)
            else:
                html = self.catalog_html(new_product_form)
                self.response.write(html)

        else:
            # Unauthorized user -- raise an error
            self.abort(401)

    def catalog_html(self, editform):
        """ Return HTML for the product catalog """

        PRODUCTS_PER_PAGE = 4

        viewcode = self.request.get('viewproduct')
        editcode = self.request.get('edit')
        category = self.request.get('cat', default_value='0')

        in_edit = (category and editcode) # Show Edit mode only if category and editcode provided
        in_one_product_view = viewcode != ''

        # If one product view or in edit, show single product
        if in_one_product_view or in_edit:

            # RETURN SINGLE PRODUCT VIEW
            if in_edit:
                # Query to get the product specified for editing
                product = Product.query_get_product(category, editcode)
            else:
                # Query to get the product specified for viewing
                product = Product.query_get_product(category, viewcode)

            # Populate catalog page
            template_values = {
                'admin_mode': users.is_current_user_admin(),
                'greeting_html': login_html(),
                'prod_categories': PRODUCT_GROUPS,
                'selected': category,
                'product': product,
                'editform': editform
            }

            one_product_template = JINJA_ENV.get_template('html/oneproduct.htm')
            one_product_html = one_product_template.render(template_values)
            return one_product_html

        else:

            # MULTIPLE PRODUCT VIEW

            if category == '0':
                # Show all products in all categories
                q_forward = Product.query_all_categories_sort_newest()
                q_backward = Product.query_all_categories_sort_oldest()
            else:
                # Show products in one category
                q_forward = Product.query_by_category_sort_newest(category)
                q_backward = Product.query_by_category_sort_oldest(category)

            page_nav = ''
            products = None
            num_products_in_query = q_forward.count()
            num_pages_to_show = num_products_in_query / PRODUCTS_PER_PAGE

            if (num_products_in_query % PRODUCTS_PER_PAGE) > 0:
                num_pages_to_show += 1

            cursor_was_passed_in = self.request.get('cursor') is not ''
            page_num = self.request.get('pg',default_value='1')

            prev_cur = ''
            next_cur = ''

            if num_products_in_query > 0:

                # Read the cursor passed in from the previous page
                cursor = Cursor(urlsafe=self.request.get('cursor'))

                # Fetch a forward cursor
                products, next_cursor, more_after = q_forward.fetch_page(
                    PRODUCTS_PER_PAGE,
                    start_cursor=cursor )

                # Fetch a backward cursor
                prev_products, prev_cursor, more_before = q_backward.fetch_page(
                    PRODUCTS_PER_PAGE,
                    start_cursor=cursor.reversed() )

                if cursor_was_passed_in and prev_cursor:
                    prev_cur = prev_cursor.urlsafe()

                if more_after and next_cursor:
                    next_cur = next_cursor.urlsafe()

        # Populate catalog page
        template_values = {
            'admin_mode': users.is_current_user_admin(),
            'greeting_html': login_html(),
            'catalog': products,
            'prod_categories': PRODUCT_GROUPS,
            'selected': category,
            'editform': editform,
            'prev_cur': prev_cur,
            'next_cur': next_cur,
            'page_num': page_num,
            'page_num_prev': int(page_num)-1,
            'page_num_next': int(page_num)+1,
            'num_pages_to_show': num_pages_to_show
        }

        catalog_template=JINJA_ENV.get_template('html/catalog.htm')
        return catalog_template.render(template_values) 
...

请帮忙。我不知道如何解决这个问题,但我需要让这个项目让学生使用。

非常感谢,

Chrys

1 个答案:

答案 0 :(得分:1)

那是因为当前项目没有附加默认存储桶。

您可以使用gsutilcloud console创建一个存储分区,然后对bucket_path函数中的值进行硬编码。

此外,您可以保留当前bucket_path帮助函数,该函数从app_identity.get_default_gcs_bucket_name()返回bucket_name,然后通过访问云中的应用引擎应用程序设置,在新控制台中创建默认存储桶控制台https://console.cloud.google.com/appengine/settings?project=

Create Default Bucket