Skip to content

Commit

Permalink
WIP #1281 As shop owner I can assign users to plans
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisjsimpson committed Feb 25, 2024
1 parent cbe30c1 commit 73b2169
Show file tree
Hide file tree
Showing 8 changed files with 248 additions and 75 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""primary keys association_table_plan_to_users
Revision ID: ac23e0d75b27
Revises: ba57f5aeba5f
Create Date: 2024-02-24 16:15:56.898449
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy import MetaData, Table, Column, Integer, String


# revision identifiers, used by Alembic.
revision = "ac23e0d75b27"
down_revision = "ba57f5aeba5f"
branch_labels = None
depends_on = None


def upgrade():
meta = MetaData()
some_table = Table(
"plan_user_associations",
meta,
Column("plan_uuid", String, nullable=False),
Column("user_id", Integer, nullable=False),
sa.ForeignKeyConstraint(
["plan_uuid"],
["plan.uuid"],
),
sa.ForeignKeyConstraint(
["user_id"],
["user.id"],
),
sa.PrimaryKeyConstraint("plan_uuid", "user_id"),
)

with op.batch_alter_table(
"plan_user_associations", copy_from=some_table
) as batch_op:
batch_op.create_primary_key(
"pk_plan_user_association", ["plan_uuid", "user_id"]
)


def downgrade():
pass
68 changes: 49 additions & 19 deletions subscribie/blueprints/admin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -496,8 +496,15 @@ def dashboard():
shop_default_country_code = get_shop_default_country_code()
saas_url = current_app.config.get("SAAS_URL")

# Query user object including archived plans, see models.py
# _do_orm_execute_hide_archived for details.
user_id = g.user.id
with current_app.app_context():
database.session.info["include_archived"] = True
user = User.query.where(User.id == user_id).first()
return render_template(
"admin/dashboard.html",
user=user,
stripe_connected=stripe_connected,
integration=integration,
loadedModules=getLoadedModules(),
Expand Down Expand Up @@ -555,13 +562,13 @@ def edit():
for userId in managersUserIds:
user = User.query.get(int(userId))
managers.append(user)
draftPlan.managers.clear()
draftPlan.managers.extend(managers)
draftPlan.users.clear()
draftPlan.users.extend(managers)

# Update plan-revisions with managers
for planRevision in plan.get_plan_revisions():
planRevision.managers.clear()
planRevision.managers.extend(managers)
planRevision.users.clear()
planRevision.users.extend(managers)

# Preserve primary icon if exists
draftPlan.primary_icon = plan.primary_icon
Expand Down Expand Up @@ -845,13 +852,13 @@ def assign_manager_to_plan():
.first()
)

plan.managers.extend(managers)
plan.users.extend(managers)

flash("Manager(s) have assigned to the plan")

database.session.commit()

return redirect(url_for("admin.subscribers"))
return redirect(url_for("admin.subscribers", action="show_active"))


@admin.route("assign-managers-to-plan")
Expand All @@ -869,30 +876,47 @@ def assign_managers_to_plan():
in models.py) is a shop owner (admin) which may login to the
Subscribie application.
"""
users = User.query.all()
with current_app.app_context():
database.session.info["include_archived"] = True
users = User.query.execution_options(include_archived=True).all()
return render_template("admin/assign_managers_to_plan.html", users=users)


@admin.route("/assign-managers-to-plans/<user_id>/assign-plan", methods=["GET", "POST"])
@login_required
def user_assign_to_plans(user_id):
user = User.query.get(user_id)
plans = Plan.query.execution_options(include_archived=True)

"""
Note the use of application context to include archived plans
Remember that "plan in user.plans" won't be true if, for example,
'plan' is defined outside of the new/enclused application context.
"""
if request.method == "POST":
# Remove if not selected
for plan in plans:
if plan in user.plans:
user.plans.remove(plan)
with current_app.app_context():
database.session.info["include_archived"] = True
plans = Plan.query.execution_options(include_archived=True).all()
user = User.query.get(user_id)
# Remove if not selected
for plan in plans:
log.info(f"Checking if plan {plan} is in user {user}")
if plan in user.plans:
user.plans.remove(plan)

# If all deselected, remove all plan assignments
if len(request.form.getlist("assign")) == 0:
user.plans.clear()

# Assign requested managers to plan
for plan_id in request.form.getlist("assign"):
plan = Plan.query.execution_options(include_archived=True).get(plan_id)
plan.users.append(user)

for plan_id in request.form.getlist("assign"):
plan = Plan.query.execution_options(include_archived=True).get(plan_id)
plan.managers.append(user)

database.session.commit()
database.session.commit()
flash("User has been assigned the selected plan(s) as a manager of them")
return redirect(url_for("admin.assign_managers_to_plan"))

plans = Plan.query.execution_options(include_archived=True).all()
user = User.query.execution_options(include_archived=True).where(User.id == user_id).first()

return render_template(
"admin/user_assign_plan.html",
user=user,
Expand Down Expand Up @@ -1429,10 +1453,16 @@ def subscribers():

people = query.order_by(desc(Person.created_at))
users = User.query.all()
user_id = g.user.id
with current_app.app_context():
database.session.info["include_archived"] = True
user = User.query.where(User.id == user_id).first()

return render_template(
"admin/subscribers.html",
people=people.all(),
users=users,
user=user,
show_active=show_active,
show_plans_i_manage=show_plans_i_manage,
action=action,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ <h3>Allow managers to quickly see plans they're responsible for</h3>
become 'managers' of specific plans. This helps them see only the Subscribers they are responsible for.</p>

<p>
Once you do so, when they login to their account, the visibility of the Subscribers they see
Once you do so, when they login to their account, the visibility of plans they manage
will be highlighted, and by default any Subscribers they are <em>not</em> a manager of given less Precedence, based
on the Plans they are assigned to as a manager.</p>
<br />
<p>
Please note this is <em>not</em> a security feature- it does <em>not</em> stop managers being able to access
all plans, it simply makes it easier for a manager of specific plans to quickly see the Subscribers
who are subscribed to the plans they manage.
who are subscribed to the plans <em>they</em> are a manager of.
</p>

<p>Select a user to assign plan(s) to them.</p>
Expand Down
4 changes: 2 additions & 2 deletions subscribie/blueprints/admin/templates/admin/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ <h3 class="card-title">Checklist</h3>

<div class="card px-3 py-3 my-3">
<h3 class="card-title justify-content-between d-flex">Stats</h3>
{% if g.user.plans | length > 0 %}
<p>You manage {{ g.user.plans | length }} plan(s). <a style="display:inline" href="{{ url_for('admin.subscribers')}}?action=show_plans_i_manage">Show my subscribers</a></p>
{% if user.plans | length > 0 %}
<p>You manage {{ user.plans | length }} plan(s). <a style="display:inline" href="{{ url_for('admin.subscribers')}}?action=show_plans_i_manage">Show my subscribers</a></p>
{% endif %}
<hr />
<p>You have: {{ num_active_subscribers }} <a href="{{ url_for('admin.subscribers', action='show_active')}}">subscribers</a> with <em>active</em> subscriptions.
Expand Down
29 changes: 13 additions & 16 deletions subscribie/blueprints/admin/templates/admin/subscribers.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{% extends "admin/layout.html" %}
{% from 'macros/plan_assign_managers_form.html' import plan_assign_managers_form %}
{% block title %} Subscribers {% endblock %}

{% block body %}
Expand All @@ -25,18 +26,18 @@ <h2 class="text-center text-dark mb-3">My Subscribers</h2>
<a href="{{url_for('admin.subscribers', action='show_donors')}}" id="show-donors" class="btn btn-primary ">Show Donors</a>
{% endif %}
{% endif %}
<a href="{{ url_for('admin.subscribers', action='show_plans_i_manage') }}" id="show-plans-I-manage" class="btn btn-primary">Show only plans I manage</a>
<a href="{{ url_for('admin.subscribers', action='show_plans_i_manage') }}" id="show-plans-I-manage" class="btn btn-primary">Show only Subscribers with plans I manage</a>
<button id="refresh_subscriptions" title="Get latest subscription statuses (active/paused/inactive)" class="btn btn-primary disable-on-click">Refresh Subscriptions</button>
</div>
<form action="#" method="GET">
<h4>Search...</h4>
{% if show_plans_i_manage %}
<div class="alert alert-warning">
<p>Showing only subscribers with plans you are a manager of</p>
<p>Showing only subscribers with plans you are a manager of.</p>
</div>
{% endif %}

{% if g.user.plans | length == 0 %}
{% if user.plans | length == 0 %}
<div class="alert alert-warning">
You don't currently have any plans assigned to your user. To assign plans to your user login,
<a href="{{ url_for('admin.edit') }}">Edit an existing plan</a> or <a href="{{ url_for('admin.add_plan') }}">Create a new plan</a> and click 'Managers' when editing or adding
Expand Down Expand Up @@ -94,22 +95,11 @@ <h4>Search...</h4>
{% for subscription in person.subscriptions %}
<li>
<div class="card">
{% if subscription.plan.managers | length == 0 %}
{% if subscription.plan.users| length == 0 %}
<div class="alert alert-warning">
<h5>Assign a manager</h5>
No manager(s) are assigned to this plan.
<form action="{{ url_for("admin.assign_manager_to_plan") }}" method="POST">
{% for user in users %}
<div class="form-check">
<input type="checkbox" class="form-check-input" name="user_id" id="{{ user.id }}" value="{{ user.id }}" />
<label for="{{ user.id }}" class="form-check-label">{{ user.email }}</label>
</div>
{% endfor %}
<input type="hidden" name="plan_uuid" value="{{ subscription.plan.uuid }}" />
<div class="form-group row mt-4">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
{{ plan_assign_managers_form(subscription.plan, users) }}
</div>
{% endif %}
<ul class="list-unstyled px-2">
Expand Down Expand Up @@ -242,6 +232,13 @@ <h5>Assign a manager</h5>
</ul>
{% endif %}
</li>
{% if subscription.plan.users | length > 0 %}
<li class="mt-2" style="padding: 0.75rem 1.25rem;"><strong>Manager(s):</strong>
{{ plan_assign_managers_form(subscription.plan, users) }}
</li>
{% endif %}


</ul>
</div>
</li>
Expand Down
15 changes: 11 additions & 4 deletions subscribie/blueprints/admin/templates/admin/user_assign_plan.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

{% block body %}

<h2 class="text-center text-dark mb-3">Choice Group - Assign Plan</h2>
<h2 class="text-center text-dark mb-3">Manager - Assign Plan(s)</h2>

<div class="container">
<ul class="breadcrumb">
Expand All @@ -18,26 +18,33 @@ <h2 class="text-center text-dark mb-3">Choice Group - Assign Plan</h2>
<div class="section">
<div class="container">

<h3>Assign Plan(s) to user: <em>{{ user.email }}</em></h3>
<h3>Assign Plan(s) to admin: <em>{{ user.email }}</em></h3>
<p class="card-subtitle text-muted">Tick which plan(s) you want to assign the user to</p>
<p class="card-subtitle text-muted mb-3">Doing so will make these plans(s) and any associated subscriptions appear more prominently for them when they login.</p>

<div class="alert alert-warning">
<h5>Why might I see duplicate plan names below?</h5>
<p>When you update a plan, such as its price or description, current subscribers keep their original plan details. This ensures they continue under the terms they originally agreed to. That's why you might see duplicated plan names below.</p>
</div>
<hr>
<div class="btn btn-success mb-4" onclick="toggleCheckboxes(true)">Check all</div>
<div class="btn btn-success mb-4" onclick="toggleCheckboxes(false)">Check none</div>
<form action="" method="POST" class="col-md-7 py-3">

<button type="submit" class="btn btn-success btn-block">Save</button>
<br />
{% for plan in plans %}
{% if plan.subscriptions | length > 0 %}
<div class="input-group mb-3">
<div class="input-group-prepend">
<div class="input-group-text">
<input type="checkbox" name="assign" class="assign-plan-to-manager-checkbox" value="{{ plan.id }}" id="{{ plan.id }}"
{% if user in plan.managers %}
{% if user in plan.users %}
checked
{% endif %}>
</div>
</div>
<label class="form-control" for="{{ plan.id }}">{{ plan.title }}</label> {{ plan.archived }}| Subscriptions: {{ plan.subscriptions|length }}
<label class="form-control" for="{{ plan.id }}">{{ plan.title }}</label>
</div>
{% endif %}
{% endfor %}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{% macro plan_assign_managers_form(plan, users) -%}

<form action="{{ url_for("admin.assign_manager_to_plan") }}" method="POST">
{% for user in users %}
<div class="form-check">
<input type="checkbox" class="form-check-input" name="user_id" id="{{ user.id }}" value="{{ user.id }}"
{% if user in plan.users %}
checked
{% endif %} />
<label for="{{ user.id }}" class="form-check-label">{{ user.email }}</label>
</div>
{% endfor %}
<input type="hidden" name="plan_uuid" value="{{ plan.uuid }}" />
<div class="form-group row mt-4">
<input type="submit" value="Update" class="btn btn-primary" />
</div>
</form>

{%- endmacro %}
Loading

0 comments on commit 73b2169

Please sign in to comment.