shop_id & active on cart table
This commit is contained in:
parent
edaa1e450e
commit
ff8bddadbb
20 changed files with 239 additions and 180 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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 ###
|
||||
2
make_post_sell/static/js/jquery-3.4.1.min.js
vendored
2
make_post_sell/static/js/jquery-3.4.1.min.js
vendored
File diff suppressed because one or more lines are too long
3
make_post_sell/static/js/jquery-3.6.0.min.js
vendored
Normal file
3
make_post_sell/static/js/jquery-3.6.0.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
make_post_sell/static/js/jquery.min.js
vendored
2
make_post_sell/static/js/jquery.min.js
vendored
|
|
@ -1 +1 @@
|
|||
jquery-3.4.1.min.js
|
||||
jquery-3.6.0.min.js
|
||||
|
|
@ -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 }}">🛒 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 }}">🛒 Cart ${{ '%0.2f' % cart.total }} ({{ cart.count }} items)</a> {{ cart.human_updated_timestamp }} {% if cart == request.active_cart %}(active){% endif %}
|
||||
</section>
|
||||
<br>
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,2 @@
|
|||
|
||||
<script async src="/static/js/jquery.min.js"></script>
|
||||
<script async src="/static/js/custom.js"></script>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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("/")
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
2
test.ini
2
test.ini
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue