From 259b09082534fbe1e76b616d564ae5e18db6de04 Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Tue, 28 Jun 2022 17:28:31 +0000 Subject: [PATCH] shop user roles: owners, editors, members shop user roles: owners, editors, members Huge pull requests. In this PR we implement role based authorization by extending the "user shop" table to include a new field for tracking a rudimentary role id integer. For now the integers are from 0,1,2 0 == owner (root) 1 == editor (virtual assistant) 2 == member (viewer) For now the member role doesn't provide any additional behavior but could be added to a future enhancement. We have now separated out privileged views into requiring the user to be part of the owner role or editor role depending on the route. We have created a new route called shop_users & it works for adding or removing new users into the active shop. As such for example, only users with owner access may access the new shop_users view and as such only they can add or remove users from the shop. --- make_post_sell/lib/mail.py | 27 ++++- make_post_sell/lib/mail_messages.py | 67 +++++++++-- make_post_sell/models/shop.py | 22 +++- make_post_sell/models/user.py | 4 +- make_post_sell/models/user_shop.py | 28 ++++- make_post_sell/request_methods.py | 2 + make_post_sell/routes.py | 3 + ...03292288a132_role_id_to_user_shop_table.py | 30 +++++ make_post_sell/templates/actions_view.j2 | 4 + make_post_sell/templates/shop_settings.j2 | 2 +- make_post_sell/templates/shop_users.j2 | 105 ++++++++++++++++++ make_post_sell/tests/test_functional.py | 7 +- make_post_sell/views/__init__.py | 61 +++++++--- make_post_sell/views/coupon.py | 7 +- make_post_sell/views/product.py | 35 +----- make_post_sell/views/shop.py | 103 +++++++++++------ 16 files changed, 402 insertions(+), 105 deletions(-) create mode 100644 make_post_sell/scripts/alembic/versions/03292288a132_role_id_to_user_shop_table.py create mode 100644 make_post_sell/templates/shop_users.j2 diff --git a/make_post_sell/lib/mail.py b/make_post_sell/lib/mail.py index 7ac42d4..4872408 100644 --- a/make_post_sell/lib/mail.py +++ b/make_post_sell/lib/mail.py @@ -17,6 +17,8 @@ from make_post_sell.lib.mail_messages import ( PURCHASE_1_HTML, SALE_1_TEXT, SALE_1_HTML, + INVITE_1_TEXT, + INVITE_1_HTML, ) @@ -33,7 +35,7 @@ def send_verification_digits_to_email(request, to_email, raw_digits): raw_digits: the raw (unencrypted) digits the user may use to verify & authenticate. """ - subject = "Verification Code" + subject = "Verification Code | {}".format(raw_digits) message_text = WELCOME_1_TEXT.format(raw_digits) message_html = WELCOME_1_HTML.format(subject, raw_digits) send_pyramid_email(request, to_email, subject, message_text, message_html) @@ -126,3 +128,26 @@ def send_sale_email(request, shop, products, total_cost): # send a separate email for each shop owner. for user in shop.users: send_pyramid_email(request, user.email, subject, message_text, message_html) + + +def send_invite_email(request, to_email, user, shop): + """ + Send an email to all shop owners regarding the sale. + + request + the request (of the successful log in attempt) + + to_email + the email address to send the shop invitation to. + + user + the user who sent the invite. + + shop + the shop the invitation is for. + """ + subject = "You have been invited to {}".format(shop.name) + join_link = request.route_url("join-or-log-in") + message_text = INVITE_1_TEXT.format(user.email, shop.name, join_link) + message_html = INVITE_1_HTML.format(subject, user.email, shop.name, join_link) + send_pyramid_email(request, to_email, subject, message_text, message_html) diff --git a/make_post_sell/lib/mail_messages.py b/make_post_sell/lib/mail_messages.py index 72d7e4e..306cf0c 100644 --- a/make_post_sell/lib/mail_messages.py +++ b/make_post_sell/lib/mail_messages.py @@ -1,11 +1,10 @@ WELCOME_1_TEXT = """ Hey there! -Here is the verification code you requested: - \n{0}\n -Type code into the challenge input field: +Here is the verification code you requested. +Type code into the challenge input field. """ WELCOME_1_HTML = """ @@ -17,13 +16,10 @@ WELCOME_1_HTML = """

Hey there!

-

- Here is the verification code you requested: -

-

{1}

+ Here is the verification code you requested. Type code into the challenge input field.

@@ -146,3 +142,60 @@ SALE_1_HTML = """ """ + + +# 0: user.email +# 1: shop.name, +# 2: log in link. +INVITE_1_TEXT = """ +Hey There! + +{0} has invited you to join the {1} shop on Make Post Sell. + +Click this link to log in. + +{2} + +Make Post Sell is a lightweight, open-source, public domain, software as a service ecommerce web application. +We hope you like our ethical approach to doing business online. + +Thanks for sharing! <3 +""" + +# 0: subject, +# 1: user.email +# 2: shop.name, +# 3: log in link. + +INVITE_1_HTML = """ + + + +{0} + + + + +

Hey There!

+ +

+{1} has invited you to join {2} on Make Post Sell. +

+ +

+click this link to log in to the shop. +

+ +

+Make Post Sell is a lightweight, open-source, public domain, software as a service ecommerce web application. +We hope you like our ethical approach to doing business online. +

+ +

+Thanks for sharing! <3 +

+ + + + +""" diff --git a/make_post_sell/models/shop.py b/make_post_sell/models/shop.py index 0432e13..5ddc325 100644 --- a/make_post_sell/models/shop.py +++ b/make_post_sell/models/shop.py @@ -113,16 +113,30 @@ class Shop(RBase, Base): UserShop.user == user, ).one_or_none() - def add_user_to_shop(self, user, default=None): + def add_user_to_shop(self, user, role_id=0): """Add User object to UserShop.""" us = self.get_usershop_for_user(user) if us is None: - us = UserShop(user=user, shop=self) + us = UserShop(user=user, shop=self, role_id=role_id) self.shop_users.append(us) - if default != None: - us.default = default return us + @property + def owners(self): + """Returns a list of user objects who have the owner role on this shop.""" + # us is a user_shop object. + return [us.user for us in self.shop_users if us.is_owner] + + @property + def editors(self): + """Returns a list of user objects who have the editor role on this shop.""" + return self.owners + [us.user for us in self.shop_users if us.is_editor] + + @property + def members(self): + """Returns a list of user objects who have the member role on this shop.""" + return self.editors + [us.user for us in self.shop_users if us.is_member] + @property def slug(self): """return slug from name""" diff --git a/make_post_sell/models/user.py b/make_post_sell/models/user.py index f478ee8..91ab879 100644 --- a/make_post_sell/models/user.py +++ b/make_post_sell/models/user.py @@ -175,7 +175,7 @@ class User(RBase, Base): def can_edit_product(self, product): """Return True or False""" - if product and self in product.shop.users: + if product and self in product.shop.editors: return True return False @@ -198,7 +198,7 @@ class User(RBase, Base): return not self.can_download_product(product) def can_edit_shop(self, shop): - if shop and self in shop.users: + if shop and self in shop.editors: return True return False diff --git a/make_post_sell/models/user_shop.py b/make_post_sell/models/user_shop.py index e7aeaab..9df1f67 100644 --- a/make_post_sell/models/user_shop.py +++ b/make_post_sell/models/user_shop.py @@ -1,11 +1,16 @@ import uuid -from sqlalchemy import Column, Boolean, BigInteger +from sqlalchemy import Column, Integer, BigInteger from .meta import Base, RBase, UUIDType, foreign_key, now_timestamp from sqlalchemy.orm import relationship, backref +role_id_to_name = { + 0: "owner", # root. + 1: "editor", # virtual assistant. + 2: "member", # viewer. +} class UserShop(RBase, Base): """ @@ -17,8 +22,7 @@ class UserShop(RBase, Base): user_id = Column(UUIDType, foreign_key("User", "id"), nullable=False) shop_id = Column(UUIDType, foreign_key("Shop", "id"), nullable=False) created_timestamp = Column(BigInteger, nullable=False) - # a user may set a default shop if they have more than 1. - default = Column(Boolean, default=False) + role_id = Column(Integer, nullable=False, default=0) user = relationship( argument="User", backref=backref("user_shops", cascade="all, delete-orphan") @@ -28,8 +32,24 @@ class UserShop(RBase, Base): argument="Shop", backref=backref("shop_users", cascade="all, delete-orphan") ) - def __init__(self, user=None, shop=None): + def __init__(self, user=None, shop=None, role_id=0): self.id = uuid.uuid1() self.user = user self.shop = shop + self.role_id = role_id self.created_timestamp = now_timestamp() + + def role_name(self): + return role_id_to_name[self.role_id] + + @property + def is_owner(self): + return self.role_id == 0 + + @property + def is_editor(self): + return self.role_id == 1 + + @property + def is_member(self): + return self.role_id == 2 diff --git a/make_post_sell/request_methods.py b/make_post_sell/request_methods.py index 42ea988..496230c 100644 --- a/make_post_sell/request_methods.py +++ b/make_post_sell/request_methods.py @@ -82,6 +82,8 @@ def includeme(config): return get_shop_by_id(request.dbsession, shop_id) elif request.product: return request.product.shop + elif request.user and request.user.active_shop: + return request.user.active_shop else: return get_shop_by_domain_name(request.dbsession, request.domain) diff --git a/make_post_sell/routes.py b/make_post_sell/routes.py index 424cef2..b6c3220 100644 --- a/make_post_sell/routes.py +++ b/make_post_sell/routes.py @@ -72,6 +72,9 @@ def includeme(config): config.add_route("shop_settings", "/s/{shop_id}/settings") + config.add_route("shop_users", "/s/{shop_id}/users") + config.add_route("shop_user_remove", "/s/{shop_id}/remove-user") + config.add_route("shop_terms_of_service", "/s/terms") config.add_route("shop_terms_of_service2", "/s/{shop_id}/terms") config.add_route("shop_edit_terms_of_service", "/s/{shop_id}/terms/edit") diff --git a/make_post_sell/scripts/alembic/versions/03292288a132_role_id_to_user_shop_table.py b/make_post_sell/scripts/alembic/versions/03292288a132_role_id_to_user_shop_table.py new file mode 100644 index 0000000..7a5f2a1 --- /dev/null +++ b/make_post_sell/scripts/alembic/versions/03292288a132_role_id_to_user_shop_table.py @@ -0,0 +1,30 @@ +"""role id to user shop table + +Revision ID: 03292288a132 +Revises: e6ac162d096f +Create Date: 2022-06-27 12:31:50.885273 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '03292288a132' +down_revision = 'e6ac162d096f' +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_user_shop', sa.Column('role_id', sa.Integer(), nullable=False, server_default="0")) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('mps_user_shop', 'role_id') + # ### end Alembic commands ### diff --git a/make_post_sell/templates/actions_view.j2 b/make_post_sell/templates/actions_view.j2 index b17e687..27e7b4b 100644 --- a/make_post_sell/templates/actions_view.j2 +++ b/make_post_sell/templates/actions_view.j2 @@ -20,6 +20,10 @@
  View Coupons +
+
+ 👤   Shop Users +

⚙   Shop Settings diff --git a/make_post_sell/templates/shop_settings.j2 b/make_post_sell/templates/shop_settings.j2 index e0f5bb9..f1e7cf8 100644 --- a/make_post_sell/templates/shop_settings.j2 +++ b/make_post_sell/templates/shop_settings.j2 @@ -274,7 +274,7 @@

Maintenance Mode 🏗️

- + If checked, enabled maintenance mode.
diff --git a/make_post_sell/templates/shop_users.j2 b/make_post_sell/templates/shop_users.j2 new file mode 100644 index 0000000..d7e91e7 --- /dev/null +++ b/make_post_sell/templates/shop_users.j2 @@ -0,0 +1,105 @@ +{% extends "base.j2" -%} + +{% block content -%} + +
+
+ +

Invite

+ +
+ + + + + + +
+ + role + + + + + + + + + + +
+ +
+ + + +
+ +
+ +

owners

+ + {% for user in owners %} +
    +
  • {{ user.name }} - {{ user.email }}
  • +
    + + +
    +
+ {% endfor %} + +

editors

+ + {% for user in editors %} +
    +
  • {{ user.name }} - {{ user.email }}
  • +
    + + +
    +
+ {% endfor %} + +

members

+ + {% for user in members %} +
    +
  • {{ user.name }} - {{ user.email }}
  • +
    + + +
    +
+ {% endfor %} + +
+
+{%- endblock -%} diff --git a/make_post_sell/tests/test_functional.py b/make_post_sell/tests/test_functional.py index 26e4b65..7046b0a 100644 --- a/make_post_sell/tests/test_functional.py +++ b/make_post_sell/tests/test_functional.py @@ -202,7 +202,6 @@ class AuthenticatedFunctionalTests(FunctionalTests): def log_in_user(self, user_creds): # log in user. - #res_login = self.testapp.get( res_login = self.testapp.post( "/verification-challenge?email={}&raw-otp={}&submit".format(*user_creds) ) @@ -259,7 +258,7 @@ class AuthenticatedFunctionalTests(FunctionalTests): self.log_in_user(self.user2_creds) redirect_res = self.testapp.get("/p/new", status=302) res = redirect_res.follow() - self.assertIn(b"First we must create a shop.", res.body) + self.assertIn(b"You must have a shop editor role to access that.", res.body) def test_create_new_shop(self, user_creds=None, shop_params=None, log_out_user=False): @@ -373,7 +372,7 @@ class AuthenticatedFunctionalTests(FunctionalTests): if log_out_user: self.testapp.get("/log-out") - def test_new_product_when_user_does_not_own_the_shop(self): + def test_new_product_when_user_does_not_have_editor_role_on_shop(self): # log in user1. self.log_in_user(self.user1_creds) @@ -395,7 +394,7 @@ class AuthenticatedFunctionalTests(FunctionalTests): params = self.product1_params redirect_res = self.testapp.post("/p/new?shop_id={}".format(shop_id), params) res = redirect_res.follow() - self.assertIn("You do not own that Shop.", res.body.decode()) + self.assertIn("You must have a shop editor role to access that.", res.body.decode()) def test_new_product_price_history(self): self.test_new_product( diff --git a/make_post_sell/views/__init__.py b/make_post_sell/views/__init__.py index 3cac3c2..7addaff 100644 --- a/make_post_sell/views/__init__.py +++ b/make_post_sell/views/__init__.py @@ -66,19 +66,52 @@ def shop_is_ready_required( # view decorator. -def super_fly_required(fn): - """This view requires that the request has a super fly admin.""" - # TODO: don't rely on this hack for super fly admin. - def wrapped(request): - if ( - request.user - and request.user.authenticated - and request.user.name == "Admin" - ): - return fn(request) - request.session.flash( - ("You must be a super fly admin to access that.", "error") - ) - return HTTPFound(get_referer_or_home(request)) +def shop_owner_required( + flash_msg="You must have a shop owner role to access that.", + flash_level="error", +): + """This view requires that the user of the request has a shop owner role. + for protecting shop settings & shop user pages.""" + def wrapped(fn): + def inner(request): + if ( + request.user is not None + and request.user.authenticated + and request.shop is not None + and request.user in request.shop.owners + ): + return fn(request) + + request.session.flash( + (flash_msg, flash_level) + ) + return HTTPFound(get_referer_or_home(request)) + return inner + return wrapped + + +# view decorator. +def shop_editor_required( + flash_msg="You must have a shop editor role to access that.", + flash_level="error", +): + """This view requires that the user of the request has a shop editor role. + for protecting product & content edit pages.""" + + def wrapped(fn): + def inner(request): + if ( + request.user is not None + and request.user.authenticated + and request.shop is not None + and request.user in request.shop.editors + ): + return fn(request) + + request.session.flash( + (flash_msg, flash_level) + ) + return HTTPFound(get_referer_or_home(request)) + return inner return wrapped diff --git a/make_post_sell/views/coupon.py b/make_post_sell/views/coupon.py index 58fa3f4..1348502 100644 --- a/make_post_sell/views/coupon.py +++ b/make_post_sell/views/coupon.py @@ -2,6 +2,7 @@ from pyramid.view import view_config from . import ( user_required, + shop_owner_required, get_referer_or_home, ) @@ -52,17 +53,13 @@ def coupon(request): @view_config(route_name="coupon_new1", renderer="coupon_new.j2") @view_config(route_name="coupon_new2", renderer="coupon_new.j2") -@user_required() +@shop_owner_required() def coupon_new(request): shop = request.user.active_shop if request.shop: shop = request.shop - if request.user not in shop.users: - request.session.flash(("You do not own that Shop.", "error")) - return HTTPFound(get_referer_or_home(request)) - code = request.params.get("code") description = request.params.get("description") action_type = request.params.get("action_type") diff --git a/make_post_sell/views/product.py b/make_post_sell/views/product.py index 2f89a1a..77ec74e 100644 --- a/make_post_sell/views/product.py +++ b/make_post_sell/views/product.py @@ -2,6 +2,7 @@ from pyramid.view import view_config from . import ( user_required, + shop_editor_required, get_referer_or_home, ) @@ -85,21 +86,13 @@ def product(request): @view_config(route_name="product_new", renderer="product_new.j2") @view_config(route_name="content_new", renderer="content_new.j2") @user_required() +@shop_editor_required() def product_new(request): - if request.shop is None and request.user.active_shop is None: - msg = ("First we must create a shop.", "info") - request.session.flash(msg) - return HTTPFound("/s/new") - shop = request.user.active_shop if request.shop: shop = request.shop - if request.user not in shop.users: - request.session.flash(("You do not own that Shop.", "error")) - return HTTPFound(get_referer_or_home(request)) - title = request.params.get("title", "") description = request.params.get("description", "") price = request.params.get("price", "") @@ -144,14 +137,10 @@ def product_new(request): @view_config(route_name="product_description_edit", renderer="markup_editor.j2") -@user_required() +@shop_editor_required() def product_edit_description(request): product = request.product - if request.user.can_not_edit_product(product): - request.session.flash(("You do not own that Product.", "error")) - return HTTPFound(get_referer_or_home(request)) - raw_markup_data = request.params.get( "markup-editor-textarea", # TODO: rename this column to description_raw. @@ -177,16 +166,12 @@ def product_edit_description(request): @view_config(route_name="product_edit", renderer="product_edit.j2") @view_config(route_name="product_edit2", renderer="product_edit.j2") -@user_required() +@shop_editor_required() def product_edit(request): product_modified = False product = request.product - if request.user.can_not_edit_product(product): - request.session.flash(("You do not own that Product.", "error")) - return HTTPFound(get_referer_or_home(request)) - title = request.params.get("title", product.title) description = request.params.get("description", product.description) price = request.params.get("price", product.price) @@ -325,7 +310,7 @@ def product_edit(request): @view_config(route_name="product_bundle_edit") @view_config(route_name="product_bundle_edit2") -@user_required() +@shop_editor_required() def product_bundle_edit(request): from ..models.product import get_products_by_ids @@ -337,10 +322,6 @@ def product_bundle_edit(request): if product.is_bundle == False: request.session.flash(("You may only add products to a Product bundle.", "error")) - if request.user.can_not_edit_product(product): - request.session.flash(("You do not own that Product bundle.", "error")) - return HTTPFound(get_referer_or_home(request)) - bundle_data = request.params.get("bundle-data", None) if bundle_data: @@ -358,7 +339,7 @@ def product_bundle_edit(request): @view_config(route_name="product_remove_bundled_product") -@user_required() +@shop_editor_required() def product_bundle_remove_product(request): from ..models.product import get_product_by_id @@ -371,10 +352,6 @@ def product_bundle_remove_product(request): if product.is_bundle == False: request.session.flash(("You may only remove products from a Product bundle.", "error")) - if request.user.can_not_edit_product(product): - request.session.flash(("You do not own that Product bundle.", "error")) - return HTTPFound(get_referer_or_home(request)) - if product.remove_product_from_bundle(bundled_product) == False: request.session.flash((product.error_message, "error")) else: diff --git a/make_post_sell/views/shop.py b/make_post_sell/views/shop.py index 882ae34..198240f 100644 --- a/make_post_sell/views/shop.py +++ b/make_post_sell/views/shop.py @@ -2,7 +2,11 @@ import mimetypes from pyramid.view import view_config -from . import user_required +from . import ( + user_required, + shop_owner_required, + shop_editor_required, +) from ..models.shop import ( Shop, @@ -17,10 +21,17 @@ from ..models.product import ( get_products_by_keywords, ) +from ..models.user import ( + get_user_by_id, + get_or_create_user_by_email, +) + from ..models.shop_search_request import ( ShopSearchRequest, ) +from ..lib.mail import send_invite_email + from pyramid.httpexceptions import HTTPFound from miscutils.phone_numbers import is_phone_number_valid @@ -72,15 +83,13 @@ def shop(request): @view_config(route_name="shop_products", renderer="shop_products.j2") -@user_required() +@shop_editor_required() def shop_products(request): msg = None shop = get_shop_from_matchdict(request) if shop is None: msg = ("That shop_id does not exist.", "error") - elif request.user.can_not_edit_shop(shop): - msg = ("That shop is not owned by you.", "error") if msg: request.session.flash(msg) @@ -157,10 +166,7 @@ def shop_new(request): else: shop = Shop(name, phone_number, billing_address, description) - if len(request.user.shops) == 0: - shop.add_user_to_shop(request.user, default=True) - else: - shop.add_user_to_shop(request.user) + shop.add_user_to_shop(request.user) request.user.set_active_shop(shop) request.dbsession.add(shop) request.dbsession.add(request.user) @@ -182,7 +188,7 @@ def shop_new(request): @view_config(route_name="user_shop_activate") -@user_required() +@shop_editor_required() def shop_activate(request): shop = get_shop_from_matchdict( request, prefetched_shop=request.user.active_shop @@ -191,9 +197,6 @@ def shop_activate(request): if shop is None: msg = ("That shop_id does not exist.", "error") - elif request.user.can_not_edit_shop(shop): - msg = ("That shop is not owned by you.", "error") - elif shop == request.user.active_shop: msg = ("This shop is already your active shop.", "success") @@ -226,16 +229,12 @@ def shop_privacy_policy(request): @view_config(route_name="shop_edit_terms_of_service", renderer="markup_editor.j2") -@user_required() +@shop_owner_required() def shop_edit_terms_of_service(request): shop = request.user.active_shop if request.shop: shop = request.shop - if request.user.can_not_edit_shop(shop): - request.session.flash(("You do not own that Shop.", "error")) - return HTTPFound(get_referer_or_home(request)) - raw_markup_data = request.params.get( "markup-editor-textarea", shop.terms_of_service_raw @@ -257,16 +256,12 @@ def shop_edit_terms_of_service(request): @view_config(route_name="shop_edit_privacy_policy", renderer="markup_editor.j2") -@user_required() +@shop_owner_required() def shop_edit_privacy_policy(request): shop = request.user.active_shop if request.shop: shop = request.shop - if request.user.can_not_edit_shop(shop): - request.session.flash(("You do not own that Shop.", "error")) - return HTTPFound(get_referer_or_home(request)) - raw_markup_data = request.params.get( "markup-editor-textarea", shop.privacy_policy_raw @@ -287,17 +282,60 @@ def shop_edit_privacy_policy(request): } +@view_config(route_name="shop_users", renderer="shop_users.j2") +@shop_owner_required() +def shop_users(request): + + email_to_invite = request.params.get("email-to-invite") + role_id = request.params.get("role-id") + + if email_to_invite and role_id: + # get or create new user if it doesn't exist. + user = get_or_create_user_by_email(request.dbsession, email_to_invite) + + # make new user a shop user of the proper role. + request.shop.add_user_to_shop(user, role_id) + + # commit changes to database. + request.dbsession.add(user) + + # send an invitation email to the new user. + send_invite_email(request, email_to_invite, request.user, request.shop) + + msg = ("You invited {} to the shop.".format(email_to_invite), "success") + request.session.flash(msg) + + + return { + "owners": request.shop.owners, + "editors": request.shop.editors, + "members": request.shop.members, + } + + +@view_config(route_name="shop_user_remove") +@shop_owner_required() +def shop_user_remove(request): + user_id = request.params.get("user_id", None) + if user_id: + user = get_user_by_id(request.dbsession, user_id) + if user: + if user == request.user: + msg = ("Sheepishly refusing to remove myself!".format(user.email), "error") + request.session.flash(msg) + else: + user_shop = request.shop.get_usershop_for_user(user) + request.dbsession.delete(user_shop) + msg = ("You removed {} from the shop.".format(user.email), "success") + request.session.flash(msg) + return HTTPFound("/s/{}/users".format(request.shop.id)) + + @view_config(route_name="shop_settings", renderer="shop_settings.j2") -@user_required() +@shop_owner_required() def shop_settings(request): - shop = request.user.active_shop - if request.shop: - shop = request.shop - - if request.user.can_not_edit_shop(shop): - request.session.flash(("You do not own that Shop.", "error")) - return HTTPFound(get_referer_or_home(request)) + shop = request.shop name = request.params.get("name", shop.name) phone_number = request.params.get("phone_number", shop.phone_number) @@ -317,7 +355,7 @@ def shop_settings(request): stripe_public_api_key = request.params.get("stripe_public_api_key", shop.stripe_public_api_key) stripe_secret_api_key = request.params.get("stripe_secret_api_key", shop.stripe_secret_api_key) - maint_mode_checkbox = request.params.get("maint-mode-checkbox", bool_to_checkbox(shop.maint_mode)) + maint_mode_checkbox = request.params.get("maint-mode-checkbox", "off") maint_mode = checkbox_to_bool(maint_mode_checkbox) s3_webhook_key = request.params.get("key") @@ -408,7 +446,6 @@ def shop_settings(request): msg = ("The shop's stripe_secret_api_key must start with 'sk_'.", "error") request.session.flash(msg) - print("{} {}".format(shop.maint_mode, maint_mode)) if shop.maint_mode != maint_mode: shop.maint_mode = maint_mode request.session.flash( @@ -508,7 +545,6 @@ def shop_settings(request): continue signed_posts[file_key]["fields"].update(condition) - return { "name": name, "description": description, @@ -523,7 +559,6 @@ def shop_settings(request): "plausible_domain_name": plausible_domain_name, "stripe_public_api_key": stripe_public_api_key, "stripe_secret_api_key": stripe_secret_api_key, - "maint_mode": maint_mode, "signed_posts": signed_posts, "get_endpoints": get_endpoints, }