MPS-20 + edit page: defensive pricing_mode flush + 2-col grid layout
Two fixes in one commit: 1) defensive: flush pricing_mode change to DB immediately after the radio writes it, instead of relying on the trailing product_modified-block flush at end of view. Fox reported saving "Make an offer with buy-now" reverts to fixed price; could not reproduce locally. Direct flush eliminates any code path between the radio change and end-of-view that might raise and abort the txn before the deferred save. 2) layout: div.edit-page goes from auto-fit minmax(240px, 1fr) (fits 4+ wells on wide screens) to a strict 2-column max grid: 1 column on mobile, exactly 2 columns above 720px. The 4 wells on the product edit page now stack 2x2 instead of 4-across. Tests: - test_flip_to_offer_with_buy_now_mode_4_persists — direct POST pricing_mode=4 (Make Offer with Buy Now) persists - test_flip_from_3_to_4_persists — Offer-only → Offer+buy-now transition persists - test_render_form_then_submit_mode_4_via_form — fetches the rendered edit form via webtest and submits it back with mode=4 selected; the closest reproduction of fox's browser flow All three pass locally. The save path is correct in the test harness. If the prod issue persists after this commit, it's almost certainly a browser-level issue (form not sending the field), not server-side. Total: 961 tests pass (was 958 + 3).
This commit is contained in:
parent
5fbaf8fbfa
commit
0652f8cf31
3 changed files with 80 additions and 1 deletions
|
|
@ -1310,10 +1310,16 @@ img.serp-thumbnail {
|
|||
|
||||
div.edit-page {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
grid-template-columns: 1fr;
|
||||
grid-gap: 20px;
|
||||
}
|
||||
|
||||
@media (min-width: 720px) {
|
||||
div.edit-page {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
hr {
|
||||
color: var(--border-light, #EEEEEE);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6071,6 +6071,72 @@ class TestPricingModeFormSection(_AuthenticatedBase):
|
|||
# Auction inherits product price as default start.
|
||||
self.assertEqual(product.auction.start_price_in_cents, 5000)
|
||||
|
||||
def test_flip_to_offer_with_buy_now_mode_4_persists(self):
|
||||
"""Reproduce: fox reports saving as 'make offer with buy now' (mode 4)
|
||||
reverts to fixed price (mode 0). Should persist as 4."""
|
||||
shop, product_id = self._make_product()
|
||||
self._post_edit(product_id, pricing_mode="4")
|
||||
self.dbsession.expire_all()
|
||||
|
||||
from ..models.product import get_product_by_id
|
||||
product = get_product_by_id(self.dbsession, product_id)
|
||||
self.assertEqual(product.pricing_mode, 4)
|
||||
self.assertTrue(product.is_offer_mode)
|
||||
self.assertTrue(product.is_buy_now_allowed)
|
||||
|
||||
def test_flip_from_3_to_4_persists(self):
|
||||
"""Test the path from mode 3 → mode 4 specifically — could be
|
||||
what fox hit if they had previously set mode 3."""
|
||||
shop, product_id = self._make_product()
|
||||
self._post_edit(product_id, pricing_mode="3")
|
||||
self.dbsession.expire_all()
|
||||
|
||||
from ..models.product import get_product_by_id
|
||||
product = get_product_by_id(self.dbsession, product_id)
|
||||
self.assertEqual(product.pricing_mode, 3)
|
||||
|
||||
# Now flip to mode 4.
|
||||
self._post_edit(product_id, pricing_mode="4")
|
||||
self.dbsession.expire_all()
|
||||
product = get_product_by_id(self.dbsession, product_id)
|
||||
self.assertEqual(product.pricing_mode, 4)
|
||||
|
||||
def test_render_form_then_submit_mode_4_via_form(self):
|
||||
"""Reproduce: fetch edit page, parse the actual form, submit it
|
||||
with pricing_mode=4 selected. Closer to fox's browser flow."""
|
||||
from ..models.product import get_product_by_id
|
||||
import re
|
||||
|
||||
shop, product_id = self._make_product()
|
||||
# Render the edit page.
|
||||
res = self.testapp.get(f"/p/{product_id}/edit", status=200)
|
||||
body = res.body.decode()
|
||||
|
||||
# Verify all 5 pricing_mode radios render.
|
||||
self.assertEqual(body.count('name="pricing_mode"'), 5)
|
||||
|
||||
# Pick out the form fields in the main form (action="*/edit").
|
||||
# webtest can fill out forms directly:
|
||||
form = res.forms[0] # not the right one — find by action
|
||||
for f in res.forms.values():
|
||||
action = f.action or ""
|
||||
if action.endswith("/edit"):
|
||||
form = f
|
||||
break
|
||||
else:
|
||||
self.fail("No form posting to /edit found")
|
||||
|
||||
# Set the radio.
|
||||
form["pricing_mode"] = "4"
|
||||
# Find the visibility field (also a radio); preserve current value.
|
||||
# webtest preserves all radio defaults if not touched.
|
||||
result = form.submit("submit")
|
||||
self.assertIn(result.status_int, (200, 302))
|
||||
|
||||
self.dbsession.expire_all()
|
||||
product = get_product_by_id(self.dbsession, product_id)
|
||||
self.assertEqual(product.pricing_mode, 4)
|
||||
|
||||
def test_flip_to_offer_does_not_create_auction(self):
|
||||
shop, product_id = self._make_product()
|
||||
self._post_edit(product_id, pricing_mode="3")
|
||||
|
|
|
|||
|
|
@ -320,6 +320,13 @@ def product_edit(request):
|
|||
if new_mode in (0, 1, 2, 3, 4) and new_mode != product.pricing_mode:
|
||||
product_modified = True
|
||||
product.pricing_mode = new_mode
|
||||
# Flush immediately so the change persists even if a later
|
||||
# block in this view raises. Defense in depth — the trailing
|
||||
# dbsession.add(product) at function end also flushes, but we
|
||||
# want to commit the pricing_mode change as soon as it is
|
||||
# validated.
|
||||
request.dbsession.add(product)
|
||||
request.dbsession.flush()
|
||||
mode_label = {
|
||||
0: "fixed price", 1: "auction", 2: "auction + buy now",
|
||||
3: "make an offer", 4: "make an offer + buy now",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue