Browse Source

edit order form and processing

funding-tag
Holger Frey 5 years ago
parent
commit
6dea8441e2
  1. 4
      ordr3/__init__.py
  2. 2
      ordr3/routes.py
  3. 1
      ordr3/schemas/__init__.py
  4. 195
      ordr3/schemas/orders.py
  5. 49
      ordr3/static/script.js
  6. 51
      ordr3/static/style.css
  7. 6
      ordr3/templates/account/login.jinja2
  8. 2
      ordr3/templates/account/myaccount.jinja2
  9. 5
      ordr3/templates/deform/mapping_item.pt
  10. 38
      ordr3/templates/deform/money_mapping.pt
  11. 38
      ordr3/templates/deform/money_mapping_disabled.pt
  12. 26
      ordr3/templates/deform/money_mapping_item.pt
  13. 26
      ordr3/templates/deform/money_mapping_item_diabled.pt
  14. 3
      ordr3/templates/deform/textinput.pt
  15. 2
      ordr3/templates/emails/activation.jinja2
  16. 2
      ordr3/templates/emails/password_reset.jinja2
  17. 2
      ordr3/templates/emails/status_change.jinja2
  18. 20
      ordr3/templates/layout_full.jinja2
  19. 23
      ordr3/templates/macros.jinja2
  20. 2
      ordr3/templates/orders/batch_delete.jinja2
  21. 2
      ordr3/templates/orders/batch_edit.jinja2
  22. 2
      ordr3/templates/orders/delete.jinja2
  23. 28
      ordr3/templates/orders/edit.jinja2
  24. 10
      ordr3/templates/orders/list.jinja2
  25. 8
      ordr3/templates/orders/list_content.jinja2
  26. 40
      ordr3/templates/orders/view.jinja2
  27. 2
      ordr3/templates/users/delete.jinja2
  28. 4
      ordr3/templates/users/edit.jinja2
  29. 4
      ordr3/templates/users/list.jinja2
  30. 8
      ordr3/templates/users/list_content.jinja2
  31. 9
      ordr3/views/__init__.py
  32. 135
      ordr3/views/orders.py
  33. 4
      ordr3/views/root.py

4
ordr3/__init__.py

@ -9,8 +9,6 @@ __version__ = "0.0.1" @@ -9,8 +9,6 @@ __version__ = "0.0.1"
from pyramid.config import Configurator
from pyramid.session import JSONSerializer, SignedCookieSessionFactory
from . import resources
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
@ -25,8 +23,6 @@ def main(global_config, **settings): @@ -25,8 +23,6 @@ def main(global_config, **settings):
require_csrf=settings["session.auto_csrf"]
)
config.set_root_factory(resources.Root)
config.include("pyramid_jinja2")
config.include(".adapters")

2
ordr3/routes.py

@ -7,4 +7,4 @@ def includeme(config): @@ -7,4 +7,4 @@ def includeme(config):
settings = config.get_settings()
age = int(settings.get("static_views.cache_max_age", 3600))
config.add_static_view("static", "static", cache_max_age=age)
# config.add_static_view('deform', 'deform:static', cache_max_age=age)
config.add_static_view("deform", "deform:static", cache_max_age=age)

1
ordr3/schemas/__init__.py

@ -14,5 +14,6 @@ def includeme(config): @@ -14,5 +14,6 @@ def includeme(config):
ordr_templates = resource_filename("ordr3", "templates/deform")
deform_templates = resource_filename("deform", "templates")
search_path = (ordr_templates, deform_templates)
# search_path = (ordr_templates, )
Form.set_zpt_renderer(search_path)

195
ordr3/schemas/orders.py

@ -0,0 +1,195 @@ @@ -0,0 +1,195 @@
""" Schemas for form input and validation """
import deform
import colander
from .base import CSRFSchema
from ..models import UserRole, OrderStatus, OrderCategory
CATEGORIES = [(c.name, c.name.capitalize()) for c in OrderCategory]
STATI = [(s.name, s.name.capitalize()) for s in OrderStatus]
class OrderStatus(colander.Schema):
""" schema for editing order status
parital schema, used in EditOrderSchema
"""
status = colander.SchemaNode(
colander.String(),
widget=deform.widget.SelectWidget(
values=STATI,
css_class="custom-select col-sm-9",
item_css_class="row",
label_css_class="col-sm-3 col-form-label",
),
)
class OrderItem(colander.Schema):
""" schema for editing order information
parital schema, used in NewOrderSchema and EditOrderSchema
"""
cas_description = colander.SchemaNode(
colander.String(),
widget=deform.widget.TextInputWidget(
item_css_class="row",
label_css_class="col-sm-3 col-form-label o3-form-copy",
css_class="col-sm-9",
),
)
category = colander.SchemaNode(
colander.String(),
widget=deform.widget.SelectWidget(
values=CATEGORIES,
css_class="custom-select col-sm-9",
item_css_class="row",
label_css_class="col-sm-3 col-form-label",
),
)
catalog_nr = colander.SchemaNode(
colander.String(),
widget=deform.widget.TextInputWidget(
item_css_class="row",
label_css_class="col-sm-3 col-form-label o3-form-copy",
css_class="col-sm-9",
),
)
vendor = colander.SchemaNode(
colander.String(),
widget=deform.widget.TextInputWidget(
item_css_class="row",
label_css_class="col-sm-3 col-form-label o3-form-copy",
css_class="col-sm-9",
),
)
package_size = colander.SchemaNode(
colander.String(),
widget=deform.widget.TextInputWidget(
item_css_class="row",
label_css_class="col-sm-3 col-form-label o3-form-copy",
css_class="col-sm-9",
),
)
class MoneyInputSchema(colander.Schema):
""" custom schema for structured money and currency input """
amount = colander.SchemaNode(
colander.Decimal(),
widget=deform.widget.MoneyInputWidget(
readonly_template="textinput_disabled.pt",
css_class="moneyinput amount",
col_css_class="col-sm-7",
),
)
currency = colander.SchemaNode(
colander.String(),
default="EUR",
widget=deform.widget.TextInputWidget(
readonly_template="textinput_disabled.pt",
css_class="moneyinput currency",
col_css_class="col-sm-2",
),
)
def __init__(self, *args, **kwargs):
""" define the custom schema templates """
if "widget" not in kwargs:
readonly = kwargs.pop("readonly", False)
kwargs["widget"] = deform.widget.MappingWidget(
category="default",
template="money_mapping.pt",
readonly_template="money_mapping_disabled.pt",
item_template="money_mapping_item.pt",
item_readonly_template="money_mapping_item_diabled.pt",
readonly=readonly,
label_css_class="col-sm-3 col-form-label o3-form-copy",
item_css_class="row",
)
super().__init__(*args, **kwargs)
class OrderPricing(colander.Schema):
""" schema for editing price information
parital schema, used in NewOrderSchema and EditOrderSchema
"""
quantity = colander.SchemaNode(
colander.Integer(),
validator=colander.Range(min=1),
widget=deform.widget.TextInputWidget(
css_class="number col-sm-9",
item_css_class="row",
label_css_class="col-sm-3 col-form-label o3-form-copy",
),
default=1,
)
unit_price = MoneyInputSchema(
readonly=False, label_css_class="col-sm-3 col-form-label o3-form-copy"
)
total_price = MoneyInputSchema(
readonly=True, label_css_class="col-sm-3 col-form-label o3-form-copy"
)
class OrderOptionals(colander.Schema):
""" schema for editing optional information
parital schema, used in NewOrderSchema and EditOrderSchema
"""
account = colander.SchemaNode(
colander.String(),
required=False,
missing="",
widget=deform.widget.TextInputWidget(
item_css_class="row",
label_css_class="col-sm-3 col-form-label o3-form-copy",
css_class="col-sm-9",
),
)
comment = colander.SchemaNode(
colander.String(),
missing="",
widget=deform.widget.TextAreaWidget(
rows=7,
item_css_class="row",
label_css_class="col-sm-3 col-form-label o3-form-copy",
css_class="col-sm-9",
),
)
class EditOrderSchema(CSRFSchema):
""" edit an order """
status = OrderStatus()
item = OrderItem()
pricing = OrderPricing()
optional = OrderOptionals()
@classmethod
def as_form(cls, request, **override):
""" returns the schema as a form """
settings = {
"buttons": ("Save Changes", "Cancel"),
"css_class": "deform o3-col-form",
}
settings.update(override)
form = super().as_form(request, **settings)
# disable the status field, if the current user is not a purchaser
if UserRole.PURCHASER.principal not in request.user.principals:
form["status"]["status"].widget = deform.widget.TextInputWidget(
template="textinput_disabled.pt",
item_css_class="row",
label_css_class="col-sm-3 col-form-label",
css_class="col-sm-9",
)
return form

49
ordr3/static/script.js

@ -22,6 +22,15 @@ var load_more = function(target) { @@ -22,6 +22,15 @@ var load_more = function(target) {
});
}
var update_price = function() {
// update total price in order form
var target = $(".item-unit_price .item-amount input");
var price = target.val().replace(/,/g, "")
var amount = $(".item-quantity input").val()
var total = price * amount
$(".item-total_price .item-amount input").val(total.toFixed(2))
}
$(function() {
// Handler for .ready() called.
@ -131,6 +140,46 @@ $(function() { @@ -131,6 +140,46 @@ $(function() {
selects.val(target.attr("data-multi-value"))
});
$(".o3-back").on("click", function(event){
// back button
history.back();
return false;
});
$(".item-unit_price .item-amount input").on("keyup", function(event) {
update_price();
});
$(".item-quantity input").on("keyup", function(event) {
update_price();
});
$(".item-unit_price .item-currency input").on("keyup", function(event) {
// update total price currency in order form
var target = $(event.delegateTarget);
var value = target.val()
$(".item-total_price .item-currency input").val(value)
});
$("label.o3-form-copy").on("click", function(event){
// copy to clipboard
var target = $(event.delegateTarget);
var parent = target.parent()
var input = parent.find(".form-control")
var value = input.first().val()
var $temp = $('<input>');
$("body").append($temp);
$temp.val(value).select();
document.execCommand("copy");
$temp.remove();
$(target).fadeTo(100, 0).delay(100).fadeTo(100, 1);
$(input).fadeTo(100, 0).delay(100).fadeTo(100, 1);
});
$('label.o3-form-copy').attr('title', 'copy to clipboard');
var infinite = new Waypoint.Infinite({
element: $('.infinite-container')[0]
});

51
ordr3/static/style.css

@ -193,3 +193,54 @@ td.o3-actions a:hover { @@ -193,3 +193,54 @@ td.o3-actions a:hover {
.o3-data-edit-multiple select {
width: 7rem;
}
a.d-inline-block.text-truncate {
max-width:100%;
}
.o3-content .table.o3-log-table th, .o3-content .table.o3-log-table td {
padding-left:0px;
padding-top:0px;
}
.o3-col-form .form-group.row {
padding-right:15px;
}
span.item-amount {
padding-left:0px;
}
span.item-currency {
padding-left:0px;
padding-right:0px;
}
input.moneyinput.amount, input.number {
text-align:right;
}
input.moneyinput.currency {
text-align:center;
}
.panel-heading {
border-bottom:1px solid #ced4da!important;
margin-bottom:1rem;
margin-top:2rem;
color:#6c757d!important;
font-weight: lighter!important;
}
label.o3-form-copy {
cursor:pointer;
}
label.o3-form-copy:hover {
text-decoration:underline;
}

6
ordr3/templates/account/login.jinja2

@ -13,7 +13,7 @@ @@ -13,7 +13,7 @@
</div>
<div class="card-body">
<h6 class="card-subtitle mb-2 text-muted text-center mt-1 mb-4">Please Log In</h6>
<form method="post" action="{{request.resource_url(request.root, 'login')}}" class="form">
<form method="post" action="{{request.root|resource_url('login')}}" class="form">
<div class="form-group">
<label for="username" class="sr-only">Username</label>
<input type="text" name="username" id="username" class="form-control text-center {% if error %}is-invalid{% endif %}" placeholder="Username" required="required">
@ -28,8 +28,8 @@ @@ -28,8 +28,8 @@
</div>
</form>
<ul class="list-group list-group-flush text-center">
<li class="list-group-item"><a href="{{request.resource_url(request.root, 'forgot')}}" class="text-secondary">Forgot your password?</a></li>
<li class="list-group-item"><a href="{{request.resource_url(request.root, 'registration')}}" class="text-secondary">Register a new account.</a></li>
<li class="list-group-item"><a href="{{request.root|resource_url('forgot')}}" class="text-secondary">Forgot your password?</a></li>
<li class="list-group-item"><a href="{{request.root|resource_url('registration')}}" class="text-secondary">Register a new account.</a></li>
</ul>
</div>
</div>

2
ordr3/templates/account/myaccount.jinja2

@ -9,7 +9,7 @@ @@ -9,7 +9,7 @@
{{form.render()|safe}}
<hr>
<p class="mt-4">
<a href="{{request.resource_url(request.root, 'mypassword')}}" class="btn btn-outline-secondary">Change Password</a>
<a href="{{request.root|resource_url('mypassword')}}" class="btn btn-outline-secondary">Change Password</a>
</p>
</div>
<div class="col-5"></div>

5
ordr3/templates/deform/mapping_item.pt

@ -5,7 +5,8 @@ @@ -5,7 +5,8 @@
hidden hidden|field.widget.hidden;
category category|field.widget.category;
structural hidden or category == 'structural';
required required|field.required;"
required required|field.required;
label_css_class label_css_class|field.widget.label_css_class|'';"
class="form-group ${field.error and 'has-error' or ''} ${field.widget.item_css_class or ''} ${field.default_item_css_class()}"
title="${description}"
id="item-${oid}"
@ -13,7 +14,7 @@ @@ -13,7 +14,7 @@
i18n:domain="deform">
<label for="${oid}"
class="control-label ${required and 'required' or ''}"
class="control-label ${required and 'required' or ''} ${label_css_class}"
tal:condition="not structural"
id="req-${oid}"
>

38
ordr3/templates/deform/money_mapping.pt

@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
<span tal:define="error_class error_class|field.widget.error_class;
description description|field.description;
title title|field.title;
hidden hidden|field.widget.hidden;
category category|field.widget.category;
item_template item_template|field.widget.item_template;
amount_field field.children[0];
currency_field field.children[1];
oid oid|amount_field.oid;
required required|amount_field.required;
label_css_class amount_field.widget.label_css_class|'';"
i18n:domain="deform"
class="moneyinput"
tal:omit-tag>
${field.start_mapping()}
<div tal:repeat="child field.children"
tal:replace="structure child.render_template(item_template)" >
</div>
${field.end_mapping()}
<p class="help-inline"
tal:define="errstr 'error-%s' % field.oid"
tal:repeat="msg amount_field.error.messages()|currency_field.error.messages()"
i18n:translate=""
tal:attributes="id repeat.msg.index==0 and errstr or
('%s-%s' % (errstr, repeat.msg.index))"
tal:condition="(amount_field.error or currency_field.error) and not field.widget.hidden">
${msg}
</p>
<p tal:condition="field.description and not field.widget.hidden"
class="help-inline" >
${field.description}
</p>
</span>

38
ordr3/templates/deform/money_mapping_disabled.pt

@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
<span tal:define="error_class error_class|field.widget.error_class;
description description|field.description;
title title|field.title;
hidden hidden|field.widget.hidden;
category category|field.widget.category;
item_template item_template|field.widget.item_readonly_template;
amount_field field.children[0];
currency_field field.children[1];
oid oid|amount_field.oid;
required required|amount_field.required;
label_css_class amount_field.widget.label_css_class|'';"
i18n:domain="deform"
class="moneyinput"
tal:omit-tag>
${field.start_mapping()}
<div tal:repeat="child field.children"
tal:replace="structure child.render_template(item_template)" >
</div>
${field.end_mapping()}
<p class="help-inline"
tal:define="errstr 'error-%s' % field.oid"
tal:repeat="msg amount_field.error.messages()|currency_field.error.messages()"
i18n:translate=""
tal:attributes="id repeat.msg.index==0 and errstr or
('%s-%s' % (errstr, repeat.msg.index))"
tal:condition="(amount_field.error or currency_field.error) and not field.widget.hidden">
${msg}
</p>
<p tal:condition="field.description and not field.widget.hidden"
class="help-inline" >
${field.description}
</p>
</span>

26
ordr3/templates/deform/money_mapping_item.pt

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
<span tal:define="error_class error_class|field.widget.error_class;
description description|field.description;
title title|field.title;
oid oid|field.oid;
hidden hidden|field.widget.hidden;
category category|field.widget.category;
structural hidden or category == 'structural';
required required|field.required;
label_css_class label_css_class|field.widget.label_css_class|'';"
class="${field.error and 'error' or ''} ${field.widget.col_css_class or ''} ${field.default_item_css_class()}"
title="${description}"
id="item-${oid}"
tal:omit-tag="structural"
i18n:domain="deform">
<div tal:define="input_prepend field.widget.input_prepend | None;
input_append field.widget.input_append | None"
tal:omit-tag="not (input_prepend or input_append)"
class="input-group">
<span class="input-group-addon"
tal:condition="input_prepend">${input_prepend}</span
><span tal:replace="structure field.serialize(cstruct).strip()"
/><span class="input-group-addon"
tal:condition="input_append">${input_append}</span>
</div>
</span>

26
ordr3/templates/deform/money_mapping_item_diabled.pt

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
<span tal:define="error_class error_class|field.widget.error_class;
description description|field.description;
title title|field.title;
oid oid|field.oid;
hidden hidden|field.widget.hidden;
category category|field.widget.category;
structural hidden or category == 'structural';
required required|field.required;
label_css_class label_css_class|field.widget.label_css_class|'';"
class="${field.error and 'error' or ''} ${field.widget.col_css_class or ''} ${field.default_item_css_class()}"
title="${description}"
id="item-${oid}"
tal:omit-tag="structural"
i18n:domain="deform">
<div tal:define="input_prepend field.widget.input_prepend | None;
input_append field.widget.input_append | None"
tal:omit-tag="not (input_prepend or input_append)"
class="input-group">
<span class="input-group-addon"
tal:condition="input_prepend">${input_prepend}</span
><span tal:replace="structure field.serialize(cstruct, readonly=True).strip()"
/><span class="input-group-addon"
tal:condition="input_append">${input_append}</span>
</div>
</span>

3
ordr3/templates/deform/textinput.pt

@ -4,10 +4,11 @@ @@ -4,10 +4,11 @@
mask mask|field.widget.mask;
mask_placeholder mask_placeholder|field.widget.mask_placeholder;
style style|field.widget.style;
required required|field.required;
"
tal:omit-tag="">
<input type="text" name="${name}" value="${cstruct}"
tal:attributes="required string: ${required|'required' or ''};
tal:attributes="required required|None;
class string: form-control ${css_class or ''} ${'is-invalid' if field.error else ''}
style style;
attributes|field.widget.attributes|{};"

2
ordr3/templates/emails/activation.jinja2

@ -2,7 +2,7 @@ Dear {{ user.first_name }}, @@ -2,7 +2,7 @@ Dear {{ user.first_name }},
Your account was activated by {{ request.user.first_name }}.
You can now log in at {{ request.resource_url(request.root) }} and order some stuff.
You can now log in at {{ request.root|resource_url() }} and order some stuff.
Regards,
The Ordr System

2
ordr3/templates/emails/password_reset.jinja2

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
Dear {{ user.first_name }},
If you forgot your password, you can set a new one by visiting this link:
{{ request.resource_url(request.root, 'reset', query={"t": data}) }}
{{ request.root|resource_url('reset', query={"t": data}) }}
The provided link is valid for one hour.

2
ordr3/templates/emails/status_change.jinja2

@ -7,7 +7,7 @@ The status of one or more of your orders have been changed by {{ request.user.fi @@ -7,7 +7,7 @@ The status of one or more of your orders have been changed by {{ request.user.fi
- new status: {{ order.status.name|title }}
{% endfor %}
You can now log in at {{ request.resource_url(request.root) }} and order some more stuff.
You find the ordering system at {{ request.root|resource_url() }}
Regards,
The Ordr System

20
ordr3/templates/layout_full.jinja2

@ -17,25 +17,27 @@ @@ -17,25 +17,27 @@
<script src="{{request.static_url('ordr3:static/jquery.waypoints.min.js')}}"></script>
<script src="{{request.static_url('ordr3:static/infinite.js')}}"></script>
<script src="{{request.static_url('ordr3:static/script.js')}}"></script>
<script src="{{request.static_url('deform:static/scripts/deform.js')}}"></script>
<script src="{{request.static_url('deform:static/scripts/jquery.maskMoney-1.4.1.js')}}"></script>
</head>
<body>
<header class="navbar navbar-expand navbar-dark bg-dark fixed-top pl-0 pr-0">
<div class=" container-fluid">
<a href="{{request.resource_url(request.root)}}" class="navbar-brand col-2 mr-0">ordr</a>
<a href="{{ request.root|resource_url() }}" class="navbar-brand col-2 mr-0">ordr</a>
<div class="col-7">
<ul class="navbar-nav">
{% if is_order_list %}
<li class="nav-item mr-3">
<a href="{{request.resource_url(request.root, 'orders', 'add') }}" class="btn btn-outline-light pt-1" title="Create a new order">
<a href="{{ request.root|resource_url('orders', 'add') }}" class="btn btn-outline-light pt-1" title="Create a new order">
{{ macros.icon("plus-circle") }}
</a>
</li>
<li class="nav-item o3-on-select mr-3">
<div class="btn-group">
<button class="btn btn-outline-light pt-1" title="Change Status" data-action="{{ request.resource_url(context, "batch-edit") }}">
<button class="btn btn-outline-light pt-1" title="Change Status" data-action="{{ context|resource_url("batch-edit") }}">
{{ macros.icon("flag") }}
</button>
<button class="btn btn-outline-light pt-1" title="Delete" data-action="{{ request.resource_url(context, "batch-delete") }}">
<button class="btn btn-outline-light pt-1" title="Delete" data-action="{{ context|resource_url("batch-delete") }}">
{{ macros.icon("trash") }}
</button>
</div>
@ -48,13 +50,13 @@ @@ -48,13 +50,13 @@
</li>
{% endif %}
<li class="nav-item mr-3">
<a href="{{request.resource_url(request.root) }}" class="btn btn-outline-light pt-1" title="Show orders">
<a href="{{ request.root|resource_url() }}" class="btn btn-outline-light pt-1" title="Show orders">
{{ macros.icon("bag") }}
</a>
</li>
{% if request.user.role.name == "ADMIN" %}
<li class="nav-item mr-3">
<a href="{{request.resource_url(request.root, 'users') }}" class="btn btn-outline-light pt-1 o3-users-link" title="User Management{% if new_user_badge >0 %}, {{ new_user_badge }} new users{% endif %}">
<a href="{{ request.root|resource_url('users') }}" class="btn btn-outline-light pt-1 o3-users-link" title="User Management{% if new_user_badge >0 %}, {{ new_user_badge }} new users{% endif %}">
{{ macros.icon("people") }}
{% if new_user_badge >0 %}
<span class="badge badge-pill badge-light">{{ new_user_badge }}</span>
@ -67,7 +69,7 @@ @@ -67,7 +69,7 @@
<div class="col-3">
<ul class="navbar-nav">
<li class="nav-item mr-3">
<form class="form-inline mt-1" method="GET" action="{{request.resource_url(request.root, 'orders') }}">
<form class="form-inline mt-1" method="GET" action="{{ request.root|resource_url('orders') }}">
<input class="form-control form-control-sm" name="search" type="search" value="{% if query_defaults and query_defaults['search'] %}{{ query_defaults['search']|default('') }}{% endif %}" placeholder="Search Orders" aria-label="Search">
</form>
</li>
@ -76,8 +78,8 @@ @@ -76,8 +78,8 @@
{{ macros.icon("gear") }}
</a>
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item small text-right" href="{{request.resource_url(request.root, 'logout')}}">Logout</a>
<a class="dropdown-item small text-right" href="{{request.resource_url(request.root, 'myaccount')}}">Edit My Account</a>
<a class="dropdown-item small text-right" href="{{ request.root|resource_url('logout')}}">Logout</a>
<a class="dropdown-item small text-right" href="{{ request.root|resource_url('myaccount')}}">Edit My Account</a>
</div>
</li>
</ul>

23
ordr3/templates/macros.jinja2

@ -35,3 +35,26 @@ @@ -35,3 +35,26 @@
<span class="badge badge-secondary">hold</span>
{% endif %}
{%- endmacro %}
{% macro change_log(order) -%}
<p class="font-weight-bold mt-4 mb-2">Status Change Log</p>
<table class="table table-hover table-borderless o3-log-table">
<thead>
<tr>
<th scope="col">Date</th>
<th scope="col">Status</th>
<th scope="col">By</th>
</tr>
</thead>
<tbody>
{% for entry in context.model.log %}
<tr>
<td>{{ entry.date|as_datetime }}</td>
<td>{{ status_label(entry.status) }}</td>
<td>{{ entry.by }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{%- endmacro %}

2
ordr3/templates/orders/batch_delete.jinja2

@ -8,7 +8,7 @@ @@ -8,7 +8,7 @@
<div class="border border-danger rounded o3-delete-warning">
<h4 class="mb-2 text-muted mb-4 text-truncate">Delete {{orders|length}} Orders <span class="text-danger">{{ context.model.cas_description }}</span></h4>
<p>You are about to delete the following orders:</p>
<form action="{{request.resource_url(context, 'batch-delete-confirm')}}" method="POST">
<form action="{{ context|resource_url('batch-delete-confirm') }}" method="POST">
<table class="table table-hover o3-data-table o3-data-hide-details">
<thead>
<tr>

2
ordr3/templates/orders/batch_edit.jinja2

@ -6,7 +6,7 @@ @@ -6,7 +6,7 @@
<div class="col-10">
<h4 class="mb-2 text-muted mb-4 text-truncate">Edit {{orders|length}} Orders <span class="text-danger">{{ context.model.cas_description }}</span></h4>
<form action="{{request.resource_url(context, 'batch-edit-confirm')}}" method="POST">
<form action="{{ context|resource_url('batch-edit-confirm') }}" method="POST">
<table class="table table-hover o3-data-table o3-data-edit-multiple">
<thead>
<tr>

2
ordr3/templates/orders/delete.jinja2

@ -28,7 +28,7 @@ @@ -28,7 +28,7 @@
</dl>
<p class="font-weight-bold mt-4 mb-4">Deleting this order is permanent and cannot be undone!</p>
<form action="{{request.resource_url(context, 'delete')}}" method="POST">
<form action="{{ context|resource_url('delete') }}" method="POST">
<p class="mt-4">
<div class="custom-control custom-switch o3-confirmation">
<input type="checkbox" class="custom-control-input" id="confirmation" value="confirmed" name="confirmation">

28
ordr3/templates/orders/edit.jinja2

@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
{% extends "ordr3:templates/layout_full.jinja2" %}
{% block subtitle %} Edit Order {{ context.model.cas_description }} {% endblock subtitle %}
{% block content %}
<div class="col-5">
<h4 class="mb-2 text-muted mb-4 text-truncate">Edit Order <span class="text-dark">{{ context.model.cas_description }}</span></h4>
{{form.render()|safe}}
<hr>
<p class="mt-4">
{% if request.has_permission("add", context.__parent__) %}
<a href="{{ context|resource_url('reorder') }}" class="btn btn-outline-primary">Reorder Item</a>
{% endif %}
{% if request.has_permission("delete", context) %}
<a href="{{ context|resource_url('delete') }}" class="btn btn-outline-danger">Delete Order</a>
{% endif %}
</p>
</div>
<div class="col-5">
<div class="h4">&nbsp;</div>
{{ macros.change_log(context.model) }}
</div>
{% endblock content %}

10
ordr3/templates/orders/list.jinja2

@ -7,24 +7,24 @@ @@ -7,24 +7,24 @@
<nav class="nav nav-pills flex-column">
<div class="nav-link disabled text-small" tabindex="-1" aria-disabled="true">All Orders</div>
<a class="nav-link {% if query_defaults['status'] == 'all' and not query_defaults['user'] and not query_defaults['search'] %}active{% endif %}" href="{{ request.resource_url(context, query=query_defaults(status=None, user=None, search=None)) }}">All</a>
<a class="nav-link {% if query_defaults['status'] == 'all' and not query_defaults['user'] and not query_defaults['search'] %}active{% endif %}" href="{{ context|resource_url(query=query_defaults(status=None, user=None, search=None)) }}">All</a>
{% for status in stati %}
<a class="nav-link {% if query_defaults['status'] == status.name.lower() and not query_defaults['user'] %}active{% endif %}" href="{{ request.resource_url(context, query=query_defaults(status=status.name.lower(), user=None, search=None)) }}">{{status.name.lower()}}</a>
<a class="nav-link {% if query_defaults['status'] == status.name.lower() and not query_defaults['user'] %}active{% endif %}" href="{{ context|resource_url(query=query_defaults(status=status.name.lower(), user=None, search=None)) }}">{{status.name.lower()}}</a>
{% endfor %}
</nav>
<nav class="nav nav-pills flex-column mt-3">
<div class="nav-link disabled text-small" tabindex="-1" aria-disabled="true">My Orders</div>
<a class="nav-link {% if query_defaults['status'] == 'all' and query_defaults['user'] == request.user.username %}active{% endif %}" href="{{ request.resource_url(context, query=query_defaults(status=None, user=request.user.username, search=None)) }}">All</a>
<a class="nav-link {% if query_defaults['status'] == 'all' and query_defaults['user'] == request.user.username %}active{% endif %}" href="{{ context|resource_url(query=query_defaults(status=None, user=request.user.username, search=None)) }}">All</a>
{% for status in stati %}
<a class="nav-link {% if query_defaults['status'] == status.name.lower() and query_defaults['user'] == request.user.username %}active{% endif %}" href="{{ request.resource_url(context, query=query_defaults(status=status.name.lower(), user=request.user.username, search=None)) }}">{{status.name.lower()}}</a>
<a class="nav-link {% if query_defaults['status'] == status.name.lower() and query_defaults['user'] == request.user.username %}active{% endif %}" href="{{ context|resource_url(query=query_defaults(status=status.name.lower(), user=request.user.username, search=None)) }}">{{status.name.lower()}}</a>
{% endfor %}
</nav>
{% if request.has_permission("batch-edit", context) %}
<nav class="nav nav-pills flex-column mt-3">
<div class="nav-link disabled text-small" tabindex="-1" aria-disabled="true">Specials</div>
<a class="nav-link {% if query_defaults['user'] == '-purchaser-' %}active{% endif %}" href="{{ request.resource_url(context, query=query_defaults(status=None, user='-purchaser-', search=None)) }}">Edited by me</a>
<a class="nav-link {% if query_defaults['user'] == '-purchaser-' %}active{% endif %}" href="{{ context|resource_url(query=query_defaults(status=None, user='-purchaser-', search=None)) }}">Edited by me</a>
</nav>
{% endif %}

8
ordr3/templates/orders/list_content.jinja2

@ -25,7 +25,7 @@ @@ -25,7 +25,7 @@
<div class="text-secondary small"><a class="o3-copy" title="copy to clipboard">{{ order.model.account }}</a></div>
</td>
<td>
<a href="{{ request.resource_url(context, query={'user':order.model.created_by})}}" title="Show orders for this user">{{ order.model.created_by}}</a>
<a href="{{ context|resource_url(query={'user':order.model.created_by})}}" title="Show orders for this user">{{ order.model.created_by}}</a>
<div class="text-secondary small">&nbsp;</div>
</td>
<td>
@ -36,17 +36,17 @@ @@ -36,17 +36,17 @@
{% if request.has_permission("edit", order) %}
<a href="{{ order|resource_url('edit') }}" title="Edit Order">{{ macros.icon("pencil")}}</a>
{% elif request.has_permission("view", order) %}
<a href="{{ request.resource_url(order) }}" title="View Order">{{ macros.icon("eye")}}</a>
<a href="{{ order|resource_url('view') }}" title="View Order">{{ macros.icon("eye")}}</a>
{% endif %}
{% if request.has_permission("delete", order) %}
<a href="{{ request.resource_url(order, 'delete') }}" title="Delete Order">{{ macros.icon("trash")}}</a>
<a href="{{ order|resource_url('delete') }}" title="Delete Order">{{ macros.icon("trash")}}</a>
{% endif %}
<div class="text-secondary small">&nbsp;</div>
</td>
</tr>
{% endfor %}
{% if next_offset %}
<tr class="infinite-more-link" href="{{ request.resource_url(context, query=query_defaults(o=next_offset)) }}">
<tr class="infinite-more-link" href="{{ context|resource_url(query=query_defaults(o=next_offset)) }}">
<td colspan="6">
<button class="btn btn-outline-primary btn-small">
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>

40
ordr3/templates/orders/view.jinja2

@ -9,13 +9,15 @@ @@ -9,13 +9,15 @@
<dl class="row mt-4">
<dt class="col-sm-4">Cas / Description</dt>
<dd class="col-sm-8">{{ context.model.cas_description }}</dd>
<dt class="col-sm-4">Category</dt>
<dd class="col-sm-8">{{ context.model.category.name|title }}</dd>
<dt class="col-sm-4">Vendor</dt>
<dd class="col-sm-8">{{ context.model.vendor }}</dd>
<dt class="col-sm-4">Catalog Nr.</dt>
<dd class="col-sm-8">{{ context.model.catalog_nr }}</dd>
<dt class="col-sm-4">Package Size</dt>
<dd class="col-sm-8">{{ context.model.package_size }}</dd>
<dt class="col-sm-4">Amount, Price</dt>
<dt class="col-sm-4">Amount * Price</dt>
<dd class="col-sm-8">{{ context.model.amount }} * {{ "%.2f"|format(context.model.unit_price) }} {{ context.model.currency }}</dd>
<dt class="col-sm-4">Price, total</dt>
<dd class="col-sm-8">{{ "%.2f"|format(context.model.total_price) }} {{ context.model.currency }}</dd>
@ -26,29 +28,27 @@ @@ -26,29 +28,27 @@
<dt class="col-sm-4">Account</dt>
<dd class="col-sm-8">{{ context.model.account|default("-", True) }}</dd>
<dt class="col-sm-4">Comment</dt>
<dd class="col-sm-8">{{ context.model.comment|default("-", True) }}</dd>
<dt class="col-sm-4">Status</dt>
<dd class="col-sm-8">{{ context.model.comment|default("-", True)|view_comment("")|nl2br|safe }}</dd>
<dt class="col-sm-4">Current Status</dt>
<dd class="col-sm-8">{{ macros.status_label(context.model.status) }}</dd>
</dl>
<p class="font-weight-bold mt-4 mb-4">Deleting this order is permanent and cannot be undone!</p>
<form action="{{request.resource_url(context, 'delete')}}" method="POST">
<p class="mt-4">
<div class="custom-control custom-switch o3-confirmation">
<input type="checkbox" class="custom-control-input" id="confirmation" value="confirmed" name="confirmation">
<label class="custom-control-label" for="confirmation">I confirm that I want to delete this order.</label>
</div>
</p>
<p class="mt-4">
<input type="hidden" name="csrf_token" value="{{csrf_token}}">
<button class="btn btn-outline-danger o3-confirm-button" type="submit" name="delete" disabled="disabled">Delete Order</button>
<button class="btn btn-outline-secondary" type="submit" name="cancel">Cancel</button>
</p>
</form>
</dl>
<p class="mt-4 mb-4">&nbsp;</p>
<p>
{% if request.has_permission("reorder", context) %}
<a href="{{ context|resource_url('reorder') }}" class="btn btn-outline-primary">Reorder</a>
{% endif %}
{% if request.has_permission("delete", context) %}
<a href="{{ context|resource_url('delete') }}" class="btn btn-outline-danger">Delete</a>
{% endif %}
<a href="{{ context.__parent__|resource_url() }}" class="btn btn-outline-secondary o3-back">Go Back</a>
</p>
</div>
<div class="col-5"></div>
<div class="col-5">
<div class="h4">&nbsp;</div>
{{ macros.change_log(context.model) }}
</div>
{% endblock content %}

2
ordr3/templates/users/delete.jinja2

@ -9,7 +9,7 @@ @@ -9,7 +9,7 @@
<h4 class="mb-2 text-muted mb-4">Delete User <span class="text-danger">{{ context.model.username }}</span></h4>
<p>You are about to delete the user {{ context.model.first_name }} {{ context.model.last_name }}.</p>
<p class="font-weight-bold mt-4 mb-4">This action is permanent and cannot be undone!</p>
<form action="{{request.resource_url(context, 'delete')}}" method="POST">
<form action="{{ context|resource_url('delete') }}" method="POST">
<p class="mt-4">
<div class="custom-control custom-switch o3-confirmation">
<input type="checkbox" class="custom-control-input" id="confirmation" value="confirmed" name="confirmation">

4
ordr3/templates/users/edit.jinja2

@ -9,9 +9,9 @@ @@ -9,9 +9,9 @@
{{form.render()|safe}}
<hr>
<p class="mt-4">
<a href="{{request.resource_url(request.context, 'password')}}" class="btn btn-outline-secondary">Reset Password</a>
<a href="{{ context|resource_url('password') }}" class="btn btn-outline-secondary">Reset Password</a>
{% if request.has_permission("delete", context) %}
<a href="{{request.resource_url(request.context, 'delete')}}" class="btn btn-outline-danger">Delete User</a>
<a href="{{ context|resource_url('delete') }}" class="btn btn-outline-danger">Delete User</a>
{% endif %}
</p>
</div>

4
ordr3/templates/users/list.jinja2

@ -7,9 +7,9 @@ @@ -7,9 +7,9 @@
<nav class="nav nav-pills flex-column">
<div class="nav-link disabled text-small" tabindex="-1" aria-disabled="true">Role</div>
<a class="nav-link {% if filter_role == 'all' %}active{% endif %}" href="{{ request.resource_url(context) }}">All</a>
<a class="nav-link {% if filter_role == 'all' %}active{% endif %}" href="{{ context|resource_url() }}">All</a>
{% for role in roles %}
<a class="nav-link {% if filter_role == role.name.lower() %}active{% endif %}" href="{{ request.resource_url(context, query={'role':role.name.lower()}) }}">{{role.name.lower()}}</a>
<a class="nav-link {% if filter_role == role.name.lower() %}active{% endif %}" href="{{ context|resource_url(query={'role':role.name.lower()}) }}">{{role.name.lower()}}</a>
{% endfor %}
</nav>

8
ordr3/templates/users/list_content.jinja2

@ -1,23 +1,23 @@ @@ -1,23 +1,23 @@
{% import 'ordr3:templates/macros.jinja2' as macros with context %}
{% for user in users %}
<tr class="infinite-item">
<td><a href="{{request.resource_url(request.root, 'orders', query={'user':user.model.username})}}" title="show orders">{{ user.model.username }}</a></td>
<td><a href="{{request.root|resource_url('orders', query={'user':user.model.username})}}" title="show orders">{{ user.model.username }}</a></td>
<td>{{ user.model.first_name }}</td>
<td>{{ user.model.last_name }}</td>
<td><a class="o3-copy text-dark" title="copy to clipboard">{{ user.model.email }}</a></td>
<td>{{ user.model.role.name.capitalize() }}</td>
<td class="o3-actions">
{% if request.has_permission("edit", user) %}
<a href="{{ request.resource_url(context, user.model.username, 'edit') }}" title="Edit user">{{ macros.icon("pencil")}}</a>
<a href="{{ context|resource_url(user.model.username, 'edit') }}" title="Edit user">{{ macros.icon("pencil")}}</a>
{% endif %}
{% if request.has_permission("delete", user) %}
<a href="{{ request.resource_url(context, user.model.username, 'delete') }}" title="Delete user">{{ macros.icon("trash")}}</a>
<a href="{{ context|resource_url(user.model.username, 'delete') }}" title="Delete user">{{ macros.icon("trash")}}</a>
{% endif %}
</td>
</tr>
{% endfor %}
{% if next_offset %}
<tr class="infinite-more-link" href="{{ request.resource_url(context, query={'o':next_offset, 'role':filter_role.lower()}) }}">
<tr class="infinite-more-link" href="{{ context|resource_url(query={'o':next_offset, 'role':filter_role.lower()}) }}">
<td colspan="6">
<button class="btn btn-outline-primary btn-small">
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>

9
ordr3/views/__init__.py

@ -4,7 +4,7 @@ some view helpers are defined here @@ -4,7 +4,7 @@ some view helpers are defined here
"""
import re
from collections import UserDict, namedtuple
from collections import UserDict
RE_SIMPLE_URL = re.compile("((?:www|http)\\S+)")
@ -37,7 +37,10 @@ def jinja_datetime(some_date): @@ -37,7 +37,10 @@ def jinja_datetime(some_date):
def _as_html_link(url, css_class):
return f'<a href="{url}" class="{css_class}">{url}</a>'
return (
f'<a href="{url}" class="{css_class} d-inline-block text-truncate">'
f"{url}</a>"
)
def jinja_extract_links(text, css_class=""):
@ -45,7 +48,7 @@ def jinja_extract_links(text, css_class=""): @@ -45,7 +48,7 @@ def jinja_extract_links(text, css_class=""):
return [_as_html_link(url, css_class) for url in links]
def jinja_view_comment(text, css_class):
def jinja_view_comment(text, css_class=""):
links = RE_SIMPLE_URL.findall(text)
for url in links:
html = _as_html_link(url, css_class)

135
ordr3/views/orders.py

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
# import deform
import deform
from sqlalchemy import or_ # , func
from pyramid.csrf import get_csrf_token
from pyramid.view import view_config
@ -6,8 +6,7 @@ from pyramid.httpexceptions import HTTPFound @@ -6,8 +6,7 @@ from pyramid.httpexceptions import HTTPFound
from . import DefaultQueryParams, get_offset
from .. import events, models, services, resources
# from ..schemas import account
from ..schemas import orders
QUERY_LIMIT = 25
@ -105,7 +104,7 @@ def batch_delete(context, request): @@ -105,7 +104,7 @@ def batch_delete(context, request):
orders = get_multiple_orders(context, request)
if not orders:
return HTTPFound(request.resource_path(context))
return HTTPFound(request.resource_url(context))
if len(orders) > QUERY_LIMIT:
request.emit(
@ -114,7 +113,7 @@ def batch_delete(context, request): @@ -114,7 +113,7 @@ def batch_delete(context, request):
f"{QUERY_LIMIT} orders in one sweep."
)
)
return HTTPFound(request.resource_path(context))
return HTTPFound(request.resource_url(context))
return {"orders": orders, "csrf_token": get_csrf_token(request)}
@ -127,14 +126,14 @@ def batch_delete(context, request): @@ -127,14 +126,14 @@ def batch_delete(context, request):
)
def batch_delete_confirmed(context, request):
if "delete" not in request.POST:
return HTTPFound(request.resource_path(context.__parent__))
return HTTPFound(request.resource_url(context.__parent__))
if request.POST.get("confirmation", "") != "confirmed":
return HTTPFound(request.resource_path(context.__parent__))
return HTTPFound(request.resource_url(context.__parent__))
orders = get_multiple_orders(context, request)
if not orders:
return HTTPFound(request.resource_path(context))
return HTTPFound(request.resource_url(context))
if len(orders) > QUERY_LIMIT:
request.emit(
@ -151,7 +150,7 @@ def batch_delete_confirmed(context, request): @@ -151,7 +150,7 @@ def batch_delete_confirmed(context, request):
f"{len(orders)} orders have been deleted."
)
)
return HTTPFound(request.resource_path(context))
return HTTPFound(request.resource_url(context))
@view_config(
@ -166,7 +165,7 @@ def batch_edit(context, request): @@ -166,7 +165,7 @@ def batch_edit(context, request):
orders = get_multiple_orders(context, request)
if not orders:
return HTTPFound(request.resource_path(context))
return HTTPFound(request.resource_url(context))
if len(orders) > QUERY_LIMIT:
request.emit(
@ -174,7 +173,7 @@ def batch_edit(context, request): @@ -174,7 +173,7 @@ def batch_edit(context, request):
f"You cannot edit more than {QUERY_LIMIT} orders in one sweep."
)
)
return HTTPFound(request.resource_path(context))
return HTTPFound(request.resource_url(context))
return {
"orders": orders,
@ -192,14 +191,14 @@ def batch_edit(context, request): @@ -192,14 +191,14 @@ def batch_edit(context, request):
)
def batch_edit_confirm(context, request):
if "change" not in request.POST:
return HTTPFound(request.resource_path(context.__parent__))
return HTTPFound(request.resource_url(context.__parent__))
orders = get_multiple_orders(context, request)
print(request.POST)
print(orders)
if not orders:
return HTTPFound(request.resource_path(context))
return HTTPFound(request.resource_url(context))
if len(orders) > QUERY_LIMIT:
request.emit(
@ -207,7 +206,7 @@ def batch_edit_confirm(context, request): @@ -207,7 +206,7 @@ def batch_edit_confirm(context, request):
f"You cannot edit more than {QUERY_LIMIT} orders in one sweep."
)
)
return HTTPFound(request.resource_path(context))
return HTTPFound(request.resource_url(context))
changes = {}
@ -236,7 +235,7 @@ def batch_edit_confirm(context, request): @@ -236,7 +235,7 @@ def batch_edit_confirm(context, request):
)
)
return HTTPFound(request.resource_path(context))
return HTTPFound(request.resource_url(context))
@view_config(
@ -250,6 +249,106 @@ def view_order(context, request): @@ -250,6 +249,106 @@ def view_order(context, request):
return {"csrf_token": get_csrf_token(request)}
@view_config(
context="ordr3:resources.Order",
permission="edit",
name="edit",
request_method="GET",
renderer="ordr3:templates/orders/edit.jinja2",
)
def edit_order(context, request):
form = orders.EditOrderSchema.as_form(request)
order = context.model
status = {"status": order.status.name}
item = {
"cas_description": order.cas_description,
"category": order.category.name,
"vendor": order.vendor,
"catalog_nr": order.catalog_nr,
"package_size": order.package_size,
}
pricing = {
"unit_price": {
"amount": "%.2f" % order.unit_price,
"currency": order.currency,
},
"quantity": order.amount,
"total_price": {
"amount": "%.2f" % order.total_price,
"currency": order.currency,
},
}
optional = {"account": order.account, "comment": order.comment}
form_data = {
"status": status,
"item": item,
"pricing": pricing,
"optional": optional,
}
form.set_appstruct(form_data)
return {"form": form}
@view_config(
context="ordr3:resources.Order",
permission="edit",
name="edit",
request_method="POST",
renderer="ordr3:templates/orders/edit.jinja2",
)
def do_edit_order(context, request):
""" process the edit order form """
print(request.POST)
form = orders.EditOrderSchema.as_form(request)
if "Save_Changes" not in request.POST:
return HTTPFound(request.resource_url(context.__parent__))
data = request.POST.items()
try:
appstruct = form.validate(data)
except deform.ValidationFailure:
return {"form": form}
# form validation sucessful, change order
order = context.model
form_status = appstruct["status"]
form_item = appstruct["item"]
form_pricing = appstruct["pricing"]
form_optional = appstruct["optional"]
order.cas_description = form_item["cas_description"]
order.category = models.OrderCategory[form_item["category"]]
order.vendor = form_item["vendor"]
order.catalog_nr = form_item["catalog_nr"]
order.package_size = form_item["package_size"]
order.unit_price = form_pricing["unit_price"]["amount"]
order.currency = form_pricing["unit_price"]["currency"]
order.amount = form_pricing["quantity"]
order.account = form_optional["account"]
order.comment = form_optional["comment"]
status = models.OrderStatus[form_status["status"]]
is_noteworthy = services.create_log_entry(order, status, request.user)
if is_noteworthy:
user = request.repo.get_user_by_username(order.created_by)
request.emit(events.OrderStatusChangeEmail(user, [order]))
request.emit(
events.FlashMessage.info(
f"The order {context.model.cas_description} has been changed."
)
)
return HTTPFound(request.resource_url(context.__parent__))
@view_config(
context="ordr3:resources.Order",
permission="delete",
@ -269,9 +368,9 @@ def delete_order(context, request): @@ -269,9 +368,9 @@ def delete_order(context, request):
)
def delete_confirmed(context, request):
if "delete" not in request.POST:
return HTTPFound(request.resource_path(context.__parent__))
return HTTPFound(request.resource_url(context.__parent__))
if request.POST.get("confirmation", "") != "confirmed":
return HTTPFound(request.resource_path(context.__parent__))
return HTTPFound(request.resource_url(context.__parent__))
request.emit(
events.FlashMessage.info(
@ -280,4 +379,4 @@ def delete_confirmed(context, request): @@ -280,4 +379,4 @@ def delete_confirmed(context, request):
)
request.repo.delete_order(context.model)
return HTTPFound(request.resource_path(context.__parent__))
return HTTPFound(request.resource_url(context.__parent__))

4
ordr3/views/root.py

@ -9,8 +9,8 @@ from pyramid.view import ( @@ -9,8 +9,8 @@ from pyramid.view import (
from pyramid.httpexceptions import HTTPFound
# @forbidden_view_config()
# @notfound_view_config()
@forbidden_view_config()
@notfound_view_config()
@view_config(
context="ordr3:resources.Root", permission="view",
)

Loading…
Cancel
Save