*: rewrite user settings to use a single page
This commit is contained in:
parent
be8cc77086
commit
fcf763ed3e
|
@ -18,7 +18,7 @@ from structlog import get_logger
|
|||
|
||||
from passbook.core.exceptions import PropertyMappingExpressionException
|
||||
from passbook.core.signals import password_changed
|
||||
from passbook.core.types import UILoginButton, UIUserSettings
|
||||
from passbook.core.types import UILoginButton
|
||||
from passbook.flows.models import Flow
|
||||
from passbook.lib.models import CreatedUpdatedModel
|
||||
from passbook.policies.models import PolicyBindingModel
|
||||
|
@ -249,9 +249,9 @@ class Source(PolicyBindingModel):
|
|||
return None
|
||||
|
||||
@property
|
||||
def ui_user_settings(self) -> Optional[UIUserSettings]:
|
||||
def ui_user_settings(self) -> Optional[str]:
|
||||
"""Entrypoint to integrate with User settings. Can either return None if no
|
||||
user settings are available, or an instanace of UIUserSettings."""
|
||||
user settings are available, or a string with the URL to fetch."""
|
||||
return None
|
||||
|
||||
def __str__(self):
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
{% extends "base/page.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load passbook_is_active %}
|
||||
{% load static %}
|
||||
{% load passbook_user_settings %}
|
||||
|
||||
{% block page_content %}
|
||||
<div class="pf-c-page__sidebar">
|
||||
<div class="pf-c-page__sidebar-body">
|
||||
<nav class="pf-c-nav" id="page-default-nav-example-primary-nav" aria-label="Global">
|
||||
<section class="pf-c-nav__section">
|
||||
<h2 class="pf-c-nav__section-title">{% trans 'General Settings' %}</h2>
|
||||
<ul class="pf-c-nav__list">
|
||||
<li class="pf-c-nav__item">
|
||||
<a href="{% url 'passbook_core:user-settings' %}"
|
||||
class="pf-c-nav__link {% is_active 'passbook_core:user-settings' %}">{% trans 'User Details' %}</a>
|
||||
</li>
|
||||
<li class="pf-c-nav__item">
|
||||
<a href="{% url 'passbook_core:user-tokens' %}"
|
||||
class="pf-c-nav__link {% is_active 'passbook_core:user-tokens' 'passbook_core:user-tokens-create' 'passbook_core:user-tokens-update' 'passbook_core:user-tokens-delete' %}">{% trans 'Tokens' %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
{% user_stages as user_stages_loc %}
|
||||
{% if user_stages_loc %}
|
||||
<section class="pf-c-nav__section">
|
||||
<h2 class="pf-c-nav__section-title">{% trans 'Stages' %}</h2>
|
||||
<ul class="pf-c-nav__list">
|
||||
{% for stage in user_stages_loc %}
|
||||
<li class="pf-c-nav__item">
|
||||
<a href="{{ stage.url }}" class="pf-c-nav__link {% if stage.url == request.get_full_path %} pf-m-current {% endif %}">
|
||||
{{ stage.name }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</section>
|
||||
{% endif %}
|
||||
{% user_sources as user_sources_loc %}
|
||||
{% if user_sources_loc %}
|
||||
<section class="pf-c-nav__section">
|
||||
<h2 class="pf-c-nav__section-title">{% trans 'Sources' %}</h2>
|
||||
<ul class="pf-c-nav__list">
|
||||
{% for source in user_sources_loc %}
|
||||
<li class="pf-c-nav__item">
|
||||
<a href="{{ source.url }}"
|
||||
class="pf-c-nav__link {% if source.url == request.get_full_path %} pf-m-current {% endif %}">
|
||||
{{ source.name }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</section>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<main role="main" class="pf-c-page__main" tabindex="-1" id="main-content">
|
||||
{% block content %}
|
||||
<section class="pf-c-page__main-section">
|
||||
<div class="pf-u-display-flex pf-u-justify-content-center">
|
||||
<div class="pf-u-w-75">
|
||||
{% block page %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
</main>
|
||||
{% endblock %}
|
|
@ -1,9 +1,21 @@
|
|||
{% extends "user/base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load passbook_user_settings %}
|
||||
|
||||
{% block page %}
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-page">
|
||||
<main role="main" class="pf-c-page__main" tabindex="-1">
|
||||
<section class="pf-c-page__main-section pf-m-light">
|
||||
<div class="pf-c-content">
|
||||
<h1>
|
||||
<i class="pf-icon pf-icon-user"></i>
|
||||
{% trans 'User Settings' %}
|
||||
</h1>
|
||||
<p>{% trans "Configure settings relevant to your user profile." %}</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="pf-c-page__main-section">
|
||||
<div class="pf-u-display-flex pf-u-justify-content-center">
|
||||
<div class="pf-u-w-75">
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__header pf-c-title pf-m-md">
|
||||
{% trans 'Update details' %}
|
||||
</div>
|
||||
|
@ -17,12 +29,50 @@
|
|||
<div class="pf-c-form__actions">
|
||||
<input class="pf-c-button pf-m-primary" type="submit" value="{% trans 'Update' %}" />
|
||||
{% if unenrollment_enabled %}
|
||||
<a class="pf-c-button pf-m-danger" href="{% url 'passbook_flows:default-unenrollment' %}?back={{ request.get_full_path }}">{% trans "Delete account" %}</a>
|
||||
<a class="pf-c-button pf-m-danger"
|
||||
href="{% url 'passbook_flows:default-unenrollment' %}?back={{ request.get_full_path }}">{% trans "Delete account" %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="pf-c-page__main-section">
|
||||
<div class="pf-u-display-flex pf-u-justify-content-center">
|
||||
<div class="pf-u-w-75">
|
||||
<pb-site-shell url="{% url 'passbook_core:user-tokens' %}">
|
||||
<div slot="body"></div>
|
||||
</pb-site-shell>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% user_stages as user_stages_loc %}
|
||||
{% for stage in user_stages_loc %}
|
||||
<section class="pf-c-page__main-section">
|
||||
<div class="pf-u-display-flex pf-u-justify-content-center">
|
||||
<div class="pf-u-w-75">
|
||||
<pb-site-shell url="{{ stage }}">
|
||||
<div slot="body"></div>
|
||||
</pb-site-shell>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endfor %}
|
||||
{% user_sources as user_sources_loc %}
|
||||
{% for source in user_sources_loc %}
|
||||
<section class="pf-c-page__main-section">
|
||||
<div class="pf-u-display-flex pf-u-justify-content-center">
|
||||
<div class="pf-u-w-75">
|
||||
<pb-site-shell url="{{ source }}">
|
||||
<div slot="body"></div>
|
||||
</pb-site-shell>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endfor %}
|
||||
</main>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,21 +1,13 @@
|
|||
{% extends "user/base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load passbook_utils %}
|
||||
|
||||
{% block content %}
|
||||
<section class="pf-c-page__main-section pf-m-light">
|
||||
<div class="pf-c-content">
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__header pf-c-title pf-m-md">
|
||||
<h1>
|
||||
<i class="pf-icon pf-icon-users"></i>
|
||||
{% trans 'Tokens' %}
|
||||
{% trans 'Manage Tokens' %}
|
||||
</h1>
|
||||
<p>{% trans "Tokens can be used to access passbook's API." %}
|
||||
</p>
|
||||
<p>{% trans "Tokens can be used to access passbook's API." %}</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
|
||||
<div class="pf-c-card">
|
||||
{% if object_list %}
|
||||
<div class="pf-c-toolbar">
|
||||
<div class="pf-c-toolbar__content">
|
||||
|
@ -86,6 +78,4 @@
|
|||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
</div>
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
"""passbook user settings template tags"""
|
||||
from typing import Iterable, List
|
||||
from typing import Iterable
|
||||
|
||||
from django import template
|
||||
from django.template.context import RequestContext
|
||||
|
||||
from passbook.core.models import Source
|
||||
from passbook.core.types import UIUserSettings
|
||||
from passbook.flows.models import Stage
|
||||
from passbook.policies.engine import PolicyEngine
|
||||
|
||||
|
@ -14,26 +13,26 @@ register = template.Library()
|
|||
|
||||
@register.simple_tag(takes_context=True)
|
||||
# pylint: disable=unused-argument
|
||||
def user_stages(context: RequestContext) -> List[UIUserSettings]:
|
||||
def user_stages(context: RequestContext) -> list[str]:
|
||||
"""Return list of all stages which apply to user"""
|
||||
_all_stages: Iterable[Stage] = Stage.objects.all().select_subclasses()
|
||||
matching_stages: List[UIUserSettings] = []
|
||||
matching_stages: list[str] = []
|
||||
for stage in _all_stages:
|
||||
user_settings = stage.ui_user_settings
|
||||
if not user_settings:
|
||||
continue
|
||||
matching_stages.append(user_settings)
|
||||
return sorted(matching_stages, key=lambda x: x.name)
|
||||
return matching_stages
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def user_sources(context: RequestContext) -> List[UIUserSettings]:
|
||||
def user_sources(context: RequestContext) -> list[str]:
|
||||
"""Return a list of all sources which are enabled for the user"""
|
||||
user = context.get("request").user
|
||||
_all_sources: Iterable[Source] = Source.objects.filter(
|
||||
enabled=True
|
||||
).select_subclasses()
|
||||
matching_sources: List[UIUserSettings] = []
|
||||
matching_sources: list[str] = []
|
||||
for source in _all_sources:
|
||||
user_settings = source.ui_user_settings
|
||||
if not user_settings:
|
||||
|
@ -42,4 +41,4 @@ def user_sources(context: RequestContext) -> List[UIUserSettings]:
|
|||
policy_engine.build()
|
||||
if policy_engine.passing:
|
||||
matching_sources.append(user_settings)
|
||||
return sorted(matching_sources, key=lambda x: x.name)
|
||||
return matching_sources
|
||||
|
|
|
@ -3,17 +3,9 @@ from dataclasses import dataclass
|
|||
from typing import Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class UIUserSettings:
|
||||
"""Dataclass for Stage and Source's user_settings"""
|
||||
|
||||
name: str
|
||||
url: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class UILoginButton:
|
||||
"""Dataclass for Source's ui_ui_login_button"""
|
||||
"""Dataclass for Source's ui_login_button"""
|
||||
|
||||
# Name, ran through i18n
|
||||
name: str
|
||||
|
|
|
@ -10,7 +10,6 @@ from model_utils.managers import InheritanceManager
|
|||
from rest_framework.serializers import BaseSerializer
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.core.types import UIUserSettings
|
||||
from passbook.lib.models import InheritanceForeignKey, SerializerModel
|
||||
from passbook.policies.models import PolicyBindingModel
|
||||
|
||||
|
@ -64,9 +63,9 @@ class Stage(SerializerModel):
|
|||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def ui_user_settings(self) -> Optional[UIUserSettings]:
|
||||
def ui_user_settings(self) -> Optional[str]:
|
||||
"""Entrypoint to integrate with User settings. Can either return None if no
|
||||
user settings are available, or an instanace of UIUserSettings."""
|
||||
user settings are available, or a string with the URL to fetch."""
|
||||
return None
|
||||
|
||||
def __str__(self):
|
||||
|
|
|
@ -7,7 +7,7 @@ from django.urls import reverse, reverse_lazy
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from passbook.core.models import Source, UserSourceConnection
|
||||
from passbook.core.types import UILoginButton, UIUserSettings
|
||||
from passbook.core.types import UILoginButton
|
||||
|
||||
|
||||
class OAuthSource(Source):
|
||||
|
@ -66,12 +66,9 @@ class OAuthSource(Source):
|
|||
return f"Callback URL: <pre>{url}</pre>"
|
||||
|
||||
@property
|
||||
def ui_user_settings(self) -> Optional[UIUserSettings]:
|
||||
def ui_user_settings(self) -> Optional[str]:
|
||||
view_name = "passbook_sources_oauth:oauth-client-user"
|
||||
return UIUserSettings(
|
||||
name=self.name,
|
||||
url=reverse(view_name, kwargs={"source_slug": self.slug}),
|
||||
)
|
||||
return reverse(view_name, kwargs={"source_slug": self.slug})
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"OAuth Source {self.name}"
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
{% extends "user/base.html" %}
|
||||
|
||||
{% load passbook_utils %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page %}
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__header pf-c-title pf-m-md">
|
||||
{% blocktrans with source_name=source.name %}
|
||||
|
@ -26,4 +22,3 @@
|
|||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -8,7 +8,6 @@ from django.utils.translation import gettext_lazy as _
|
|||
from django.views import View
|
||||
from rest_framework.serializers import BaseSerializer
|
||||
|
||||
from passbook.core.types import UIUserSettings
|
||||
from passbook.flows.models import ConfigurableStage, Stage
|
||||
|
||||
|
||||
|
@ -36,13 +35,10 @@ class OTPStaticStage(ConfigurableStage, Stage):
|
|||
return OTPStaticStageForm
|
||||
|
||||
@property
|
||||
def ui_user_settings(self) -> Optional[UIUserSettings]:
|
||||
return UIUserSettings(
|
||||
name="Static OTP",
|
||||
url=reverse(
|
||||
def ui_user_settings(self) -> Optional[str]:
|
||||
return reverse(
|
||||
"passbook_stages_otp_static:user-settings",
|
||||
kwargs={"stage_uuid": self.stage_uuid},
|
||||
),
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
{% extends "user/base.html" %}
|
||||
|
||||
{% load passbook_utils %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page %}
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__header pf-c-title pf-m-md">
|
||||
{% trans "Static One-Time Passwords" %}
|
||||
|
@ -33,4 +29,3 @@
|
|||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -8,7 +8,6 @@ from django.utils.translation import gettext_lazy as _
|
|||
from django.views import View
|
||||
from rest_framework.serializers import BaseSerializer
|
||||
|
||||
from passbook.core.types import UIUserSettings
|
||||
from passbook.flows.models import ConfigurableStage, Stage
|
||||
|
||||
|
||||
|
@ -43,13 +42,10 @@ class OTPTimeStage(ConfigurableStage, Stage):
|
|||
return OTPTimeStageForm
|
||||
|
||||
@property
|
||||
def ui_user_settings(self) -> Optional[UIUserSettings]:
|
||||
return UIUserSettings(
|
||||
name="Time-based OTP",
|
||||
url=reverse(
|
||||
def ui_user_settings(self) -> Optional[str]:
|
||||
return reverse(
|
||||
"passbook_stages_otp_time:user-settings",
|
||||
kwargs={"stage_uuid": self.stage_uuid},
|
||||
),
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
{% extends "user/base.html" %}
|
||||
|
||||
{% load passbook_utils %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page %}
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__header pf-c-title pf-m-md">
|
||||
{% trans "Time-based One-Time Passwords" %}
|
||||
|
@ -30,4 +26,3 @@
|
|||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -8,3 +8,4 @@ class PassbookStagePasswordConfig(AppConfig):
|
|||
name = "passbook.stages.password"
|
||||
label = "passbook_stages_password"
|
||||
verbose_name = "passbook Stages.Password"
|
||||
mountpoint = "-/user/password/"
|
||||
|
|
|
@ -4,15 +4,12 @@ from typing import Optional, Type
|
|||
from django.contrib.postgres.fields import ArrayField
|
||||
from django.db import models
|
||||
from django.forms import ModelForm
|
||||
from django.shortcuts import reverse
|
||||
from django.utils.http import urlencode
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views import View
|
||||
from rest_framework.serializers import BaseSerializer
|
||||
from django.shortcuts import reverse
|
||||
|
||||
from passbook.core.types import UIUserSettings
|
||||
from passbook.flows.models import ConfigurableStage, Stage
|
||||
from passbook.flows.views import NEXT_ARG_NAME
|
||||
|
||||
|
||||
class PasswordStage(ConfigurableStage, Stage):
|
||||
|
@ -51,12 +48,10 @@ class PasswordStage(ConfigurableStage, Stage):
|
|||
return PasswordStageForm
|
||||
|
||||
@property
|
||||
def ui_user_settings(self) -> Optional[UIUserSettings]:
|
||||
def ui_user_settings(self) -> Optional[str]:
|
||||
if not self.configure_flow:
|
||||
return None
|
||||
base_url = reverse("passbook_flows:configure", kwargs={"stage_uuid": self.pk})
|
||||
args = urlencode({NEXT_ARG_NAME: reverse("passbook_core:user-settings")})
|
||||
return UIUserSettings(name=_("Change password"), url=f"{base_url}?{args}")
|
||||
return reverse("passbook_stages_password:user-settings", kwargs={"stage_uuid": self.pk})
|
||||
|
||||
def __str__(self):
|
||||
return f"Password Stage {self.name}"
|
||||
|
|
|
@ -52,7 +52,7 @@ class PasswordStageView(FormView, StageView):
|
|||
"""Authentication stage which authenticates against django's AuthBackend"""
|
||||
|
||||
form_class = PasswordForm
|
||||
template_name = "stages/password/backend.html"
|
||||
template_name = "stages/password/flow-form.html"
|
||||
|
||||
def get_form(self, form_class=None) -> PasswordForm:
|
||||
form = super().get_form(form_class=form_class)
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
{% extends "base/page.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load passbook_utils %}
|
||||
|
||||
{% block body %}
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__header pf-c-title pf-m-md">
|
||||
{% trans 'Reset your password' %}
|
||||
</div>
|
||||
<div class="pf-c-card__body">
|
||||
<a class="pf-c-button pf-m-primary" href="{{ url }}">
|
||||
{% trans 'Change password' %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -0,0 +1,10 @@
|
|||
"""Password stage urls"""
|
||||
from django.urls import path
|
||||
|
||||
from passbook.stages.password.views import UserSettingsCardView
|
||||
|
||||
urlpatterns = [
|
||||
path(
|
||||
"<uuid:stage_uuid>/change-card/", UserSettingsCardView.as_view(), name="user-settings"
|
||||
),
|
||||
]
|
|
@ -0,0 +1,20 @@
|
|||
from typing import Any
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
from django.shortcuts import reverse
|
||||
from django.utils.http import urlencode
|
||||
|
||||
from passbook.flows.views import NEXT_ARG_NAME
|
||||
|
||||
class UserSettingsCardView(LoginRequiredMixin, TemplateView):
|
||||
|
||||
template_name = "stages/password/user-settings-card.html"
|
||||
|
||||
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
|
||||
base_url = reverse("passbook_flows:configure", kwargs={"stage_uuid": self.kwargs["stage_uuid"]})
|
||||
args = urlencode({NEXT_ARG_NAME: reverse("passbook_core:user-settings")})
|
||||
|
||||
kwargs = super().get_context_data(**kwargs)
|
||||
kwargs["url"] = f"{base_url}?{args}"
|
||||
return kwargs
|
File diff suppressed because one or more lines are too long
Reference in New Issue