From 6b30a78f2debe7f866c238ed0d58f1ad1e7a0118 Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Tue, 6 Sep 2022 16:19:09 +0000 Subject: [PATCH] Add, activate, and remove active user addresses. --- make_post_sell/models/__init__.py | 1 + make_post_sell/models/invoice.py | 5 +- make_post_sell/models/meta.py | 2 + make_post_sell/models/user.py | 18 ++++++ make_post_sell/models/user_address.py | 31 ++++++++++ make_post_sell/routes.py | 4 ++ .../d38d649cb682_address_migration.py | 31 ++++++++++ make_post_sell/scripts/initialize_db.py | 2 +- make_post_sell/templates/user_addresses.j2 | 53 +++++++++++++++++ make_post_sell/templates/user_settings.j2 | 15 ++--- make_post_sell/views/user.py | 57 +++++++++++++++++++ 11 files changed, 207 insertions(+), 12 deletions(-) create mode 100644 make_post_sell/models/user_address.py create mode 100644 make_post_sell/scripts/alembic/versions/d38d649cb682_address_migration.py create mode 100644 make_post_sell/templates/user_addresses.j2 diff --git a/make_post_sell/models/__init__.py b/make_post_sell/models/__init__.py index 5de95c3..4abf079 100644 --- a/make_post_sell/models/__init__.py +++ b/make_post_sell/models/__init__.py @@ -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 * diff --git a/make_post_sell/models/invoice.py b/make_post_sell/models/invoice.py index 4cdf9d6..7916d18 100644 --- a/make_post_sell/models/invoice.py +++ b/make_post_sell/models/invoice.py @@ -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") diff --git a/make_post_sell/models/meta.py b/make_post_sell/models/meta.py index 23b93d0..209f0e0 100644 --- a/make_post_sell/models/meta.py +++ b/make_post_sell/models/meta.py @@ -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", diff --git a/make_post_sell/models/user.py b/make_post_sell/models/user.py index 71d0b1c..5a3f609 100644 --- a/make_post_sell/models/user.py +++ b/make_post_sell/models/user.py @@ -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.""" diff --git a/make_post_sell/models/user_address.py b/make_post_sell/models/user_address.py new file mode 100644 index 0000000..7a21ce0 --- /dev/null +++ b/make_post_sell/models/user_address.py @@ -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) diff --git a/make_post_sell/routes.py b/make_post_sell/routes.py index 5d67a94..9cc7ea1 100644 --- a/make_post_sell/routes.py +++ b/make_post_sell/routes.py @@ -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") diff --git a/make_post_sell/scripts/alembic/versions/d38d649cb682_address_migration.py b/make_post_sell/scripts/alembic/versions/d38d649cb682_address_migration.py new file mode 100644 index 0000000..c2d23f8 --- /dev/null +++ b/make_post_sell/scripts/alembic/versions/d38d649cb682_address_migration.py @@ -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 ### diff --git a/make_post_sell/scripts/initialize_db.py b/make_post_sell/scripts/initialize_db.py index ab35f7e..dc46323 100644 --- a/make_post_sell/scripts/initialize_db.py +++ b/make_post_sell/scripts/initialize_db.py @@ -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 diff --git a/make_post_sell/templates/user_addresses.j2 b/make_post_sell/templates/user_addresses.j2 new file mode 100644 index 0000000..31f5e0a --- /dev/null +++ b/make_post_sell/templates/user_addresses.j2 @@ -0,0 +1,53 @@ +{% extends "base.j2" -%} + +{% block call_to_action %} +{% endblock call_to_action %} + +{% block content -%} + +
+
+ +

Active Address

+ +
{{- request.user.active_address.data -}}
+ + {% if request.user.addresses.count() >= 2 %} + +

Saved Addresses

+ + {% for address in request.user.addresses %} + {% if address.id != request.user.active_address_id %} +
{{- address.data -}}
+ Delete + Activate +
+
+ {% endif %} + {% endfor %} + + {% endif %} + +
+ +
+ +
+ + + + + + + +
+ +
+
+ +{%- endblock -%} diff --git a/make_post_sell/templates/user_settings.j2 b/make_post_sell/templates/user_settings.j2 index 1b85b90..e75fe73 100644 --- a/make_post_sell/templates/user_settings.j2 +++ b/make_post_sell/templates/user_settings.j2 @@ -35,9 +35,6 @@ placeholder = "Full Name" required /> -
-
- -
-
-
-
-
- +
+
@@ -75,8 +68,10 @@ {% if request.shop and request.shop.is_ready %} My Purchases -

+
Payment Preferences +
+ Shipping Addresses {% endif %}
diff --git a/make_post_sell/views/user.py b/make_post_sell/views/user.py index 015eed2..9a2d6db 100644 --- a/make_post_sell/views/user.py +++ b/make_post_sell/views/user.py @@ -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")