【问题标题】:Buying many products at once from a webshop从网上商店一次购买许多产品
【发布时间】:2012-01-31 06:48:41
【问题描述】:

只编程一种产品以通过我的支付系统 (api.payson.se) 出售非常简单,但同时购买不同数量的多种产品给我带来了麻烦,因为它没有实施而且我没有实施有一个好主意怎么做。现在我有一个解决方案,我只是把它放在一起,但它的建模和控制流程非常快速和肮脏,我想知道这是否可以接受或是否需要重写。系统现在可以让我进入商店(第 1 步)并输入我想购买的产品的金额

然后,如果我按购买(“Köp”),我的 Python 会正确计算总和,这适用于我所说的总和的金额和产品的任何组合,并且此页面还可以列出规范,但尚未实现: 瑞典货币的总金额是正确的,它已向我的数据存储写入了状态为“未付款”的订单,其中包含订购的产品以及数据存储中每种产品的金额: 然后,用户可以取消购买或继续通过支付系统 api.payson.se 进行实际支付: 因此,我需要做的就是听取 Payson 的响应并更新已付款订单的状态。但是我的解决方案看起来不是很干净,我想知道我是否可以继续使用这样的代码,数据模型是两个字符串列表,一个带有数量,一个带有哪个产品(项目 ID),因为这是我可以解决的最简单的方法它,但它不能直接访问,只能从列表中访问。我可以使用更好的数据模型吗?

进行处理的代码有点混乱,可以使用比字符串和列表更好的数据模型和算法:

class ShopHandler(NewBaseHandler):

    @user_required
    def get(self):
        user = \
            auth_models.User.get_by_id(long(self.auth.get_user_by_session()['user_id'
                ]))
        self.render_jinja('shop.htm', items=Item.recent(), user=user)
        return ''

    @user_required
    def post(self, command):
        user = \
            auth_models.User.get_by_id(long(self.auth.get_user_by_session()['user_id'
                ]))
        logging.info('in shophandler http post item id'+self.request.get('item'))

        items = [ self.request.get('items[1]'),self.request.get('items[2]'),self.request.get('items[3]'),self.request.get('items[4]'),self.request.get('items[5]'),self.request.get('items[6]'),self.request.get('items[7]'),self.request.get('items[8]')   ]   

        amounts = [ self.request.get('amounts[1]'),self.request.get('amounts[2]'),self.request.get('amounts[3]'),self.request.get('amounts[4]'),self.request.get('amounts[5]'),self.request.get('amounts[6]'),self.request.get('amounts[7]'),self.request.get('amounts[8]')  ]
        total = 0
        total = int(self.request.get('amounts[1]'))* long(Item.get_by_id(long(self.request.get('items[1]'))).price_fraction()) if self.request.get('amounts[1]') else total
        total = total + int(self.request.get('amounts[2]'))* long(Item.get_by_id(long(self.request.get('items[2]'))).price_fraction()) if self.request.get('amounts[2]') else total
        total = total + int(self.request.get('amounts[3]'))* long(Item.get_by_id(long(self.request.get('items[3]'))).price_fraction()) if self.request.get('amounts[3]') else total
        total = total + int(self.request.get('amounts[4]'))* long(Item.get_by_id(long(self.request.get('items[4]'))).price_fraction()) if self.request.get('amounts[4]') else total
        total = total + int(self.request.get('amounts[5]'))* long(Item.get_by_id(long(self.request.get('items[5]'))).price_fraction()) if self.request.get('amounts[5]') else total
        total = total + int(self.request.get('amounts[6]'))* long(Item.get_by_id(long(self.request.get('items[6]'))).price_fraction()) if self.request.get('amounts[6]') else total
        total = total + int(self.request.get('amounts[7]'))* long(Item.get_by_id(long(self.request.get('items[7]'))).price_fraction()) if self.request.get('amounts[7]') else total
        total = total + int(self.request.get('amounts[8]'))* long(Item.get_by_id(long(self.request.get('items[8]'))).price_fraction()) if self.request.get('amounts[8]') else total
        logging.info('total:'+str(total))
        trimmed = str(total)+',00'
        order = model.Order(status='UNPAID')
        order.items = items
        order.amounts = amounts
        order.put()
        logging.info('order was written')
        ExtraCost = 0
        GuaranteeOffered = 2
        OkUrl = 'http://' + self.request.host + r'/paysonreceive/'
        Key = '3110fb33-6122-4032-b25a-329b430de6b6'
        text = 'niklasro@gmail.com' + ':' + str(trimmed) + ':' + str(ExtraCost) \
            + ':' + OkUrl + ':' + str(GuaranteeOffered) + Key
        m = hashlib.md5()

        BuyerEmail = user.email
        AgentID = 11366
        self.render_jinja('order.htm', order=order, user=user, total=total, Generated_MD5_Hash_Value = hashlib.md5(text).hexdigest(), BuyerEmail=user.email, Description='Bnano Webshop', trimmed=trimmed, OkUrl=OkUrl, BuyerFirstName=user.firstname, BuyerLastName=user.lastname)

我的订单模型(并非所有字段都使用)是

class Order(db.Model):
  '''a transaction'''
  item = db.ReferenceProperty(Item)
  items = db.StringListProperty()
  amounts = db.StringListProperty()
  owner = db.UserProperty()
  purchaser = db.UserProperty()
  created = db.DateTimeProperty(auto_now_add=True)
  status = db.StringProperty( choices=( 'NEW', 'CREATED', 'ERROR', 'CANCELLED', 'RETURNED', 'COMPLETED', 'UNPAID', 'PAID' ) )
  status_detail = db.StringProperty()
  reference = db.StringProperty()
  secret = db.StringProperty() # to verify return_url
  debug_request = db.TextProperty()
  debug_response = db.TextProperty()
  paykey = db.StringProperty()
  shipping = db.TextProperty()

产品的型号,即物品是

class Item(db.Model):
  '''an item for sale'''
  owner = db.UserProperty() #optional
  created = db.DateTimeProperty(auto_now_add=True)
  title = db.StringProperty(required=True)
  price = db.IntegerProperty() # cents / fractions, use price_decimal to get price in dollar / wholes
  image = db.BlobProperty()
  enabled = db.BooleanProperty(default=True)
  silver = db.IntegerProperty() #number of silver

  def price_dollars( self ):
    return self.price / 100.0

  def price_fraction( self ):
    return self.price / 100.0

  def price_silver( self ): #number of silvers an item "is worth"
    return self.silver / 1000.000

  def price_decimal( self ):
    return decimal.Decimal( str( self.price / 100.0 ) )

  def price_display( self ):
    return str(self.price_fraction()).replace('.',',')

  @staticmethod
  def recent():
    return Item.all().filter( "enabled =", True ).order('-created').fetch(10)

我认为您现在已经知道发生了什么,并且这种方式对用户有效,但代码看起来并不好。你认为我可以留下这样的代码并继续保留这个“解决方案”还是我必须重写以使其更合适?商店里只有 8 种产品,使用此解决方案很难添加新的待售商品,因为我必须重新编写不完美的脚本。

您能否发表评论或回答,我很高兴收到一些关于我的用例的快速而肮脏的解决方案的反馈。

谢谢

更新

我进行了重写以允许添加新产品,以下似乎比以前更好:

class ShopHandler(NewBaseHandler):

    @user_required
    def get(self):
        user = \
            auth_models.User.get_by_id(long(self.auth.get_user_by_session()['user_id'
                ]))
        self.render_jinja('shop.htm', items=Item.recent(), user=user)
        return ''

    @user_required
    def post(self, command):
        user = \
            auth_models.User.get_by_id(long(self.auth.get_user_by_session()['user_id'
                ]))
        logging.info('in shophandler http post')

        total = 0
        order = model.Order(status='UNPAID')

        for item in self.request.POST:
            amount = self.request.POST[item]
            logging.info('item:'+str(item))
            purchase = Item.get_by_id(long(item))
            order.items.append(purchase.key())
            order.amounts.append(int(amount))
            order.put()
            price = purchase.price_fraction()
            logging.info('amount:'+str(amount))
            logging.info('product price:'+str(price))
            total = total + price*int(amount)

        logging.info('total:'+str(total))
        order.total = str(total)
        order.put()
        trimmed = str(total).replace('.',',') + '0'
        ExtraCost = 0
        GuaranteeOffered = 2
        OkUrl = 'http://' + self.request.host + r'/paysonreceive/'
        Key = '6230fb54-7842-3456-b43a-349b340de3b8'
        text = 'niklasro@gmail.com' + ':' + str(trimmed) + ':' \
            + str(ExtraCost) + ':' + OkUrl + ':' \
            + str(GuaranteeOffered) + Key
        m = hashlib.md5()
        BuyerEmail = user.email  # if user.email else user.auth_id[0]
        AgentID = 11366
        self.render_jinja(
            'order.htm',
            order=order,
            user=user,
            total=total,
            Generated_MD5_Hash_Value=hashlib.md5(text).hexdigest(),
            BuyerEmail=user.email,
            Description='Bnano Webshop',
            trimmed=trimmed,
            OkUrl=OkUrl,
            BuyerFirstName=user.firstname,
            BuyerLastName=user.lastname,
            )

【问题讨论】:

  • 比以前好多了。现在您不必担心添加新项目;)

标签: python google-app-engine google-cloud-datastore python-2.7 webshop


【解决方案1】:

伙计,这是一个非常奇怪的代码。如果您想在您的商店中添加新商品,您必须重写您的商店脚本。 在第一次从界面取消链接您的项目时,您必须使用您的项目 ID 和数量向控制器发送 POST 请求,我不知道 gae 请求对象是如何工作的,但它必须是这样的: 从您的订单页面发出 POST 请求,其中包含真正需要 {"item_id":"qnt"} 的项目。 在控制器中时,您可以获取所有对象,例如:

for item, qnt in request.POST:
    {do something with each item, for example where you can sum total}

等等 不要直接将控制器与您的接口链接。如果你想制作真正灵活的应用程序,你必须编写更多的抽象代码。

【讨论】:

  • 但是我实际上如何将数据作为字典发布?字段应该如何?我不知道如何发布类似{"item_id":"qnt"} 的内容。如果可以,请提供更多信息。
  • 我将把它重新编码为金额字段<input class="standard_input" name="{{ item.key().id()}}" size="2" autocomplete="off" type="text"> 然后我会得到一个名为 item_id 和值金额的字典。你认为这会以你提议的方式更好地工作吗?否则我不知道如何从模板发布字典。
  • 我将其改写为更好的形状,并将新版本发布到原始问题中。新版本似乎好多了,并且允许添加新项目,但我不确定我是否想到了所有内容。
【解决方案2】:

我将尝试专注于您的代码中一个非常明显的问题,但其中有很多问题我不打算讨论。我的建议是立即停止。您正在实施一个基于网络的支付系统。你真的应该把它留给有更多技能和经验的人。 “基于网络”在确保安全的同时很难做到正确,但是在线支付系统是具有数十年经验的高薪顾问所获得的高薪,但他们仍然设法弄错了很经常。你让自己承担很多法律责任。

如果您仍然对此一无所知,请阅读The Python Tutorial 从头到尾,可能多读几遍。 Python 是一种非常不同的语言,与您在脑海中塞进的任何经典 OOP 语言都不同。之后,至少浏览其他文档。如果您在这些方面遇到问题,请阅读 O'Reilly 的 Python 书籍;从另一个角度接近它应该会有所帮助。在你完成所有这些(可能同时)之后,尽可能多地编写代码,如果你做错了不会让你被遗忘。那么也许你可以写一个订单/支付系统。

如果这听起来很苛刻,我很抱歉,但世界不再需要劣质网店; 1999 为我们解决了这个问题。

无论如何,继续你的代码:D 当你写一些重复的东西并像这样复制粘贴时:

items = [ self.request.get('items[1]'),self.request.get('items[2]'),self.request.get('items[3]'),self.request.get('items[4]'),self.request.get('items[5]'),self.request.get('items[6]'),self.request.get('items[7]'),self.request.get('items[8]')   ]

您应该对自己说:“等一下!重复性任务正是计算机的设计初衷。”你可以让你的文本编辑器来做(见 Vim 宏),但是简洁(但不是太简洁;)代码总是比长代码更好,因为你可以更快地维护它,更不容易出现程序员错误,并且更容易调试,更不用说你节省了不复制和粘贴的时间,所以让我们改进代码。

以下是我将如何在 Python 中修改它(高级程序员在头脑中这样做,或者直接跳到最后):

#1. with a for loop
MAX_ITEMS = 8
items = []
for i in range(MAX_ITEMS):
    items.append(self.request.get('items[{}]'.format(i + 1))

#2. with a list comprehension
MAX_ITEMS = 8
items = [self.request.get('items[{}]'.format(i + 1)) for i in range(MAX_ITEMS)]

实际上,限制项目数量是相当业余的,只会让您的用户感到沮丧。你可以这样修复它:

items = []
i = 0
while True:
    try:
        items.append(self.request[i + 1]) #attempt to get the next item
    except IndexError as exc: #but if it fails...
        break #we must be at the last one
    i += 1

我认为这是您现在应该离开的方式,因为它清晰但不重复。但是,您可以使用the itertools module 中的函数进一步缩短它。

一些快速提示:

  • 避免字符串连接,尤其是在用户提供字符串的情况下,尤其是在涉及用户通过 Web 提供的字符串时。使用 str.format 和 "%d" % (5,) 模数字符串格式。奖励:您不必将所有内容都转换为字符串!
  • 从中间取出这些常量(例如,ExtraCost = 2)并将它们放在安全的地方(在模块的顶部,或在包中的特殊文件中)
  • 您过于信任用户方式:在for item in self.request.POST:,您假设请求中的所有内容都将是一个项目,并且您进行验证.
  • 请,请,请。 从不关闭自动完成功能。我真的不知道为什么存在这个属性,除了烦人。

【讨论】:

  • 非常感谢您的回答。我的代码可以立即完成购买,我相信你非常熟练,可以在你的 API 头脑中完成。你有 Python 植入物,所以你可以在你的头脑中散列 MD5 吗?我真的认为不循环是丑陋的,因此我在这里发布了代码以了解如何循环的策略。其余的细节,如 python 中的字符串处理,我可以在重构中修复。为了为许多项目保存哪个项目和该项目的多少,就像说它就像一个字典,我听从了你的建议,放弃了item[],转而支持item.key().id()和数量。
  • 除了提交之外的唯一输入字段现在是<input class="standard_input" name="{{ item.key().id()}}" size="2" type="text">,它将将该特定项目的编号作为该项目的值发布。那么,我“假设”请求中的所有内容都将成为一个项目,这难道不是事实吗?我同意我的第一个版本相当糟糕,但它可以完成您无法在脑海中完成的购买,我的目标是启用购买而不是编写干净的代码。我可以编写干净的代码,但这意味着不能真正完成购买。我很高兴看到建设性的批评
  • @NickRosencrantz:我需要澄清一下。我并不是说我可以像雨人一样在脑海中执行代码。我是说我可以修改冗长的代码以在精神上简洁的代码。您误解了重构的含义。这并不意味着在将代码投入生产后修复错误或糟糕的编码实践。
  • @NickRosencrantz:关于输入,您的 HTML 指定了一个输入,但除非您在非常受控、安全的环境中运行,否则无法保证来自客户端的数据是从您的 HTML 生成的.有许多浏览器插件和其他软件(恶意和合法)可以让您修改 POST 数据,因此您不能相信它是某种格式。
  • @NickRosencrantz:关于“现在发布错误代码,稍后修复”的策略,你真的应该更加小心,否则你最终会遇到非常简单的错误like First State Superannuation in Australia mistakenly permitting users access to other's reports
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-08-16
  • 1970-01-01
  • 1970-01-01
  • 2011-01-18
  • 2021-10-10
  • 1970-01-01
  • 2019-08-16
相关资源
最近更新 更多