Browse Source

added editing of own account

funding-tag
Holger Frey 5 years ago
parent
commit
f82d5bf746
  1. 3
      ordr3/resources.py
  2. 27
      ordr3/schemas/account.py
  3. 29
      ordr3/templates/account/myaccount.jinja2
  4. 62
      ordr3/views/account.py
  5. 125
      tests/test_repo.py
  6. 14
      tests/test_services.py

3
ordr3/resources.py

@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
import abc
from pyramid.security import Allow, Everyone
from pyramid.security import Allow, Everyone, Authenticated
from sqlalchemy.inspection import inspect
@ -61,6 +61,7 @@ class Root(BaseResource): @@ -61,6 +61,7 @@ class Root(BaseResource):
(Allow, Everyone, "logout"),
(Allow, Everyone, "registration"),
(Allow, Everyone, "view"),
(Allow, Authenticated, "account"),
]

27
ordr3/schemas/account.py

@ -114,3 +114,30 @@ class ResetPasswordSchema(CSRFSchema): @@ -114,3 +114,30 @@ class ResetPasswordSchema(CSRFSchema):
}
settings.update(override)
return super().as_form(request, **settings)
class MyAccountSchema(CSRFSchema):
""" edit an account """
user_name = colander.SchemaNode(
colander.String(),
widget=deform.widget.TextInputWidget(
template="textinput_disabled.pt", css_class="o3-reg-username"
),
)
first_name = colander.SchemaNode(colander.String(),)
last_name = colander.SchemaNode(colander.String(),)
email = colander.SchemaNode(
colander.String(),
validator=colander.Email(),
widget=deform.widget.TextInputWidget(template="email.pt",),
)
@classmethod
def as_form(cls, request, **override):
settings = {
"buttons": ("Save", "Cancel"),
"css_class": "form-horizontal registration",
}
settings.update(override)
return super().as_form(request, **settings)

29
ordr3/templates/account/myaccount.jinja2

@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
{% extends "ordr3:templates/layout_small.jinja2" %}
{% block subtitle %} My Account {% endblock subtitle %}
{% block content %}
<div class="container">
<div class="row o3-registration-card">
<div class="col"></div>
<div class="col">
<div class="card">
<div class="card-header bg-dark text-light">
<h2 class="card-title text-center pt-1">Ordr</h5>
</div>
<div class="card-body">
<h6 class="card-subtitle mb-2 text-muted text-center mt-1 mb-4">Edit your account</h6>
{{form.render()|safe}}
<hr>
<p class="mt-4">
<a href="{{request.resource_url(request.root, 'mypassword')}}" class="btn btn-outline-secondary">Change Password</a>
</p>
</div>
</div>
</div>
<div class="col"></div>
</div>
</div>
{% endblock content %}

62
ordr3/views/account.py

@ -8,7 +8,7 @@ from pyramid.httpexceptions import HTTPFound @@ -8,7 +8,7 @@ from pyramid.httpexceptions import HTTPFound
from .. import models, security, services
from ..repo import RepoItemNotFound
from ..events import PasswordResetEmail
from ..events import FlashMessage, PasswordResetEmail
from ..schemas import account
@ -225,4 +225,62 @@ def reset_password(context, request): @@ -225,4 +225,62 @@ def reset_password(context, request):
)
def password_reseted(context, request):
return {}
# http://localhost:6543/reset?t=69e24c08-1cb2-4656-987a-4791175f3368
@view_config(
context="ordr3:resources.Root",
name="myaccount",
permission="account",
request_method="GET",
renderer="ordr3:templates/account/myaccount.jinja2",
)
def myaccount(context, request):
form = account.MyAccountSchema.as_form(request)
form_data = {
"user_name": request.user.username,
"first_name": request.user.first_name,
"last_name": request.user.last_name,
"email": request.user.email,
}
form.set_appstruct(form_data)
return {"form": form}
@view_config(
context="ordr3:resources.Root",
name="myaccount",
permission="account",
request_method="POST",
renderer="ordr3:templates/account/myaccount.jinja2",
)
def edit_myaccount(context, request):
if "Cancel" in request.POST:
return HTTPFound(request.resource_path(request.root))
form = account.MyAccountSchema.as_form(request)
data = request.POST.items()
try:
appstruct = form.validate(data)
except deform.ValidationFailure:
return {"form": form}
request.user.first_name = appstruct["first_name"]
request.user.last_name = appstruct["last_name"]
request.user.email = appstruct["email"]
return HTTPFound(request.resource_path(request.root))
@view_config(
context="ordr3:resources.Root", name="mypassword", permission="account",
)
def myaccount_reset_link(context, request):
token = services.create_token_for_user(request.repo, request.user)
request.emit(PasswordResetEmail(request.user, token.token))
request.emit(
FlashMessage.info(
f"A password reset link has been sent to {request.user.email}."
)
)
return HTTPFound(request.resource_path(request.root))

125
tests/test_repo.py

@ -52,6 +52,20 @@ def example_users(): @@ -52,6 +52,20 @@ def example_users():
]
@pytest.fixture()
def example_tokens():
from ordr3.models import PasswordResetToken
from datetime import datetime, timedelta
valid = datetime.utcnow() + timedelta(days=2)
invalid = datetime.utcnow() - timedelta(days=2)
return [
PasswordResetToken("valid_token", 1, valid),
PasswordResetToken("invalid_token", 2, invalid),
]
def test_sql_repo_add_order(session, example_orders):
from ordr3.repo import SqlAlchemyRepository
from ordr3.models import OrderItem
@ -76,6 +90,17 @@ def test_sql_repo_get_order(session, example_orders): @@ -76,6 +90,17 @@ def test_sql_repo_get_order(session, example_orders):
assert example_orders[1] == repo.get_order(2)
def test_sql_repo_get_order_raises_exception(session, example_orders):
from ordr3.repo import SqlAlchemyRepository, RepoItemNotFound
repo = SqlAlchemyRepository(session)
repo.add_order(example_orders[0])
session.flush()
with pytest.raises(RepoItemNotFound):
repo.get_order(2)
def test_sql_repo_list_orders(session, example_orders):
from ordr3.repo import SqlAlchemyRepository
@ -112,6 +137,17 @@ def test_sql_repo_get_user(session, example_users): @@ -112,6 +137,17 @@ def test_sql_repo_get_user(session, example_users):
assert example_users[1] == repo.get_user(2)
def test_sql_repo_get_user_raises_exception(session, example_users):
from ordr3.repo import SqlAlchemyRepository, RepoItemNotFound
repo = SqlAlchemyRepository(session)
repo.add_user(example_users[0])
session.flush()
with pytest.raises(RepoItemNotFound):
repo.get_user(2)
def test_sql_repo_get_user_by_username(session, example_users):
from ordr3.repo import SqlAlchemyRepository
@ -123,6 +159,17 @@ def test_sql_repo_get_user_by_username(session, example_users): @@ -123,6 +159,17 @@ def test_sql_repo_get_user_by_username(session, example_users):
assert example_users[1] == repo.get_user_by_username("Me")
def test_sql_repo_get_user_by_username_exception(session, example_users):
from ordr3.repo import SqlAlchemyRepository, RepoItemNotFound
repo = SqlAlchemyRepository(session)
repo.add_user(example_users[0])
session.flush()
with pytest.raises(RepoItemNotFound):
repo.get_user_by_username("unknown user name")
def test_sql_repo_get_user_by_email(session, example_users):
from ordr3.repo import SqlAlchemyRepository
@ -134,6 +181,17 @@ def test_sql_repo_get_user_by_email(session, example_users): @@ -134,6 +181,17 @@ def test_sql_repo_get_user_by_email(session, example_users):
assert example_users[1] == repo.get_user_by_email("jane.doe")
def test_sql_repo_get_user_by_email_exception(session, example_users):
from ordr3.repo import SqlAlchemyRepository, RepoItemNotFound
repo = SqlAlchemyRepository(session)
repo.add_user(example_users[0])
session.flush()
with pytest.raises(RepoItemNotFound):
repo.get_user_by_email("unknown email")
def test_sql_repo_list_users(session, example_users):
from ordr3.repo import SqlAlchemyRepository
@ -157,3 +215,70 @@ def test_sql_search_vendor(session, example_users): @@ -157,3 +215,70 @@ def test_sql_search_vendor(session, example_users):
assert repo.search_vendor("sa") == "Sigma Aldrich"
assert repo.search_vendor("unknown") is None
def test_sql_repo_add_reset_token(session, example_tokens):
from ordr3.repo import SqlAlchemyRepository
from ordr3.models import PasswordResetToken
repo = SqlAlchemyRepository(session)
repo.add_reset_token(example_tokens[0])
session.flush()
token = session.query(PasswordResetToken).first()
assert token == example_tokens[0]
def test_sql_repo_delete_reset_token(session, example_tokens):
from ordr3.repo import SqlAlchemyRepository
from ordr3.models import PasswordResetToken
repo = SqlAlchemyRepository(session)
repo.add_reset_token(example_tokens[0])
repo.add_reset_token(example_tokens[1])
session.flush()
repo.delete_reset_token(example_tokens[0])
tokens = session.query(PasswordResetToken).all()
assert tokens == [example_tokens[1]]
def test_sql_repo_get_reset_token(session, example_tokens):
from ordr3.repo import SqlAlchemyRepository
repo = SqlAlchemyRepository(session)
repo.add_reset_token(example_tokens[0])
repo.add_reset_token(example_tokens[1])
session.flush()
token = repo.get_reset_token("valid_token")
assert token == example_tokens[0]
def test_sql_repo_get_reset_token_raises_exception(session, example_tokens):
from ordr3.repo import SqlAlchemyRepository, RepoItemNotFound
repo = SqlAlchemyRepository(session)
repo.add_reset_token(example_tokens[0])
session.flush()
with pytest.raises(RepoItemNotFound):
repo.get_reset_token("unknown token")
def test_clear_stale_reset_tokens(session, example_tokens):
from ordr3.repo import SqlAlchemyRepository
from ordr3.models import PasswordResetToken
repo = SqlAlchemyRepository(session)
repo.add_reset_token(example_tokens[0])
repo.add_reset_token(example_tokens[1])
session.flush()
repo.clear_stale_reset_tokens()
tokens = session.query(PasswordResetToken).all()
assert tokens == [example_tokens[0]]

14
tests/test_services.py

@ -403,3 +403,17 @@ def test_get_user_from_reset_token_unknown_user(): @@ -403,3 +403,17 @@ def test_get_user_from_reset_token_unknown_user():
result = services.get_user_from_reset_token(repo, "identifier")
assert result is None
def test_create_token_for_user():
from ordr3.services import create_token_for_user
from ordr3.models import PasswordResetToken, User
repo = FakeOrderRepository(None)
user = User(*list("ABCDEFG"))
result = create_token_for_user(repo, user)
assert isinstance(result, PasswordResetToken)
assert result.user_id == "A"
assert repo._tokens == {result}

Loading…
Cancel
Save