Compare commits
8 Commits
7eb66fbf24
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
356d916e26 | ||
|
|
a28424c07c | ||
|
|
37ceb37b82 | ||
|
|
0c9b8d6aa2 | ||
|
|
123f752946 | ||
|
|
a420bfa7f0 | ||
|
|
42f4a8b68f | ||
|
|
e653acb921 |
55
alembic/versions/c3d4e5f6a7b8_add_page_tracking_to_orders.py
Normal file
55
alembic/versions/c3d4e5f6a7b8_add_page_tracking_to_orders.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
"""add page_config_id to orders, market_place_id to cart_items
|
||||||
|
|
||||||
|
Revision ID: c3d4e5f6a7b8
|
||||||
|
Revises: b2c3d4e5f6a7
|
||||||
|
Create Date: 2026-02-10
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
revision = 'c3d4e5f6a7b8'
|
||||||
|
down_revision = 'b2c3d4e5f6a7'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# 1. Add market_place_id to cart_items
|
||||||
|
op.add_column(
|
||||||
|
'cart_items',
|
||||||
|
sa.Column('market_place_id', sa.Integer(), nullable=True),
|
||||||
|
)
|
||||||
|
op.create_foreign_key(
|
||||||
|
'fk_cart_items_market_place_id',
|
||||||
|
'cart_items',
|
||||||
|
'market_places',
|
||||||
|
['market_place_id'],
|
||||||
|
['id'],
|
||||||
|
ondelete='SET NULL',
|
||||||
|
)
|
||||||
|
op.create_index('ix_cart_items_market_place_id', 'cart_items', ['market_place_id'])
|
||||||
|
|
||||||
|
# 2. Add page_config_id to orders
|
||||||
|
op.add_column(
|
||||||
|
'orders',
|
||||||
|
sa.Column('page_config_id', sa.Integer(), nullable=True),
|
||||||
|
)
|
||||||
|
op.create_foreign_key(
|
||||||
|
'fk_orders_page_config_id',
|
||||||
|
'orders',
|
||||||
|
'page_configs',
|
||||||
|
['page_config_id'],
|
||||||
|
['id'],
|
||||||
|
ondelete='SET NULL',
|
||||||
|
)
|
||||||
|
op.create_index('ix_orders_page_config_id', 'orders', ['page_config_id'])
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
op.drop_index('ix_orders_page_config_id', table_name='orders')
|
||||||
|
op.drop_constraint('fk_orders_page_config_id', 'orders', type_='foreignkey')
|
||||||
|
op.drop_column('orders', 'page_config_id')
|
||||||
|
|
||||||
|
op.drop_index('ix_cart_items_market_place_id', table_name='cart_items')
|
||||||
|
op.drop_constraint('fk_cart_items_market_place_id', 'cart_items', type_='foreignkey')
|
||||||
|
op.drop_column('cart_items', 'market_place_id')
|
||||||
@@ -413,13 +413,23 @@ class CartItem(Base):
|
|||||||
nullable=False,
|
nullable=False,
|
||||||
server_default=func.now(),
|
server_default=func.now(),
|
||||||
)
|
)
|
||||||
|
market_place_id: Mapped[int | None] = mapped_column(
|
||||||
|
ForeignKey("market_places.id", ondelete="SET NULL"),
|
||||||
|
nullable=True,
|
||||||
|
index=True,
|
||||||
|
)
|
||||||
|
|
||||||
deleted_at: Mapped[datetime | None] = mapped_column(
|
deleted_at: Mapped[datetime | None] = mapped_column(
|
||||||
DateTime(timezone=True),
|
DateTime(timezone=True),
|
||||||
nullable=True,
|
nullable=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Relationships
|
# Relationships
|
||||||
|
|
||||||
|
market_place: Mapped["MarketPlace | None"] = relationship(
|
||||||
|
"MarketPlace",
|
||||||
|
foreign_keys=[market_place_id],
|
||||||
|
)
|
||||||
product: Mapped["Product"] = relationship(
|
product: Mapped["Product"] = relationship(
|
||||||
"Product",
|
"Product",
|
||||||
back_populates="cart_items",
|
back_populates="cart_items",
|
||||||
|
|||||||
@@ -17,6 +17,12 @@ class Order(Base):
|
|||||||
user_id: Mapped[Optional[int]] = mapped_column(ForeignKey("users.id"), nullable=True)
|
user_id: Mapped[Optional[int]] = mapped_column(ForeignKey("users.id"), nullable=True)
|
||||||
session_id: Mapped[Optional[str]] = mapped_column(String(64), index=True, nullable=True)
|
session_id: Mapped[Optional[str]] = mapped_column(String(64), index=True, nullable=True)
|
||||||
|
|
||||||
|
page_config_id: Mapped[Optional[int]] = mapped_column(
|
||||||
|
ForeignKey("page_configs.id", ondelete="SET NULL"),
|
||||||
|
nullable=True,
|
||||||
|
index=True,
|
||||||
|
)
|
||||||
|
|
||||||
status: Mapped[str] = mapped_column(
|
status: Mapped[str] = mapped_column(
|
||||||
String(32),
|
String(32),
|
||||||
nullable=False,
|
nullable=False,
|
||||||
@@ -68,6 +74,11 @@ class Order(Base):
|
|||||||
back_populates="order",
|
back_populates="order",
|
||||||
lazy="selectin",
|
lazy="selectin",
|
||||||
)
|
)
|
||||||
|
page_config: Mapped[Optional["PageConfig"]] = relationship(
|
||||||
|
"PageConfig",
|
||||||
|
foreign_keys=[page_config_id],
|
||||||
|
lazy="selectin",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class OrderItem(Base):
|
class OrderItem(Base):
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from suma_browser.app.csrf import generate_csrf_token
|
|||||||
from suma_browser.app.authz import has_access
|
from suma_browser.app.authz import has_access
|
||||||
from suma_browser.app.filters import register as register_filters
|
from suma_browser.app.filters import register as register_filters
|
||||||
|
|
||||||
from .urls import coop_url, market_url, cart_url, events_url, login_url
|
from .urls import coop_url, market_url, cart_url, events_url, login_url, page_cart_url
|
||||||
|
|
||||||
|
|
||||||
def setup_jinja(app: Quart) -> None:
|
def setup_jinja(app: Quart) -> None:
|
||||||
@@ -93,6 +93,7 @@ def setup_jinja(app: Quart) -> None:
|
|||||||
app.jinja_env.globals["cart_url"] = cart_url
|
app.jinja_env.globals["cart_url"] = cart_url
|
||||||
app.jinja_env.globals["events_url"] = events_url
|
app.jinja_env.globals["events_url"] = events_url
|
||||||
app.jinja_env.globals["login_url"] = login_url
|
app.jinja_env.globals["login_url"] = login_url
|
||||||
|
app.jinja_env.globals["page_cart_url"] = page_cart_url
|
||||||
|
|
||||||
# register jinja filters
|
# register jinja filters
|
||||||
register_filters(app)
|
register_filters(app)
|
||||||
|
|||||||
@@ -37,10 +37,10 @@ def events_url(path: str = "/") -> str:
|
|||||||
return app_url("events", path)
|
return app_url("events", path)
|
||||||
|
|
||||||
|
|
||||||
def market_url_for(market_slug: str, path: str = "/") -> str:
|
def page_cart_url(page_slug: str, path: str = "/") -> str:
|
||||||
if not path.startswith("/"):
|
if not path.startswith("/"):
|
||||||
path = "/" + path
|
path = "/" + path
|
||||||
return market_url(f"/{market_slug}{path}")
|
return cart_url(f"/{page_slug}{path}")
|
||||||
|
|
||||||
|
|
||||||
def login_url(next_url: str = "") -> str:
|
def login_url(next_url: str = "") -> str:
|
||||||
|
|||||||
@@ -103,7 +103,7 @@
|
|||||||
{% if g.user %}
|
{% if g.user %}
|
||||||
<form
|
<form
|
||||||
method="post"
|
method="post"
|
||||||
action="{{ url_for('cart.checkout')|host }}"
|
action="{{ url_for('page_cart.page_checkout')|host if page_post is defined and page_post else url_for('cart_global.checkout')|host }}"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
>
|
>
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<a
|
<a
|
||||||
href="{{ url_for('cart.view_cart')|host }}"
|
href="{{ cart_url('/') }}"
|
||||||
class="inline-flex items-center px-3 py-2 text-xs sm:text-sm rounded-full border border-stone-300 bg-white hover:bg-stone-50 transition"
|
class="inline-flex items-center px-3 py-2 text-xs sm:text-sm rounded-full border border-stone-300 bg-white hover:bg-stone-50 transition"
|
||||||
>
|
>
|
||||||
<i class="fa fa-shopping-cart mr-2" aria-hidden="true"></i>
|
<i class="fa fa-shopping-cart mr-2" aria-hidden="true"></i>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{% import 'macros/links.html' as links %}
|
{% import 'macros/links.html' as links %}
|
||||||
{% macro header_row(oob=False) %}
|
{% macro header_row(oob=False) %}
|
||||||
{% call links.menu_row(id='cart-row', oob=oob) %}
|
{% call links.menu_row(id='cart-row', oob=oob) %}
|
||||||
{% call links.link(url_for('cart.view_cart'), hx_select_search ) %}
|
{% call links.link(cart_url('/'), hx_select_search ) %}
|
||||||
<i class="fa fa-shopping-cart"></i>
|
<i class="fa fa-shopping-cart"></i>
|
||||||
<h2 class="text-xl font-bold">cart</h2>
|
<h2 class="text-xl font-bold">cart</h2>
|
||||||
{% endcall %}
|
{% endcall %}
|
||||||
|
|||||||
@@ -37,6 +37,16 @@
|
|||||||
<div>{{calendar.name}}</div>
|
<div>{{calendar.name}}</div>
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
|
{# Markets #}
|
||||||
|
{% for m in markets %}
|
||||||
|
<a
|
||||||
|
href="{{ market_url('/' + post.slug + '/' + m.slug + '/') }}"
|
||||||
|
class="{{styles.nav_button_less_pad}}">
|
||||||
|
<i class="fa fa-shopping-bag" aria-hidden="true"></i>
|
||||||
|
<div>{{m.name}}</div>
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
|
|
||||||
{% if not quantity %}
|
{% if not quantity %}
|
||||||
<form
|
<form
|
||||||
action="{{ market_url('/product/' + slug + '/cart') }}"
|
action="{{ url_for('market.browse.product.cart', slug=slug) }}"
|
||||||
method="post"
|
method="post"
|
||||||
hx-post="{{ market_url('/product/' + slug + '/cart') }}"
|
hx-post="{{ url_for('market.browse.product.cart', slug=slug) }}"
|
||||||
hx-target="#cart-mini"
|
hx-target="#cart-mini"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
class="rounded flex items-center"
|
class="rounded flex items-center"
|
||||||
@@ -38,9 +38,9 @@
|
|||||||
<div class="rounded flex items-center gap-2">
|
<div class="rounded flex items-center gap-2">
|
||||||
<!-- minus -->
|
<!-- minus -->
|
||||||
<form
|
<form
|
||||||
action="{{ market_url('/product/' + slug + '/cart') }}"
|
action="{{ url_for('market.browse.product.cart', slug=slug) }}"
|
||||||
method="post"
|
method="post"
|
||||||
hx-post="{{ market_url('/product/' + slug + '/cart') }}"
|
hx-post="{{ url_for('market.browse.product.cart', slug=slug) }}"
|
||||||
hx-target="#cart-mini"
|
hx-target="#cart-mini"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
>
|
>
|
||||||
@@ -80,9 +80,9 @@
|
|||||||
|
|
||||||
<!-- plus -->
|
<!-- plus -->
|
||||||
<form
|
<form
|
||||||
action="{{ market_url('/product/' + slug + '/cart') }}"
|
action="{{ url_for('market.browse.product.cart', slug=slug) }}"
|
||||||
method="post"
|
method="post"
|
||||||
hx-post="{{ market_url('/product/' + slug + '/cart') }}"
|
hx-post="{{ url_for('market.browse.product.cart', slug=slug) }}"
|
||||||
hx-target="#cart-mini"
|
hx-target="#cart-mini"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
>
|
>
|
||||||
@@ -139,7 +139,7 @@
|
|||||||
<div class="flex flex-col sm:flex-row sm:items-start justify-between gap-2 sm:gap-3">
|
<div class="flex flex-col sm:flex-row sm:items-start justify-between gap-2 sm:gap-3">
|
||||||
<div class="min-w-0">
|
<div class="min-w-0">
|
||||||
<h2 class="text-sm sm:text-base md:text-lg font-semibold text-stone-900">
|
<h2 class="text-sm sm:text-base md:text-lg font-semibold text-stone-900">
|
||||||
{% set href=market_url('/product/' + p.slug + '/') %}
|
{% set href=url_for('market.browse.product.product_detail', slug=p.slug) %}
|
||||||
<a
|
<a
|
||||||
href="{{ href }}"
|
href="{{ href }}"
|
||||||
hx_get="{{href}}"
|
hx_get="{{href}}"
|
||||||
@@ -189,9 +189,9 @@
|
|||||||
<div class="flex items-center gap-2 text-xs sm:text-sm text-stone-700">
|
<div class="flex items-center gap-2 text-xs sm:text-sm text-stone-700">
|
||||||
<span class="text-[0.65rem] sm:text-xs uppercase tracking-wide text-stone-500">Quantity</span>
|
<span class="text-[0.65rem] sm:text-xs uppercase tracking-wide text-stone-500">Quantity</span>
|
||||||
<form
|
<form
|
||||||
action="{{ market_url('/product/' + p.slug + '/cart') }}"
|
action="{{ url_for('market.browse.product.cart', slug=p.slug) }}"
|
||||||
method="post"
|
method="post"
|
||||||
hx-post="{{ market_url('/product/' + p.slug + '/cart') }}"
|
hx-post="{{ url_for('market.browse.product.cart', slug=p.slug) }}"
|
||||||
hx-target="#cart-mini"
|
hx-target="#cart-mini"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
>
|
>
|
||||||
@@ -212,9 +212,9 @@
|
|||||||
{{ item.quantity }}
|
{{ item.quantity }}
|
||||||
</span>
|
</span>
|
||||||
<form
|
<form
|
||||||
action="{{ market_url('/product/' + p.slug + '/cart') }}"
|
action="{{ url_for('market.browse.product.cart', slug=p.slug) }}"
|
||||||
method="post"
|
method="post"
|
||||||
hx-post="{{ market_url('/product/' + p.slug + '/cart') }}"
|
hx-post="{{ url_for('market.browse.product.cart', slug=p.slug) }}"
|
||||||
hx-target="#cart-mini"
|
hx-target="#cart-mini"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{% set _app_slugs = {'market': market_url('/'), 'cart': cart_url('/')} %}
|
{% set _app_slugs = {'cart': cart_url('/')} %}
|
||||||
<div class="flex flex-col sm:flex-row sm:items-center gap-2 border-r border-stone-200 mr-2 sm:max-w-2xl"
|
<div class="flex flex-col sm:flex-row sm:items-center gap-2 border-r border-stone-200 mr-2 sm:max-w-2xl"
|
||||||
id="menu-items-nav-wrapper">
|
id="menu-items-nav-wrapper">
|
||||||
{% from 'macros/scrolling_menu.html' import scrolling_menu with context %}
|
{% from 'macros/scrolling_menu.html' import scrolling_menu with context %}
|
||||||
|
|||||||
Reference in New Issue
Block a user