shop_id & active on cart table

This commit is contained in:
Russell Ballestrini 2022-07-13 18:11:40 +00:00
parent edaa1e450e
commit ff8bddadbb
20 changed files with 239 additions and 180 deletions

View file

@ -2,7 +2,7 @@ import uuid
import json
from sqlalchemy.orm import relationship
from sqlalchemy.orm import relationship, backref
from sqlalchemy.ext.associationproxy import association_proxy
@ -29,7 +29,6 @@ from .shop import get_shops_by_ids
from .cart_coupon import CartCoupon
from ..lib.currency import cents_to_dollars
try:
@ -38,6 +37,11 @@ except:
from six import u as unicode
def timestamp_to_datetime(timestamp):
"""Accepts a milliseconds timestamp integer and returns a datetime"""
from datetime import datetime
return datetime.fromtimestamp(timestamp / 1000.0)
def timestamp_to_ago_string(timestamp):
"""Accepts a timestamp and returns a human readable string"""
from ago import human
@ -46,12 +50,9 @@ def timestamp_to_ago_string(timestamp):
class Cart(RBase, Base):
"""This class tracks a User's shopping carts."""
# only a cart whose id is in the session is considered active.
active = False
id = Column(UUIDType, primary_key=True, index=True)
user_id = Column(UUIDType, foreign_key("User", "id"), nullable=True)
shop_id = Column(UUIDType, foreign_key("Shop", "id"), nullable=True)
name = Column(Unicode(64))
public = Column(Boolean, default=False)
json_cart = Column(UnicodeText, default=unicode("{}"))
@ -59,11 +60,14 @@ class Cart(RBase, Base):
created_timestamp = Column(BigInteger, nullable=False)
updated_timestamp = Column(BigInteger, nullable=False)
user = relationship(argument="User", uselist=False, lazy="joined")
active = Column(Boolean, default=False)
# many to many uses association_proxy.
coupons = association_proxy("cart_coupons", "coupon", creator=lambda c: CartCoupon(coupon=c))
user = relationship(argument="User", uselist=False, lazy="joined")
shop = relationship(argument="Shop", uselist=False, lazy="joined")
def __init__(self, user=None):
# since shopping carts are a private thing which may optionally
# be granted public access, we use random uuid4 UUIDs to prevent

View file

@ -1,12 +1,14 @@
import uuid
from sqlalchemy import Column, BigInteger, Boolean, Unicode, UnicodeText, func
from sqlalchemy import and_
from .meta import Base, RBase, UUIDType, now_timestamp, foreign_key, get_object_by_id, get_objects_by_ids
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.orm import relationship
from sqlalchemy.orm import relationship, backref
from .user_shop import UserShop
@ -60,8 +62,10 @@ class Shop(RBase, Base):
# these keys allow make_post_sell to charge
# cards on behalf of the shop's stripe account.
stripe_secret_api_key = Column(Unicode(64), nullable=True)
stripe_public_api_key = Column(Unicode(64), nullable=True)
#stripe_secret_api_key = Column(Unicode(64), nullable=True)
#stripe_public_api_key = Column(Unicode(64), nullable=True)
stripe_secret_api_key = Column(Unicode(128), nullable=True)
stripe_public_api_key = Column(Unicode(128), nullable=True)
created_timestamp = Column(BigInteger, nullable=False)
@ -74,6 +78,8 @@ class Shop(RBase, Base):
# many to many uses association_proxy.
users = association_proxy("shop_users", "user", creator=lambda u: UserShop(user=u))
carts = relationship(argument="Cart", lazy="dynamic", back_populates="shop")
# many to many uses association_proxy.
stripe_users = association_proxy(
"stripe_users", "user", creator=lambda u: StripeUserShop(user=u)
@ -190,7 +196,7 @@ class Shop(RBase, Base):
return None
# create a new stripe customer.
customer = self.stripe.Customer.create(email=user.email)
customer = self.stripe.Customer.create(email=user.email, expand=["sources"])
# create a new many-to-many user to shop stripe customer.
stripe_user_shop = StripeUserShop(user, self)
@ -200,8 +206,9 @@ class Shop(RBase, Base):
# flush relationship to database.
self.dbsession.add(stripe_user_shop)
self.dbsession.flush()
return customer
return self.stripe.Customer.retrieve(stripe_customer_id)
return self.stripe.Customer.retrieve(stripe_customer_id, expand=["sources"])
def list_stripe_charges(self, stripe_customer=None):
"""
@ -285,6 +292,35 @@ class Shop(RBase, Base):
self.terms_of_service_raw = new_terms_of_service_raw
self.terms_of_service_html = markdown_to_html(self.terms_of_service_raw, self)
def get_carts_for_user(self, user):
"""return all of this Shop's Cart objects for the given User."""
from .cart import Cart
return self.carts.filter(and_(Cart.shop == self, Cart.user == user))
def make_cart_active_for_user(self, user, cart):
for c in self.get_carts_for_user(user):
if cart != c:
c.active = False
else:
c.active = True
self.dbsession.add(c)
self.dbsession.flush()
def get_active_cart_for_user(self, user):
carts = self.get_carts_for_user(user)
for cart in carts:
if cart.active == True:
return cart
def create_new_cart_for_user(self, user):
from .cart import Cart
cart = Cart()
cart.user = user
cart.shop = self
self.dbsession.add(cart)
self.dbsession.flush()
self.make_cart_active_for_user(user, cart)
return cart
def is_shop_name_available(dbsession, name):
return not _shop_by_name_query(dbsession, unicode(name)).count()

View file

@ -8,7 +8,7 @@ from .meta import Base, RBase, UUIDType, now_timestamp, foreign_key, get_object_
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.orm import relationship
from sqlalchemy.orm import relationship, backref
from .user_shop import UserShop
@ -49,7 +49,7 @@ class User(RBase, Base):
authenticated = False
id = Column(UUIDType, primary_key=True, index=True)
active_cart_id = Column(UUIDType, nullable=False)
active_shop_id = Column(UUIDType, default=None)
name = Column(Unicode(64), unique=True, nullable=False)
full_name = Column(Unicode(64))
@ -62,7 +62,12 @@ class User(RBase, Base):
gravatar = Column(Boolean, default=False)
verified = Column(Boolean, default=False)
disabled = Column(Boolean, default=False)
# TODO: remove this column.
active_cart_id = Column(UUIDType, nullable=False)
# TODO: remove this column.
active_cart_count = Column(Integer, default=0, nullable=False)
# TODO: remove this column.
active_cart_total_in_cents = Column(BigInteger, default=0, nullable=False)
# 0 is dark, 1 is light, could have more themes when h@ckz0rs unite.
@ -98,6 +103,10 @@ class User(RBase, Base):
self.id = uuid.uuid1()
self.email = unicode(email)
# TODO: remove this column.
self.active_cart_id = uuid.uuid1()
self.new_password()
# don't password throttle new User objects.
self.password_timestamp = 0
@ -212,17 +221,6 @@ class User(RBase, Base):
def can_not_edit_shop(self, shop):
return not self.can_edit_shop(shop)
@property
def active_cart(self):
if hasattr(self, "_active_cart") == False:
self._active_cart = get_cart_by_id(self.dbsession, self.active_cart_id)
return self._active_cart
def new_active_cart(self):
self._active_cart = Cart(user=self)
self.active_cart_id = self._active_cart.id
return self._active_cart
def owns_cart(self, cart):
return self == cart.user
@ -286,11 +284,7 @@ def get_or_create_user_by_email(dbsession, email):
if user is None:
# create new User for unverified user.
user = User(email)
cart = user.new_active_cart()
if not is_user_name_available(dbsession, user.name):
user.name = generate_user_name(dbsession)
# make sure the new user's default active cart exists.
dbsession.add(cart)
dbsession.flush()
return user

View file

@ -30,41 +30,6 @@ def includeme(config):
return user
def add_active_cart(request):
"""
Load active cart from session or
create cart and add to session.
Either way, always returns a Cart.
"""
cart_modified = False
cart_id = request.session.get("active_cart_id", None)
if cart_id is not None:
cart = get_cart_by_id(request.dbsession, cart_id)
else:
# if no active cart in session, create one.
cart = Cart()
# When the serializer is JSON instead of pickle and I encode to
# TypeError: Unicode-objects must be encoded before hashing
#request.session["active_cart_id"] = str(cart.id)
# Still using the JSON serializer when I encode to
# to utf-8 in order to work with bytes, I get this error:
# TypeError: Object of type bytes is not JSON serializable
#request.session["active_cart_id"] = str(cart.id).encode("utf-8")
# this appears to work fine with pickle.
request.session["active_cart_id"] = str(cart.id)
# The issue started when attempting to port from Python 2 to 3.
request.dbsession.add(cart)
request.dbsession.flush()
# only a cart whose id is in the session is considered active.
cart.active = True
return cart
def add_product(request):
"""Return Product object or None from route."""
@ -89,12 +54,36 @@ def includeme(config):
else:
return get_shop_by_domain_name(request.dbsession, request.domain)
def add_shop_param(request):
"""return query param for shop_id or empty string."""
if request.shop is not None:
if request.user in request.shop.users:
return "?shop_id={}".format(request.shop.id)
return ""
def add_active_cart(request):
"""
Load active cart for user for shop or
create cart and add it to the user as active.
Either way, always return a Cart.
"""
if request.user and request.shop:
cart = request.shop.get_active_cart_for_user(request.user)
if cart is None:
cart = request.shop.create_new_cart_for_user(request.user)
return cart
else:
return request.session_cart
def add_session_cart(request):
"""Return Cart from session or None."""
cart_id = request.session.get("active_cart_id", None)
if cart_id is not None:
cart = get_cart_by_id(request.dbsession, cart_id)
else:
# if no active cart in session, create one.
cart = Cart()
cart.shop = request.shop
cart.active = True
request.session["active_cart_id"] = str(cart.id)
request.dbsession.add(cart)
request.dbsession.flush()
return cart
def add_market(request):
"""Return Market object or None."""
@ -174,12 +163,14 @@ def includeme(config):
# Register functions to app config as request methods.
# To prevent multiple DB lookups, cache result with `reify=True`.
config.add_request_method(add_user, "user", reify=True)
config.add_request_method(add_active_cart, "active_cart", reify=True)
config.add_request_method(add_session_cart, "session_cart", reify=True)
config.add_request_method(add_product, "product", reify=True)
config.add_request_method(add_shop, "shop", reify=True)
config.add_request_method(add_shop_param, "shop_param", reify=True)
config.add_request_method(add_market, "market", reify=True)

View file

@ -0,0 +1,37 @@
"""add shop_id to cart
Revision ID: 201924e6306f
Revises: aad3d454a6f4
Create Date: 2022-07-10 13:21:40.740356
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '201924e6306f'
down_revision = 'aad3d454a6f4'
branch_labels = None
depends_on = None
from make_post_sell.models.meta import UUIDType
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('mps_cart', sa.Column('shop_id', UUIDType, nullable=True))
op.add_column('mps_cart', sa.Column('active', sa.Boolean(), nullable=True))
op.create_index(op.f('ix_mps_cart_id'), 'mps_cart', ['id'], unique=False)
#op.create_foreign_key(None, 'mps_cart', 'mps_user', ['user_id'], ['id'])
#op.create_foreign_key(None, 'mps_cart', 'mps_shop', ['shop_id'], ['id'])
#op.drop_column('mps_user_shop', 'default')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_mps_cart_id'), table_name='mps_cart')
op.drop_column('mps_cart', 'active')
op.drop_column('mps_cart', 'shop_id')
# ### end Alembic commands ###

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
jquery-3.4.1.min.js
jquery-3.6.0.min.js

View file

@ -6,10 +6,10 @@
<h2>Your saved carts:</h2>
{% for cart in request.user.carts %}
{% for cart in request.shop.get_carts_for_user(request.user) %}
<section class="cart">
<a class=""cart-and-count"" href="/cart/{{ cart.id }}">&#128722; Cart ${{ '%0.2f' % cart.total }} ({{ cart.count }} items)</a> {{ cart.human_updated_timestamp }} {% if cart.id == request.user.active_cart_id %}(active){% endif %}
<a class=""cart-and-count"" href="/cart/{{ cart.id }}">&#128722; Cart ${{ '%0.2f' % cart.total }} ({{ cart.count }} items)</a> {{ cart.human_updated_timestamp }} {% if cart == request.active_cart %}(active){% endif %}
</section>
<br>

View file

@ -57,13 +57,6 @@ For {{ coupon.human_discount }}
<form method="post" action="/coupon/apply" onsubmit="submit.disabled = true; return true;">
<input
name = "cart_id"
type = "hidden"
id = "cart_id_input"
value = "{{ request.active_cart.id }}"
required />
<input
name = "coupon_id"
type = "hidden"

View file

@ -1,3 +1,2 @@
<script async src="/static/js/jquery.min.js"></script>
<script async src="/static/js/custom.js"></script>

View file

@ -66,9 +66,8 @@
{% macro new_card() %}
<script src="/static/js/jquery.min.js"></script>
<script src="https://js.stripe.com/v3/"></script>
<form action="/billing/add-card" method="post" id="payment-form">
<form action="/billing/add-card?shop_id={{ request.shop.id }}" method="post" id="payment-form">
{% include 'snippets/csrf.j2' %}
<input type="hidden" name="return-to" value="{{ request.path_url }}">
<div class="form-row">
<div id="card-element">

View file

@ -102,7 +102,7 @@ class AuthenticatedFunctionalTests(FunctionalTests):
"description": "russell's shop sells some great digital downloads.",
"stripe_public_api_key": environ["MPS_TEST_STRIPE_PUBLIC_API_KEY"],
"stripe_secret_api_key": environ["MPS_TEST_STRIPE_SECRET_API_KEY"],
"domain_name": "localhost",
"domain_name": "localhost.localhost",
}
self.shop2_params = {
@ -112,7 +112,7 @@ class AuthenticatedFunctionalTests(FunctionalTests):
"description": "joe's shop sells some bad digital downloads.",
"stripe_public_api_key": environ["MPS_TEST_STRIPE_PUBLIC_API_KEY"],
"stripe_secret_api_key": environ["MPS_TEST_STRIPE_SECRET_API_KEY"],
"domain_name": "localhost",
"domain_name": "localhost.localhost",
}
self.product1_params = {
@ -308,6 +308,8 @@ class AuthenticatedFunctionalTests(FunctionalTests):
if log_out_user:
self.testapp.get("/log-out")
return shop
def test_new_shop_invalid_shop_name(self):
self.log_in_user(self.user1_creds)
params = self.shop1_params
@ -337,7 +339,7 @@ class AuthenticatedFunctionalTests(FunctionalTests):
def test_new_product_missing_required_fields(self):
# log in and create a new shop.
self.test_create_new_shop(
shop = self.test_create_new_shop(
user_creds=self.user1_creds,
shop_params=self.shop1_params,
log_out_user=False
@ -345,7 +347,9 @@ class AuthenticatedFunctionalTests(FunctionalTests):
product_params = self.product1_params
del product_params["description"]
res = self.testapp.post("/p/new", product_params)
#redirect_res1 = self.testapp.post("/p/new", product_params)
#res = redirect_res1.follow()
res = self.testapp.post("/p/new?shop_id={}".format(shop.id), product_params)
self.assertIn("You must fill out all fields.", res.body.decode())
def test_new_product(self, user_creds=None, shop_params=None, product_params=None, log_out_user=False):
@ -360,12 +364,12 @@ class AuthenticatedFunctionalTests(FunctionalTests):
product_params = self.product1_params
# log in and create a new shop.
self.test_create_new_shop(
shop = self.test_create_new_shop(
user_creds=user_creds,
shop_params=shop_params,
)
redirect_res = self.testapp.post("/p/new", product_params)
redirect_res = self.testapp.post("/p/new?shop_id={}".format(shop.id), product_params)
res = redirect_res.follow()
self.assertIn("Great, next you may upload files.", res.body.decode())
@ -434,35 +438,43 @@ class AuthenticatedFunctionalTests(FunctionalTests):
all_products = get_all_products(self.dbsession)
product = all_products.one()
shop = get_shop_by_name(self.dbsession, self.shop1_params["name"])
# print("{},{},{}".format(product.id, product.title, product.price))
# log in user2.
self.log_in_user(self.user2_creds)
# add product to cart.
redirect_res1 = self.testapp.get("/cart/add?product_id={}".format(product.id))
redirect_res1 = self.testapp.get("/cart/add?product_id={}&shop_id={}".format(product.id, shop.id))
redirect_res2 = redirect_res1.follow()
res = redirect_res2.follow()
res_body = res.body.decode()
self.assertIn("You added \"russell\'s product\" to your cart.", res_body)
self.assertIn("Cart $3.50 (1)", res_body)
carts = get_all_carts(self.dbsession)
# make sure we have 2 carts.
self.assertEqual(2, carts.count())
# make sure we have 4 carts. request.active_cart & request.session_cart for each user.
self.assertEqual(4, carts.count())
# make sure user2 has 1 item in active Cart.
self.assertEqual(1, self.user2.active_cart.count)
self.dbsession.refresh(shop)
self.dbsession.refresh(self.user1)
self.dbsession.refresh(self.user2)
user_1_cart = shop.get_active_cart_for_user(self.user1)
user_2_cart = shop.get_active_cart_for_user(self.user2)
# make sure user1 has 0 items in active Cart.
self.assertEqual(0, self.user1.active_cart.count)
self.assertEqual(0, user_1_cart.count)
# make sure user2 has 1 item in active Cart.
self.assertEqual(1, user_2_cart.count)
redirect_res = self.testapp.get(
"/u/cart/{}/checkout?shop_id={}".format(
self.user2.active_cart.id,
product.shop.id
user_2_cart.id,
shop.id
)
)
redirect_res2 = redirect_res1.follow()
@ -470,7 +482,7 @@ class AuthenticatedFunctionalTests(FunctionalTests):
self.assertIn("Please enter your payment information.", res.body.decode())
res = self.testapp.post(
"/billing/add-card?shop_id={}".format(product.shop.id),
"/billing/add-card?shop_id={}".format(shop.id),
{
"email": self.user2.email,
"stripeToken": "tok_visa",
@ -479,27 +491,29 @@ class AuthenticatedFunctionalTests(FunctionalTests):
res = self.testapp.get(
"/u/cart/{}/checkout?shop_id={}".format(
self.user2.active_cart.id,
product.shop.id
user_2_cart.id,
shop.id
)
)
res_body = res.body.decode()
self.assertIn("Please confirm your order.", res_body)
self.assertIn("Visa", res_body)
#self.assertIn("Visa", res_body)
self.assertIn("Are you sure you want to charge", res_body)
redirect_res1 = self.testapp.get(
"/u/cart/{}/complete/checkout?shop_id={}".format(
self.user2.active_cart.id,
product.shop.id
user_2_cart.id,
shop.id
)
)
res = redirect_res1.follow()
redirect_res2 = redirect_res1.follow()
res = redirect_res2.follow()
res_body = res.body.decode()
self.assertIn("Success, you have completed the purchase!", res_body)
shop = get_shop_by_name(self.dbsession, self.shop1_params["name"])
# refresh shop attributes from database.
self.dbsession.refresh(shop)
stripe_customer = shop.stripe_customer(self.user2)
@ -566,19 +580,22 @@ class AuthenticatedFunctionalTests(FunctionalTests):
# log in user2.
self.log_in_user(self.user2_creds)
# query the new shop from database.
shop = get_shop_by_name(self.dbsession, self.shop1_params["name"])
coupons = get_coupons_by_code(self.dbsession, self.coupon1_params["code"])
coupon = coupons[0]
# apply coupon to user2's active cart.
self.testapp.post(
"/coupon/apply?cart_id={}&coupon_id={}".format(
self.user2.active_cart.id,
"/coupon/apply?&coupon_id={}&shop_id={}".format(
coupon.id,
shop.id,
)
)
# verify coupon was applied.
res_redirect1 = self.testapp.get("/cart")
res_redirect1 = self.testapp.get("/cart?shop_id={}".format(shop.id))
res = res_redirect1.follow()
res_body = res.body.decode()
self.assertIn("Slippery Halloween Party", res_body)
@ -590,37 +607,40 @@ class AuthenticatedFunctionalTests(FunctionalTests):
product = all_products.one()
# add product to user2's active cart.
self.testapp.get("/cart/add?product_id={}".format(product.uuid_str))
self.testapp.get("/cart/add?product_id={}&shop_id={}".format(product.uuid_str, shop.id))
# add billing details for user2 for this shop.
res = self.testapp.post(
"/billing/add-card?shop_id={}".format(product.shop.id),
"/billing/add-card?shop_id={}".format(shop.id),
{
"email": self.user2.email,
"stripeToken": "tok_visa",
},
)
# why is this None?
user_2_cart = shop.get_active_cart_for_user(self.user2)
# attempt to checkout before coupon is properly qualified.
# shop total must be over cart_qualifier (test defaults to $10).
res_redirect1 = self.testapp.get(
"/u/cart/{}/checkout?shop_id={}".format(
self.user2.active_cart.id,
product.shop.id
user_2_cart.id,
shop.id
)
)
res = res_redirect1.follow()
self.assertIn("Please review coupon terms: shop total not met.", res.body.decode())
# add more product to user2's active cart, to pass cart_qualifier.
self.testapp.get("/cart/add?product_id={}".format(product.uuid_str))
self.testapp.get("/cart/add?product_id={}".format(product.uuid_str))
self.testapp.get("/cart/add?product_id={}&shop_id={}".format(product.uuid_str, shop.id))
self.testapp.get("/cart/add?product_id={}&shop_id={}".format(product.uuid_str, shop.id))
# checkout coupon confirmation unblocked.
res = self.testapp.get(
"/u/cart/{}/checkout?shop_id={}".format(
self.user2.active_cart.id,
product.shop.id
user_2_cart.id,
shop.id
)
)
self.assertIn("Please confirm your order.", res.body.decode())

View file

@ -9,7 +9,7 @@ def get_referer_or_home(request):
def user_required(
flash_msg="You must log in to access that area.",
flash_level="error",
return_to_route_name="",
redirect_to_route_name="",
):
"""This view requires that the request has a user."""
@ -19,9 +19,8 @@ def user_required(
if request.user and request.user.authenticated:
return fn(request)
request.session.flash((flash_msg, flash_level))
return_to = ""
if return_to_route_name:
return_to = request.route_url(return_to_route_name)
if redirect_to_route_name:
return HTTPFound(request.route_url(redirect_to_route_name))
return HTTPFound(get_referer_or_home(request))
return inner

View file

@ -109,18 +109,19 @@ def verification_challenge(request):
request.session.flash(msg)
# merge any products in the session cart
# into user's main active cart.
user.active_cart.merge_in_cart(request.active_cart)
# make session cart match user's active cart.
request.session["active_cart_id"] = str(user.active_cart.id)
# into request user's active cart for this shop.
request.active_cart.merge_in_cart(request.session_cart)
# delete old session only cart.
request.dbsession.delete(request.active_cart)
request.dbsession.delete(request.session_cart)
# make session cart match user's active cart.
request.session["active_cart_id"] = str(request.active_cart.id)
# save user and user's active cart.
request.dbsession.add(user)
request.dbsession.add(user.active_cart)
request.dbsession.add(request.active_cart)
request.dbsession.flush()
return HTTPFound("/")

View file

@ -9,7 +9,11 @@ from . import (
@view_config(route_name="billing", renderer="billing.j2")
@user_required(return_to_route_name="billing")
@user_required(
flash_msg="Verify your email to access Payment Preferences.",
flash_level="info",
redirect_to_route_name="join-or-log-in"
)
@shop_is_ready_required()
def billing(request):
return {}
@ -19,24 +23,24 @@ def billing(request):
@user_required()
@shop_is_ready_required()
def add_card(request):
# try to get the return_to uri from posted parameters.
return_to = request.params.get("return-to", "/billing")
stripe_customer = request.shop.stripe_customer(request.user, create=True)
try:
source = request.stripe_customer.sources.create(
source = stripe_customer.sources.create(
source=request.params.get("stripeToken")
)
request.stripe_customer.default_source = source
#source = stripe_customer.create_source(
# id=stripe_customer.id,
# source=request.params.get("stripeToken")
#)
stripe_customer.default_source = source
request.session.flash(("You saved a new card.", "success"))
request.stripe_customer.save()
stripe_customer.save()
except request.shop.stripe.error.CardError as e:
body = e.json_body
err = body.get("error", {})
request.session.flash((err.get("message"), "error"))
return HTTPFound(return_to)
return HTTPFound("/billing")
@view_config(route_name="confirm-update-card", renderer="update-card.j2")

View file

@ -34,18 +34,12 @@ def get_cart_from_matchdict(request):
@view_config(route_name="user_carts", renderer="carts.j2")
@user_required()
def carts(request):
if request.user.carts.count() == 1:
# since this user only has one cart, redirect to it.
return HTTPFound("/cart/{}".format(request.user.carts[0].id))
return {}
def save_cart(request):
if request.active_cart.is_empty == False:
new_cart = request.user.new_active_cart()
request.dbsession.add(new_cart)
request.dbsession.add(request.user)
request.session["active_cart_id"] = str(new_cart.id)
new_cart = request.shop.create_new_cart_for_user(request.user)
msg = ("Cart saved for later.", "info")
request.session.flash(msg)
@ -54,7 +48,7 @@ def save_cart(request):
@user_required(
flash_msg="To <b>save</b> your cart, please verify your email address below.",
flash_level="info",
return_to_route_name="cart"
redirect_to_route_name="join-or-log-in"
)
def cart_save(request):
save_cart(request)
@ -124,15 +118,12 @@ def cart_activate(request):
else:
if request.user.owns_cart(cart):
msg = ("You activated that cart.", "success")
request.session["active_cart_id"] = str(cart.id)
request.user.active_cart_id = cart.id
request.shop.make_cart_active_for_user(request.user, cart)
else:
msg = ("You made a copy and activated that public cart.", "success")
new_cart = request.user.new_active_cart()
new_cart = request.shop.create_new_cart_for_user(request.user)
new_cart.merge_in_cart(cart)
request.dbsession.add(new_cart)
request.dbsession.add(request.user)
request.session["active_cart_id"] = str(new_cart.id)
request.session.flash(msg)
return HTTPFound("/cart/{}".format(new_cart.id))
@ -233,9 +224,9 @@ def cart_add_product(request):
request.session.flash(msg)
return HTTPFound(get_referer_or_home(request))
elif not request.is_saas_domain and request.shop.uuid_str != product.shop_uuid_str:
request.session.flash(("Refusing to add a product from another shop to cart.", "error"))
return HTTPFound("/cart")
#elif not request.is_saas_domain and request.shop.uuid_str != product.shop_uuid_str:
# request.session.flash(("Refusing to add a product from another shop to cart.", "error"))
# return HTTPFound("/cart")
msg = ('You added "{}" to your cart.'.format(product.title), "success")
request.active_cart.add_product(product)
@ -336,7 +327,7 @@ def cart_quantity_product(request):
@user_required(
flash_msg="To <b>checkout</b> your cart, please verify your email address below.",
flash_level="info",
return_to_route_name="cart"
redirect_to_route_name="join-or-log-in"
)
@shop_is_ready_required()
def cart_checkout(request):
@ -411,7 +402,6 @@ def cart_complete_checkout(request):
# which has InvoiceLineItems for Products derived from more than one Shop.
invoice = Invoice(user=request.user)
invoice.shop = request.shop
invoice.market = request.market

View file

@ -79,36 +79,26 @@ def coupon_new(request):
@view_config(route_name="coupon_apply_to_cart", request_method="POST")
@user_required()
def coupon_apply_to_cart(request):
cart_id = request.params.get("cart_id")
coupon_id = request.params.get("coupon_id")
cart = get_cart_by_id(request.dbsession, cart_id)
coupon = get_coupon_by_id(request.dbsession, coupon_id)
if cart is None:
msg = ("That cart_id does not exist.", "error")
elif coupon is None:
if coupon is None:
msg = ("That coupon_id does not exist.", "error")
elif request.user.does_not_own_cart(cart):
msg = ("You do not own that cart.", "error")
else:
if coupon not in cart.coupons:
msg = ("The coupon was applied to your cart.", "success")
cart.coupons.append(coupon)
request.dbsession.add(cart)
request.dbsession.flush()
else:
msg = ("The coupon was already applied to your cart.", "success")
request.session.flash(msg)
return HTTPFound("/cart/{}".format(cart_id))
return HTTPFound("/cart/{}".format(request.active_cart.id))
if coupon not in request.active_cart.coupons:
msg = ("The coupon was applied to your cart.", "success")
request.active_cart.coupons.append(coupon)
request.dbsession.add(request.active_cart)
request.dbsession.flush()
else:
msg = ("The coupon was already applied to your cart.", "success")
request.session.flash(msg)
return HTTPFound(get_referer_or_home(request))
return HTTPFound("/cart/{}".format(request.active_cart.id))
@view_config(route_name="coupon_remove_from_cart", request_method="POST")

View file

@ -64,6 +64,7 @@ def get_shop_from_matchdict(request, prefetched_shop=None):
@view_config(route_name="home", renderer="home.j2")
def home(request):
products = None
print(request.active_cart)
if request.shop:
products = get_products_from_a_shop(request.shop)
return {

View file

@ -33,7 +33,7 @@ setup = make_post_sell.scripts.pshell.setup
[alembic]
# path to migration scripts
script_location = make_post_sell:scripts/alembic
sqlalchemy.url = sqlite:///%(here)s/make_post_sell.sqlite
sqlalchemy.url = sqlite:///%(here)s/test_make_post_sell.sqlite
[server:main]
use = egg:waitress#main