Merge branch 'address' into 'master'

Add, activate, and remove active user addresses.

See merge request engineering/make-post-sell/make_post_sell!9
This commit is contained in:
Russell Ballestrini 2022-09-06 16:19:09 +00:00
commit f01ba4458c
11 changed files with 207 additions and 12 deletions

View file

@ -10,6 +10,7 @@ from .shop import *
from .user import *
from .user_shop import *
from .user_product import *
from .user_address import *
from .price import *
from .cart import *
from .cart_coupon import *

View file

@ -1,6 +1,6 @@
import uuid
from sqlalchemy import Column, BigInteger, Boolean, Integer
from sqlalchemy import Column, BigInteger, Boolean, Integer, UnicodeText
from .meta import Base, RBase, UUIDType, foreign_key, now_timestamp
@ -93,6 +93,9 @@ class Invoice(RBase, Base):
# the timestamp when the invoice was created.
created_timestamp = Column(BigInteger, nullable=False)
# the user's address to ship the item to.
delivery_address = Column(UnicodeText, nullable=True)
# one to one.
user = relationship(argument="User", uselist=False, lazy="joined")

View file

@ -16,6 +16,7 @@ UUIDType = TempUUIDType(binary=False)
CLASS_TO_TABLE = {
"Address": "mps_address",
"Shop": "mps_shop",
"Product": "mps_product",
"Coupon": "mps_coupon",
@ -23,6 +24,7 @@ CLASS_TO_TABLE = {
"User": "mps_user",
"UserShop": "mps_user_shop",
"UserProduct": "mps_user_product",
"UserAddress": "mps_user_address",
"Price": "mps_price",
"License": "mps_license",
"Cart": "mps_cart",

View file

@ -16,6 +16,8 @@ from .stripe_user_shop import StripeUserShop
from .user_product import UserProduct
from .user_address import UserAddress, get_user_address_by_id
from .shop import Shop
from .cart import Cart
@ -70,6 +72,8 @@ class User(RBase, Base):
# TODO: remove this column.
active_cart_total_in_cents = Column(BigInteger, default=0, nullable=False)
active_address_id = Column(UUIDType, nullable=True)
# 0 is dark, 1 is light, could have more themes when h@ckz0rs unite.
theme_id = Column(BigInteger, nullable=False, default=1)
@ -97,6 +101,8 @@ class User(RBase, Base):
# this is ideal when we want to further filter the results.
carts = relationship(argument="Cart", lazy="dynamic", back_populates="user")
addresses = relationship(argument="UserAddress", lazy="dynamic", back_populates="user")
def __init__(self, email):
self.name = unicode("user-{}".format(generate_password(size=8)))
self.created_timestamp = now_timestamp()
@ -227,6 +233,18 @@ class User(RBase, Base):
def does_not_own_cart(self, cart):
return not self.owns_cart(cart)
def new_address(self, new_address):
user_address = UserAddress(new_address)
user_address.user = self
self.active_address_id = user_address.uuid_str
return user_address
@property
def active_address(self):
for address in self.addresses:
if address.id == self.active_address_id:
return address
def _user_by_name_query(dbsession, name):
"""query User by case insensitive name."""

View file

@ -0,0 +1,31 @@
import uuid
from sqlalchemy import Column, BigInteger, UnicodeText
from .meta import Base, RBase, UUIDType, now_timestamp, foreign_key, get_object_by_id
from sqlalchemy.orm import relationship
class UserAddress(RBase, Base):
"""This class represents a physical mail address."""
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)
data = Column(UnicodeText)
created_timestamp = Column(BigInteger, nullable=False)
user = relationship(argument="User", uselist=False, lazy="joined")
#shop = relationship(argument="Shop", uselist=False, lazy="joined")
def __init__(self, data):
self.created_timestamp = now_timestamp()
self.id = uuid.uuid1()
self.data = data
def get_user_address_by_id(dbsession, user_address_id):
"""Try to get UserAddress object by id or return None."""
return get_object_by_id(dbsession, user_address_id, UserAddress)

View file

@ -28,6 +28,10 @@ def includeme(config):
# user routes.
config.add_route("user_settings", "/u/settings")
config.add_route("user_purchases", "/u/purchases")
config.add_route("user_addresses", "/u/addresses")
config.add_route("user_address_save", "/u/addresses/save")
config.add_route("user_address_delete", "/u/addresses/{address_id}/delete")
config.add_route("user_address_activate", "/u/addresses/{address_id}/activate")
config.add_route("user_shops", "/u/shops")

View file

@ -0,0 +1,31 @@
"""address migration
Revision ID: d38d649cb682
Revises: b8be6cd51cbd
Create Date: 2022-09-03 12:46:49.118477
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'd38d649cb682'
down_revision = 'b8be6cd51cbd'
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_invoice', sa.Column('delivery_address', sa.UnicodeText(), nullable=True))
op.add_column('mps_user', sa.Column('active_address_id', UUIDType, nullable=True))
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('mps_user', 'active_address_id')
op.drop_column('mps_invoice', 'delivery_address')
# ### end Alembic commands ###

View file

@ -62,7 +62,7 @@ def main(argv=sys.argv):
try:
with env['request'].tm:
dbsession = env['request'].dbsession
setup_models(dbsession, settings)
#setup_models(dbsession, settings)
except OperationalError:
print('''
Pyramid is having a problem using your SQL database. The problem

View file

@ -0,0 +1,53 @@
{% extends "base.j2" -%}
{% block call_to_action %}
{% endblock call_to_action %}
{% block content -%}
<section class="two-column">
<section class="well">
<h3>Active Address</h3>
<pre>{{- request.user.active_address.data -}}</pre>
{% if request.user.addresses.count() >= 2 %}
<h3>Saved Addresses</h3>
{% for address in request.user.addresses %}
{% if address.id != request.user.active_address_id %}
<pre>{{- address.data -}}</pre>
<a href="/u/addresses/{{ address.id }}/delete">Delete</a>
<a href="/u/addresses/{{ address.id }}/activate">Activate</a>
<br/>
<br/>
{% endif %}
{% endfor %}
{% endif %}
</section>
<section class="well">
<form method="post" action="/u/addresses/save">
<label for="new_address_input">New Address</label>
<textarea
name = "new_address"
id = "new_address_input"
class = "mps-billing-address"
placeholder = "New Address"
required /></textarea>
<input type="submit" name="submit" class="mps-submit" value="Save Address" />
<br/>
</section>
</section>
{%- endblock -%}

View file

@ -35,9 +35,6 @@
placeholder = "Full Name"
required />
<br />
<br />
<!-- not implemented.
<fieldset>
@ -52,20 +49,16 @@
</fieldset>
-->
<br />
<input type="submit" name="submit" class="mps-submit" value="Save Settings" />
<br />
<br />
</form>
</section>
</section>
<br />
<br />
<br />
<section class="one-column">
<section class="well">
@ -75,8 +68,10 @@
{% if request.shop and request.shop.is_ready %}
<a href="/u/purchases" class="product-download-button mps-button">My Purchases</a>
<br/><br/>
<br/>
<a href="/billing" class="product-edit-button mps-button">Payment Preferences</a>
<br/>
<a href="/u/addresses" class="product-edit-button mps-button">Shipping Addresses</a>
{% endif %}
</section>

View file

@ -1,5 +1,7 @@
from pyramid.view import view_config
from pyramid.httpexceptions import HTTPFound
from . import (
user_required,
shop_is_ready_required,
@ -62,3 +64,58 @@ def user_settings(request):
"full_name": full_name,
}
@view_config(route_name="user_addresses", renderer="user_addresses.j2")
@user_required()
def user_addresses(request):
return {}
@view_config(route_name="user_address_save")
@user_required()
def user_address_save(request):
new_address = request.params.get("new_address", None)
if new_address:
user_address = request.user.new_address(new_address)
request.dbsession.add(user_address)
request.dbsession.flush()
msg = ("You saved that address.", "success")
request.session.flash(msg)
return HTTPFound("/u/addresses")
@view_config(route_name="user_address_delete")
@user_required()
def user_address_delete(request):
address_id = request.matchdict.get("address_id")
if request.user.active_address.uuid_str == address_id:
msg = ("You may not delete the active address.", "error")
request.session.flash(msg)
else:
for address in request.user.addresses:
if address.uuid_str == address_id:
request.dbsession.delete(address)
request.dbsession.flush()
msg = ("You deleted that address.", "success")
request.session.flash(msg)
return HTTPFound("/u/addresses")
@view_config(route_name="user_address_activate")
@user_required()
def user_address_activate(request):
address_id = request.matchdict.get("address_id")
for address in request.user.addresses:
if address.uuid_str == address_id:
request.user.active_address_id = address_id
request.dbsession.add(request.user)
request.dbsession.flush()
msg = ("You activated that address.", "success")
request.session.flash(msg)
return HTTPFound("/u/addresses")