Compare commits
100 Commits
master
...
php2python
Author | SHA1 | Date |
---|---|---|
Holger Frey | 62a25301a8 | 7 years ago |
Holger Frey | 947a4798f1 | 7 years ago |
Holger Frey | 20e18bed57 | 7 years ago |
Holger Frey | 880416ade6 | 7 years ago |
Holger Frey | 31f22e3128 | 7 years ago |
Holger Frey | c17e15ee6d | 7 years ago |
Holger Frey | df7f0a29e2 | 7 years ago |
Holger Frey | bc78ffcdde | 7 years ago |
Holger Frey | 4ba8214262 | 7 years ago |
Holger Frey | 6c76c8d76e | 7 years ago |
Holger Frey | 14aa56d111 | 7 years ago |
Holger Frey | f7f45bbdd8 | 7 years ago |
Holger Frey | 7a757a0ef7 | 7 years ago |
Holger Frey | 1585964dc5 | 7 years ago |
Holger Frey | 0f6d51e22a | 7 years ago |
Holger Frey | de71b6fa9d | 7 years ago |
Holger Frey | 71c1b5fd76 | 7 years ago |
Holger Frey | 648454fffc | 7 years ago |
Holger Frey | a1d9d30b68 | 7 years ago |
Holger Frey | b5da0d3411 | 7 years ago |
Holger Frey | f74918b058 | 7 years ago |
Holger Frey | d9d630a2af | 7 years ago |
Holger Frey | 7e601040b9 | 7 years ago |
Holger Frey | ed9d9c22e1 | 7 years ago |
Holger Frey | 59bc6a8f6c | 7 years ago |
Holger Frey | b9e3be30ce | 7 years ago |
Holger Frey | 76e0d4e034 | 7 years ago |
Holger Frey | 8a2c072c35 | 7 years ago |
Holger Frey | 2178f37d6a | 7 years ago |
Holger Frey | e8305b97a3 | 7 years ago |
Holger Frey | bfc782560d | 7 years ago |
Holger Frey | 26f7ea962c | 7 years ago |
Holger Frey | 8c4adaad1f | 7 years ago |
Holger Frey | a5a13585d6 | 7 years ago |
Holger Frey | 2add325e5a | 7 years ago |
Holger Frey | 1d2f5ab0dd | 7 years ago |
Holger Frey | f6f609957c | 7 years ago |
Holger Frey | abb0a6afc1 | 7 years ago |
Holger Frey | 627826ab03 | 7 years ago |
Holger Frey | db474eec20 | 7 years ago |
Holger Frey | 30df8627db | 7 years ago |
Holger Frey | c638ede5a3 | 7 years ago |
Holger Frey | ecf938353e | 7 years ago |
Holger Frey | 5e5fe19af3 | 7 years ago |
Holger Frey | f6b1b127d6 | 7 years ago |
Holger Frey | 437872da91 | 7 years ago |
Holger Frey | f0ee6c205e | 7 years ago |
Holger Frey | 99bf420f51 | 7 years ago |
Holger Frey | fe6e13aa09 | 7 years ago |
Holger Frey | e82416882b | 7 years ago |
Holger Frey | 27ea681279 | 7 years ago |
Holger Frey | bafb16453a | 7 years ago |
Holger Frey | c754b23daa | 7 years ago |
Holger Frey | 914b17e193 | 7 years ago |
Holger Frey | f216996d89 | 7 years ago |
Holger Frey | ad73f427db | 7 years ago |
Holger Frey | bd0b8fd0a2 | 7 years ago |
Holger Frey | b01bf05d82 | 7 years ago |
Holger Frey | d12374d25c | 7 years ago |
Holger Frey | da4db69146 | 7 years ago |
Holger Frey | 018531f163 | 7 years ago |
Holger Frey | d5dd56e254 | 7 years ago |
Holger Frey | 149dfbc19e | 7 years ago |
Holger Frey | cbd6329142 | 7 years ago |
Holger Frey | 35537e1ae2 | 7 years ago |
Holger Frey | 6ed06eff82 | 7 years ago |
Holger Frey | 15de311063 | 7 years ago |
Holger Frey | e06acbb26b | 7 years ago |
Holger Frey | 738e4b90b4 | 7 years ago |
Holger Frey | ae6cbac58d | 7 years ago |
Holger Frey | ee9df92675 | 7 years ago |
Holger Frey | d15523e8a2 | 7 years ago |
Holger Frey | 7a919daacf | 7 years ago |
Holger Frey | 1285202483 | 7 years ago |
Holger Frey | d641266b0d | 7 years ago |
Holger Frey | db53f7e7c1 | 7 years ago |
Holger Frey | 44d5e26ca6 | 7 years ago |
Holger Frey | 19de23927e | 7 years ago |
Holger Frey | d41d541eb0 | 7 years ago |
Holger Frey | df13a65727 | 7 years ago |
Holger Frey | 5b0709c8cb | 7 years ago |
Holger Frey | 0cb5057c00 | 7 years ago |
Holger Frey | 6f334852eb | 7 years ago |
Holger Frey | ca578303f1 | 7 years ago |
Holger Frey | 725a23979b | 7 years ago |
Holger Frey | 4c8e5c69da | 7 years ago |
Holger Frey | 3bfe633260 | 7 years ago |
Holger Frey | a1291e5774 | 7 years ago |
Holger Frey | 77d58da148 | 7 years ago |
Holger Frey | 553e01f119 | 7 years ago |
Holger Frey | 3d1f52ff8f | 7 years ago |
Holger Frey | 779be58392 | 7 years ago |
Holger Frey | 0ae93d5514 | 7 years ago |
Holger Frey | 3e51a8cda8 | 7 years ago |
Holger Frey | 9906795d6d | 7 years ago |
Holger Frey | 3f5b98aaef | 7 years ago |
Holger Frey | ac703eee12 | 7 years ago |
Holger Frey | e2c872a9f5 | 7 years ago |
Holger Frey | 8ac004bb4a | 7 years ago |
Holger Frey | cbd851e4a1 | 7 years ago |
102 changed files with 126910 additions and 342 deletions
@ -1,10 +1,43 @@
@@ -1,10 +1,43 @@
|
||||
=========================== |
||||
Ordr2 - CPI Ordering System |
||||
=========================== |
||||
============================================== |
||||
Ordr2 - CPI Ordering System, php2python branch |
||||
============================================== |
||||
|
||||
This is a rewrite in Python of the original Ordr system by Sebastian Sebald |
||||
that can still be found here: https://github.com/sebald/Ordr |
||||
|
||||
Features |
||||
|
||||
Installation |
||||
------------ |
||||
|
||||
Installation consists of three steps: |
||||
|
||||
1. clone the project and checkout the php2python branch |
||||
> git clone https://git.cpi.imtek.uni-freiburg.de/holgi/ordr2 |
||||
> cd ordr2 |
||||
> git checkout php2pyton |
||||
|
||||
2. create a python virtual environment and activate it |
||||
> python3 -m venv ordr-venv |
||||
> source ordr-venv/bin/activate |
||||
|
||||
3. install the cloned package and deactivate the environment |
||||
(ordr-venv) > pip install . |
||||
> deactivate |
||||
|
||||
|
||||
Updating |
||||
-------- |
||||
|
||||
* TODO |
||||
updating consists of three steps: |
||||
|
||||
1. Update the source code |
||||
> cd ordr2 |
||||
> git pull origin php2python |
||||
|
||||
2. activate the python virtual environment |
||||
> source ordr-venv/bin/activate |
||||
|
||||
3. install the new version and deactivate the environment |
||||
(ordr-venv) > pip install . |
||||
> deactivate |
||||
|
||||
|
@ -1,20 +1,28 @@
@@ -1,20 +1,28 @@
|
||||
# -*- coding: utf-8 -*- |
||||
|
||||
''' Top-level package for Ordr2. ''' |
||||
|
||||
__author__ = 'Holger Frey' |
||||
__email__ = 'frey@imtek.de' |
||||
__version__ = '0.0.1' |
||||
__version__ = '0.1.4' |
||||
|
||||
|
||||
from pyramid.config import Configurator |
||||
from pyramid.session import SignedCookieSessionFactory |
||||
|
||||
|
||||
def main(global_config, **settings): |
||||
''' This function returns a Pyramid WSGI application. ''' |
||||
config = Configurator(settings=settings) |
||||
config.include('pyramid_jinja2') |
||||
config.include('.models') |
||||
|
||||
session_factory = SignedCookieSessionFactory(settings['session.secret']) |
||||
config.set_session_factory(session_factory) |
||||
config.set_default_csrf_options(require_csrf=settings['session.auto_csrf']) |
||||
|
||||
config.include('.resources') |
||||
config.include('.models') |
||||
config.include('.security') |
||||
config.include('.views') |
||||
config.include('pyramid_jinja2') |
||||
|
||||
config.scan() |
||||
|
||||
return config.make_wsgi_app() |
||||
|
@ -0,0 +1,77 @@
@@ -0,0 +1,77 @@
|
||||
''' custom events and event subsribers ''' |
||||
|
||||
from pyramid.events import NewRequest, subscriber |
||||
from pyramid.renderers import render |
||||
from pyramid_mailer.message import Message |
||||
|
||||
from ordr2.views import set_display_defaults |
||||
|
||||
|
||||
# custom events |
||||
|
||||
class UserLogIn(object): |
||||
''' notify on user log in ''' |
||||
def __init__(self, request, user): |
||||
self.request = request |
||||
self.user = user |
||||
|
||||
|
||||
class UserNotification(object): |
||||
''' base class for user notifications ''' |
||||
|
||||
subject = None |
||||
template = None |
||||
|
||||
def __init__(self, request, user, data=None): |
||||
self.request = request |
||||
self.user = user |
||||
self.data = data |
||||
|
||||
|
||||
class AccountActivation(UserNotification): |
||||
''' user notification for account activation ''' |
||||
subject='[ordr] Your account was activated' |
||||
template = 'ordr2:templates/emails/activation.jinja2' |
||||
|
||||
|
||||
class PasswordReset(UserNotification): |
||||
''' user notification for password reset link ''' |
||||
subject='[ordr] Password Reset' |
||||
template = 'ordr2:templates/emails/password_reset.jinja2' |
||||
|
||||
class OrderStatusChange(UserNotification): |
||||
''' user notification for order status change ''' |
||||
subject='[ordr] Order Status Change' |
||||
template = 'ordr2:templates/emails/order.jinja2' |
||||
|
||||
|
||||
# subsribers for events |
||||
|
||||
@subscriber(UserLogIn) |
||||
def set_display_defaults_on_log_in(event): |
||||
''' set column display defaults at every login ''' |
||||
set_display_defaults(event.request) |
||||
|
||||
|
||||
@subscriber(NewRequest) |
||||
def check_display_defaults(event): |
||||
''' check if column display preferences are set in sesssion ''' |
||||
if event.request.user and 'display' not in event.request.session: |
||||
set_display_defaults(event.request) |
||||
|
||||
|
||||
@subscriber(UserNotification) |
||||
def notify_user(event): |
||||
''' notify a user about an event ''' |
||||
body = render( |
||||
event.template, |
||||
{'user': event.user, 'data': event.data}, |
||||
event.request |
||||
) |
||||
message = Message( |
||||
subject=event.subject, |
||||
sender=event.request.registry.settings['mail.default_sender'], |
||||
recipients=[event.user.email], |
||||
html=body |
||||
) |
||||
event.request.mailer.send(message) |
@ -1,18 +0,0 @@
@@ -1,18 +0,0 @@
|
||||
from sqlalchemy import ( |
||||
Column, |
||||
Index, |
||||
Integer, |
||||
Text, |
||||
) |
||||
|
||||
from .meta import Base |
||||
|
||||
|
||||
class MyModel(Base): |
||||
__tablename__ = 'models' |
||||
id = Column(Integer, primary_key=True) |
||||
name = Column(Text) |
||||
value = Column(Integer) |
||||
|
||||
|
||||
Index('my_index', MyModel.name, unique=True, mysql_length=255) |
@ -0,0 +1,135 @@
@@ -0,0 +1,135 @@
|
||||
''' Consumables, Categories, Orders and Order Status Database Models ''' |
||||
|
||||
import bcrypt |
||||
import enum |
||||
import uuid |
||||
|
||||
from collections import namedtuple |
||||
from datetime import datetime |
||||
from sqlalchemy import ( |
||||
Column, |
||||
DateTime, |
||||
Enum, |
||||
Float, |
||||
Integer, |
||||
Text, |
||||
) |
||||
|
||||
from .meta import Base |
||||
|
||||
|
||||
class Category(enum.Enum): |
||||
''' Categories of consumables and orders ''' |
||||
CHEMICAL = 'chemical' |
||||
DISPOSABLE = 'disposable' |
||||
SOLVENT = 'solvent' |
||||
BIOLAB = 'biolab' |
||||
|
||||
|
||||
class OrderStatus(enum.Enum): |
||||
''' status of the order ''' |
||||
OPEN = 'open' |
||||
APPROVAL = 'approval' |
||||
ORDERED = 'ordered' |
||||
COMPLETED = 'completed' |
||||
|
||||
|
||||
class Consumable(Base): |
||||
''' A consumable ''' |
||||
|
||||
__tablename__ = 'consumables' |
||||
|
||||
id = Column(Integer, primary_key=True) |
||||
|
||||
cas_description = Column(Text, nullable=False) |
||||
category = Column(Enum(Category), nullable=False) |
||||
catalog_nr = Column(Text, nullable=False) |
||||
vendor = Column(Text, nullable=False) |
||||
package_size = Column(Text, nullable=False) |
||||
unit_price = Column(Float, nullable=False) |
||||
currency = Column(Text, nullable=False, default='EUR') |
||||
comment = Column(Text, nullable=False, default='') |
||||
date_created = Column(DateTime, nullable=False, default=datetime.utcnow) |
||||
date_modified = Column( |
||||
DateTime, |
||||
nullable=False, |
||||
default=datetime.utcnow, |
||||
onupdate=datetime.utcnow |
||||
) |
||||
|
||||
def __str__(self): |
||||
''' string representation ''' |
||||
return '{!s} ({!s})'.format(self.cas_description, self.vendor) |
||||
|
||||
|
||||
class Order(Base): |
||||
''' An order ''' |
||||
|
||||
__tablename__ = 'orders' |
||||
|
||||
id = Column(Integer, primary_key=True) |
||||
|
||||
status = Column(Enum(OrderStatus), nullable=False) |
||||
|
||||
cas_description = Column(Text, nullable=False) |
||||
category = Column(Enum(Category), nullable=False) |
||||
catalog_nr = Column(Text, nullable=False) |
||||
vendor = Column(Text, nullable=False) |
||||
package_size = Column(Text, nullable=False) |
||||
|
||||
unit_price = Column(Float, nullable=False) |
||||
currency = Column(Text, nullable=False, default='EUR') |
||||
amount = Column(Integer, nullable=False) |
||||
total_price = Column(Float, nullable=False) |
||||
|
||||
account = Column(Text, nullable=False, default='') |
||||
comment = Column(Text, nullable=False, default='') |
||||
|
||||
created_date = Column(DateTime, nullable=False, default=datetime.utcnow) |
||||
created_by = Column(Text, nullable=False) |
||||
approval_date = Column(DateTime, nullable=True) |
||||
approval_by = Column(Text, nullable=False, default='') |
||||
ordered_date = Column(DateTime, nullable=True) |
||||
ordered_by = Column(Text, nullable=False, default='') |
||||
completed_date = Column(DateTime, nullable=True) |
||||
completed_by = Column(Text, nullable=False, default='') |
||||
|
||||
|
||||
def __str__(self): |
||||
''' string representation ''' |
||||
return '{!s} ({!s})'.format(self.cas_description, self.vendor) |
||||
|
||||
|
||||
def _date_info(self, some_date, some_one): |
||||
''' string representaton of date and user ''' |
||||
if not some_date: |
||||
# no date, no string |
||||
return '' |
||||
if some_one: |
||||
# in the new system a status change also stores the purchaser |
||||
return '{!s} by {!s}'.format( |
||||
some_date.strftime('%Y-%m-%d %H:%M'), |
||||
some_one |
||||
) |
||||
# historical data does not have a purchaser associated with a date |
||||
return '{!s}'.format(some_date.strftime('%Y-%m-%d %H:%M')) |
||||
|
||||
@property |
||||
def placed(self): |
||||
''' string representation for placed on / by ''' |
||||
return self._date_info(self.created_date, self.created_by) |
||||
|
||||
@property |
||||
def approved(self): |
||||
''' string representation for approval on / by ''' |
||||
return self._date_info(self.approval_date, self.approval_by) |
||||
|
||||
@property |
||||
def ordered(self): |
||||
''' string representation for ordered on / by ''' |
||||
return self._date_info(self.ordered_date, self.ordered_by) |
||||
|
||||
@property |
||||
def completed(self): |
||||
''' string representation for completed on / by ''' |
||||
return self._date_info(self.completed_date, self.completed_by) |
@ -0,0 +1,95 @@
@@ -0,0 +1,95 @@
|
||||
''' User Account and Roles Models ''' |
||||
|
||||
import bcrypt |
||||
import enum |
||||
import uuid |
||||
|
||||
from collections import namedtuple |
||||
from datetime import datetime |
||||
from sqlalchemy import ( |
||||
Column, |
||||
Date, |
||||
Enum, |
||||
Integer, |
||||
Text, |
||||
) |
||||
|
||||
from .meta import Base |
||||
|
||||
|
||||
class Role(enum.Enum): |
||||
''' roles of user accounts ''' |
||||
|
||||
NEW = 'new' |
||||
USER = 'user' |
||||
PURCHASER = 'purchaser' |
||||
ADMIN = 'admin' |
||||
INACTIVE = 'inactive' |
||||
|
||||
@property |
||||
def principal(self): |
||||
''' returns the principal identifier of the role ''' |
||||
return 'role:' + self.value.lower() |
||||
|
||||
|
||||
class User(Base): |
||||
''' A user of the application ''' |
||||
|
||||
__tablename__ = 'users' |
||||
|
||||
id = Column(Integer, primary_key=True) |
||||
|
||||
user_name = Column(Text, nullable=False, unique=True) |
||||
first_name = Column(Text, nullable=False) |
||||
last_name = Column(Text, nullable=False) |
||||
email = Column(Text, nullable=False, unique=True) |
||||
password_hash = Column(Text, nullable=False) |
||||
role = Column(Enum(Role), nullable=False) |
||||
password_reset = Column(Text, nullable=False, default='') |
||||
date_created = Column(Date, nullable=False, default=datetime.utcnow) |
||||
|
||||
@property |
||||
def principal(self): |
||||
''' returns the principal identifier for the user ''' |
||||
return 'user:' + self.user_name |
||||
|
||||
@property |
||||
def role_principals(self): |
||||
''' returns the principal identifiers for the user's role ''' |
||||
principals = [self.role.principal] |
||||
if self.role is Role.PURCHASER: |
||||
# a purchaser is also a user |
||||
principals.append(Role.USER.principal) |
||||
elif self.role is Role.ADMIN: |
||||
# an admin is also a purchaser and a user |
||||
principals.append(Role.USER.principal) |
||||
principals.append(Role.PURCHASER.principal) |
||||
return principals |
||||
|
||||
@property |
||||
def is_active(self): |
||||
''' check if it is an active user account ''' |
||||
return self.role in (Role.USER, Role.PURCHASER, Role.ADMIN) |
||||
|
||||
def set_password(self, password): |
||||
''' hashes a new password ''' |
||||
pwhash = bcrypt.hashpw(password.encode('utf8'), bcrypt.gensalt()) |
||||
self.password_hash = pwhash.decode('utf8') |
||||
|
||||
def check_password(self, password): |
||||
''' compares a password with a stored password hash ''' |
||||
if self.password_hash: |
||||
expected_hash = self.password_hash.encode('utf8') |
||||
return bcrypt.checkpw(password.encode('utf8'), expected_hash) |
||||
return False |
||||
|
||||
def generate_password_token(self): |
||||
''' generates a token for a password reset link ''' |
||||
token = uuid.uuid4() |
||||
self.password_reset = token.hex |
||||
return token.hex |
||||
|
||||
def __str__(self): |
||||
''' string representation ''' |
||||
return '{!s}'.format(self.user_name) |
||||
|
@ -0,0 +1,64 @@
@@ -0,0 +1,64 @@
|
||||
''' Resources for User Accounts ''' |
||||
|
||||
from pyramid.security import Allow, Authenticated, Deny, DENY_ALL, Everyone |
||||
|
||||
from ordr2.models import User |
||||
|
||||
from .base import BaseResource |
||||
|
||||
|
||||
class PasswordResetAccount(BaseResource): |
||||
''' resource for passwort change representing a reset token ''' |
||||
|
||||
def __acl__(self): |
||||
''' access controll list ''' |
||||
return [ |
||||
(Allow, Everyone, 'reset'), |
||||
DENY_ALL |
||||
] |
||||
|
||||
|
||||
class PasswordReset(BaseResource): |
||||
''' resource for passwort reset link ''' |
||||
|
||||
def __acl__(self): |
||||
''' access controll list ''' |
||||
return [ |
||||
(Allow, Everyone, 'reset'), |
||||
DENY_ALL |
||||
] |
||||
|
||||
def __getitem__(self, key): |
||||
''' queries the database for a password reset token ''' |
||||
key = key.strip() |
||||
if key: |
||||
account = self.request.dbsession.\ |
||||
query(User).\ |
||||
filter_by(password_reset=key).\ |
||||
first() |
||||
if account: |
||||
return PasswordResetAccount(key, self, account) |
||||
raise KeyError |
||||
|
||||
|
||||
class Account(BaseResource): |
||||
''' User Account and Settings ''' |
||||
|
||||
nodes = {'reset': PasswordReset} |
||||
|
||||
def __init__(self, name, parent): |
||||
super().__init__(name, parent) |
||||
self.model = self.request.user |
||||
|
||||
|
||||
def __acl__(self): |
||||
''' access controll list ''' |
||||
return [ |
||||
(Allow, Everyone, 'login'), |
||||
(Allow, Everyone, 'logout'), |
||||
(Deny, Authenticated, 'register'), |
||||
(Allow, Everyone, 'register'), |
||||
(Allow, Authenticated, 'settings'), |
||||
(Allow, Everyone, 'reset'), |
||||
DENY_ALL |
||||
] |
@ -0,0 +1,195 @@
@@ -0,0 +1,195 @@
|
||||
''' Resources for the Admin Section ''' |
||||
|
||||
from sqlalchemy import or_ |
||||
|
||||
from pyramid.security import Allow, Authenticated, Deny, DENY_ALL, Everyone |
||||
|
||||
from .base import BaseResource, PaginationResourceMixin |
||||
from ordr2.models import Category, Consumable, User, Role |
||||
|
||||
|
||||
# user accounr resources |
||||
|
||||
class UserAccount(BaseResource): |
||||
''' Resource for a user account ''' |
||||
def __acl__(self): |
||||
''' Access Controll List ''' |
||||
return [ |
||||
(Allow, 'role:admin', 'view'), |
||||
(Allow, 'role:admin', 'edit'), |
||||
(Allow, 'role:admin', 'delete'), |
||||
DENY_ALL |
||||
] |
||||
|
||||
|
||||
class UserList(BaseResource, PaginationResourceMixin): |
||||
''' Resource for a list of users ''' |
||||
|
||||
sql_model_class = User |
||||
child_resource_class = UserAccount |
||||
default_sorting = 'user.asc' |
||||
default_items_per_page = 12 |
||||
|
||||
def __acl__(self): |
||||
''' Access Controll List ''' |
||||
return [ |
||||
(Allow, 'role:admin', 'view'), |
||||
(Allow, 'role:admin', 'edit'), |
||||
(Allow, 'role:admin', 'delete'), |
||||
DENY_ALL |
||||
] |
||||
|
||||
def prepare_filtered_query(self, dbsession, filter_params): |
||||
''' setup the base filtered query ''' |
||||
query = dbsession.query(self.sql_model_class) |
||||
|
||||
# filter by role |
||||
role_name = filter_params.get('role', None) |
||||
try: |
||||
role_name = role_name.lower() |
||||
role = Role(role_name) |
||||
query = query.filter_by(role=role) |
||||
except (AttributeError, ValueError): |
||||
role_name = None |
||||
self.filters['role'] = role_name |
||||
|
||||
# filter by search term |
||||
search = filter_params.get('search', None) |
||||
if search: |
||||
term = '%{}%'.format(search) |
||||
query = query.filter( |
||||
or_( |
||||
self.sql_model_class.user_name.ilike(term), |
||||
self.sql_model_class.first_name.ilike(term), |
||||
self.sql_model_class.last_name.ilike(term), |
||||
self.sql_model_class.email.ilike(term) |
||||
) |
||||
) |
||||
self.filters['search'] = search |
||||
|
||||
return query |
||||
|
||||
def prepare_sorted_query(self, query, sorting): |
||||
''' add sorting to the base query ''' |
||||
available_fields = { |
||||
'user': 'user_name', |
||||
'first': 'first_name', |
||||
'last': 'last_name', |
||||
'email': 'email', |
||||
'role': 'role' |
||||
} |
||||
name = available_fields.get(sorting.field, None) |
||||
model_field = getattr(self.sql_model_class, name, None) |
||||
if model_field: |
||||
sort_func = sorting.func(model_field) |
||||
query = query.order_by(sort_func) |
||||
|
||||
# add default sorting |
||||
default_sort = self.parse_sort_parameters(self.default_sorting) |
||||
if sorting.field != default_sort.field: |
||||
default_sort = self.parse_sort_parameters(self.default_sorting) |
||||
query = self.prepare_sorted_query(query, default_sort) |
||||
|
||||
return query |
||||
|
||||
|
||||
|
||||
# consumables resources |
||||
|
||||
class ConsumableResource(BaseResource): |
||||
''' Resource for one consumable ''' |
||||
def __acl__(self): |
||||
''' Access Controll List ''' |
||||
return [ |
||||
(Allow, 'role:admin', 'view'), |
||||
(Allow, 'role:admin', 'edit'), |
||||
(Allow, 'role:admin', 'delete'), |
||||
DENY_ALL |
||||
] |
||||
|
||||
|
||||
class ConsumableList(BaseResource, PaginationResourceMixin): |
||||
''' Resource for a list of consumables ''' |
||||
|
||||
sql_model_class = Consumable |
||||
child_resource_class = ConsumableResource |
||||
default_sorting = 'cas.asc' |
||||
default_items_per_page = 12 |
||||
|
||||
def __acl__(self): |
||||
''' Access Controll List ''' |
||||
return [ |
||||
(Allow, 'role:admin', 'view'), |
||||
(Allow, 'role:admin', 'create'), |
||||
(Allow, 'role:admin', 'edit'), |
||||
(Allow, 'role:admin', 'delete'), |
||||
DENY_ALL |
||||
] |
||||
|
||||
|
||||
def prepare_filtered_query(self, dbsession, filter_params): |
||||
''' setup the base filtered query ''' |
||||
query = dbsession.query(self.sql_model_class) |
||||
|
||||
# filter by category |
||||
category_name = filter_params.get('category', None) |
||||
try: |
||||
category_name = category_name.lower() |
||||
category = Category(category_name) |
||||
query = query.filter_by(category=category) |
||||
except (AttributeError, ValueError): |
||||
category_name = None |
||||
self.filters['category'] = category_name |
||||
|
||||
# filter by search term |
||||
search = filter_params.get('search', None) |
||||
if search: |
||||
term = '%{}%'.format(search) |
||||
query = query.filter( |
||||
or_( |
||||
self.sql_model_class.cas_description.ilike(term), |
||||
self.sql_model_class.vendor.ilike(term), |
||||
self.sql_model_class.catalog_nr.ilike(term) |
||||
) |
||||
) |
||||
self.filters['search'] = search |
||||
|
||||
return query |
||||
|
||||
def prepare_sorted_query(self, query, sorting): |
||||
''' add sorting to the base query ''' |
||||
available_fields = { |
||||
'cas': 'cas_description', |
||||
'category': 'category', |
||||
'catalog': 'catalog_nr', |
||||
'vendor': 'vendor', |
||||
'pkg': 'package_size', |
||||
'price': 'unit_price', |
||||
'currency': 'currency' |
||||
} |
||||
name = available_fields.get(sorting.field, None) |
||||
model_field = getattr(self.sql_model_class, name, None) |
||||
if model_field: |
||||
sort_func = sorting.func(model_field) |
||||
query = query.order_by(sort_func) |
||||
|
||||
# add default sorting |
||||
default_sort = self.parse_sort_parameters(self.default_sorting) |
||||
if sorting.field != default_sort.field: |
||||
default_sort = self.parse_sort_parameters(self.default_sorting) |
||||
query = self.prepare_sorted_query(query, default_sort) |
||||
|
||||
return query |
||||
|
||||
|
||||
class Admin(BaseResource): |
||||
''' Resource for the admin section ''' |
||||
|
||||
nodes = { |
||||
'users': UserList, |
||||
'consumables': ConsumableList, |
||||
} |
||||
|
||||
def __acl__(self): |
||||
''' Access Controll List ''' |
||||
return [ (Allow, 'role:admin', 'view') ] |
@ -0,0 +1,303 @@
@@ -0,0 +1,303 @@
|
||||
''' Base Resource and Mixin classes, not to be used directly ''' |
||||
|
||||
from collections import namedtuple |
||||
|
||||
from pyramid.security import DENY_ALL |
||||
from sqlalchemy import asc, desc |
||||
from sqlalchemy.inspection import inspect |
||||
|
||||
|
||||
class BaseResource(object): |
||||
''' Base Resource for all other resources ''' |
||||
|
||||
__parent__ = None |
||||
__name__ = None |
||||
|
||||
request = None |
||||
model = None |
||||
nodes = dict() |
||||
|
||||
nav_highlight = None |
||||
|
||||
def __init__(self, name, parent, sql_model_instance=None): |
||||
self.__name__ = name |
||||
self.__parent__ = parent |
||||
self.request = parent.request |
||||
self.model = sql_model_instance |
||||
|
||||
# call to super().__init_() needed to set up PaginationMixin |
||||
super().__init__() |
||||
|
||||
def __acl__(self): |
||||
''' Access controll list ''' |
||||
return [ DENY_ALL ] |
||||
|
||||
|
||||
def __getitem__(self, key): |
||||
''' returns child resources ''' |
||||
klass = self.nodes.get(key, None) |
||||
if klass: |
||||
return klass(key, self) |
||||
try: |
||||
return super().__getitem__(key) |
||||
except AttributeError: |
||||
raise KeyError() |
||||
|
||||
|
||||
@classmethod |
||||
def from_sqla(cls, sql_model_instance, parent): |
||||
''' initializes a resource from an SQLalchemy object ''' |
||||
primary_keys = inspect(sql_model_instance).identity |
||||
if primary_keys is None: |
||||
raise ValueError('Cannot init resource for primary key: None') |
||||
elif len(primary_keys) != 1: |
||||
raise ValueError('Cannot init resource for composite primary key') |
||||
primary_key = str(primary_keys[0]) |
||||
return cls(primary_key, parent, sql_model_instance) |
||||
|
||||
|
||||
class Pagination(object): |
||||
''' calculates pagination information |
||||
|
||||
Available instance attributes |
||||
|
||||
count: total number of items |
||||
items: number of items displayed per page, aliased by items_per_page |
||||
first: first page number |
||||
last: last page number |
||||
current: current page number |
||||
previous: previous page number |
||||
next: next page number |
||||
window: page window, e.g: |
||||
lets assume the current page is 10 and window size is 7 |
||||
self.window = [7, 8, 9, 10, 11, 12, 13] |
||||
''' |
||||
|
||||
default_items = 25 |
||||
default_window_size = 7 |
||||
|
||||
def __init__(self, current, count, items=None, window_size=None): |
||||
''' calculates pagination information |
||||
|
||||
Parameters: |
||||
current: current pages |
||||
count: total number of items |
||||
items: number of items displayed per pages |
||||
window_size: size of pagination window |
||||
''' |
||||
# ensure values are integers |
||||
current = self._ensure_int(current, 1) |
||||
count = self._ensure_int(count, 0) |
||||
items = self._ensure_int(items, self.default_items) |
||||
window_size = self._ensure_int(window_size, self.default_window_size) |
||||
|
||||
# set the simples values that won't change |
||||
self.count = count |
||||
self.items = self.items_per_page = items |
||||
|
||||
# calculate number of pages |
||||
pages = (count - 1) // items + 1 |
||||
self.first = 1 |
||||
self.last = max(self.first, pages) |
||||
self.current = self._is_valid(current, default=self.first) |
||||
self.previous = self._is_valid(self.current - 1) |
||||
self.next = self._is_valid(self.current + 1) |
||||
|
||||
# window calculations |
||||
# example: lets assume the current page is 10 and window size is 7 |
||||
# self.window = [7, 8, 9, 10, 11, 12, 13] |
||||
half_window = window_size // 2 |
||||
start = self.current - half_window |
||||
end = self.current + half_window |
||||
calculated_window = range(start, end + 1) |
||||
self.window = [p for p in calculated_window if self._is_valid(p)] |
||||
|
||||
def _is_valid(self, page, default=None): |
||||
''' checks if the given page is valid, returns default if not ''' |
||||
if self.count and self.first <= page <= self.last: |
||||
return page |
||||
return default |
||||
|
||||
def _ensure_int(self, value, default): |
||||
''' converts the value to integer, returns default if it fails ''' |
||||
try: |
||||
return int(value) |
||||
except Exception: |
||||
return default |
||||
|
||||
|
||||
# named tuple for parameters used in sorting |
||||
SortParameter = namedtuple('SortParameter', 'text field direction func') |
||||
|
||||
|
||||
class PaginationResourceMixin(object): |
||||
''' mixin providing pagination information for simple sql models |
||||
|
||||
class attributes that must be defined in child classes: |
||||
sql_model_class: sqlalchemy model class |
||||
child_resource_class: resource representing a sqlalchemy model |
||||
default_sorting: string for default soring behaviour, |
||||
e.g. 'created_on.desc' |
||||
default_items_per_page: default number of items displayed per page |
||||
|
||||
|
||||
available instance attributes: |
||||
pages: pagination information, see Pagination class |
||||
sorting: sorting parameter, processed from request.GET |
||||
filters: filter parameters, processed from request.GET |
||||
|
||||
''' |
||||
|
||||
# class attributes that must be defined in child classes |
||||
sql_model_class = None |
||||
child_resource_class = None |
||||
default_sorting = None |
||||
default_items_per_page = 25 |
||||
|
||||
# attributes set by processing request.GET |
||||
pages = None |
||||
sorting = None |
||||
filters = {} |
||||
|
||||
# keys for request.GET processing |
||||
query_key_current_page = 'p' |
||||
query_key_items_per_page = 'n' |
||||
query_key_sorting = 'o' |
||||
|
||||
# base sqlalchemy query object |
||||
_base_query = None |
||||
|
||||
|
||||
def __init__(self): |
||||
''' sets parameters from request.GET ''' |
||||
# first we need to remove non-filter parameters from GET |
||||
params = dict(self.request.GET) |
||||
page = params.pop(self.query_key_current_page, 1) |
||||
items = params.pop( |
||||
self.query_key_items_per_page, |
||||
self.default_items_per_page |
||||
) |
||||
sort = params.pop(self.query_key_sorting, self.default_sorting) |
||||
|
||||
# we can now setup a base query with applied filters |
||||
self._base_query = self.prepare_filtered_query( |
||||
self.request.dbsession, |
||||
params |
||||
) |
||||
|
||||
# with this base query, the pagination can be calculated: |
||||
count = self._base_query.count() |
||||
self.pages = Pagination(page, count, items) |
||||
|
||||
# and we should check that we can sort results later |
||||
self.sorting = self.parse_sort_parameters(sort) |
||||
if self.sorting is None: |
||||
msg = 'Error in default sorting {}'.format(self.default_sorting) |
||||
raise ValueError(msg) |
||||
|
||||
|
||||
def prepare_filtered_query(self, dbsession, filter_params): |
||||
''' setup the base filtered query |
||||
|
||||
An example: |
||||
|
||||
def prepare_filtered_query(self, dbsession, filter_params): |
||||
query = dbsession.query(self.sql_model_class) |
||||
|
||||
by_username = filter_params.get('username', None) |
||||
if by_username is not None: |
||||
query = query.filter_by(username=by_username) |
||||
# don't forget to remember the filter |
||||
self.filters['username'] = by_username |
||||
|
||||
return query |
||||
''' |
||||
msg = 'Query setup must be implemented in child class' |
||||
raise NotImplementedError(msg) |
||||
|
||||
|
||||
def prepare_sorted_query(self, query, sorting): |
||||
''' add sorting to the base query |
||||
|
||||
An example: |
||||
|
||||
def prepare_sorted_query(self, query, sorting): |
||||
model_field = getattr(self.sql_model_class, sorting.field) |
||||
sort_func = sorting.func(model_field) |
||||
return query.order_by(sort_func) |
||||
''' |
||||
msg = 'Query setup must be implemented in child class' |
||||
raise NotImplementedError(msg) |
||||
|
||||
|
||||
def parse_sort_parameters(self, sort_param): |
||||
''' parses a string that might contain sorting information ''' |
||||
sort_functions = { 'asc': asc, 'desc': desc} |
||||
try: |
||||
sort_param = sort_param.lower() |
||||
field, direction = sort_param.split('.', 1) |
||||
func = sort_functions[direction] |
||||
return SortParameter(sort_param, field, direction, func) |
||||
except (AttributeError, IndexError, KeyError, ValueError): |
||||
return None |
||||
|
||||
|
||||
def items(self): |
||||
''' returns the items of the current page as resources''' |
||||
if not self.pages.count: |
||||
return [] |
||||
|
||||
# calculate the offset from the paging information |
||||
offset = (self.pages.current - 1) * self.pages.items_per_page |
||||
|
||||
# prepare the query including sorting, offset and limit |
||||
query = self.prepare_sorted_query(self._base_query, self.sorting) |
||||
query = query.offset(offset).limit(self.pages.items_per_page) |
||||
|
||||
# return a list of resources representing the items found by the query |
||||
return [ |
||||
self.child_resource_class.from_sqla(item, self) |
||||
for item |
||||
in query.all() |
||||
] |
||||
|
||||
|
||||
def query_params(self, *args, **kwargs): |
||||
''' returns a dict with query parameters for a new request |
||||
|
||||
The entries are set by the parsed request.GET parameters used to |
||||
construct the queries. If a new request comes in with the parameters |
||||
provided, it will return the same child resources in the same order. |
||||
the query parameters can be overridden by providing tuples or |
||||
keyword arguments with new values. |
||||
''' |
||||
params = { |
||||
self.query_key_current_page: self.pages.current, |
||||
self.query_key_items_per_page: self.pages.items, |
||||
self.query_key_sorting: self.sorting.text |
||||
} |
||||
params.update(self.filters) |
||||
params.update(args) |
||||
params.update(kwargs) |
||||
# remove items that have None as a value |
||||
filtered = {k: v for k, v in params.items() if v is not None} |
||||
return filtered |
||||
|
||||
|
||||
def url(self, *args, **kwargs): |
||||
''' shortcut for creating a url pointing to this resource |
||||
|
||||
shortcut for: |
||||
request.resource_url(self, query=self.query_params(*args, **kwargs)) |
||||
''' |
||||
params = self.query_params(*args, **kwargs) |
||||
return self.request.resource_url(self, query=params) |
||||
|
||||
|
||||
def __getitem__(self, key): |
||||
''' returns a child resource representing a sqlalchemy model ''' |
||||
model = self.request.dbsession.query(self.sql_model_class).get(key) |
||||
if not model: |
||||
raise KeyError() |
||||
return self.child_resource_class.from_sqla(model, self) |
||||
|
@ -0,0 +1,114 @@
@@ -0,0 +1,114 @@
|
||||
from sqlalchemy import or_ |
||||
|
||||
from pyramid.security import Allow, Authenticated, Deny, DENY_ALL, Everyone |
||||
|
||||
from .base import BaseResource, PaginationResourceMixin |
||||
from ordr2.models import Category, Order, OrderStatus |
||||
|
||||
|
||||
|
||||
class OrderResource(BaseResource): |
||||
''' Resource representing one order ''' |
||||
|
||||
def __acl__(self): |
||||
''' Access controll list ''' |
||||
acl = [ |
||||
(Allow, 'role:user', 'view'), |
||||
(Allow, 'role:user', 'create'), |
||||
(Allow, 'role:purchaser', 'edit'), |
||||
(Allow, 'role:purchaser', 'delete'), |
||||
] |
||||
# open orders may be edited and deleted by the user that placed them |
||||
if self.model.status == OrderStatus.OPEN: |
||||
acl.append( |
||||
(Allow, 'user:' + str(self.model.created_by), 'edit') |
||||
) |
||||
acl.append( |
||||
(Allow, 'user:' + str(self.model.created_by), 'delete') |
||||
) |
||||
acl.append(DENY_ALL) |
||||
return acl |
||||
|
||||
|
||||
class OrderList(BaseResource, PaginationResourceMixin): |
||||
''' Resource representing a list of orders ''' |
||||
|
||||
sql_model_class = Order |
||||
child_resource_class = OrderResource |
||||
default_sorting = 'created.desc' |
||||
default_items_per_page = 12 |
||||
|
||||
def __acl__(self): |
||||
''' Access controll list ''' |
||||
return [ |
||||
(Allow, 'role:user', 'view'), |
||||
(Allow, 'role:user', 'create'), |
||||
(Allow, 'role:purchaser', 'edit'), |
||||
(Allow, 'role:purchaser', 'delete'), |
||||
DENY_ALL |
||||
] |
||||
|
||||
def prepare_filtered_query(self, dbsession, filter_params): |
||||
''' setup the base filtered query ''' |
||||
query = dbsession.query(self.sql_model_class) |
||||
|
||||
# filter by status |
||||
status_name = filter_params.get('status', None) |
||||
try: |
||||
status_name = status_name.lower() |
||||
status = OrderStatus(status_name) |
||||
query = query.filter_by(status=status) |
||||
except (AttributeError, ValueError): |
||||
status_name = None |
||||
self.filters['status'] = status_name |
||||
|
||||
# filter by user |
||||
user_name = filter_params.get('user', None) |
||||
if user_name: |
||||
query = query.filter_by(created_by=user_name) |
||||
self.filters['user'] = user_name |
||||
|
||||
# filter by search term |
||||
search = filter_params.get('search', None) |
||||
if search: |
||||
term = '%{}%'.format(search) |
||||
query = query.filter( |
||||
or_( |
||||
self.sql_model_class.cas_description.ilike(term), |
||||
self.sql_model_class.vendor.ilike(term), |
||||
self.sql_model_class.catalog_nr.ilike(term), |
||||
self.sql_model_class.account.ilike(term), |
||||
self.sql_model_class.created_by.ilike(term) |
||||
) |
||||
) |
||||
self.filters['search'] = search |
||||
|
||||
return query |
||||
|
||||
def prepare_sorted_query(self, query, sorting): |
||||
''' add sorting to the base query ''' |
||||
available_fields = { |
||||
'cas': 'cas_description', |
||||
'category': 'category', |
||||
'catalog': 'catalog_nr', |
||||
'vendor': 'vendor', |
||||
'price': 'unit_price', |
||||
'amount': 'amount', |
||||
'total': 'total_price', |
||||
'created': 'created_date', |
||||
'user': 'created_by', |
||||
'status': 'status', |
||||
} |
||||
name = available_fields.get(sorting.field, None) |
||||
model_field = getattr(self.sql_model_class, name, None) |
||||
if model_field: |
||||
sort_func = sorting.func(model_field) |
||||
query = query.order_by(sort_func) |
||||
|
||||
# add default sorting |
||||
default_sort = self.parse_sort_parameters(self.default_sorting) |
||||
if sorting.field != default_sort.field: |
||||
default_sort = self.parse_sort_parameters(self.default_sorting) |
||||
query = self.prepare_sorted_query(query, default_sort) |
||||
|
||||
return query |
@ -0,0 +1,72 @@
@@ -0,0 +1,72 @@
|
||||
''' Schemas for form input and validation ''' |
||||
|
||||
import colander |
||||
import deform |
||||
|
||||
from deform.renderer import configure_zpt_renderer |
||||
|
||||
from .helpers import ( |
||||
deferred_csrf_default, |
||||
deferred_csrf_validator |
||||
) |
||||
|
||||
# Make Deform widgets aware of our widget template paths |
||||
configure_zpt_renderer(['ordr2:templates/deform']) |
||||
|
||||
|
||||
# Base Schema |
||||
|
||||
class CSRFSchema(colander.Schema): |
||||
''' base class for schemas with csrf validation ''' |
||||
|
||||
csrf_token = colander.SchemaNode( |
||||
colander.String(), |
||||
default=deferred_csrf_default, |
||||
validator=deferred_csrf_validator, |
||||
widget=deform.widget.HiddenWidget(), |
||||
) |
||||
|
||||
@classmethod |
||||
def as_form(cls, request, **kwargs): |
||||
''' returns the schema as a form ''' |
||||
url = kwargs.pop('url', None) |
||||
if not url: |
||||
url = request.resource_url(request.context, request.view_name) |
||||
schema = cls().bind(request=request) |
||||
form = deform.Form(schema, action=url, **kwargs) |
||||
return form |
||||
|
||||
|
||||
|
||||
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' |
||||
), |
||||
) |
||||
currency = colander.SchemaNode( |
||||
colander.String(), |
||||
default='EUR', |
||||
widget=deform.widget.TextInputWidget( |
||||
readonly_template='textinput_disabled.pt', |
||||
css_class='moneyinput currency' |
||||
) |
||||
) |
||||
|
||||
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, |
||||
) |
||||
super().__init__(*args, **kwargs) |
@ -0,0 +1,155 @@
@@ -0,0 +1,155 @@
|
||||
import colander |
||||
import deform |
||||
|
||||
from ordr2.models import Role |
||||
|
||||
from . import CSRFSchema |
||||
from .helpers import ( |
||||
deferred_unique_email_validator, |
||||
deferred_unique_username_validator, |
||||
deferred_password_validator |
||||
) |
||||
|
||||
|
||||
ROLES = [(role.name, role.value.capitalize()) for role in Role] |
||||
|
||||
# schema for user registration |
||||
|
||||
class RegistrationSchema(CSRFSchema): |
||||
''' new user registration ''' |
||||
|
||||
user_name = colander.SchemaNode( |
||||
colander.String(), |
||||
widget=deform.widget.TextInputWidget( |
||||
template='textinput_disabled.pt' |
||||
), |
||||
description='automagically generated for you', |
||||
validator = deferred_unique_username_validator, |
||||
) |
||||
first_name = colander.SchemaNode( |
||||
colander.String() |
||||
) |
||||
last_name = colander.SchemaNode( |
||||
colander.String() |
||||
) |
||||
email = colander.SchemaNode( |
||||
colander.String(), |
||||
validator=deferred_unique_email_validator |
||||
) |
||||
password = colander.SchemaNode( |
||||
colander.String(), |
||||
widget=deform.widget.CheckedPasswordWidget() |
||||
) |
||||
|
||||
@classmethod |
||||
def as_form(cls, request, **override): |
||||
settings = { |
||||
'buttons': ('Create Account', 'Cancel'), |
||||
'css_class': 'form-horizontal registration' |
||||
} |
||||
settings.update(override) |
||||
return super().as_form(request, **settings) |
||||
|
||||
|
||||
# schema for user settings |
||||
|
||||
class UserSchema(CSRFSchema): |
||||
''' user settings schema ''' |
||||
|
||||
user_name = colander.SchemaNode( |
||||
colander.String(), |
||||
widget=deform.widget.TextInputWidget( |
||||
template='textinput_disabled.pt' |
||||
), |
||||
) |
||||
first_name = colander.SchemaNode( |
||||
colander.String() |
||||
) |
||||
last_name = colander.SchemaNode( |
||||
colander.String() |
||||
) |
||||
email = colander.SchemaNode( |
||||
colander.String(), |
||||
validator=deferred_unique_email_validator |
||||
) |
||||
role = colander.SchemaNode( |
||||
colander.String(), |
||||
widget=deform.widget.SelectWidget(values=ROLES) |
||||
) |
||||
|
||||
@classmethod |
||||
def as_form(cls, request, **override): |
||||
settings = { |
||||
'buttons': ( |
||||
deform.Button(name='save', title='Save changes'), |
||||
deform.Button( |
||||
name='delete', |
||||
title='Delete user', |
||||
css_class='btn-danger' |
||||
), |
||||
deform.Button(name='reset', title='Reset password'), |
||||
deform.Button(name='cancel', title='Cancel') |
||||
), |
||||
'css_class': 'form-horizontal', |
||||
} |
||||
settings.update(override) |
||||
return super().as_form(request, **settings) |
||||
|
||||
|
||||
class ChangePasswordSchema(CSRFSchema): |
||||
''' change password of an account ''' |
||||
|
||||
new_password = colander.SchemaNode( |
||||
colander.String(), |
||||
widget=deform.widget.CheckedPasswordWidget(), |
||||
missing='' |
||||
) |
||||
|
||||
|
||||
class ConfirmSettingsSchema(CSRFSchema): |
||||
''' confirm changes with current password ''' |
||||
|
||||
current_password = colander.SchemaNode( |
||||
colander.String(), |
||||
widget=deform.widget.PasswordWidget(), |
||||
description='Enter your current password to confirm changes', |
||||
validator=deferred_password_validator |
||||
) |
||||
|
||||
|
||||
class SettingsSchema(CSRFSchema): |
||||
general = UserSchema() |
||||
change_password = ChangePasswordSchema() |
||||
confirm_changes = ConfirmSettingsSchema() |
||||
|
||||
@classmethod |
||||
def as_form(cls, request, **override): |
||||
settings = { |
||||
'buttons': ('Save Settings', 'Cancel'), |
||||
'css_class': 'form-horizontal user-settings' |
||||
} |
||||
settings.update(override) |
||||
form = super().as_form(request, **settings) |
||||
# disable the role field for user settings |
||||
form['general']['role'].widget = deform.widget.TextInputWidget( |
||||
template='textinput_disabled.pt' |
||||
) |
||||
return form |
||||
|
||||
|
||||
class ResetPasswordSchema(CSRFSchema): |
||||
''' reset password of an account ''' |
||||
|
||||
new_password = colander.SchemaNode( |
||||
colander.String(), |
||||
widget=deform.widget.CheckedPasswordWidget() |
||||
) |
||||
|
||||
@classmethod |
||||
def as_form(cls, request, **override): |
||||
settings = { |
||||
'buttons': ('Change Password', 'Cancel'), |
||||
'css_class': 'form-horizontal' |
||||
} |
||||
settings.update(override) |
||||
return super().as_form(request, **settings) |
@ -0,0 +1,62 @@
@@ -0,0 +1,62 @@
|
||||
import colander |
||||
import deform |
||||
|
||||
from pyramid.csrf import get_csrf_token, check_csrf_token |
||||
|
||||
from ordr2.models import User |
||||
|
||||
|
||||
@colander.deferred |
||||
def deferred_csrf_default(node, kw): |
||||
''' sets the current csrf token ''' |
||||
request = kw.get('request') |
||||
return get_csrf_token(request) |
||||
|
||||
|
||||
@colander.deferred |
||||
def deferred_csrf_validator(node, kw): |
||||
''' validates a submitted csrf token ''' |
||||
def validate_csrf(node, value): |
||||
request = kw.get('request') |
||||
if not check_csrf_token(request, raises=False): |
||||
raise colander.Invalid(node, 'Bad CSRF token') |
||||
return validate_csrf |
||||
|
||||
|
||||
@colander.deferred |
||||
def deferred_unique_username_validator(node, kw): |
||||
''' checks if an username is not registered already ''' |
||||
|
||||
def validate_unique_username(node, value): |
||||
request = kw.get('request') |
||||
user = request.dbsession.query(User).filter_by(user_name=value).first() |
||||
if user is not None: |
||||
raise colander.Invalid(node, 'User name already registered') |
||||
return validate_unique_username |
||||
|
||||
|
||||
@colander.deferred |
||||
def deferred_unique_email_validator(node, kw): |
||||
''' checks if an email is not registered already ''' |
||||
email_validator = colander.Email() |
||||
|
||||
def validate_unique_email(node, value): |
||||
email_validator(node, value) # raises exception on invalid address |
||||
request = kw.get('request') |
||||
user = request.dbsession.query(User).filter_by(email=value).first() |
||||
if user not in (None, request.context.model): |
||||
# allow existing email addresses if |
||||
# it belongs to the user that is currently edited |
||||
raise colander.Invalid(node, 'Email address in use') |
||||
return validate_unique_email |
||||
|
||||
|
||||
@colander.deferred |
||||
def deferred_password_validator(node, kw): |
||||
''' checks password confirmation for settings ''' |
||||
|
||||
def validate_password_confirmation(node, value): |
||||
request = kw.get('request') |
||||
if request.user is None or not request.user.check_password(value): |
||||
raise colander.Invalid(node, 'Wrong password') |
||||
return validate_password_confirmation |
@ -0,0 +1,211 @@
@@ -0,0 +1,211 @@
|
||||
''' schemas for creating and editing orders and consumables''' |
||||
|
||||
import colander |
||||
import deform |
||||
|
||||
from ordr2.models import Category, OrderStatus |
||||
|
||||
from . import CSRFSchema, MoneyInputSchema |
||||
|
||||
|
||||
# key / value pairs for select fields |
||||
|
||||
CATEGORIES = [(c.name, c.value.capitalize()) for c in Category] |
||||
STATI = [(s.name, s.value.capitalize()) for s in OrderStatus] |
||||
|
||||
|
||||
class ConsumableSchema(CSRFSchema): |
||||
''' edit or add consumable ''' |
||||
|
||||
cas_description = colander.SchemaNode( |
||||
colander.String() |
||||
) |
||||
category = colander.SchemaNode( |
||||
colander.String(), |
||||
widget=deform.widget.SelectWidget(values=CATEGORIES) |
||||
) |
||||
catalog_nr = colander.SchemaNode( |
||||
colander.String() |
||||
) |
||||
vendor = colander.SchemaNode( |
||||
colander.String() |
||||
) |
||||
package_size = colander.SchemaNode( |
||||
colander.String() |
||||
) |
||||
unit_price = MoneyInputSchema( |
||||
readonly=False |
||||
) |
||||
comment = colander.SchemaNode( |
||||
colander.String(), |
||||
widget=deform.widget.TextAreaWidget(rows=5), |
||||
missing='' |
||||
) |
||||
|
||||
@classmethod |
||||
def as_form(cls, request, **override): |
||||
''' returns the schema as a form ''' |
||||
# define buttons separately for a new consumable and one to edit |
||||
is_new_consumable = override.pop('is_new_consumable', False) |
||||
if is_new_consumable: |
||||
settings = { |
||||
'buttons': ( |
||||
deform.Button(name='save', title='Add Consumable'), |
||||
deform.Button(name='cancel', title='Cancel') |
||||
), |
||||
'css_class': 'form-horizontal', |
||||
} |
||||
else: |
||||
settings = { |
||||
'buttons': ( |
||||
deform.Button(name='save', title='Save changes'), |
||||
deform.Button( |
||||
name='delete', |
||||
title='Delete Consumable', |
||||
css_class='btn-danger' |
||||
), |
||||
deform.Button(name='cancel', title='Cancel') |
||||
), |
||||
'css_class': 'form-horizontal', |
||||
} |
||||
settings.update(override) |
||||
return super().as_form(request, **settings) |
||||
|
||||
|
||||
class OrderInformation(colander.Schema): |
||||
''' schema for editing order status |
||||
|
||||
parital schema, used in EditOrderSchema |
||||
''' |
||||
|
||||
status = colander.SchemaNode( |
||||
colander.String(), |
||||
widget=deform.widget.SelectWidget(values=STATI) |
||||
) |
||||
|
||||
|
||||
class OrderItem(colander.Schema): |
||||
''' schema for editing item information |
||||
|
||||
parital schema, used in NewOrderSchema and EditOrderSchema |
||||
''' |
||||
|
||||
cas_description = colander.SchemaNode( |
||||
colander.String() |
||||
) |
||||
category = colander.SchemaNode( |
||||
colander.String(), |
||||
widget=deform.widget.SelectWidget(values=CATEGORIES) |
||||
) |
||||
catalog_nr = colander.SchemaNode( |
||||
colander.String() |
||||
) |
||||
vendor = colander.SchemaNode( |
||||
colander.String() |
||||
) |
||||
package_size = colander.SchemaNode( |
||||
colander.String() |
||||
) |
||||
|
||||
|
||||
class OrderPricing(colander.Schema): |
||||
''' schema for editing price information |
||||
|
||||
parital schema, used in NewOrderSchema and EditOrderSchema |
||||
''' |
||||
|
||||
unit_price = MoneyInputSchema( |
||||
readonly=False |
||||
) |
||||
quantity = colander.SchemaNode( |
||||
colander.Integer(), |
||||
validator=colander.Range(min=1), |
||||
widget=deform.widget.TextInputWidget( |
||||
css_class='number' |
||||
), |
||||
default=1 |
||||
) |
||||
total_price = MoneyInputSchema( |
||||
readonly=True |
||||
) |
||||
|
||||
|
||||
class OrderOptionals(colander.Schema): |
||||
''' schema for editing optional order information |
||||
|
||||
parital schema, used in NewOrderSchema and EditOrderSchema |
||||
''' |
||||
account = colander.SchemaNode( |
||||
colander.String(), |
||||
missing='' |
||||
) |
||||
comment = colander.SchemaNode( |
||||
colander.String(), |
||||
widget=deform.widget.TextAreaWidget(rows=5), |
||||
missing='' |
||||
) |
||||
|
||||
|
||||
class NewOrderSchema(CSRFSchema): |
||||
''' schema for a new order ''' |
||||
|
||||
item_information = OrderItem() |
||||
pricing = OrderPricing() |
||||
optional_information = OrderOptionals() |
||||
|
||||
@classmethod |
||||
def as_form(cls, request, **override): |
||||
''' returns the schema as a form ''' |
||||
settings = { |
||||
'buttons': ( |
||||
deform.Button(name='save', title='Place Order'), |
||||
deform.Button(name='cancel', title='Cancel') |
||||
), |
||||
'css_class': 'form-horizontal' |
||||
} |
||||
settings.update(override) |
||||
return super().as_form(request, **settings) |
||||
|
||||
|
||||
class EditOrderSchema(CSRFSchema): |
||||
''' schema for editing an order ''' |
||||
|
||||
order_information = OrderInformation( |
||||
widget=deform.widget.MappingWidget( |
||||
template='order_info_mapping.pt' |
||||
) |
||||
) |
||||
item_information = OrderItem() |
||||
pricing = OrderPricing() |
||||
optional_information = OrderOptionals() |
||||
|
||||
@classmethod |
||||
def as_form(cls, request, **override): |
||||
''' returns the schema as a form ''' |
||||
settings = { |
||||
'buttons': ( |
||||
deform.Button(name='save', title='Edit Order'), |
||||
deform.Button( |
||||
name='reorder', |
||||
title='Reorder', |
||||
css_class='btn-success' |
||||
), |
||||
deform.Button( |
||||
name='delete', |
||||
title='Delete Order', |
||||
css_class='btn-danger' |
||||
), |
||||
deform.Button(name='cancel', title='Cancel') |
||||
), |
||||
'css_class': 'form-horizontal' |
||||
} |
||||
settings.update(override) |
||||
form = super().as_form(request, **settings) |
||||
|
||||
# disable the status field, if the current user is not a purchaser |
||||
if not 'role:purchaser' in request.user.role_principals: |
||||
form['order_information']['status'].widget = \ |
||||
deform.widget.TextInputWidget( |
||||
template='textinput_disabled.pt' |
||||
) |
||||
return form |
@ -1 +1 @@
@@ -1 +1 @@
|
||||
# package |
||||
''' command line scripts ''' |
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,715 @@
@@ -0,0 +1,715 @@
|
||||
%YAML 1.1 |
||||
--- |
||||
# imtekordr.users |
||||
- |
||||
id: 99 |
||||
username: "MartinSchoenstein" |
||||
first_name: "Martin" |
||||
last_name: "Schoenstein" |
||||
password: "ea8458396a0c17954f9a9bc5b835f81148a39bcb" |
||||
email: "schoenst@imtek.de" |
||||
role: "Purchaser" |
||||
date_created: "2012-04-05 14:29:21" |
||||
- |
||||
id: 98 |
||||
username: "DanielaMoessner" |
||||
first_name: "Daniela" |
||||
last_name: "Moessner" |
||||
password: "5ee566ad84396a2a26d7f6fcdf589a261f202569" |
||||
email: "moessner@imtek.de" |
||||
role: "Purchaser" |
||||
date_created: "2012-03-30 09:35:40" |
||||
- |
||||
id: 97 |
||||
username: "AlexanderDietz" |
||||
first_name: "Alexander" |
||||
last_name: "Dietz" |
||||
password: "621d0004efe268724a86473649ae3ced2871e1c9" |
||||
email: "alexander.dietz@imtek.uni-freiburg.de" |
||||
role: "Purchaser" |
||||
date_created: "2012-03-29 16:39:29" |
||||
- |
||||
id: 94 |
||||
username: "ASasdasdgase" |
||||
first_name: "ASasd" |
||||
last_name: "asdgase" |
||||
password: "7c4a8d09ca3762af61e59520943dc26494f8941b" |
||||
email: "axsd@sdasd.com" |
||||
role: "Inactive" |
||||
date_created: "2012-02-15 15:27:34" |
||||
- |
||||
id: 82 |
||||
username: "SebastianSebald" |
||||
first_name: "Sebastian" |
||||
last_name: "Sebald" |
||||
password: "9b3e1fa230b050fb324086e2fcc94b1b9aa2ac35" |
||||
email: "sebastian.sebald@gmail.com" |
||||
role: "Admin" |
||||
date_created: "2012-01-20 13:12:53" |
||||
- |
||||
id: 89 |
||||
username: "HolgerFrey" |
||||
first_name: "Holger" |
||||
last_name: "Frey" |
||||
password: "9999fc0774280169f52232de4500e0077a314d0c" |
||||
email: "holger.frey@imtek.uni-freiburg.de" |
||||
role: "Admin" |
||||
date_created: "2012-01-20 21:35:40" |
||||
- |
||||
id: 100 |
||||
username: "MaxMustermann" |
||||
first_name: "Max" |
||||
last_name: "Mustermann" |
||||
password: "af0ba23d48fe7db684d9a9d13eb5ec0f5183081c" |
||||
email: "oswaldprucker@web.de" |
||||
role: "Inactive" |
||||
date_created: "2012-05-16 08:40:37" |
||||
- |
||||
id: 91 |
||||
username: "NataliaSchatz" |
||||
first_name: "Natalia" |
||||
last_name: "Schatz" |
||||
password: "5f89fc9b6033aa210658489a14bf865a096dd702" |
||||
email: "schatz@imtek.de" |
||||
role: "Admin" |
||||
date_created: "2012-01-20 21:37:32" |
||||
- |
||||
id: 92 |
||||
username: "OswaldPrucker" |
||||
first_name: "Oswald" |
||||
last_name: "Prucker" |
||||
password: "4aa75e8bc73f4bc072133a10acb728633d83eb20" |
||||
email: "prucker@imtek.de" |
||||
role: "Admin" |
||||
date_created: "2012-01-20 22:22:27" |
||||
- |
||||
id: 96 |
||||
username: "MalwinaPajestka" |
||||
first_name: "Malwina" |
||||
last_name: "Pajestka" |
||||
password: "cf8abbfad4ea9a10200e6b0b6008ac55dcbc499e" |
||||
email: "malwina.pajestka@imtek.de" |
||||
role: "Purchaser" |
||||
date_created: "2012-03-29 14:47:44" |
||||
- |
||||
id: 155 |
||||
username: "AlexandraSchneider" |
||||
first_name: "Alexandra" |
||||
last_name: "Schneider" |
||||
password: "f3d4facdde3f40ad5abaf8b8bb17f2da7b08af32" |
||||
email: "alexandra.schneider@jupiter.uni-freiburg" |
||||
role: "User" |
||||
date_created: "2015-08-04 16:45:44" |
||||
- |
||||
id: 102 |
||||
username: "MichaelHenze" |
||||
first_name: "Michael" |
||||
last_name: "Henze" |
||||
password: "2dfa7ae9d549ae3e152c1621b111d92029972749" |
||||
email: "michael.henze@imtek.uni-freiburg.de" |
||||
role: "User" |
||||
date_created: "2012-05-22 17:00:41" |
||||
- |
||||
id: 154 |
||||
username: "VaniaWidyaya" |
||||
first_name: "Vania" |
||||
last_name: "Widyaya" |
||||
password: "93b8e920dce9eb632b4698a77ca63a1dd6f3d14c" |
||||
email: "vania.widyaya@imtek.uni-freiburg.de" |
||||
role: "User" |
||||
date_created: "2015-05-06 09:47:09" |
||||
- |
||||
id: 104 |
||||
username: "RomanErath" |
||||
first_name: "Roman" |
||||
last_name: "Erath" |
||||
password: "452f3991d26d97c62cdf6bbcb31ef2be9b42df1d" |
||||
email: "roman.erath@imtek.uni-freiburg.de" |
||||
role: "User" |
||||
date_created: "2012-05-29 13:42:51" |
||||
- |
||||
id: 106 |
||||
username: "WibkeHartleb" |
||||
first_name: "Wibke" |
||||
last_name: "Hartleb" |
||||
password: "aeea4f54de35d5abe4d0fcbb8116df4842bc7304" |
||||
email: "hartleb@imtek.de" |
||||
role: "Inactive" |
||||
date_created: "2012-06-04 16:19:55" |
||||
- |
||||
id: 107 |
||||
username: "FranziskaDorner" |
||||
first_name: "Franziska" |
||||
last_name: "Dorner" |
||||
password: "0320f2762d397cb4225f36bcad23a1d6ec9a5dd5" |
||||
email: "franziska.dorner@imtek.de" |
||||
role: "Inactive" |
||||
date_created: "2012-06-04 16:28:24" |
||||
- |
||||
id: 156 |
||||
username: "ChinnawutPipatpanukul" |
||||
first_name: "Chinnawut" |
||||
last_name: "Pipatpanukul" |
||||
password: "fc0b55c7a97cb2b16d94844dd3b260435cb69acd" |
||||
email: "polymer_chin@hotmail.com" |
||||
role: "User" |
||||
date_created: "2015-09-07 17:03:11" |
||||
- |
||||
id: 110 |
||||
username: "SimonSchuster" |
||||
first_name: "Simon" |
||||
last_name: "Schuster" |
||||
password: "5cd79755dcc701a0a72bbeacd5a45aa932c3d63f" |
||||
email: "schuster@imtek.uni-freiburg.de" |
||||
role: "Inactive" |
||||
date_created: "2012-06-18 15:27:14" |
||||
- |
||||
id: 111 |
||||
username: "JonGreen" |
||||
first_name: "Jon" |
||||
last_name: "Green" |
||||
password: "871f35fb0bc758f97dcc9dc484b9002014322c1b" |
||||
email: "jon.green@imtek.uni-freiburg.de" |
||||
role: "Inactive" |
||||
date_created: "2012-06-27 15:30:31" |
||||
- |
||||
id: 113 |
||||
username: "PengZou" |
||||
first_name: "Peng" |
||||
last_name: "Zou" |
||||
password: "50d1199385e3b7c39d382017e281e37782d84026" |
||||
email: "peng.zou@imtek.uni-freiburg.de" |
||||
role: "User" |
||||
date_created: "2012-07-30 10:38:40" |
||||
- |
||||
id: 114 |
||||
username: "HolgerKlapproth" |
||||
first_name: "Holger" |
||||
last_name: "Klapproth" |
||||
password: "ec2ec7fad8702059c81783d7cb720d288ffb6786" |
||||
email: "holger.klapproth@imtek.de" |
||||
role: "User" |
||||
date_created: "2012-07-31 12:39:58" |
||||
- |
||||
id: 115 |
||||
username: "TobiasHeitzler" |
||||
first_name: "Tobias" |
||||
last_name: "Heitzler" |
||||
password: "1d119fcf4eb869e99de044125cbe54960e84b1de" |
||||
email: "tobias.heitzler@jupiter.uni-freiburg.de" |
||||
role: "Inactive" |
||||
date_created: "2012-08-20 10:27:36" |
||||
- |
||||
id: 116 |
||||
username: "VitaliyKondrashov" |
||||
first_name: "Vitaliy" |
||||
last_name: "Kondrashov" |
||||
password: "a194f7e6584fd78fc6fd87aea8244d7d28cecdfb" |
||||
email: "vitaliy.kondrashov@imtek.de" |
||||
role: "Inactive" |
||||
date_created: "2012-09-12 12:37:03" |
||||
- |
||||
id: 117 |
||||
username: "MaraFlorea" |
||||
first_name: "Mara" |
||||
last_name: "Florea" |
||||
password: "2c7d26e5a650f1452596c5d6859f1de70e9dfda0" |
||||
email: "mara.florea@fmf-uni-freiburg.de" |
||||
role: "User" |
||||
date_created: "2012-09-24 09:57:37" |
||||
- |
||||
id: 118 |
||||
username: "NicoleBirsner" |
||||
first_name: "Nicole" |
||||
last_name: "Birsner" |
||||
password: "205e21a7eb6a9475d252cc34ec5652a48cd40a1b" |
||||
email: "birsner@imtek.de" |
||||
role: "User" |
||||
date_created: "2012-09-26 16:11:50" |
||||
- |
||||
id: 119 |
||||
username: "KarenLienkamp" |
||||
first_name: "Karen" |
||||
last_name: "Lienkamp" |
||||
password: "810616773a6895260bb6b1f06436bf5919b9ac7e" |
||||
email: "lienkamp@imtek.uni-freiburg.de" |
||||
role: "Purchaser" |
||||
date_created: "2012-10-17 13:38:47" |
||||
- |
||||
id: 120 |
||||
username: "ChristophScheibelein" |
||||
first_name: "Christoph" |
||||
last_name: "Scheibelein" |
||||
password: "d52b154b64b76cb7d41c900b6dc04075568f5881" |
||||
email: "christoph.scheibelein@imtek.de" |
||||
role: "User" |
||||
date_created: "2012-10-30 17:00:57" |
||||
- |
||||
id: 151 |
||||
username: "CrispinAmiriNaini" |
||||
first_name: "Crispin" |
||||
last_name: "AmiriNaini" |
||||
password: "a5e0427ef5e4b47165950f2233767158c6305d72" |
||||
email: "crispin.amiri@imtek.de" |
||||
role: "User" |
||||
date_created: "2015-01-16 15:17:00" |
||||
- |
||||
id: 121 |
||||
username: "DavidBoschert" |
||||
first_name: "David" |
||||
last_name: "Boschert" |
||||
password: "219ca82924d6388be4be803104e721c28c7bd8ed" |
||||
email: "david.boschert@frias.uni-freiburg.de" |
||||
role: "User" |
||||
date_created: "2013-01-22 12:01:21" |
||||
- |
||||
id: 122 |
||||
username: "NilsKorf" |
||||
first_name: "Nils" |
||||
last_name: "Korf" |
||||
password: "2785030b6e08e0c1b5ff80080382efbe11efd644" |
||||
email: "nils.korf@bcf.uni-freiburg.de" |
||||
role: "Inactive" |
||||
date_created: "2013-01-29 13:26:11" |
||||
- |
||||
id: 123 |
||||
username: "FrankScherag" |
||||
first_name: "Frank" |
||||
last_name: "Scherag" |
||||
password: "acd383d906d92779723d121ac3399aa0d84dde00" |
||||
email: "Frank.Scherag@imtek.uni-freiburg.de" |
||||
role: "User" |
||||
date_created: "2013-03-07 10:49:57" |
||||
- |
||||
id: 124 |
||||
username: "AnnaSchuler" |
||||
first_name: "Anna" |
||||
last_name: "Schuler" |
||||
password: "e546a402c7ee629029bad23803b14768207fd61a" |
||||
email: "anne-katrin.schuler@imtek.uni-freiburg.d" |
||||
role: "User" |
||||
date_created: "2013-03-12 13:07:07" |
||||
- |
||||
id: 157 |
||||
username: "SebastianAnders" |
||||
first_name: "Sebastian" |
||||
last_name: "Anders" |
||||
password: "3d48159e1c5a08a06aa8149208791f686effaba6" |
||||
email: "sebastian.anders@imtek.de" |
||||
role: "User" |
||||
date_created: "2015-09-24 10:34:21" |
||||
- |
||||
id: 126 |
||||
username: "MarcelRothfelder" |
||||
first_name: "Marcel" |
||||
last_name: "Rothfelder" |
||||
password: "cb70c818d0a3ab0d595fabbe7eadcf96d7e003f9" |
||||
email: "marcel.rothfelder@fmf.uni-freiburg.de" |
||||
role: "User" |
||||
date_created: "2013-03-28 09:42:44" |
||||
- |
||||
id: 127 |
||||
username: "AnneBuderer" |
||||
first_name: "Anne" |
||||
last_name: "Buderer" |
||||
password: "574f939d78617aef36d6240e3cd5c90eb390aa73" |
||||
email: "anne.buderer@imtek.uni-freiburg.de" |
||||
role: "User" |
||||
date_created: "2013-04-02 10:20:27" |
||||
- |
||||
id: 153 |
||||
username: "SureshBanda" |
||||
first_name: "Suresh" |
||||
last_name: "Banda" |
||||
password: "91b33a824a0aaac74bfd465ab0ad253885896b20" |
||||
email: "suresh.banda@imtek.de" |
||||
role: "User" |
||||
date_created: "2015-02-19 14:43:41" |
||||
- |
||||
id: 152 |
||||
username: "RolandHoenes" |
||||
first_name: "Roland" |
||||
last_name: "Hoenes" |
||||
password: "ad972ff6f6c8e9c1b9fd44a1b9774877482d0974" |
||||
email: "roland.hoenes@imtek.de" |
||||
role: "User" |
||||
date_created: "2015-02-03 09:42:28" |
||||
- |
||||
id: 129 |
||||
username: "AnselmHoppmann" |
||||
first_name: "Anselm" |
||||
last_name: "Hoppmann" |
||||
password: "98fe5b48a33f8618fe08f9ca4b8dfb3256fa7e00" |
||||
email: "anselm.hoppmann@gmx.de" |
||||
role: "Inactive" |
||||
date_created: "2013-06-04 10:27:08" |
||||
- |
||||
id: 130 |
||||
username: "ShararehAsiaee" |
||||
first_name: "Sharareh" |
||||
last_name: "Asiaee" |
||||
password: "d01d59fb35a1fbbba731f81f28fb43baf3eeb5e5" |
||||
email: "sharareh.sahneh@imtek.de" |
||||
role: "User" |
||||
date_created: "2013-07-04 11:58:37" |
||||
- |
||||
id: 132 |
||||
username: "MarcZinggeler" |
||||
first_name: "Marc" |
||||
last_name: "Zinggeler" |
||||
password: "2aead214ed9af75a0dbd8a9c50341ba2a5945451" |
||||
email: "marc.zinggeler@imtek.uni-freiburg.de" |
||||
role: "User" |
||||
date_created: "2013-07-05 12:23:34" |
||||
- |
||||
id: 133 |
||||
username: "XiaoqiangHou" |
||||
first_name: "Xiaoqiang" |
||||
last_name: "Hou" |
||||
password: "f717b7dc2f620d074151d544bec5328481d7faac" |
||||
email: "xiaoqiang.hou@imtek.de" |
||||
role: "User" |
||||
date_created: "2013-07-05 16:44:39" |
||||
- |
||||
id: 134 |
||||
username: "SamarKazan" |
||||
first_name: "Samar" |
||||
last_name: "Kazan" |
||||
password: "334ef431feb818a657a5d413c341bce0d28cd407" |
||||
email: "samar.kazan@imtek.uni-freiburg.de" |
||||
role: "User" |
||||
date_created: "2013-07-10 14:30:57" |
||||
- |
||||
id: 136 |
||||
username: "SaschaEngel" |
||||
first_name: "Sascha" |
||||
last_name: "Engel" |
||||
password: "94299c5dc1b3e954f0d7500d2bc3770a66bbd325" |
||||
email: "sascha.engel@imtek.uni-freiburg.de" |
||||
role: "Inactive" |
||||
date_created: "2013-08-09 13:47:01" |
||||
- |
||||
id: 137 |
||||
username: "MartinKoerner" |
||||
first_name: "Martin" |
||||
last_name: "Koerner" |
||||
password: "115e67def35ce67ede63c93d7d81479d828d705c" |
||||
email: "martin.koerner@imtek.de" |
||||
role: "User" |
||||
date_created: "2013-08-26 15:02:45" |
||||
- |
||||
id: 138 |
||||
username: "UrmilShah" |
||||
first_name: "Urmil" |
||||
last_name: "Shah" |
||||
password: "27bf49a7ec0d428a4e65ac5fbe88bd0bd8d55643" |
||||
email: "shahurmil86@yahoo.com" |
||||
role: "User" |
||||
date_created: "2013-10-29 14:28:59" |
||||
- |
||||
id: 139 |
||||
username: "DavidSchwaerzle" |
||||
first_name: "David" |
||||
last_name: "Schwaerzle" |
||||
password: "938791e4907d0673c3baf5278b574f11b45c2fbb" |
||||
email: "david.schwaerzle@gmx.de" |
||||
role: "User" |
||||
date_created: "2013-10-29 14:29:02" |
||||
- |
||||
id: 147 |
||||
username: "MonikaKurowska" |
||||
first_name: "Monika" |
||||
last_name: "Kurowska" |
||||
password: "a5fcbf21e6919599279dbe9c4adfea9878686321" |
||||
email: "monikkurowska@gmail.com" |
||||
role: "User" |
||||
date_created: "2014-10-02 09:21:18" |
||||
- |
||||
id: 140 |
||||
username: "PhilipKotrade" |
||||
first_name: "Philip" |
||||
last_name: "Kotrade" |
||||
password: "8200057b2e20c4e5b801336841e6849de8bf3716" |
||||
email: "Philip.Kotrade@imtek.uni-freiburg.de" |
||||
role: "User" |
||||
date_created: "2013-11-07 11:19:22" |
||||
- |
||||
id: 141 |
||||
username: "NiklasSchoenberg" |
||||
first_name: "Niklas" |
||||
last_name: "Schoenberg" |
||||
password: "6a2bf1397988b6da3c2857c2466a2396365880a0" |
||||
email: "schoenberg@imtek.de" |
||||
role: "User" |
||||
date_created: "2013-11-07 15:35:13" |
||||
- |
||||
id: 142 |
||||
username: "MatthiasMenzel" |
||||
first_name: "Matthias" |
||||
last_name: "Menzel" |
||||
password: "8b1a8e1c534016c16396dc2308dbb9410221df20" |
||||
email: "matthias.menzel@imtek.uni-freiburg.de" |
||||
role: "User" |
||||
date_created: "2013-12-10 12:40:26" |
||||
- |
||||
id: 144 |
||||
username: "MostafaMahmoud" |
||||
first_name: "Mostafa" |
||||
last_name: "Mahmoud" |
||||
password: "1dc1feb8629b3292065654e2a50160177b6e5aab" |
||||
email: "mostafasafwat@live.com" |
||||
role: "Inactive" |
||||
date_created: "2014-02-28 15:49:50" |
||||
- |
||||
id: 145 |
||||
username: "HeidiPerez" |
||||
first_name: "Heidi" |
||||
last_name: "Perez" |
||||
password: "b3223810838aee0d0d08bef11ae6ab8139ead98d" |
||||
email: "heidi.perez@frias.uni-freiburg.de" |
||||
role: "Inactive" |
||||
date_created: "2014-04-23 13:32:24" |
||||
- |
||||
id: 146 |
||||
username: "EstherRiga" |
||||
first_name: "Esther" |
||||
last_name: "Riga" |
||||
password: "f58b22fe282c539d9504e4f5023f30a5e694dae4" |
||||
email: "esther.riga@imtek.uni-freiburg.de" |
||||
role: "User" |
||||
date_created: "2014-07-08 13:34:35" |
||||
- |
||||
id: 148 |
||||
username: "thomasseery" |
||||
first_name: "thomas" |
||||
last_name: "seery" |
||||
password: "808b3bf707af545878b2a14fe8d29649f6d5c227" |
||||
email: "seery@mail.ims.uconn.edu" |
||||
role: "Inactive" |
||||
date_created: "2014-10-16 16:24:24" |
||||
- |
||||
id: 149 |
||||
username: "VanessaWeiss" |
||||
first_name: "Vanessa" |
||||
last_name: "Weiss" |
||||
password: "cdab1dc399f2db48b9f0d7edf241ffd5163ccf97" |
||||
email: "vanessa.weiss@imtek.de" |
||||
role: "User" |
||||
date_created: "2014-11-21 13:47:35" |
||||
- |
||||
id: 150 |
||||
username: "AndreasMader" |
||||
first_name: "Andreas" |
||||
last_name: "Mader" |
||||
password: "ef18eaebcdc6ec5cc07f1d60f07ff0056ff6fc9d" |
||||
email: "andreas.mader@imtek.de" |
||||
role: "Inactive" |
||||
date_created: "2014-12-02 15:34:58" |
||||
- |
||||
id: 158 |
||||
username: "JuliaSaar" |
||||
first_name: "Julia" |
||||
last_name: "Saar" |
||||
password: "c931d353713d309431819e824d6b8d5ffcc91660" |
||||
email: "Julia.Saar91@googlemail.com" |
||||
role: "User" |
||||
date_created: "2015-10-26 09:35:14" |
||||
- |
||||
id: 159 |
||||
username: "MariaVoehringer" |
||||
first_name: "Maria" |
||||
last_name: "Voehringer" |
||||
password: "4538f730a9e9ad282753a447376a7bb4537ab24c" |
||||
email: "maria.voehringer@gmx.de" |
||||
role: "User" |
||||
date_created: "2015-11-23 11:00:08" |
||||
- |
||||
id: 160 |
||||
username: "JessicaBean" |
||||
first_name: "Jessica" |
||||
last_name: "Bean" |
||||
password: "61095da8cef256650a98ad2941ba71892f15c958" |
||||
email: "jessica.bean@fmf.uni-freiburg.de" |
||||
role: "User" |
||||
date_created: "2015-11-24 15:22:32" |
||||
- |
||||
id: 161 |
||||
username: "SimonZunker" |
||||
first_name: "Simon" |
||||
last_name: "Zunker" |
||||
password: "f19814fbf8f2cbdfee3fff746553869c30d3828b" |
||||
email: "simon.zunker@imtek.uni-freiburg.de" |
||||
role: "User" |
||||
date_created: "2016-01-26 16:42:14" |
||||
- |
||||
id: 162 |
||||
username: "WeiChen" |
||||
first_name: "Wei" |
||||
last_name: "Chen" |
||||
password: "cb0aed09cd8838e6a4a319d284315ff51069ad75" |
||||
email: "wei.chen@imtek.uni-freiburg.de" |
||||
role: "User" |
||||
date_created: "2016-01-27 09:53:00" |
||||
- |
||||
id: 163 |
||||
username: "AndreasWalder" |
||||
first_name: "Andreas" |
||||
last_name: "Walder" |
||||
password: "19dc4b581986466ef062d3e9d4eb2ae862e88a39" |
||||
email: "andreas.walder@imtek.uni-freiburg.de" |
||||
role: "User" |
||||
date_created: "2016-02-12 14:47:11" |
||||
- |
||||
id: 164 |
||||
username: "KrisdaSudprasert" |
||||
first_name: "Krisda" |
||||
last_name: "Sudprasert" |
||||
password: "8520af56684c336d8403d0436b59010ea427a602" |
||||
email: "srati_wonnuch@hotmail.com" |
||||
role: "Inactive" |
||||
date_created: "2016-03-02 14:26:40" |
||||
- |
||||
id: 165 |
||||
username: "ThananthornKanokwijitsilp" |
||||
first_name: "Thananthorn" |
||||
last_name: "Kanokwijitsilp" |
||||
password: "bf7b831ec66bdba869dbed4bbf203661ab5a2a6e" |
||||
email: "kanokwijitsilp@imtek.de" |
||||
role: "User" |
||||
date_created: "2016-03-09 10:23:26" |
||||
- |
||||
id: 166 |
||||
username: "AlexanderStraub" |
||||
first_name: "Alexander" |
||||
last_name: "Straub" |
||||
password: "0cabd9350fe909cedb21776957fcf72f00a3210e" |
||||
email: "alexander.straub@jupiter.uni-freiburg.de" |
||||
role: "User" |
||||
date_created: "2016-05-25 09:48:39" |
||||
- |
||||
id: 167 |
||||
username: "TaisukeKojima" |
||||
first_name: "Taisuke" |
||||
last_name: "Kojima" |
||||
password: "da53b8d3a7cbee5aaa7985a6330d8e83206aba5e" |
||||
email: "taisuke.kojima@imtek.uni-freiburg.de" |
||||
role: "User" |
||||
date_created: "2016-06-03 13:09:43" |
||||
- |
||||
id: 168 |
||||
username: "StefanMuellers" |
||||
first_name: "Stefan" |
||||
last_name: "Muellers" |
||||
password: "653a9b2a076040f8246a5143901b0c1628176b27" |
||||
email: "stefan.muellers@imtek.uni-freiburg.de" |
||||
role: "User" |
||||
date_created: "2016-06-20 09:00:46" |
||||
- |
||||
id: 169 |
||||
username: "JonasKost" |
||||
first_name: "Jonas" |
||||
last_name: "Kost" |
||||
password: "7bfa62293f70e1d6ee017642223cdbf7ae227f2d" |
||||
email: "jonas.kost@imtek.uni-freiburg.de" |
||||
role: "User" |
||||
date_created: "2016-07-11 09:57:57" |
||||
- |
||||
id: 170 |
||||
username: "SimonSchoelch" |
||||
first_name: "Simon" |
||||
last_name: "Schoelch" |
||||
password: "fb747c7e015579361c416d4a59fbffe11a21be2f" |
||||
email: "simon.schoelch@gmail.com" |
||||
role: "User" |
||||
date_created: "2016-07-11 14:44:54" |
||||
- |
||||
id: 171 |
||||
username: "ZhuolingDeng" |
||||
first_name: "Zhuoling" |
||||
last_name: "Deng" |
||||
password: "9374fdfaf8bc080b30233819f7f59bdb97b7a292" |
||||
email: "zhuoling.deng@merkur.uni-freiburg.de" |
||||
role: "User" |
||||
date_created: "2016-08-03 10:33:55" |
||||
- |
||||
id: 172 |
||||
username: "TaoZou" |
||||
first_name: "Tao" |
||||
last_name: "Zou" |
||||
password: "0982e0a944c95b1b8d479e9635a4d05c656dd2e1" |
||||
email: "zout1219@msn.cn" |
||||
role: "User" |
||||
date_created: "2016-08-11 10:49:50" |
||||
- |
||||
id: 173 |
||||
username: "StefanBritz" |
||||
first_name: "Stefan" |
||||
last_name: "Britz" |
||||
password: "c0836a94f6b666febedda96600fc8bec2999e086" |
||||
email: "stefan.britz@imtek.uni-freiburg.de" |
||||
role: "User" |
||||
date_created: "2016-11-02 13:18:23" |
||||
- |
||||
id: 174 |
||||
username: "AliceEickenscheidt" |
||||
first_name: "Alice" |
||||
last_name: "Eickenscheidt" |
||||
password: "eb1db0df34b947382e2edeab0300f34f660f2fc9" |
||||
email: "alice.eickenscheidt@imtek.uni-freiburg.d" |
||||
role: "User" |
||||
date_created: "2016-11-03 13:18:08" |
||||
- |
||||
id: 175 |
||||
username: "SarahFontaine" |
||||
first_name: "Sarah" |
||||
last_name: "Fontaine" |
||||
password: "5ace8b390b394d6438d4f691f06613996f61f919" |
||||
email: "sarah.fontaine@uranus.uni-freiburg.de" |
||||
role: "User" |
||||
date_created: "2016-11-09 13:24:59" |
||||
- |
||||
id: 176 |
||||
username: "BidhariPidhatika" |
||||
first_name: "Bidhari" |
||||
last_name: "Pidhatika" |
||||
password: "9a68eb1ffa40cf74e8498d5641d16de456ac37ad" |
||||
email: "bpidhatika@gmail.com" |
||||
role: "User" |
||||
date_created: "2016-12-06 10:18:04" |
||||
- |
||||
id: 177 |
||||
username: "LukasMetzler" |
||||
first_name: "Lukas" |
||||
last_name: "Metzler" |
||||
password: "0778b41d0d82cda54ba3f7176c566272cac16f88" |
||||
email: "lukas.metzler@imtek.de" |
||||
role: "User" |
||||
date_created: "2016-12-22 14:19:15" |
||||
- |
||||
id: 179 |
||||
username: "KatrinTuecking" |
||||
first_name: "Katrin" |
||||
last_name: "Tuecking" |
||||
password: "9fb28ff3449a1436cd964aef6d5aa695ddcc1636" |
||||
email: "katrintuecking@web.de" |
||||
role: "User" |
||||
date_created: "2017-02-07 15:01:30" |
||||
- |
||||
id: 180 |
||||
username: "CarmenEger" |
||||
first_name: "Carmen" |
||||
last_name: "Eger" |
||||
password: "f3bbfcf8a5f5430d020afdaae6e8f2745366bd0e" |
||||
email: "carmen.eger@pluto.uni-freiburg.de" |
||||
role: "User" |
||||
date_created: "2017-02-20 10:32:28" |
||||
- |
||||
id: 181 |
||||
username: "DeepaPantulu" |
||||
first_name: "Deepa" |
||||
last_name: "Pantulu" |
||||
password: "641c92fce59bfa5ad4d80c47d0f932f853ced64c" |
||||
email: "deepa.pantulu@imtek.uni-freiburg.de" |
||||
role: "User" |
||||
date_created: "2017-04-06 10:22:54" |
||||
... |
@ -0,0 +1,55 @@
@@ -0,0 +1,55 @@
|
||||
''' User Authentication and Authorization ''' |
||||
|
||||
from pyramid.authentication import AuthTktAuthenticationPolicy |
||||
from pyramid.authorization import ACLAuthorizationPolicy |
||||
from pyramid.security import Authenticated, Everyone |
||||
|
||||
from .models import User |
||||
|
||||
|
||||
class AuthenticationPolicy(AuthTktAuthenticationPolicy): |
||||
''' How to authenticate users ''' |
||||
|
||||
def authenticated_userid(self, request): |
||||
''' returns the id of an authenticated user |
||||
|
||||
heavy lifting done in get_user() attached to request |
||||
''' |
||||
user = request.user |
||||
if user is not None: |
||||
return user.id |
||||
|
||||
def effective_principals(self, request): |
||||
''' returns a list of principals for the user ''' |
||||
principals = [Everyone] |
||||
user = request.user |
||||
if user is not None: |
||||
principals.append(Authenticated) |
||||
principals.append(user.principal) |
||||
principals.extend(user.role_principals) |
||||
return principals |
||||
|
||||
|
||||
def get_user(request): |
||||
''' retrieves the user object by the unauthenticated user id ''' |
||||
user_id = request.unauthenticated_userid |
||||
if user_id is not None: |
||||
user = request.dbsession.query(User).filter_by(id=user_id).first() |
||||
if user and user.is_active: |
||||
return user |
||||
return None |
||||
|
||||
|
||||
def includeme(config): |
||||
''' initializing authentication and authorization for the Pyramid app |
||||
|
||||
Activate this setup using ``config.include('ordr2.security')``. |
||||
''' |
||||
settings = config.get_settings() |
||||
authn_policy = AuthenticationPolicy( |
||||
settings['auth.secret'], |
||||
hashalg='sha512', |
||||
) |
||||
config.set_authentication_policy(authn_policy) |
||||
config.set_authorization_policy(ACLAuthorizationPolicy()) |
||||
config.add_request_method(get_user, 'user', reify=True) |
@ -0,0 +1,567 @@
@@ -0,0 +1,567 @@
|
||||
/*! |
||||
* Bootstrap Responsive v2.0.0 |
||||
* |
||||
* Copyright 2012 Twitter, Inc |
||||
* Licensed under the Apache License v2.0 |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Designed and built with all the love in the world @twitter by @mdo and @fat. |
||||
*/ |
||||
.hidden { |
||||
display: none; |
||||
visibility: hidden; |
||||
} |
||||
@media (max-width: 480px) { |
||||
.nav-collapse { |
||||
-webkit-transform: translate3d(0, 0, 0); |
||||
} |
||||
.page-header h1 small { |
||||
display: block; |
||||
line-height: 18px; |
||||
} |
||||
input[class*="span"], |
||||
select[class*="span"], |
||||
textarea[class*="span"], |
||||
.uneditable-input { |
||||
display: block; |
||||
width: 100%; |
||||
height: 28px; |
||||
/* Make inputs at least the height of their button counterpart */ |
||||
|
||||
/* Makes inputs behave like true block-level elements */ |
||||
|
||||
-webkit-box-sizing: border-box; |
||||
/* Older Webkit */ |
||||
|
||||
-moz-box-sizing: border-box; |
||||
/* Older FF */ |
||||
|
||||
-ms-box-sizing: border-box; |
||||
/* IE8 */ |
||||
|
||||
box-sizing: border-box; |
||||
/* CSS3 spec*/ |
||||
|
||||
} |
||||
.input-prepend input[class*="span"], .input-append input[class*="span"] { |
||||
width: auto; |
||||
} |
||||
input[type="checkbox"], input[type="radio"] { |
||||
border: 1px solid #ccc; |
||||
} |
||||
.form-horizontal .control-group > label { |
||||
float: none; |
||||
width: auto; |
||||
padding-top: 0; |
||||
text-align: left; |
||||
} |
||||
.form-horizontal .controls { |
||||
margin-left: 0; |
||||
} |
||||
.form-horizontal .control-list { |
||||
padding-top: 0; |
||||
} |
||||
.form-horizontal .form-actions { |
||||
padding-left: 10px; |
||||
padding-right: 10px; |
||||
} |
||||
.modal { |
||||
position: absolute; |
||||
top: 10px; |
||||
left: 10px; |
||||
right: 10px; |
||||
width: auto; |
||||
margin: 0; |
||||
} |
||||
.modal.fade.in { |
||||
top: auto; |
||||
} |
||||
.modal-header .close { |
||||
padding: 10px; |
||||
margin: -10px; |
||||
} |
||||
.carousel-caption { |
||||
position: static; |
||||
} |
||||
} |
||||
@media (max-width: 768px) { |
||||
.container { |
||||
width: auto; |
||||
padding: 0 20px; |
||||
} |
||||
.row-fluid { |
||||
width: 100%; |
||||
} |
||||
.row { |
||||
margin-left: 0; |
||||
} |
||||
.row > [class*="span"], .row-fluid > [class*="span"] { |
||||
float: none; |
||||
display: block; |
||||
width: auto; |
||||
margin: 0; |
||||
} |
||||
} |
||||
@media (min-width: 768px) and (max-width: 980px) { |
||||
.row { |
||||
margin-left: -20px; |
||||
*zoom: 1; |
||||
} |
||||
.row:before, .row:after { |
||||
display: table; |
||||
content: ""; |
||||
} |
||||
.row:after { |
||||
clear: both; |
||||
} |
||||
[class*="span"] { |
||||
float: left; |
||||
margin-left: 20px; |
||||
} |
||||
.span1 { |
||||
width: 42px; |
||||
} |
||||
.span2 { |
||||
width: 104px; |
||||
} |
||||
.span3 { |
||||
width: 166px; |
||||
} |
||||
.span4 { |
||||
width: 228px; |
||||
} |
||||
.span5 { |
||||
width: 290px; |
||||
} |
||||
.span6 { |
||||
width: 352px; |
||||
} |
||||
.span7 { |
||||
width: 414px; |
||||
} |
||||
.span8 { |
||||
width: 476px; |
||||
} |
||||
.span9 { |
||||
width: 538px; |
||||
} |
||||
.span10 { |
||||
width: 600px; |
||||
} |
||||
.span11 { |
||||
width: 662px; |
||||
} |
||||
.span12, .container { |
||||
width: 724px; |
||||
} |
||||
.offset1 { |
||||
margin-left: 82px; |
||||
} |
||||
.offset2 { |
||||
margin-left: 144px; |
||||
} |
||||
.offset3 { |
||||
margin-left: 206px; |
||||
} |
||||
.offset4 { |
||||
margin-left: 268px; |
||||
} |
||||
.offset5 { |
||||
margin-left: 330px; |
||||
} |
||||
.offset6 { |
||||
margin-left: 392px; |
||||
} |
||||
.offset7 { |
||||
margin-left: 454px; |
||||
} |
||||
.offset8 { |
||||
margin-left: 516px; |
||||
} |
||||
.offset9 { |
||||
margin-left: 578px; |
||||
} |
||||
.offset10 { |
||||
margin-left: 640px; |
||||
} |
||||
.offset11 { |
||||
margin-left: 702px; |
||||
} |
||||
.row-fluid { |
||||
width: 100%; |
||||
*zoom: 1; |
||||
} |
||||
.row-fluid:before, .row-fluid:after { |
||||
display: table; |
||||
content: ""; |
||||
} |
||||
.row-fluid:after { |
||||
clear: both; |
||||
} |
||||
.row-fluid > [class*="span"] { |
||||
float: left; |
||||
margin-left: 2.762430939%; |
||||
} |
||||
.row-fluid > [class*="span"]:first-child { |
||||
margin-left: 0; |
||||
} |
||||
.row-fluid .span1 { |
||||
width: 5.801104972%; |
||||
} |
||||
.row-fluid .span2 { |
||||
width: 14.364640883%; |
||||
} |
||||
.row-fluid .span3 { |
||||
width: 22.928176794%; |
||||
} |
||||
.row-fluid .span4 { |
||||
width: 31.491712705%; |
||||
} |
||||
.row-fluid .span5 { |
||||
width: 40.055248616%; |
||||
} |
||||
.row-fluid .span6 { |
||||
width: 48.618784527%; |
||||
} |
||||
.row-fluid .span7 { |
||||
width: 57.182320438000005%; |
||||
} |
||||
.row-fluid .span8 { |
||||
width: 65.74585634900001%; |
||||
} |
||||
.row-fluid .span9 { |
||||
width: 74.30939226%; |
||||
} |
||||
.row-fluid .span10 { |
||||
width: 82.87292817100001%; |
||||
} |
||||
.row-fluid .span11 { |
||||
width: 91.436464082%; |
||||
} |
||||
.row-fluid .span12 { |
||||
width: 99.999999993%; |
||||
} |
||||
input.span1, textarea.span1, .uneditable-input.span1 { |
||||
width: 32px; |
||||
} |
||||
input.span2, textarea.span2, .uneditable-input.span2 { |
||||
width: 94px; |
||||
} |
||||
input.span3, textarea.span3, .uneditable-input.span3 { |
||||
width: 156px; |
||||
} |
||||
input.span4, textarea.span4, .uneditable-input.span4 { |
||||
width: 218px; |
||||
} |
||||
input.span5, textarea.span5, .uneditable-input.span5 { |
||||
width: 280px; |
||||
} |
||||
input.span6, textarea.span6, .uneditable-input.span6 { |
||||
width: 342px; |
||||
} |
||||
input.span7, textarea.span7, .uneditable-input.span7 { |
||||
width: 404px; |
||||
} |
||||
input.span8, textarea.span8, .uneditable-input.span8 { |
||||
width: 466px; |
||||
} |
||||
input.span9, textarea.span9, .uneditable-input.span9 { |
||||
width: 528px; |
||||
} |
||||
input.span10, textarea.span10, .uneditable-input.span10 { |
||||
width: 590px; |
||||
} |
||||
input.span11, textarea.span11, .uneditable-input.span11 { |
||||
width: 652px; |
||||
} |
||||
input.span12, textarea.span12, .uneditable-input.span12 { |
||||
width: 714px; |
||||
} |
||||
} |
||||
@media (max-width: 980px) { |
||||
body { |
||||
padding-top: 0; |
||||
} |
||||
.navbar-fixed-top { |
||||
position: static; |
||||
margin-bottom: 18px; |
||||
} |
||||
.navbar-fixed-top .navbar-inner { |
||||
padding: 5px; |
||||
} |
||||
.navbar .container { |
||||
width: auto; |
||||
padding: 0; |
||||
} |
||||
.navbar .brand { |
||||
padding-left: 10px; |
||||
padding-right: 10px; |
||||
margin: 0 0 0 -5px; |
||||
} |
||||
.navbar .nav-collapse { |
||||
clear: left; |
||||
} |
||||
.navbar .nav { |
||||
float: none; |
||||
margin: 0 0 9px; |
||||
} |
||||
.navbar .nav > li { |
||||
float: none; |
||||
} |
||||
.navbar .nav > li > a { |
||||
margin-bottom: 2px; |
||||
} |
||||
.navbar .nav > .divider-vertical { |
||||
display: none; |
||||
} |
||||
.navbar .nav > li > a, .navbar .dropdown-menu a { |
||||
padding: 6px 15px; |
||||
font-weight: bold; |
||||
color: #999999; |
||||
-webkit-border-radius: 3px; |
||||
-moz-border-radius: 3px; |
||||
border-radius: 3px; |
||||
} |
||||
.navbar .dropdown-menu li + li a { |
||||
margin-bottom: 2px; |
||||
} |
||||
.navbar .nav > li > a:hover, .navbar .dropdown-menu a:hover { |
||||
background-color: #222222; |
||||
} |
||||
.navbar .dropdown-menu { |
||||
position: static; |
||||
top: auto; |
||||
left: auto; |
||||
float: none; |
||||
display: block; |
||||
max-width: none; |
||||
margin: 0 15px; |
||||
padding: 0; |
||||
background-color: transparent; |
||||
border: none; |
||||
-webkit-border-radius: 0; |
||||
-moz-border-radius: 0; |
||||
border-radius: 0; |
||||
-webkit-box-shadow: none; |
||||
-moz-box-shadow: none; |
||||
box-shadow: none; |
||||
} |
||||
.navbar .dropdown-menu:before, .navbar .dropdown-menu:after { |
||||
display: none; |
||||
} |
||||
.navbar .dropdown-menu .divider { |
||||
display: none; |
||||
} |
||||
.navbar-form, .navbar-search { |
||||
float: none; |
||||
padding: 9px 15px; |
||||
margin: 9px 0; |
||||
border-top: 1px solid #222222; |
||||
border-bottom: 1px solid #222222; |
||||
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); |
||||
-moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); |
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); |
||||
} |
||||
.navbar .nav.pull-right { |
||||
float: none; |
||||
margin-left: 0; |
||||
} |
||||
.navbar-static .navbar-inner { |
||||
padding-left: 10px; |
||||
padding-right: 10px; |
||||
} |
||||
.btn-navbar { |
||||
display: block; |
||||
} |
||||
.nav-collapse { |
||||
overflow: hidden; |
||||
height: 0; |
||||
} |
||||
} |
||||
@media (min-width: 980px) { |
||||
.nav-collapse.collapse { |
||||
height: auto !important; |
||||
} |
||||
} |
||||
@media (min-width: 1200px) { |
||||
.row { |
||||
margin-left: -30px; |
||||
*zoom: 1; |
||||
} |
||||
.row:before, .row:after { |
||||
display: table; |
||||
content: ""; |
||||
} |
||||
.row:after { |
||||
clear: both; |
||||
} |
||||
[class*="span"] { |
||||
float: left; |
||||
margin-left: 30px; |
||||
} |
||||
.span1 { |
||||
width: 70px; |
||||
} |
||||
.span2 { |
||||
width: 170px; |
||||
} |
||||
.span3 { |
||||
width: 270px; |
||||
} |
||||
.span4 { |
||||
width: 370px; |
||||
} |
||||
.span5 { |
||||
width: 470px; |
||||
} |
||||
.span6 { |
||||
width: 570px; |
||||
} |
||||
.span7 { |
||||
width: 670px; |
||||
} |
||||
.span8 { |
||||
width: 770px; |
||||
} |
||||
.span9 { |
||||
width: 870px; |
||||
} |
||||
.span10 { |
||||
width: 970px; |
||||
} |
||||
.span11 { |
||||
width: 1070px; |
||||
} |
||||
.span12, .container { |
||||
width: 1170px; |
||||
} |
||||
.offset1 { |
||||
margin-left: 130px; |
||||
} |
||||
.offset2 { |
||||
margin-left: 230px; |
||||
} |
||||
.offset3 { |
||||
margin-left: 330px; |
||||
} |
||||
.offset4 { |
||||
margin-left: 430px; |
||||
} |
||||
.offset5 { |
||||
margin-left: 530px; |
||||
} |
||||
.offset6 { |
||||
margin-left: 630px; |
||||
} |
||||
.offset7 { |
||||
margin-left: 730px; |
||||
} |
||||
.offset8 { |
||||
margin-left: 830px; |
||||
} |
||||
.offset9 { |
||||
margin-left: 930px; |
||||
} |
||||
.offset10 { |
||||
margin-left: 1030px; |
||||
} |
||||
.offset11 { |
||||
margin-left: 1130px; |
||||
} |
||||
.row-fluid { |
||||
width: 100%; |
||||
*zoom: 1; |
||||
} |
||||
.row-fluid:before, .row-fluid:after { |
||||
display: table; |
||||
content: ""; |
||||
} |
||||
.row-fluid:after { |
||||
clear: both; |
||||
} |
||||
.row-fluid > [class*="span"] { |
||||
float: left; |
||||
margin-left: 2.564102564%; |
||||
} |
||||
.row-fluid > [class*="span"]:first-child { |
||||
margin-left: 0; |
||||
} |
||||
.row-fluid .span1 { |
||||
width: 5.982905983%; |
||||
} |
||||
.row-fluid .span2 { |
||||
width: 14.529914530000001%; |
||||
} |
||||
.row-fluid .span3 { |
||||
width: 23.076923077%; |
||||
} |
||||
.row-fluid .span4 { |
||||
width: 31.623931624%; |
||||
} |
||||
.row-fluid .span5 { |
||||
width: 40.170940171000005%; |
||||
} |
||||
.row-fluid .span6 { |
||||
width: 48.717948718%; |
||||
} |
||||
.row-fluid .span7 { |
||||
width: 57.264957265%; |
||||
} |
||||
.row-fluid .span8 { |
||||
width: 65.81196581200001%; |
||||
} |
||||
.row-fluid .span9 { |
||||
width: 74.358974359%; |
||||
} |
||||
.row-fluid .span10 { |
||||
width: 82.905982906%; |
||||
} |
||||
.row-fluid .span11 { |
||||
width: 91.45299145300001%; |
||||
} |
||||
.row-fluid .span12 { |
||||
width: 100%; |
||||
} |
||||
input.span1, textarea.span1, .uneditable-input.span1 { |
||||
width: 60px; |
||||
} |
||||
input.span2, textarea.span2, .uneditable-input.span2 { |
||||
width: 160px; |
||||
} |
||||
input.span3, textarea.span3, .uneditable-input.span3 { |
||||
width: 260px; |
||||
} |
||||
input.span4, textarea.span4, .uneditable-input.span4 { |
||||
width: 360px; |
||||
} |
||||
input.span5, textarea.span5, .uneditable-input.span5 { |
||||
width: 460px; |
||||
} |
||||
input.span6, textarea.span6, .uneditable-input.span6 { |
||||
width: 560px; |
||||
} |
||||
input.span7, textarea.span7, .uneditable-input.span7 { |
||||
width: 660px; |
||||
} |
||||
input.span8, textarea.span8, .uneditable-input.span8 { |
||||
width: 760px; |
||||
} |
||||
input.span9, textarea.span9, .uneditable-input.span9 { |
||||
width: 860px; |
||||
} |
||||
input.span10, textarea.span10, .uneditable-input.span10 { |
||||
width: 960px; |
||||
} |
||||
input.span11, textarea.span11, .uneditable-input.span11 { |
||||
width: 1060px; |
||||
} |
||||
input.span12, textarea.span12, .uneditable-input.span12 { |
||||
width: 1160px; |
||||
} |
||||
.thumbnails { |
||||
margin-left: -30px; |
||||
} |
||||
.thumbnails > li { |
||||
margin-left: 30px; |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,51 @@
@@ -0,0 +1,51 @@
|
||||
* { |
||||
margin: 0; |
||||
} |
||||
body { |
||||
background: url(../img/bg.png) repeat-x scroll 0 0 #FCFCFC; |
||||
margin: 10px 20px; |
||||
} |
||||
h1 { |
||||
font-family: 'Anton', sans-serif; |
||||
font-size: 45px; |
||||
margin-bottom: 25px; |
||||
} |
||||
p { |
||||
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; |
||||
font-size: 16px; |
||||
line-height: 24px; |
||||
margin-bottom: 9px; |
||||
} |
||||
a { |
||||
color: #0088CC; |
||||
text-decoration: none; |
||||
} |
||||
small { |
||||
color: #888888; |
||||
font-size: 10px; |
||||
} |
||||
strong { |
||||
display: block; |
||||
margin: 15px 0 35px; |
||||
font-size: 25px; |
||||
} |
||||
.signature { |
||||
margin-top: 20px; |
||||
} |
||||
.signature .brand { |
||||
font-family: 'Anton', sans-serif; |
||||
font-size: 20px; |
||||
} |
||||
.footprint { |
||||
border-top: 1px solid #E5E5E5; |
||||
margin-top: 20px; |
||||
} |
||||
.footprint .icon-dbs { |
||||
background-image: url(../img/sprite.png); |
||||
background-position: 0px -214px; |
||||
width: 174px; |
||||
display: block; |
||||
height: 26px; |
||||
opacity: 0.5; |
||||
margin: 5px 0 0 120px;; |
||||
} |
@ -0,0 +1,753 @@
@@ -0,0 +1,753 @@
|
||||
html, body { |
||||
height: 100%; |
||||
} |
||||
|
||||
body { |
||||
background-color: #FCFCFC; |
||||
|
||||
} |
||||
|
||||
/*Opera Fix*/ |
||||
body:before {/* thanks to Maleika (Kohoutec)*/ |
||||
content:""; |
||||
height:100%; |
||||
float:left; |
||||
width:0; |
||||
margin-top:-32767px;/* thank you Erik J - negate effect of float*/ |
||||
} |
||||
|
||||
.rounded-box { |
||||
-webkit-border-radius: 5px; |
||||
-moz-border-radius: 5px; |
||||
border-radius: 5px; |
||||
background-color: #EEEEEE; |
||||
padding: 10px; |
||||
} |
||||
.brand { |
||||
font-family: 'Anton', sans-serif; |
||||
color: #049CDB; |
||||
} |
||||
|
||||
.right { |
||||
float: right; |
||||
} |
||||
|
||||
.left { |
||||
float: left; |
||||
} |
||||
|
||||
.clear:after { |
||||
content: "."; |
||||
visibility: hidden; |
||||
display: block; |
||||
height: 0; |
||||
clear: both; |
||||
} |
||||
|
||||
.bordered { |
||||
border-top: 1px solid #EEEEEE; |
||||
border-bottom: 1px solid #EEEEEE; |
||||
padding: 7px 0; |
||||
margin: 35px 0 15px; |
||||
} |
||||
|
||||
/*================================ BT FIXES ================================*/ |
||||
|
||||
.sidebar-nav { |
||||
padding: 9px 0; |
||||
} |
||||
|
||||
.tooltip { |
||||
z-index: 2000 |
||||
} |
||||
|
||||
/*================================= LAYOUT =================================*/ |
||||
|
||||
.content { |
||||
min-height: 100%; |
||||
} |
||||
|
||||
.content:before { |
||||
display: table; |
||||
content: ""; |
||||
height: 40px; |
||||
zoom: 1; |
||||
} |
||||
|
||||
.content.controls:before { |
||||
background: none repeat scroll 0 0 #F1F1F1; |
||||
border-bottom: 1px solid #D2D2D2; |
||||
content: ""; |
||||
display: table; |
||||
height: 86px; |
||||
margin-bottom: -47px; |
||||
width: 100%; |
||||
} |
||||
|
||||
.content .container-fluid, .content .container { |
||||
overflow:auto; |
||||
padding-bottom: 53px; |
||||
} |
||||
|
||||
footer { |
||||
position: relative; |
||||
margin: -53px 20px 0; /* negative value of footer height */ |
||||
height: 53px; |
||||
clear:both; |
||||
text-align: center; |
||||
} |
||||
|
||||
footer a { |
||||
display: inline-block; |
||||
height: 26px; |
||||
opacity: 0.5; |
||||
-webkit-transition: all 0.5s ease 0s; |
||||
-moz-transition: all 0.5s ease 0s; |
||||
-o-transition: all 0.5s ease 0s; |
||||
transition: all 0.5s ease 0s; |
||||
} |
||||
|
||||
footer a:hover { |
||||
opacity: 1; |
||||
} |
||||
|
||||
footer .copy{ |
||||
border-top: 1px solid #E5E5E5; |
||||
padding-top: 15px; |
||||
} |
||||
|
||||
footer .icon-html { |
||||
background-image: url(../img/sprite.png); |
||||
background-position: -174px -214px; |
||||
width: 60px; |
||||
margin-left: 114px; |
||||
} |
||||
|
||||
footer .icon-dbs { |
||||
background-image: url(../img/sprite.png); |
||||
background-position: 0px -214px; |
||||
height: 26px; |
||||
width: 174px; |
||||
} |
||||
|
||||
/*================================ BUTTONS ================================*/ |
||||
|
||||
.btn-flat { |
||||
text-decoration: none; |
||||
text-shadow: 0 1px 0 #fff; |
||||
font: bold 14px Helvetica, Arial, sans-serif; |
||||
color: #444; |
||||
line-height: 17px; |
||||
display: inline-block; |
||||
margin: 0 15px 0 0; |
||||
padding: 5px 15px; |
||||
background: #F3F3F3; |
||||
border: solid 1px #D9D9D9; |
||||
border-radius: 4px; |
||||
-webkit-border-radius: 4px; |
||||
-moz-border-radius: 4px; |
||||
-webkit-transition: border-color .20s; |
||||
-moz-transition: border-color .20s; |
||||
-o-transition: border-color .20s; |
||||
transition: border-color .20s; |
||||
} |
||||
|
||||
a.btn-flat { |
||||
cursor: pointer; |
||||
height: 18px; |
||||
} |
||||
|
||||
button.btn-flat { |
||||
height: 30px; |
||||
} |
||||
|
||||
.btn-flat:hover { |
||||
text-decoration: none; |
||||
background: #F4F4F4; |
||||
border-color: #C0C0C0; |
||||
color: #333; |
||||
} |
||||
|
||||
.btn-flat:active { |
||||
border-color: #0069D6; |
||||
color: #0069D6; |
||||
-moz-box-shadow: inset 0 0 10px #D4D4D4; |
||||
-webkit-box-shadow: inset 0 0 10px #D4D4D4; |
||||
box-shadow: inset 0 0 10px #D4D4D4; |
||||
} |
||||
|
||||
.btn-flat i { |
||||
background-image: url(../img/sprite.png); |
||||
display: inline-block; |
||||
height: 18px; |
||||
width: 18px; |
||||
} |
||||
|
||||
.btn-flat i.pencil { |
||||
background-position: -72px 0px; |
||||
} |
||||
.btn-flat:hover i.pencil { |
||||
background-position: -72px -18px; |
||||
} |
||||
|
||||
.btn-flat i.trash { |
||||
background-position: -90px 0px; |
||||
} |
||||
.btn-flat:hover i.trash { |
||||
background-position: -90px -18px; |
||||
} |
||||
|
||||
.btn-flat i.add { |
||||
background-position: -108px 0px; |
||||
} |
||||
.btn-flat:hover i.add { |
||||
background-position: -108px -18px; |
||||
} |
||||
|
||||
.btn-flat i.abacus { |
||||
background-position: -126px 0px; |
||||
} |
||||
.btn-flat:hover i.abacus { |
||||
background-position: -126px -18px; |
||||
} |
||||
|
||||
.btn-flat i.eye { |
||||
background-position: -144px 0px; |
||||
} |
||||
.btn-flat:hover i.eye { |
||||
background-position: -144px -18px; |
||||
} |
||||
|
||||
.btn-flat i.group { |
||||
background-position: -54px 0; |
||||
} |
||||
|
||||
.btn-flat:hover i.group { |
||||
background-position: -54px -18px; |
||||
} |
||||
|
||||
.btn-flat i.reset { |
||||
background-position: -162px 0; |
||||
} |
||||
|
||||
.btn-flat:hover i.reset { |
||||
background-position: -162px -18px; |
||||
} |
||||
|
||||
.btn-flat i.clip { |
||||
background-position: -180px 0; |
||||
} |
||||
|
||||
.btn-flat:hover i.clip { |
||||
background-position: -180px -18px; |
||||
} |
||||
|
||||
.btn-flat i.download { |
||||
background-position: -198px 0; |
||||
} |
||||
|
||||
.btn-flat:hover i.download { |
||||
background-position: -198px -18px; |
||||
} |
||||
|
||||
.page-controls .btn-group, .page-controls .actions, .page-controls .btn-flat { |
||||
float: left; |
||||
display: block; |
||||
} |
||||
|
||||
.page-controls .btn-group { |
||||
margin-right: 15px; |
||||
} |
||||
|
||||
.btn-group + .btn-group { |
||||
margin-left: 0; |
||||
} |
||||
|
||||
.btn-group .btn-flat { |
||||
position: relative; |
||||
float: left; |
||||
margin-left: -1px; |
||||
margin-right: 0px; |
||||
-webkit-border-radius: 0; |
||||
-moz-border-radius: 0; |
||||
border-radius: 0; |
||||
} |
||||
|
||||
.btn-group .btn-flat:first-child { |
||||
margin-left: 0; |
||||
-webkit-border-top-left-radius: 4px; |
||||
-moz-border-radius-topleft: 4px; |
||||
border-top-left-radius: 4px; |
||||
-webkit-border-bottom-left-radius: 4px; |
||||
-moz-border-radius-bottomleft: 4px; |
||||
border-bottom-left-radius: 4px; |
||||
} |
||||
|
||||
.btn-group .btn-flat:last-child { |
||||
-webkit-border-top-right-radius: 4px; |
||||
-moz-border-radius-topright: 4px; |
||||
border-top-right-radius: 4px; |
||||
-webkit-border-bottom-right-radius: 4px; |
||||
-moz-border-radius-bottomright: 4px; |
||||
border-bottom-right-radius: 4px; |
||||
} |
||||
|
||||
.search [type="search"] { |
||||
padding: 5px 4px; |
||||
} |
||||
|
||||
.search .add-on { |
||||
cursor: pointer; |
||||
padding: 5px; |
||||
} |
||||
|
||||
.search [type="submit"] { |
||||
background-image: url(../img/sprite.png); |
||||
background-position: 0px -68px; |
||||
background-color: #F5F5F5; |
||||
border: none; |
||||
height: 18px; |
||||
width: 18px; |
||||
display: inline-block; |
||||
overflow: hidden; |
||||
text-indent: -9999px; |
||||
} |
||||
|
||||
.search .autocomplete { |
||||
background-image: url(../img/sprite.png); |
||||
background-position: -18px -68px; |
||||
background-color: #F5F5F5; |
||||
height: 18px; |
||||
width: 18px; |
||||
display: inline-block; |
||||
overflow: hidden; |
||||
text-indent: -9999px; |
||||
} |
||||
|
||||
.search .add-on:active { |
||||
border-color: #0069D6; |
||||
color: #0069D6; |
||||
-moz-box-shadow: inset 0 0 3px #D4D4D4; |
||||
-webkit-box-shadow: inset 0 0 3px #D4D4D4; |
||||
box-shadow: inset 0 0 3px #D4D4D4; |
||||
} |
||||
|
||||
.typeahead.dropdown-menu { |
||||
overflow: hidden; |
||||
} |
||||
|
||||
.btn-group.marking-needed, .btn-flat.marking-needed { |
||||
display: none; |
||||
} |
||||
|
||||
/*================================ NAVIGATION ================================*/ |
||||
|
||||
.navbar .brand, .navbar .brand:hover { |
||||
color: #049CDB; |
||||
} |
||||
|
||||
#user-options a { |
||||
color: #555555; |
||||
text-shadow: none; |
||||
} |
||||
|
||||
#user-options .user-name { |
||||
color: #0088CC; |
||||
} |
||||
|
||||
#user-options:hover .user-name { |
||||
text-decoration: underline; |
||||
} |
||||
|
||||
.page-controls { |
||||
display: block; |
||||
height: 30px; |
||||
margin-bottom: 25px; |
||||
padding: 8px 0; |
||||
} |
||||
|
||||
.page-controls:after { |
||||
content: "."; |
||||
visibility: hidden; |
||||
display: block; |
||||
height: 0; |
||||
clear: both; |
||||
} |
||||
|
||||
.page-controls h1 { |
||||
line-height: 30px; |
||||
} |
||||
|
||||
.page-controls form { |
||||
margin-bottom: 0; |
||||
} |
||||
|
||||
.page-controls .input-append.search { |
||||
float: right; |
||||
} |
||||
|
||||
/*================================ TABLES ================================*/ |
||||
|
||||
thead { |
||||
background-color: #E0F3FF; |
||||
border-bottom: 2px solid #0064CD; |
||||
} |
||||
th { |
||||
border-top: none; |
||||
} |
||||
th.sortable { |
||||
cursor: pointer; |
||||
background-color: #E0F3FF; |
||||
} |
||||
|
||||
th.sortable.headerSortDown, th.sortable.headerSortUp { |
||||
background-color: #C7E9FF; |
||||
} |
||||
th, th a { |
||||
color: #0064CD; |
||||
} |
||||
th a:hover { |
||||
text-decoration: none; |
||||
color: #0064CD; |
||||
} |
||||
tbody tr:hover { |
||||
background-color: #FAFAFA; |
||||
} |
||||
|
||||
td.center, th.center { |
||||
text-align: center; |
||||
vertical-align: middle; |
||||
} |
||||
|
||||
table .header:hover:after { |
||||
border-width: 4px 4px 0; |
||||
} |
||||
|
||||
table .action { |
||||
background-image: url(../img/sprite.png); |
||||
margin-bottom: -5px; |
||||
margin-right: 2px; |
||||
height: 16px; |
||||
width: 16px; |
||||
display: inline-block; |
||||
overflow: hidden; |
||||
text-indent: -9999px; |
||||
background-repeat: no-repeat; |
||||
-webkit-transition: background-image 0.20s linear; |
||||
-moz-transition: background-image 0.20s linear; |
||||
-o-transition: background-image 0.20s linear; |
||||
transition: background-image 0.20s linear; |
||||
zoom: 1; |
||||
filter: alpha(opacity=40); |
||||
opacity: 0.4; |
||||
} |
||||
|
||||
table .action:hover { |
||||
filter: alpha(opacity=100); |
||||
opacity: 1; |
||||
} |
||||
|
||||
table .action.delete { |
||||
background-position: 0px -36px; |
||||
} |
||||
|
||||
table .action.delete:hover { |
||||
background-position: 0px -52px; |
||||
} |
||||
|
||||
table .action.edit { |
||||
background-position: -16px -36px; |
||||
} |
||||
|
||||
table .action.edit:hover { |
||||
background-position: -16px -52px; |
||||
} |
||||
|
||||
table .action.eye { |
||||
background-position: -145px 0px; |
||||
} |
||||
|
||||
table .action.eye:hover { |
||||
background-position: -145px -18px; |
||||
} |
||||
|
||||
|
||||
|
||||
|
||||
/*================================ MODALS ================================*/ |
||||
|
||||
.modal form { |
||||
margin: 0; |
||||
} |
||||
|
||||
.modal-body .option { |
||||
font-size: 15px; |
||||
} |
||||
|
||||
.modal-body .option a { |
||||
font-weight: bold; |
||||
} |
||||
|
||||
.modal-body .checklist { |
||||
margin: 0 20px; |
||||
} |
||||
|
||||
.modal-body .checklist:after { |
||||
content: "."; |
||||
visibility: hidden; |
||||
display: block; |
||||
height: 0; |
||||
clear: both; |
||||
} |
||||
|
||||
.modal-body .checklist .checkbox { |
||||
margin-bottom: 10px; |
||||
} |
||||
|
||||
.modal-body .checklist .left { |
||||
float: left; |
||||
width: 50%; |
||||
} |
||||
|
||||
.modal-body .checklist .right { |
||||
float: right; |
||||
width: 50%; |
||||
} |
||||
|
||||
.modal-body .checklist .checkbox input { |
||||
margin-left: -20px; |
||||
} |
||||
|
||||
.modal-body .help-block { |
||||
margin-bottom: 15px; |
||||
} |
||||
|
||||
/*============================= WELCOME PAGE =============================*/ |
||||
|
||||
.welcome .content { |
||||
background: url(../img/bg.png) repeat-x scroll 0 0 #FCFCFC; |
||||
} |
||||
|
||||
#welcome { |
||||
margin: 90px 0 80px; |
||||
text-align: center; |
||||
} |
||||
|
||||
#welcome h1 { |
||||
font-family: 'Anton',sans-serif; |
||||
font-size: 120px; |
||||
line-height: 1; |
||||
margin-bottom: 5px; |
||||
} |
||||
|
||||
#welcome .brand { |
||||
text-transform: none; |
||||
} |
||||
|
||||
#welcome .quote { |
||||
color: #878787; |
||||
font-size: 16px; |
||||
} |
||||
|
||||
/*============================= ACCOUNT =============================*/ |
||||
|
||||
#register-successful, #access-denied { |
||||
margin: 90px 0 50px; |
||||
text-align: center; |
||||
} |
||||
|
||||
#register-successful h1, #access-denied h1 { |
||||
font-family: 'Anton',sans-serif; |
||||
font-size: 80px; |
||||
line-height: 1; |
||||
margin-bottom: 5px; |
||||
} |
||||
|
||||
#access-denied { |
||||
margin-top: 150px; |
||||
} |
||||
|
||||
#access-denied h1 { |
||||
color: #B94A48; |
||||
} |
||||
|
||||
hgroup .info { |
||||
color: #878787; |
||||
font-size: 16px; |
||||
line-height: 24px; |
||||
} |
||||
|
||||
.account.login h1 { |
||||
border-bottom: 1px solid #EEEEEE; |
||||
font-family: 'Anton',sans-serif; |
||||
font-size: 50px; |
||||
line-height: 50px; |
||||
margin: 80px 0 20px; |
||||
padding-bottom: 5px; |
||||
} |
||||
|
||||
/*============================== ADMIN AREA ==============================*/ |
||||
|
||||
.admin-options a:hover [class*="span"] { |
||||
background-color: #EEEEEE; |
||||
-webkit-transition: background-color 0.50s linear; |
||||
-moz-transition: background-color 0.50s linear; |
||||
-o-transition: background-color 0.50s linear; |
||||
transition: background-color 0.50s linear; |
||||
} |
||||
|
||||
.admin-options .option { |
||||
background-image: url(../img/sprite.png); |
||||
background-position: 0px -86px; |
||||
margin: 0 auto; |
||||
width: 128px; |
||||
height: 128px; |
||||
} |
||||
|
||||
.admin-options .option.shop { |
||||
background-position: -128px -86px; |
||||
} |
||||
|
||||
.admin-options h2 { |
||||
margin-top: 5px; |
||||
text-align: center; |
||||
} |
||||
|
||||
.admin-options [class*="span"] { |
||||
padding: 20px 0; |
||||
background-color: #F7F7F7; |
||||
} |
||||
|
||||
/*============================== FAQ ==============================*/ |
||||
|
||||
.faq h1 { |
||||
font-family: 'Anton',sans-serif; |
||||
font-size: 60px; |
||||
line-height: 1; |
||||
margin: 30px 0; |
||||
text-align: center; |
||||
} |
||||
|
||||
.faq h2 { |
||||
color: #FCFCFC; |
||||
font-size: 28px; |
||||
line-height: 1; |
||||
background-color: #049CDB; |
||||
padding: 10px; |
||||
margin-bottom: 5px; |
||||
-webkit-border-radius: 5px; |
||||
-moz-border-radius: 5px; |
||||
border-radius: 5px; |
||||
} |
||||
|
||||
.faq section { |
||||
margin-bottom: 35px; |
||||
} |
||||
|
||||
/*============================= ACTIONS =============================*/ |
||||
|
||||
.action-header { |
||||
margin-bottom: 15px; |
||||
} |
||||
|
||||
/*============================== FORMS ==============================*/ |
||||
|
||||
.control-group.information { |
||||
margin: 0; |
||||
} |
||||
|
||||
.control-group.information p { |
||||
padding: 5px 0; |
||||
color: #AAAAAA; |
||||
} |
||||
|
||||
/*=========================== ORDER SPLASH ===========================*/ |
||||
|
||||
.accordion { |
||||
margin-top: 5px; |
||||
} |
||||
|
||||
.accordion li { |
||||
background-color: #EAEAEA; |
||||
cursor: pointer; |
||||
font-size: 16px; |
||||
margin: 5px 0; |
||||
padding: 12px 10px; |
||||
-webkit-border-radius: 5px; |
||||
-moz-border-radius: 5px; |
||||
border-radius: 5px; |
||||
-webkit-transition: background-color 0.1s ease 0s; |
||||
-moz-transition: background-color 0.1s ease 0s; |
||||
-o-transition: background-color 0.1s ease 0s; |
||||
transition: background-color 0.1s ease 0s; |
||||
} |
||||
|
||||
.accordion li:hover { |
||||
background-color: #DDDDDD; |
||||
} |
||||
|
||||
|
||||
.accordion li span { |
||||
font-size: 13px; |
||||
color: #A0A0A0; |
||||
} |
||||
|
||||
.accordion-heading { |
||||
background-color: #333333; |
||||
padding: 0; |
||||
-webkit-border-radius: 5px; |
||||
-moz-border-radius: 5px; |
||||
border-radius: 5px; |
||||
-webkit-transition: background-color 0.1s ease 0s; |
||||
-moz-transition: background-color 0.1s ease 0s; |
||||
-o-transition: background-color 0.1s ease 0s; |
||||
transition: background-color 0.1s ease 0s; |
||||
} |
||||
|
||||
.accordion-heading a { |
||||
color: #EEEEEE; |
||||
display: block; |
||||
font-size: 18px; |
||||
font-weight: bold; |
||||
line-height: 27px; |
||||
padding: 8px 15px; |
||||
text-decoration: none; |
||||
outline: none; |
||||
-webkit-transition: color 0.1s ease 0s; |
||||
-moz-transition: color 0.1s ease 0s; |
||||
-o-transition: color 0.1s ease 0s; |
||||
transition: color 0.1s ease 0s; |
||||
} |
||||
|
||||
.accordion-heading a:hover { |
||||
color: #049CDB; |
||||
} |
||||
|
||||
/*========================= RESPONSIVE =========================*/ |
||||
|
||||
@media (max-width: 1200px) { |
||||
.page-controls h1 { font-size: 25px; } |
||||
} |
||||
|
||||
|
||||
/*================ STYLES FOR php2python BRANCH ================*/ |
||||
|
||||
input[value="password:mapping"] + div { margin-bottom:10px; } |
||||
input[value="new_password:mapping"] + div { margin-bottom:10px; } |
||||
|
||||
.form-horizontal.user-settings fieldset > .controls { margin-left:0; } |
||||
.user-settings .panel-heading, |
||||
.edit-order .panel-heading { |
||||
font-size:150%; |
||||
padding-top: 20px; |
||||
padding-bottom: 20px; |
||||
margin-bottom: 20px; |
||||
border-bottom: 1px solid #aaa;} |
||||
div.alert a { color:inherit; text-decoration:underline; } |
||||
td.column-pkg, td.column-price, td.column-total, td.column-amount { |
||||
text-align:right;} |
||||
input.number { text-align:right; } |
||||
.moneyinput .amount { width:167px; text-align:right;} |
||||
.moneyinput .currency { width:30px; text-align:center;} |
||||
.controls .form-control-static { padding-top:5px; } |
||||
.form-like-display.form-horizontal .control-group { margin-bottom:0px; } |
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 16 KiB |
@ -0,0 +1,91 @@
@@ -0,0 +1,91 @@
|
||||
/* ========================================================== |
||||
* bootstrap-alert.js v2.0.0 |
||||
* http://twitter.github.com/bootstrap/javascript.html#alerts
|
||||
* ========================================================== |
||||
* Copyright 2012 Twitter, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* ========================================================== */ |
||||
|
||||
|
||||
!function( $ ){ |
||||
|
||||
"use strict" |
||||
|
||||
/* ALERT CLASS DEFINITION |
||||
* ====================== */ |
||||
|
||||
var dismiss = '[data-dismiss="alert"]' |
||||
, Alert = function ( el ) { |
||||
$(el).on('click', dismiss, this.close) |
||||
} |
||||
|
||||
Alert.prototype = { |
||||
|
||||
constructor: Alert |
||||
|
||||
, close: function ( e ) { |
||||
var $this = $(this) |
||||
, selector = $this.attr('data-target') |
||||
, $parent |
||||
|
||||
if (!selector) { |
||||
selector = $this.attr('href') |
||||
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
|
||||
} |
||||
|
||||
$parent = $(selector) |
||||
$parent.trigger('close') |
||||
|
||||
e && e.preventDefault() |
||||
|
||||
$parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent()) |
||||
|
||||
$parent.removeClass('in') |
||||
|
||||
function removeElement() { |
||||
$parent.remove() |
||||
$parent.trigger('closed') |
||||
} |
||||
|
||||
$.support.transition && $parent.hasClass('fade') ? |
||||
$parent.on($.support.transition.end, removeElement) : |
||||
removeElement() |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
/* ALERT PLUGIN DEFINITION |
||||
* ======================= */ |
||||
|
||||
$.fn.alert = function ( option ) { |
||||
return this.each(function () { |
||||
var $this = $(this) |
||||
, data = $this.data('alert') |
||||
if (!data) $this.data('alert', (data = new Alert(this))) |
||||
if (typeof option == 'string') data[option].call($this) |
||||
}) |
||||
} |
||||
|
||||
$.fn.alert.Constructor = Alert |
||||
|
||||
|
||||
/* ALERT DATA-API |
||||
* ============== */ |
||||
|
||||
$(function () { |
||||
$('body').on('click.alert.data-api', dismiss, Alert.prototype.close) |
||||
}) |
||||
|
||||
}( window.jQuery ) |
@ -0,0 +1,136 @@
@@ -0,0 +1,136 @@
|
||||
/* ============================================================= |
||||
* bootstrap-collapse.js v2.0.0 |
||||
* http://twitter.github.com/bootstrap/javascript.html#collapse
|
||||
* ============================================================= |
||||
* Copyright 2012 Twitter, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* ============================================================ */ |
||||
|
||||
!function( $ ){ |
||||
|
||||
"use strict" |
||||
|
||||
var Collapse = function ( element, options ) { |
||||
this.$element = $(element) |
||||
this.options = $.extend({}, $.fn.collapse.defaults, options) |
||||
|
||||
if (this.options["parent"]) { |
||||
this.$parent = $(this.options["parent"]) |
||||
} |
||||
|
||||
this.options.toggle && this.toggle() |
||||
} |
||||
|
||||
Collapse.prototype = { |
||||
|
||||
constructor: Collapse |
||||
|
||||
, dimension: function () { |
||||
var hasWidth = this.$element.hasClass('width') |
||||
return hasWidth ? 'width' : 'height' |
||||
} |
||||
|
||||
, show: function () { |
||||
var dimension = this.dimension() |
||||
, scroll = $.camelCase(['scroll', dimension].join('-')) |
||||
, actives = this.$parent && this.$parent.find('.in') |
||||
, hasData |
||||
|
||||
if (actives && actives.length) { |
||||
hasData = actives.data('collapse') |
||||
actives.collapse('hide') |
||||
hasData || actives.data('collapse', null) |
||||
} |
||||
|
||||
this.$element[dimension](0) |
||||
this.transition('addClass', 'show', 'shown') |
||||
this.$element[dimension](this.$element[0][scroll]) |
||||
|
||||
} |
||||
|
||||
, hide: function () { |
||||
var dimension = this.dimension() |
||||
this.reset(this.$element[dimension]()) |
||||
this.transition('removeClass', 'hide', 'hidden') |
||||
this.$element[dimension](0) |
||||
} |
||||
|
||||
, reset: function ( size ) { |
||||
var dimension = this.dimension() |
||||
|
||||
this.$element |
||||
.removeClass('collapse') |
||||
[dimension](size || 'auto') |
||||
[0].offsetWidth |
||||
|
||||
this.$element.addClass('collapse') |
||||
} |
||||
|
||||
, transition: function ( method, startEvent, completeEvent ) { |
||||
var that = this |
||||
, complete = function () { |
||||
if (startEvent == 'show') that.reset() |
||||
that.$element.trigger(completeEvent) |
||||
} |
||||
|
||||
this.$element |
||||
.trigger(startEvent) |
||||
[method]('in') |
||||
|
||||
$.support.transition && this.$element.hasClass('collapse') ? |
||||
this.$element.one($.support.transition.end, complete) : |
||||
complete() |
||||
} |
||||
|
||||
, toggle: function () { |
||||
this[this.$element.hasClass('in') ? 'hide' : 'show']() |
||||
} |
||||
|
||||
} |
||||
|
||||
/* COLLAPSIBLE PLUGIN DEFINITION |
||||
* ============================== */ |
||||
|
||||
$.fn.collapse = function ( option ) { |
||||
return this.each(function () { |
||||
var $this = $(this) |
||||
, data = $this.data('collapse') |
||||
, options = typeof option == 'object' && option |
||||
if (!data) $this.data('collapse', (data = new Collapse(this, options))) |
||||
if (typeof option == 'string') data[option]() |
||||
}) |
||||
} |
||||
|
||||
$.fn.collapse.defaults = { |
||||
toggle: true |
||||
} |
||||
|
||||
$.fn.collapse.Constructor = Collapse |
||||
|
||||
|
||||
/* COLLAPSIBLE DATA-API |
||||
* ==================== */ |
||||
|
||||
$(function () { |
||||
$('body').on('click.collapse.data-api', '[data-toggle=collapse]', function ( e ) { |
||||
var $this = $(this), href |
||||
, target = $this.attr('data-target') |
||||
|| e.preventDefault() |
||||
|| (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7
|
||||
, option = $(target).data('collapse') ? 'toggle' : $this.data() |
||||
$(target).collapse(option) |
||||
}) |
||||
}) |
||||
|
||||
}( window.jQuery ) |
@ -0,0 +1,92 @@
@@ -0,0 +1,92 @@
|
||||
/* ============================================================ |
||||
* bootstrap-dropdown.js v2.0.0 |
||||
* http://twitter.github.com/bootstrap/javascript.html#dropdowns
|
||||
* ============================================================ |
||||
* Copyright 2012 Twitter, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* ============================================================ */ |
||||
|
||||
|
||||
!function( $ ){ |
||||
|
||||
"use strict" |
||||
|
||||
/* DROPDOWN CLASS DEFINITION |
||||
* ========================= */ |
||||
|
||||
var toggle = '[data-toggle="dropdown"]' |
||||
, Dropdown = function ( element ) { |
||||
var $el = $(element).on('click.dropdown.data-api', this.toggle) |
||||
$('html').on('click.dropdown.data-api', function () { |
||||
$el.parent().removeClass('open') |
||||
}) |
||||
} |
||||
|
||||
Dropdown.prototype = { |
||||
|
||||
constructor: Dropdown |
||||
|
||||
, toggle: function ( e ) { |
||||
var $this = $(this) |
||||
, selector = $this.attr('data-target') |
||||
, $parent |
||||
, isActive |
||||
|
||||
if (!selector) { |
||||
selector = $this.attr('href') |
||||
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
|
||||
} |
||||
|
||||
$parent = $(selector) |
||||
$parent.length || ($parent = $this.parent()) |
||||
|
||||
isActive = $parent.hasClass('open') |
||||
|
||||
clearMenus() |
||||
!isActive && $parent.toggleClass('open') |
||||
|
||||
return false |
||||
} |
||||
|
||||
} |
||||
|
||||
function clearMenus() { |
||||
$(toggle).parent().removeClass('open') |
||||
} |
||||
|
||||
|
||||
/* DROPDOWN PLUGIN DEFINITION |
||||
* ========================== */ |
||||
|
||||
$.fn.dropdown = function ( option ) { |
||||
return this.each(function () { |
||||
var $this = $(this) |
||||
, data = $this.data('dropdown') |
||||
if (!data) $this.data('dropdown', (data = new Dropdown(this))) |
||||
if (typeof option == 'string') data[option].call($this) |
||||
}) |
||||
} |
||||
|
||||
$.fn.dropdown.Constructor = Dropdown |
||||
|
||||
|
||||
/* APPLY TO STANDARD DROPDOWN ELEMENTS |
||||
* =================================== */ |
||||
|
||||
$(function () { |
||||
$('html').on('click.dropdown.data-api', clearMenus) |
||||
$('body').on('click.dropdown.data-api', toggle, Dropdown.prototype.toggle) |
||||
}) |
||||
|
||||
}( window.jQuery ) |
@ -0,0 +1,209 @@
@@ -0,0 +1,209 @@
|
||||
/* ========================================================= |
||||
* bootstrap-modal.js v2.0.0 |
||||
* http://twitter.github.com/bootstrap/javascript.html#modals
|
||||
* ========================================================= |
||||
* Copyright 2012 Twitter, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* ========================================================= */ |
||||
|
||||
|
||||
!function( $ ){ |
||||
|
||||
"use strict" |
||||
|
||||
/* MODAL CLASS DEFINITION |
||||
* ====================== */ |
||||
|
||||
var Modal = function ( content, options ) { |
||||
this.options = $.extend({}, $.fn.modal.defaults, options) |
||||
this.$element = $(content) |
||||
.delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this)) |
||||
} |
||||
|
||||
Modal.prototype = { |
||||
|
||||
constructor: Modal |
||||
|
||||
, toggle: function () { |
||||
return this[!this.isShown ? 'show' : 'hide']() |
||||
} |
||||
|
||||
, show: function () { |
||||
var that = this |
||||
|
||||
if (this.isShown) return |
||||
|
||||
$('body').addClass('modal-open') |
||||
|
||||
this.isShown = true |
||||
this.$element.trigger('show') |
||||
|
||||
escape.call(this) |
||||
backdrop.call(this, function () { |
||||
var transition = $.support.transition && that.$element.hasClass('fade') |
||||
|
||||
!that.$element.parent().length && that.$element.appendTo(document.body) //don't move modals dom position
|
||||
|
||||
that.$element |
||||
.show() |
||||
|
||||
if (transition) { |
||||
that.$element[0].offsetWidth // force reflow
|
||||
} |
||||
|
||||
that.$element.addClass('in') |
||||
|
||||
transition ? |
||||
that.$element.one($.support.transition.end, function () { that.$element.trigger('shown') }) : |
||||
that.$element.trigger('shown') |
||||
|
||||
}) |
||||
} |
||||
|
||||
, hide: function ( e ) { |
||||
e && e.preventDefault() |
||||
|
||||
if (!this.isShown) return |
||||
|
||||
var that = this |
||||
this.isShown = false |
||||
|
||||
$('body').removeClass('modal-open') |
||||
|
||||
escape.call(this) |
||||
|
||||
this.$element |
||||
.trigger('hide') |
||||
.removeClass('in') |
||||
|
||||
$.support.transition && this.$element.hasClass('fade') ? |
||||
hideWithTransition.call(this) : |
||||
hideModal.call(this) |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
/* MODAL PRIVATE METHODS |
||||
* ===================== */ |
||||
|
||||
function hideWithTransition() { |
||||
var that = this |
||||
, timeout = setTimeout(function () { |
||||
that.$element.off($.support.transition.end) |
||||
hideModal.call(that) |
||||
}, 500) |
||||
|
||||
this.$element.one($.support.transition.end, function () { |
||||
clearTimeout(timeout) |
||||
hideModal.call(that) |
||||
}) |
||||
} |
||||
|
||||
function hideModal( that ) { |
||||
this.$element |
||||
.hide() |
||||
.trigger('hidden') |
||||
|
||||
backdrop.call(this) |
||||
} |
||||
|
||||
function backdrop( callback ) { |
||||
var that = this |
||||
, animate = this.$element.hasClass('fade') ? 'fade' : '' |
||||
|
||||
if (this.isShown && this.options.backdrop) { |
||||
var doAnimate = $.support.transition && animate |
||||
|
||||
this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />') |
||||
.appendTo(document.body) |
||||
|
||||
if (this.options.backdrop != 'static') { |
||||
this.$backdrop.click($.proxy(this.hide, this)) |
||||
} |
||||
|
||||
if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
|
||||
|
||||
this.$backdrop.addClass('in') |
||||
|
||||
doAnimate ? |
||||
this.$backdrop.one($.support.transition.end, callback) : |
||||
callback() |
||||
|
||||
} else if (!this.isShown && this.$backdrop) { |
||||
this.$backdrop.removeClass('in') |
||||
|
||||
$.support.transition && this.$element.hasClass('fade')? |
||||
this.$backdrop.one($.support.transition.end, $.proxy(removeBackdrop, this)) : |
||||
removeBackdrop.call(this) |
||||
|
||||
} else if (callback) { |
||||
callback() |
||||
} |
||||
} |
||||
|
||||
function removeBackdrop() { |
||||
this.$backdrop.remove() |
||||
this.$backdrop = null |
||||
} |
||||
|
||||
function escape() { |
||||
var that = this |
||||
if (this.isShown && this.options.keyboard) { |
||||
$(document).on('keyup.dismiss.modal', function ( e ) { |
||||
e.which == 27 && that.hide() |
||||
}) |
||||
} else if (!this.isShown) { |
||||
$(document).off('keyup.dismiss.modal') |
||||
} |
||||
} |
||||
|
||||
|
||||
/* MODAL PLUGIN DEFINITION |
||||
* ======================= */ |
||||
|
||||
$.fn.modal = function ( option ) { |
||||
return this.each(function () { |
||||
var $this = $(this) |
||||
, data = $this.data('modal') |
||||
, options = typeof option == 'object' && option |
||||
if (!data) $this.data('modal', (data = new Modal(this, options))) |
||||
if (typeof option == 'string') data[option]() |
||||
else data.show() |
||||
}) |
||||
} |
||||
|
||||
$.fn.modal.defaults = { |
||||
backdrop: true |
||||
, keyboard: true |
||||
} |
||||
|
||||
$.fn.modal.Constructor = Modal |
||||
|
||||
|
||||
/* MODAL DATA-API |
||||
* ============== */ |
||||
|
||||
$(function () { |
||||
$('body').on('click.modal.data-api', '[data-toggle="modal"]', function ( e ) { |
||||
var $this = $(this), href |
||||
, $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
|
||||
, option = $target.data('modal') ? 'toggle' : $.extend({}, $target.data(), $this.data()) |
||||
|
||||
e.preventDefault() |
||||
$target.modal(option) |
||||
}) |
||||
}) |
||||
|
||||
}( window.jQuery ) |
@ -0,0 +1,270 @@
@@ -0,0 +1,270 @@
|
||||
/* =========================================================== |
||||
* bootstrap-tooltip.js v2.0.0 |
||||
* http://twitter.github.com/bootstrap/javascript.html#tooltips
|
||||
* Inspired by the original jQuery.tipsy by Jason Frame |
||||
* =========================================================== |
||||
* Copyright 2012 Twitter, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* ========================================================== */ |
||||
|
||||
!function( $ ) { |
||||
|
||||
"use strict" |
||||
|
||||
/* TOOLTIP PUBLIC CLASS DEFINITION |
||||
* =============================== */ |
||||
|
||||
var Tooltip = function ( element, options ) { |
||||
this.init('tooltip', element, options) |
||||
} |
||||
|
||||
Tooltip.prototype = { |
||||
|
||||
constructor: Tooltip |
||||
|
||||
, init: function ( type, element, options ) { |
||||
var eventIn |
||||
, eventOut |
||||
|
||||
this.type = type |
||||
this.$element = $(element) |
||||
this.options = this.getOptions(options) |
||||
this.enabled = true |
||||
|
||||
if (this.options.trigger != 'manual') { |
||||
eventIn = this.options.trigger == 'hover' ? 'mouseenter' : 'focus' |
||||
eventOut = this.options.trigger == 'hover' ? 'mouseleave' : 'blur' |
||||
this.$element.on(eventIn, this.options.selector, $.proxy(this.enter, this)) |
||||
this.$element.on(eventOut, this.options.selector, $.proxy(this.leave, this)) |
||||
} |
||||
|
||||
this.options.selector ? |
||||
(this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) : |
||||
this.fixTitle() |
||||
} |
||||
|
||||
, getOptions: function ( options ) { |
||||
options = $.extend({}, $.fn[this.type].defaults, options, this.$element.data()) |
||||
|
||||
if (options.delay && typeof options.delay == 'number') { |
||||
options.delay = { |
||||
show: options.delay |
||||
, hide: options.delay |
||||
} |
||||
} |
||||
|
||||
return options |
||||
} |
||||
|
||||
, enter: function ( e ) { |
||||
var self = $(e.currentTarget)[this.type](this._options).data(this.type) |
||||
|
||||
if (!self.options.delay || !self.options.delay.show) { |
||||
self.show() |
||||
} else { |
||||
self.hoverState = 'in' |
||||
setTimeout(function() { |
||||
if (self.hoverState == 'in') { |
||||
self.show() |
||||
} |
||||
}, self.options.delay.show) |
||||
} |
||||
} |
||||
|
||||
, leave: function ( e ) { |
||||
var self = $(e.currentTarget)[this.type](this._options).data(this.type) |
||||
|
||||
if (!self.options.delay || !self.options.delay.hide) { |
||||
self.hide() |
||||
} else { |
||||
self.hoverState = 'out' |
||||
setTimeout(function() { |
||||
if (self.hoverState == 'out') { |
||||
self.hide() |
||||
} |
||||
}, self.options.delay.hide) |
||||
} |
||||
} |
||||
|
||||
, show: function () { |
||||
var $tip |
||||
, inside |
||||
, pos |
||||
, actualWidth |
||||
, actualHeight |
||||
, placement |
||||
, tp |
||||
|
||||
if (this.hasContent() && this.enabled) { |
||||
$tip = this.tip() |
||||
this.setContent() |
||||
|
||||
if (this.options.animation) { |
||||
$tip.addClass('fade') |
||||
} |
||||
|
||||
placement = typeof this.options.placement == 'function' ? |
||||
this.options.placement.call(this, $tip[0], this.$element[0]) : |
||||
this.options.placement |
||||
|
||||
inside = /in/.test(placement) |
||||
|
||||
$tip |
||||
.remove() |
||||
.css({ top: 0, left: 0, display: 'block' }) |
||||
.appendTo(inside ? this.$element : document.body) |
||||
|
||||
pos = this.getPosition(inside) |
||||
|
||||
actualWidth = $tip[0].offsetWidth |
||||
actualHeight = $tip[0].offsetHeight |
||||
|
||||
switch (inside ? placement.split(' ')[1] : placement) { |
||||
case 'bottom': |
||||
tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2} |
||||
break |
||||
case 'top': |
||||
tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2} |
||||
break |
||||
case 'left': |
||||
tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth} |
||||
break |
||||
case 'right': |
||||
tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width} |
||||
break |
||||
} |
||||
|
||||
$tip |
||||
.css(tp) |
||||
.addClass(placement) |
||||
.addClass('in') |
||||
} |
||||
} |
||||
|
||||
, setContent: function () { |
||||
var $tip = this.tip() |
||||
$tip.find('.tooltip-inner').html(this.getTitle()) |
||||
$tip.removeClass('fade in top bottom left right') |
||||
} |
||||
|
||||
, hide: function () { |
||||
var that = this |
||||
, $tip = this.tip() |
||||
|
||||
$tip.removeClass('in') |
||||
|
||||
function removeWithAnimation() { |
||||
var timeout = setTimeout(function () { |
||||
$tip.off($.support.transition.end).remove() |
||||
}, 500) |
||||
|
||||
$tip.one($.support.transition.end, function () { |
||||
clearTimeout(timeout) |
||||
$tip.remove() |
||||
}) |
||||
} |
||||
|
||||
$.support.transition && this.$tip.hasClass('fade') ? |
||||
removeWithAnimation() : |
||||
$tip.remove() |
||||
} |
||||
|
||||
, fixTitle: function () { |
||||
var $e = this.$element |
||||
if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') { |
||||
$e.attr('data-original-title', $e.attr('title') || '').removeAttr('title') |
||||
} |
||||
} |
||||
|
||||
, hasContent: function () { |
||||
return this.getTitle() |
||||
} |
||||
|
||||
, getPosition: function (inside) { |
||||
return $.extend({}, (inside ? {top: 0, left: 0} : this.$element.offset()), { |
||||
width: this.$element[0].offsetWidth |
||||
, height: this.$element[0].offsetHeight |
||||
}) |
||||
} |
||||
|
||||
, getTitle: function () { |
||||
var title |
||||
, $e = this.$element |
||||
, o = this.options |
||||
|
||||
title = $e.attr('data-original-title') |
||||
|| (typeof o.title == 'function' ? o.title.call($e[0]) : o.title) |
||||
|
||||
title = title.toString().replace(/(^\s*|\s*$)/, "") |
||||
|
||||
return title |
||||
} |
||||
|
||||
, tip: function () { |
||||
return this.$tip = this.$tip || $(this.options.template) |
||||
} |
||||
|
||||
, validate: function () { |
||||
if (!this.$element[0].parentNode) { |
||||
this.hide() |
||||
this.$element = null |
||||
this.options = null |
||||
} |
||||
} |
||||
|
||||
, enable: function () { |
||||
this.enabled = true |
||||
} |
||||
|
||||
, disable: function () { |
||||
this.enabled = false |
||||
} |
||||
|
||||
, toggleEnabled: function () { |
||||
this.enabled = !this.enabled |
||||
} |
||||
|
||||
, toggle: function () { |
||||
this[this.tip().hasClass('in') ? 'hide' : 'show']() |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
/* TOOLTIP PLUGIN DEFINITION |
||||
* ========================= */ |
||||
|
||||
$.fn.tooltip = function ( option ) { |
||||
return this.each(function () { |
||||
var $this = $(this) |
||||
, data = $this.data('tooltip') |
||||
, options = typeof option == 'object' && option |
||||
if (!data) $this.data('tooltip', (data = new Tooltip(this, options))) |
||||
if (typeof option == 'string') data[option]() |
||||
}) |
||||
} |
||||
|
||||
$.fn.tooltip.Constructor = Tooltip |
||||
|
||||
$.fn.tooltip.defaults = { |
||||
animation: true |
||||
, delay: 0 |
||||
, selector: false |
||||
, placement: 'top' |
||||
, trigger: 'hover' |
||||
, title: '' |
||||
, template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>' |
||||
} |
||||
|
||||
}( window.jQuery ) |
@ -0,0 +1,51 @@
@@ -0,0 +1,51 @@
|
||||
/* =================================================== |
||||
* bootstrap-transition.js v2.0.0 |
||||
* http://twitter.github.com/bootstrap/javascript.html#transitions
|
||||
* =================================================== |
||||
* Copyright 2012 Twitter, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* ========================================================== */ |
||||
|
||||
!function( $ ) { |
||||
|
||||
$(function () { |
||||
|
||||
"use strict" |
||||
|
||||
/* CSS TRANSITION SUPPORT (https://gist.github.com/373874) |
||||
* ======================================================= */ |
||||
|
||||
$.support.transition = (function () { |
||||
var thisBody = document.body || document.documentElement |
||||
, thisStyle = thisBody.style |
||||
, support = thisStyle.transition !== undefined || thisStyle.WebkitTransition !== undefined || thisStyle.MozTransition !== undefined || thisStyle.MsTransition !== undefined || thisStyle.OTransition !== undefined |
||||
|
||||
return support && { |
||||
end: (function () { |
||||
var transitionEnd = "TransitionEnd" |
||||
if ( $.browser.webkit ) { |
||||
transitionEnd = "webkitTransitionEnd" |
||||
} else if ( $.browser.mozilla ) { |
||||
transitionEnd = "transitionend" |
||||
} else if ( $.browser.opera ) { |
||||
transitionEnd = "oTransitionEnd" |
||||
} |
||||
return transitionEnd |
||||
}()) |
||||
} |
||||
})() |
||||
|
||||
}) |
||||
|
||||
}( window.jQuery ) |
@ -0,0 +1,335 @@
@@ -0,0 +1,335 @@
|
||||
/* ============================================================= |
||||
* bootstrap-typeahead.js v2.3.2 |
||||
* http://getbootstrap.com/2.3.2/javascript.html#typeahead
|
||||
* ============================================================= |
||||
* Copyright 2013 Twitter, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* ============================================================ */ |
||||
|
||||
|
||||
!function($){ |
||||
|
||||
"use strict"; // jshint ;_;
|
||||
|
||||
|
||||
/* TYPEAHEAD PUBLIC CLASS DEFINITION |
||||
* ================================= */ |
||||
|
||||
var Typeahead = function (element, options) { |
||||
this.$element = $(element) |
||||
this.options = $.extend({}, $.fn.typeahead.defaults, options) |
||||
this.matcher = this.options.matcher || this.matcher |
||||
this.sorter = this.options.sorter || this.sorter |
||||
this.highlighter = this.options.highlighter || this.highlighter |
||||
this.updater = this.options.updater || this.updater |
||||
this.source = this.options.source |
||||
this.$menu = $(this.options.menu) |
||||
this.shown = false |
||||
this.listen() |
||||
} |
||||
|
||||
Typeahead.prototype = { |
||||
|
||||
constructor: Typeahead |
||||
|
||||
, select: function () { |
||||
var val = this.$menu.find('.active').attr('data-value') |
||||
this.$element |
||||
.val(this.updater(val)) |
||||
.change() |
||||
return this.hide() |
||||
} |
||||
|
||||
, updater: function (item) { |
||||
return item |
||||
} |
||||
|
||||
, show: function () { |
||||
var pos = $.extend({}, this.$element.position(), { |
||||
height: this.$element[0].offsetHeight |
||||
}) |
||||
|
||||
this.$menu |
||||
.insertAfter(this.$element) |
||||
.css({ |
||||
top: pos.top + pos.height |
||||
, left: pos.left |
||||
}) |
||||
.show() |
||||
|
||||
this.shown = true |
||||
return this |
||||
} |
||||
|
||||
, hide: function () { |
||||
this.$menu.hide() |
||||
this.shown = false |
||||
return this |
||||
} |
||||
|
||||
, lookup: function (event) { |
||||
var items |
||||
|
||||
this.query = this.$element.val() |
||||
|
||||
if (!this.query || this.query.length < this.options.minLength) { |
||||
return this.shown ? this.hide() : this |
||||
} |
||||
|
||||
items = $.isFunction(this.source) ? this.source(this.query, $.proxy(this.process, this)) : this.source |
||||
|
||||
return items ? this.process(items) : this |
||||
} |
||||
|
||||
, process: function (items) { |
||||
var that = this |
||||
|
||||
items = $.grep(items, function (item) { |
||||
return that.matcher(item) |
||||
}) |
||||
|
||||
items = this.sorter(items) |
||||
|
||||
if (!items.length) { |
||||
return this.shown ? this.hide() : this |
||||
} |
||||
|
||||
return this.render(items.slice(0, this.options.items)).show() |
||||
} |
||||
|
||||
, matcher: function (item) { |
||||
return ~item.toLowerCase().indexOf(this.query.toLowerCase()) |
||||
} |
||||
|
||||
, sorter: function (items) { |
||||
var beginswith = [] |
||||
, caseSensitive = [] |
||||
, caseInsensitive = [] |
||||
, item |
||||
|
||||
while (item = items.shift()) { |
||||
if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item) |
||||
else if (~item.indexOf(this.query)) caseSensitive.push(item) |
||||
else caseInsensitive.push(item) |
||||
} |
||||
|
||||
return beginswith.concat(caseSensitive, caseInsensitive) |
||||
} |
||||
|
||||
, highlighter: function (item) { |
||||
var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&') |
||||
return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) { |
||||
return '<strong>' + match + '</strong>' |
||||
}) |
||||
} |
||||
|
||||
, render: function (items) { |
||||
var that = this |
||||
|
||||
items = $(items).map(function (i, item) { |
||||
i = $(that.options.item).attr('data-value', item) |
||||
i.find('a').html(that.highlighter(item)) |
||||
return i[0] |
||||
}) |
||||
|
||||
items.first().addClass('active') |
||||
this.$menu.html(items) |
||||
return this |
||||
} |
||||
|
||||
, next: function (event) { |
||||
var active = this.$menu.find('.active').removeClass('active') |
||||
, next = active.next() |
||||
|
||||
if (!next.length) { |
||||
next = $(this.$menu.find('li')[0]) |
||||
} |
||||
|
||||
next.addClass('active') |
||||
} |
||||
|
||||
, prev: function (event) { |
||||
var active = this.$menu.find('.active').removeClass('active') |
||||
, prev = active.prev() |
||||
|
||||
if (!prev.length) { |
||||
prev = this.$menu.find('li').last() |
||||
} |
||||
|
||||
prev.addClass('active') |
||||
} |
||||
|
||||
, listen: function () { |
||||
this.$element |
||||
.on('focus', $.proxy(this.focus, this)) |
||||
.on('blur', $.proxy(this.blur, this)) |
||||
.on('keypress', $.proxy(this.keypress, this)) |
||||
.on('keyup', $.proxy(this.keyup, this)) |
||||
|
||||
if (this.eventSupported('keydown')) { |
||||
this.$element.on('keydown', $.proxy(this.keydown, this)) |
||||
} |
||||
|
||||
this.$menu |
||||
.on('click', $.proxy(this.click, this)) |
||||
.on('mouseenter', 'li', $.proxy(this.mouseenter, this)) |
||||
.on('mouseleave', 'li', $.proxy(this.mouseleave, this)) |
||||
} |
||||
|
||||
, eventSupported: function(eventName) { |
||||
var isSupported = eventName in this.$element |
||||
if (!isSupported) { |
||||
this.$element.setAttribute(eventName, 'return;') |
||||
isSupported = typeof this.$element[eventName] === 'function' |
||||
} |
||||
return isSupported |
||||
} |
||||
|
||||
, move: function (e) { |
||||
if (!this.shown) return |
||||
|
||||
switch(e.keyCode) { |
||||
case 9: // tab
|
||||
case 13: // enter
|
||||
case 27: // escape
|
||||
e.preventDefault() |
||||
break |
||||
|
||||
case 38: // up arrow
|
||||
e.preventDefault() |
||||
this.prev() |
||||
break |
||||
|
||||
case 40: // down arrow
|
||||
e.preventDefault() |
||||
this.next() |
||||
break |
||||
} |
||||
|
||||
e.stopPropagation() |
||||
} |
||||
|
||||
, keydown: function (e) { |
||||
this.suppressKeyPressRepeat = ~$.inArray(e.keyCode, [40,38,9,13,27]) |
||||
this.move(e) |
||||
} |
||||
|
||||
, keypress: function (e) { |
||||
if (this.suppressKeyPressRepeat) return |
||||
this.move(e) |
||||
} |
||||
|
||||
, keyup: function (e) { |
||||
switch(e.keyCode) { |
||||
case 40: // down arrow
|
||||
case 38: // up arrow
|
||||
case 16: // shift
|
||||
case 17: // ctrl
|
||||
case 18: // alt
|
||||
break |
||||
|
||||
case 9: // tab
|
||||
case 13: // enter
|
||||
if (!this.shown) return |
||||
this.select() |
||||
break |
||||
|
||||
case 27: // escape
|
||||
if (!this.shown) return |
||||
this.hide() |
||||
break |
||||
|
||||
default: |
||||
this.lookup() |
||||
} |
||||
|
||||
e.stopPropagation() |
||||
e.preventDefault() |
||||
} |
||||
|
||||
, focus: function (e) { |
||||
this.focused = true |
||||
} |
||||
|
||||
, blur: function (e) { |
||||
this.focused = false |
||||
if (!this.mousedover && this.shown) this.hide() |
||||
} |
||||
|
||||
, click: function (e) { |
||||
e.stopPropagation() |
||||
e.preventDefault() |
||||
this.select() |
||||
this.$element.focus() |
||||
} |
||||
|
||||
, mouseenter: function (e) { |
||||
this.mousedover = true |
||||
this.$menu.find('.active').removeClass('active') |
||||
$(e.currentTarget).addClass('active') |
||||
} |
||||
|
||||
, mouseleave: function (e) { |
||||
this.mousedover = false |
||||
if (!this.focused && this.shown) this.hide() |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
/* TYPEAHEAD PLUGIN DEFINITION |
||||
* =========================== */ |
||||
|
||||
var old = $.fn.typeahead |
||||
|
||||
$.fn.typeahead = function (option) { |
||||
return this.each(function () { |
||||
var $this = $(this) |
||||
, data = $this.data('typeahead') |
||||
, options = typeof option == 'object' && option |
||||
if (!data) $this.data('typeahead', (data = new Typeahead(this, options))) |
||||
if (typeof option == 'string') data[option]() |
||||
}) |
||||
} |
||||
|
||||
$.fn.typeahead.defaults = { |
||||
source: [] |
||||
, items: 8 |
||||
, menu: '<ul class="typeahead dropdown-menu"></ul>' |
||||
, item: '<li><a href="#"></a></li>' |
||||
, minLength: 1 |
||||
} |
||||
|
||||
$.fn.typeahead.Constructor = Typeahead |
||||
|
||||
|
||||
/* TYPEAHEAD NO CONFLICT |
||||
* =================== */ |
||||
|
||||
$.fn.typeahead.noConflict = function () { |
||||
$.fn.typeahead = old |
||||
return this |
||||
} |
||||
|
||||
|
||||
/* TYPEAHEAD DATA-API |
||||
* ================== */ |
||||
|
||||
$(document).on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) { |
||||
var $this = $(this) |
||||
if ($this.data('typeahead')) return |
||||
$this.typeahead($this.data()) |
||||
}) |
||||
|
||||
}(window.jQuery); |
@ -0,0 +1,118 @@
@@ -0,0 +1,118 @@
|
||||
$(document).ready(function() { |
||||
|
||||
function capitalize(s){ |
||||
return s.replace( /\b./g, function(a){ return a.toUpperCase(); } ); |
||||
}; |
||||
|
||||
function generate_user_name() { |
||||
var first_name = $('.registration .item-first_name input').val(); |
||||
var last_name = $('.registration .item-last_name input').val(); |
||||
var user_name = capitalize(first_name) + capitalize(last_name); |
||||
return user_name.replace( /\s/g, '') |
||||
}; |
||||
|
||||
// autocomplete of the username (registration form)
|
||||
|
||||
//$('.registration .item-user_name input').val( generate_user_name() );
|
||||
$('.registration .item-first_name input').keyup(function() { |
||||
$('.registration .item-user_name input').val( generate_user_name() ); |
||||
}); |
||||
$('.registration .item-last_name input').keyup(function() { |
||||
$('.registration .item-user_name input').val( generate_user_name() ); |
||||
}); |
||||
|
||||
|
||||
|
||||
// "dispatch" clicking a th to the corresponding anchor
|
||||
$('th.sortable').click( function() { |
||||
window.location = $(this).children('a').attr('href'); |
||||
}); |
||||
|
||||
// "dispatch" clicking list item in collapse to the corresponding anchor
|
||||
$('.accordion li').click( function() { |
||||
window.location = $(this).children('a').attr('href'); |
||||
}); |
||||
|
||||
// the mark all
|
||||
$('input[name="mark_all"]').click(function() { |
||||
var checked_status = this.checked; |
||||
$('input[name*="marked"]').each(function() { |
||||
this.checked = checked_status; |
||||
}); |
||||
if( $('input[name="mark_all"]').is(':checked') && $('.marking-needed').is(':hidden') ) { |
||||
$('.marking-needed').fadeIn("fast"); |
||||
} else if( $('input[name="mark_all"]').is(':checked') && $('.marking-needed').is(':visible') ) { |
||||
// do nothing
|
||||
} else { |
||||
$('.marking-needed').fadeOut("fast"); |
||||
} |
||||
}); |
||||
|
||||
// show actions only if some data is marked
|
||||
$('input[name="marked"]').change(function() { |
||||
if( $('input[name*="marked"]').is(':checked') ) { |
||||
if( $('.marking-needed').is(':hidden') ) |
||||
$('.marking-needed').fadeIn("slow"); |
||||
} else { |
||||
$('.marking-needed').fadeOut("slow"); |
||||
} |
||||
}); |
||||
|
||||
// quick-actions
|
||||
$('.quick-action a[data-value]').click(function(event) { |
||||
event.preventDefault(); |
||||
var value = $(this).attr('data-value'); |
||||
var action = $(this).closest('div').attr('data-action'); |
||||
$('select[name*='+action+']').each(function() { |
||||
$(this).val(value); |
||||
}); |
||||
}); |
||||
|
||||
// submit search
|
||||
$('.search-form input[type="search"]').keypress(function(event) { |
||||
if( event.keyCode == 13 && $('.typeahead.dropdown-menu').is(':hidden') ) |
||||
$('.search-form').submit(); |
||||
}); |
||||
|
||||
// aside filter
|
||||
$('.filter input[type="checkbox"]').click( function() { |
||||
if( $(this).is(':checked') ) { |
||||
$(this).parents('li').addClass('active'); |
||||
} else { |
||||
$(this).parents('li').removeClass('active'); |
||||
} |
||||
}); |
||||
|
||||
// tooltips
|
||||
$('.page-controls').tooltip({ |
||||
selector: "[rel=tooltip]" |
||||
}); |
||||
|
||||
// calculator
|
||||
if ( $('.item-unit_price input[name="amount"]').length ) { |
||||
if( $('.item-unit_price input[name="amount"]').val() != '' && $('input[name="quantity"]').val() != '' ){ |
||||
var total = $('.item-unit_price input[name="amount"]').val().replace(",", "") * $('input[name="quantity"]').val(); |
||||
total = Math.round(total*100)/100; |
||||
$('.item-total_price input[name="amount"]').attr( 'value', total ); |
||||
} |
||||
$('.item-unit_price input[name="amount"], input[name="quantity"]').keyup(function() { |
||||
var total = $('.item-unit_price input[name="amount"]').val().replace(",", "") * $('input[name="quantity"]').val(); |
||||
total = Math.round(total*100)/100; |
||||
$('.item-total_price input[name="amount"]').attr( 'value', total ); |
||||
}); |
||||
} |
||||
|
||||
if ( $('.item-unit_price input[name="currency"]').length ) { |
||||
// added currency to total price
|
||||
if( $('.item-unit_price input[name="currency"]').val() != '' ){ |
||||
$('.item-total_price input[name="currency"]').attr( 'value', $('.item-unit_price input[name="currency"]').val() ); |
||||
} |
||||
$('.item-unit_price input[name="currency"]').keyup(function() { |
||||
$('.item-total_price input[name="currency"]').attr( 'value', $('.item-unit_price input[name="currency"]').val() ); |
||||
}); |
||||
$('.item-unit_price input[name="currency"]').change(function() { |
||||
$('.item-total_price input[name="currency"]').attr( 'value', $('.item-unit_price input[name="currency"]').val() ); |
||||
}); |
||||
} |
||||
|
||||
}); |
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
@ -1,154 +0,0 @@
@@ -1,154 +0,0 @@
|
||||
@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700); |
||||
body { |
||||
font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; |
||||
font-weight: 300; |
||||
color: #ffffff; |
||||
background: #bc2131; |
||||
} |
||||
h1, |
||||
h2, |
||||
h3, |
||||
h4, |
||||
h5, |
||||
h6 { |
||||
font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; |
||||
font-weight: 300; |
||||
} |
||||
p { |
||||
font-weight: 300; |
||||
} |
||||
.font-normal { |
||||
font-weight: 400; |
||||
} |
||||
.font-semi-bold { |
||||
font-weight: 600; |
||||
} |
||||
.font-bold { |
||||
font-weight: 700; |
||||
} |
||||
.starter-template { |
||||
margin-top: 250px; |
||||
} |
||||
.starter-template .content { |
||||
margin-left: 10px; |
||||
} |
||||
.starter-template .content h1 { |
||||
margin-top: 10px; |
||||
font-size: 60px; |
||||
} |
||||
.starter-template .content h1 .smaller { |
||||
font-size: 40px; |
||||
color: #f2b7bd; |
||||
} |
||||
.starter-template .content .lead { |
||||
font-size: 25px; |
||||
color: #f2b7bd; |
||||
} |
||||
.starter-template .content .lead .font-normal { |
||||
color: #ffffff; |
||||
} |
||||
.starter-template .links { |
||||
float: right; |
||||
right: 0; |
||||
margin-top: 125px; |
||||
} |
||||
.starter-template .links ul { |
||||
display: block; |
||||
padding: 0; |
||||
margin: 0; |
||||
} |
||||
.starter-template .links ul li { |
||||
list-style: none; |
||||
display: inline; |
||||
margin: 0 10px; |
||||
} |
||||
.starter-template .links ul li:first-child { |
||||
margin-left: 0; |
||||
} |
||||
.starter-template .links ul li:last-child { |
||||
margin-right: 0; |
||||
} |
||||
.starter-template .links ul li.current-version { |
||||
color: #f2b7bd; |
||||
font-weight: 400; |
||||
} |
||||
.starter-template .links ul li a, a { |
||||
color: #f2b7bd; |
||||
text-decoration: underline; |
||||
} |
||||
.starter-template .links ul li a:hover, a:hover { |
||||
color: #ffffff; |
||||
text-decoration: underline; |
||||
} |
||||
.starter-template .links ul li .icon-muted { |
||||
color: #eb8b95; |
||||
margin-right: 5px; |
||||
} |
||||
.starter-template .links ul li:hover .icon-muted { |
||||
color: #ffffff; |
||||
} |
||||
.starter-template .copyright { |
||||
margin-top: 10px; |
||||
font-size: 0.9em; |
||||
color: #f2b7bd; |
||||
text-transform: lowercase; |
||||
float: right; |
||||
right: 0; |
||||
} |
||||
@media (max-width: 1199px) { |
||||
.starter-template .content h1 { |
||||
font-size: 45px; |
||||
} |
||||
.starter-template .content h1 .smaller { |
||||
font-size: 30px; |
||||
} |
||||
.starter-template .content .lead { |
||||
font-size: 20px; |
||||
} |
||||
} |
||||
@media (max-width: 991px) { |
||||
.starter-template { |
||||
margin-top: 0; |
||||
} |
||||
.starter-template .logo { |
||||
margin: 40px auto; |
||||
} |
||||
.starter-template .content { |
||||
margin-left: 0; |
||||
text-align: center; |
||||
} |
||||
.starter-template .content h1 { |
||||
margin-bottom: 20px; |
||||
} |
||||
.starter-template .links { |
||||
float: none; |
||||
text-align: center; |
||||
margin-top: 60px; |
||||
} |
||||
.starter-template .copyright { |
||||
float: none; |
||||
text-align: center; |
||||
} |
||||
} |
||||
@media (max-width: 767px) { |
||||
.starter-template .content h1 .smaller { |
||||
font-size: 25px; |
||||
display: block; |
||||
} |
||||
.starter-template .content .lead { |
||||
font-size: 16px; |
||||
} |
||||
.starter-template .links { |
||||
margin-top: 40px; |
||||
} |
||||
.starter-template .links ul li { |
||||
display: block; |
||||
margin: 0; |
||||
} |
||||
.starter-template .links ul li .icon-muted { |
||||
display: none; |
||||
} |
||||
.starter-template .copyright { |
||||
margin-top: 20px; |
||||
} |
||||
} |
@ -1,8 +0,0 @@
@@ -1,8 +0,0 @@
|
||||
{% extends "layout.jinja2" %} |
||||
|
||||
{% block content %} |
||||
<div class="content"> |
||||
<h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy scaffold</span></h1> |
||||
<p class="lead"><span class="font-semi-bold">404</span> Page Not Found</p> |
||||
</div> |
||||
{% endblock content %} |
@ -0,0 +1,39 @@
@@ -0,0 +1,39 @@
|
||||
{% extends "ordr2:templates/layout.jinja2" %} |
||||
{% import 'ordr2:templates/macros.jinja2' as macros with context %} |
||||
|
||||
{% block subtitle %} Login {% endblock subtitle %} |
||||
|
||||
{% block content %} |
||||
<div class="content"> |
||||
<div class="container"> |
||||
<div class="row"> |
||||
<div class="span6 offset3"> |
||||
<h1>Log in</h1> |
||||
{{ macros.flash_messages() }} |
||||
</div> |
||||
</div> |
||||
<div class="row"> |
||||
<div class="span6 offset3"> |
||||
<form action="{{request.resource_url(request.root, 'account', 'login')}}" method="post" class="form-horizontal"> |
||||
<fieldset class="control-group"> |
||||
<label for="input01" class="control-label">Username</label> |
||||
<div class="controls"> |
||||
<input type="text" name="username" class="span3" size="30"> |
||||
</div> |
||||
</fieldset> |
||||
<fieldset class="control-group"> |
||||
<label for="password" class="control-label">Password</label> |
||||
<div class="controls"> |
||||
<input type="password" name="password" class="span3" size="30"> |
||||
</div> |
||||
</fieldset> |
||||
<fieldset class="form-actions"> |
||||
<input type="hidden" name="csrf_token" value="{{get_csrf_token()}}"> |
||||
<button class="btn primary large" type="submit">Log in</button> |
||||
</fieldset> |
||||
</form> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
{% endblock content %} |
@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
|
||||
{% extends "ordr2:templates/layout.jinja2" %} |
||||
{% import 'ordr2:templates/macros.jinja2' as macros with context %} |
||||
|
||||
{% block subtitle %} Account | Reset Password {% endblock subtitle %} |
||||
|
||||
{% block content %} |
||||
<div class="content controls"> |
||||
|
||||
<div class="container-fluid"> |
||||
<div class="row-fluid"> |
||||
<div class="page-controls"> |
||||
<h1>Reset your Password</h1> |
||||
</div> |
||||
</div> |
||||
<div class="row"> |
||||
<div class="span8"> |
||||
{{ macros.flash_messages() }} |
||||
{{form.render()|safe}} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
</div> |
||||
{% endblock content %} |
@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
|
||||
{% extends "ordr2:templates/layout.jinja2" %} |
||||
{% import 'ordr2:templates/macros.jinja2' as macros with context %} |
||||
|
||||
{% block subtitle %} Register {% endblock subtitle %} |
||||
|
||||
{% block content %} |
||||
<div class="content controls"> |
||||
|
||||
<div class="container-fluid"> |
||||
<div class="row-fluid"> |
||||
<div class="page-controls"> |
||||
<h1>Register</h1> |
||||
</div> |
||||
</div> |
||||
<div class="row-fluid"> |
||||
<div class="span10"> |
||||
{{form.render()|safe}} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
</div> |
||||
{% endblock content %} |
@ -0,0 +1,32 @@
@@ -0,0 +1,32 @@
|
||||
{% extends "ordr2:templates/layout.jinja2" %} |
||||
{% import 'ordr2:templates/macros.jinja2' as macros with context %} |
||||
|
||||
{% block subtitle %} Register {% endblock subtitle %} |
||||
|
||||
{% block content %} |
||||
<div class="content"> |
||||
|
||||
<div class="container"> |
||||
<div class="row"> |
||||
|
||||
<div class="span12"> |
||||
<hgroup id="register-successful"> |
||||
<h1>Registration successful!!</h1> |
||||
</hgroup> |
||||
</div> |
||||
|
||||
<div class="span10 offset1"> |
||||
{{ macros.flash_messages() }} |
||||
<div class="alert alert-info"> |
||||
<h4 class="alert-heading">Not so fast!</h4> |
||||
<p> |
||||
Before you can log in your account has first to be activated by an admin. |
||||
So lean back and read through the <a href="{{ request.resource_url(request.root, 'faq') }}">FAQ Page</a>. |
||||
Maybe the information will help you use this software better. |
||||
</p> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
{% endblock content %} |
@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
|
||||
{% extends "ordr2:templates/layout.jinja2" %} |
||||
{% import 'ordr2:templates/macros.jinja2' as macros with context %} |
||||
|
||||
{% block subtitle %} Account | Settings {% endblock subtitle %} |
||||
|
||||
{% block content %} |
||||
<div class="content controls"> |
||||
|
||||
<div class="container-fluid"> |
||||
<div class="row-fluid"> |
||||
<div class="page-controls"> |
||||
<h1>Account Settings</h1> |
||||
</div> |
||||
</div> |
||||
<div class="row"> |
||||
<div class="span8"> |
||||
{{ macros.flash_messages() }} |
||||
{{form.render()|safe}} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
</div> |
||||
{% endblock content %} |
@ -0,0 +1,36 @@
@@ -0,0 +1,36 @@
|
||||
{% extends "ordr2:templates/layout.jinja2" %} |
||||
{% import 'ordr2:templates/macros.jinja2' as macros with context %} |
||||
|
||||
{% block subtitle %} Admin {% endblock subtitle %} |
||||
|
||||
{% block content %} |
||||
<div class="content controls"> |
||||
|
||||
<div class="container-fluid"> |
||||
|
||||
<div class="row-fluid"> |
||||
<div class="page-controls"> |
||||
<h1>Register</h1> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="row-fluid">{{ macros.flash_messages() }}</div> |
||||
|
||||
<div class="row admin-options"> |
||||
<a href="{{ request.resource_url(context, 'users') }}"> |
||||
<div class="span4 rounded-box"> |
||||
<div class="option user"></div> |
||||
<h2>Mangage Users</h2> |
||||
</div> |
||||
</a> |
||||
<a href="{{ request.resource_url(context, 'consumables') }}"> |
||||
<div class="span4 rounded-box"> |
||||
<div class="option shop"></div> |
||||
<h2>Manage Consumables</h2> |
||||
</div> |
||||
</a> |
||||
</div> |
||||
</div> |
||||
|
||||
</div> |
||||
{% endblock content %} |
@ -0,0 +1,70 @@
@@ -0,0 +1,70 @@
|
||||
{% extends "ordr2:templates/layout.jinja2" %} |
||||
{% import 'ordr2:templates/macros.jinja2' as macros with context %} |
||||
|
||||
{% block subtitle %} Admin | Consumable | Confirm Delete {% endblock subtitle %} |
||||
|
||||
{% block content %} |
||||
<div class="content controls"> |
||||
|
||||
<div class="container-fluid"> |
||||
<div class="row-fluid"> |
||||
<div class="page-controls"> |
||||
<h1>Delete Consumable{{ 's' if consumables|length > 1 }}</h1> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="row"> |
||||
<div class="span10"> |
||||
|
||||
<div class="action-header"> |
||||
<h3>The following consumable{{ 's' if consumables|length > 1 }} will be deleted:</h3> |
||||
</div> |
||||
|
||||
<form action="{{ request.resource_url(context, 'delete') }}" method="POST" class="action"> |
||||
<input type="hidden" name="csrf_token" value="{{get_csrf_token()}}"> |
||||
|
||||
<table class="table"> |
||||
<thead> |
||||
<th>Cas / Description</th> |
||||
<th>Category</th> |
||||
<th>Catalog Nr</th> |
||||
<th>Vendor</th> |
||||
<th>Package Size</th> |
||||
<th>Unit Prize</th> |
||||
<th>Currency</th> |
||||
</thead> |
||||
|
||||
<tbody> |
||||
{% for consumable in consumables %} |
||||
<tr> |
||||
<td class="column-user"> |
||||
<input type="hidden" name="consumable" value="{{ consumable.id }}"> |
||||
{{ consumable.cas_description }} |
||||
</td> |
||||
<td class="column-category">{{ consumable.category.name|capitalize }}</td> |
||||
<td class="column-catalog">{{ consumable.catalog_nr }}</td> |
||||
<td class="column-vendor">{{ consumable.vendor }}</td> |
||||
<td class="column-pkg">{{ consumable.package_size }}</td> |
||||
<td class="column-price">{{ '%.2f'|format(consumable.unit_price) }}</td> |
||||
<td class="column-currency">{{ consumable.currency }}</td> |
||||
</tr> |
||||
{% endfor %} |
||||
</tbody> |
||||
</table> |
||||
|
||||
<fieldset class="form-actions"> |
||||
<div class="right"> |
||||
<button name="delete" type="submit" value="submit" class="btn btn-large btn-danger">Delete Consumable{{ 's' if consumables|length > 1 }}</button> |
||||
<button name="cancel" type="submit" value="cancel" class="btn btn-large">Cancel</button> |
||||
</div> |
||||
</fieldset> |
||||
|
||||
</form> |
||||
|
||||
</div> |
||||
</div> |
||||
|
||||
</div> |
||||
|
||||
</div> |
||||
{% endblock content %} |
@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
|
||||
{% extends "ordr2:templates/layout.jinja2" %} |
||||
{% import 'ordr2:templates/macros.jinja2' as macros with context %} |
||||
|
||||
{% block subtitle %} Admin | Consumable | {{ context.model.cas_description }} {% endblock subtitle %} |
||||
|
||||
{% block content %} |
||||
<div class="content controls"> |
||||
|
||||
<div class="container-fluid"> |
||||
<div class="row-fluid"> |
||||
<div class="page-controls"> |
||||
<h1>Edit Consumable: {{ context.model.cas_description }}</h1> |
||||
</div> |
||||
</div> |
||||
<div class="row"> |
||||
<div class="span8"> |
||||
{{ macros.flash_messages() }} |
||||
{{form.render()|safe}} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
</div> |
||||
{% endblock content %} |
@ -0,0 +1,81 @@
@@ -0,0 +1,81 @@
|
||||
{% extends "ordr2:templates/layout.jinja2" %} |
||||
{% import 'ordr2:templates/macros.jinja2' as macros with context %} |
||||
|
||||
{% block subtitle %} Admin | Consumables {% endblock subtitle %} |
||||
|
||||
{% block content %} |
||||
<div class="content controls"> |
||||
<div class="container-fluid controls"> |
||||
|
||||
<div class="row-fluid"> |
||||
<div class="span2"> |
||||
<div class="page-controls"> |
||||
<h1> |
||||
Consumables |
||||
</h1> |
||||
</div> |
||||
{{ macros.filter_box('Category', 'category', categories, {'search':None}) }} |
||||
</div> |
||||
|
||||
<div class="span10"> |
||||
<div class="page-controls"> |
||||
<form action="{{ request.resource_url(context, 'actions') }}" method="POST"> |
||||
<input type="hidden" name="csrf_token" value="{{get_csrf_token()}}"> |
||||
<div class="input-append search"> |
||||
<input type="search" name="search" size="30" placeholder="Search" value="{{ context.filters.get('search', None) or '' }}"> |
||||
<label class="add-on"> |
||||
<button type="submit" class="search" name="action" value="search">Search</button> |
||||
</label> |
||||
</div> |
||||
</form> |
||||
<div class="actions"> |
||||
<a href="{{ request.resource_url(context, 'new') }}" rel="tooltip" data-original-title="New" class="btn-flat single"><i class="add"></i></a> |
||||
</div> |
||||
</div> |
||||
|
||||
{{ macros.flash_messages() }} |
||||
|
||||
{% if consumables %} |
||||
<table class="table"> |
||||
<thead> |
||||
{{ macros.sortable_table_header('Cas / Description', 'cas') }} |
||||
{{ macros.sortable_table_header('Category', 'category') }} |
||||
{{ macros.sortable_table_header('Catalog Nr', 'catalog') }} |
||||
{{ macros.sortable_table_header('Vendor', 'vendor') }} |
||||
{{ macros.sortable_table_header('Package Size', 'pkg') }} |
||||
{{ macros.sortable_table_header('Unit Prize', 'price') }} |
||||
{{ macros.sortable_table_header('Currency', 'currency') }} |
||||
<th>Actions</th> |
||||
</thead> |
||||
|
||||
<tbody> |
||||
{% for consumable in consumables %} |
||||
<tr> |
||||
<td class="column-cas">{{ consumable.model.cas_description }}</td> |
||||
<td class="column-category">{{ consumable.model.category.name|capitalize }}</td> |
||||
<td class="column-catalog">{{ consumable.model.catalog_nr }}</td> |
||||
<td class="column-vendor">{{ consumable.model.vendor }}</td> |
||||
<td class="column-pkg">{{ consumable.model.package_size }}</td> |
||||
<td class="column-price">{{ '%.2f'|format(consumable.model.unit_price) }}</td> |
||||
<td class="column-currency">{{ consumable.model.currency }}</td> |
||||
<td> |
||||
<a href="{{ request.resource_url(consumable) }}" class="action edit" title="Edit Consumable">edit</a> |
||||
<a href="{{ request.resource_url(consumable, 'delete') }}" class="action delete" title="Delete Consumable">delete</a> |
||||
</td> |
||||
</tr> |
||||
{% endfor %} |
||||
</tbody> |
||||
</table> |
||||
|
||||
{{ macros.pagination() }} |
||||
{% else %} |
||||
<div class="alert alert-block alert-error"> |
||||
<h4 class="alert-heading">Oh snap! Nothing to display!</h4> |
||||
<p>Your query didn't return any data.</p> |
||||
</div> |
||||
{% endif %} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
{% endblock content %} |
@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
|
||||
{% extends "ordr2:templates/layout.jinja2" %} |
||||
{% import 'ordr2:templates/macros.jinja2' as macros with context %} |
||||
|
||||
{% block subtitle %} Admin | Consumable | Add {% endblock subtitle %} |
||||
|
||||
{% block content %} |
||||
<div class="content controls"> |
||||
|
||||
<div class="container-fluid"> |
||||
<div class="row-fluid"> |
||||
<div class="page-controls"> |
||||
<h1>Add Consumable</h1> |
||||
</div> |
||||
</div> |
||||
<div class="row"> |
||||
<div class="span8"> |
||||
{{ macros.flash_messages() }} |
||||
{{form.render()|safe}} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
</div> |
||||
{% endblock content %} |
@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
|
||||
{% extends "ordr2:templates/layout.jinja2" %} |
||||
{% import 'ordr2:templates/macros.jinja2' as macros with context %} |
||||
|
||||
{% block subtitle %} Admin | User | {{ context.model.user_name }} {% endblock subtitle %} |
||||
|
||||
{% block content %} |
||||
<div class="content controls"> |
||||
|
||||
<div class="container-fluid"> |
||||
<div class="row-fluid"> |
||||
<div class="page-controls"> |
||||
<h1>Edit User: {{ context.model.user_name }}</h1> |
||||
</div> |
||||
</div> |
||||
<div class="row"> |
||||
<div class="span8"> |
||||
{{ macros.flash_messages() }} |
||||
{{form.render()|safe}} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
</div> |
||||
{% endblock content %} |
@ -0,0 +1,135 @@
@@ -0,0 +1,135 @@
|
||||
{% extends "ordr2:templates/layout.jinja2" %} |
||||
{% import 'ordr2:templates/macros.jinja2' as macros with context %} |
||||
|
||||
{% block subtitle %} Admin | Users {% endblock subtitle %} |
||||
|
||||
{% block content %} |
||||
<div class="content controls"> |
||||
<div class="container-fluid controls"> |
||||
|
||||
<div class="row-fluid"> |
||||
<div class="span2"> |
||||
<div class="page-controls"> |
||||
<h1> |
||||
Users |
||||
</h1> |
||||
</div> |
||||
{{ macros.filter_box('Role', 'role', roles, {'search':None}) }} |
||||
</div> |
||||
|
||||
<div class="span10"> |
||||
<form action="{{ request.resource_url(context, 'actions') }}" method="POST"> |
||||
<input type="hidden" name="csrf_token" value="{{get_csrf_token()}}"> |
||||
<div class="page-controls"> |
||||
<div class="input-append search"> |
||||
<input type="search" name="search" size="30" placeholder="Search" value="{{ context.filters.get('search', None) or '' }}"> |
||||
<label class="add-on"> |
||||
<button type="submit" class="search" name="action" value="search">Search</button> |
||||
</label> |
||||
</div> |
||||
<div class="actions"> |
||||
<div class="btn-group marking-needed"> |
||||
<button rel="tooltip" data-original-title="Change Role" class="btn-flat group" name="action" type="submit" value="role"><i class="group"></i></button> |
||||
<button rel="tooltip" data-original-title="Delete" class="btn-flat delete" name="action" type="submit" value="delete"><i class="trash"></i></button> |
||||
</div> |
||||
<a href="#modal-display" rel="tooltip" data-original-title="Display Options" class="btn-flat single" data-toggle="modal"><i class="eye"></i></a> |
||||
</div> |
||||
</div> |
||||
|
||||
{{ macros.flash_messages() }} |
||||
|
||||
{% if users %} |
||||
{{ macros.show_or_hide_columns('users') }} |
||||
|
||||
<table class="table"> |
||||
<thead> |
||||
<th class="center"> |
||||
<input type="checkbox" value="all" name="mark_all" id="mark_all"> |
||||
</th> |
||||
{{ macros.sortable_table_header('Username', 'user') }} |
||||
{{ macros.sortable_table_header('First Name', 'first') }} |
||||
{{ macros.sortable_table_header('Last Name', 'last') }} |
||||
{{ macros.sortable_table_header('Email', 'email') }} |
||||
{{ macros.sortable_table_header('Role', 'role') }} |
||||
<th>Actions</th> |
||||
</thead> |
||||
|
||||
<tbody> |
||||
{% for user in users %} |
||||
<tr> |
||||
<td class="center"> |
||||
<input type="checkbox" name="marked" value="{{ user.model.id }}"> |
||||
</td> |
||||
<td class="column-user"> |
||||
<a href="{{ request.resource_url(request.root, 'orders', query={'user': user.model.user_name}) }}" title="click to view all orders from user">{{ user.model.user_name }}</a> |
||||
</td> |
||||
<td class="column-first">{{ user.model.first_name }} </td> |
||||
<td class="column-last">{{ user.model.last_name }} </td> |
||||
<td class="column-email">{{ user.model.email }} </td> |
||||
<td class="column-role">{{ user.model.role.value.capitalize() }} </td> |
||||
<td> |
||||
<a href="{{ request.resource_url(user) }}" class="action edit" title="Edit User">edit</a> |
||||
<a href="{{ request.resource_url(user, 'delete') }}" class="action delete" title="Delete User">delete</a> |
||||
</td> |
||||
</tr> |
||||
{% endfor %} |
||||
</tbody> |
||||
</table> |
||||
|
||||
{{ macros.pagination() }} |
||||
{% else %} |
||||
<div class="alert alert-block alert-error"> |
||||
<h4 class="alert-heading">Oh snap! Nothing to display!</h4> |
||||
<p>Your query didn't return any data.</p> |
||||
</div> |
||||
{% endif %} |
||||
</form> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div id="modal-display" class="modal hide fade"> |
||||
<form action="{{ request.resource_url(context, 'changeview', query=context.query_params()) }}" method="POST" class="checkslist"> |
||||
<input type="hidden" name="csrf_token" value="{{get_csrf_token()}}"> |
||||
{% set display = request.session.get('display', dict()) %} |
||||
{% set settings = display.get('users', dict()) %} |
||||
<div class="modal-header"> |
||||
<a href="#" class="close" data-dismiss="modal">×</a> |
||||
<h3>Display Options</h3> |
||||
</div> |
||||
<div class="modal-body"> |
||||
<p class="help-block"><span class="label notice">Notice</span> If the displayed information is too cluttered, deselect some fields below. This will temporaly remove them from your view and should help you stay on top of things.</p> |
||||
<div class="checklist"> |
||||
<fieldset class="left"> |
||||
<label class="checkbox"> |
||||
<input type="checkbox" checked="checked" value="user" name="display" disabled="disabled" > |
||||
Username |
||||
</label> |
||||
<label class="checkbox"> |
||||
<input type="checkbox" {{ 'checked="checked"' if settings.get('first') }} value="first" name="display"> |
||||
First Name |
||||
</label> |
||||
<label class="checkbox"> |
||||
<input type="checkbox" {{ 'checked="checked"' if settings.get('last') }} value="last" name="display"> |
||||
Last Name |
||||
</label> |
||||
</fieldset> |
||||
<fieldset class="right"> |
||||
<label class="checkbox"> |
||||
<input type="checkbox" {{ 'checked="checked"' if settings.get('email') }} value="email" name="display"> |
||||
Email |
||||
</label> |
||||
<label class="checkbox"> |
||||
<input type="checkbox" checked="checked" value="role" name="display" disabled="disabled" > |
||||
Role |
||||
</label> |
||||
</fieldset> |
||||
</div> |
||||
</div> |
||||
<div class="modal-footer"> |
||||
<button class="btn btn-primary" type="submit">Apply Changes</button> |
||||
<a data-dismiss="modal" class="btn" href="#">Close</a> |
||||
</div> |
||||
</form> |
||||
</div> |
||||
</div> |
||||
{% endblock content %} |
@ -0,0 +1,79 @@
@@ -0,0 +1,79 @@
|
||||
{% extends "ordr2:templates/layout.jinja2" %} |
||||
{% import 'ordr2:templates/macros.jinja2' as macros with context %} |
||||
|
||||
{% block subtitle %} Admin | Users | Change Roles {% endblock subtitle %} |
||||
|
||||
{% block content %} |
||||
<div class="content controls"> |
||||
|
||||
<div class="container-fluid"> |
||||
<div class="row-fluid"> |
||||
<div class="page-controls"> |
||||
<h1>Change Role of User{{ 's' if accounts|length > 1 }}</h1> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="row"> |
||||
<div class="span10"> |
||||
|
||||
<div class="action-header"> |
||||
<h3>The role of the following user{{ 's' if accounts|length > 1 }} will be changed:</h3> |
||||
</div> |
||||
|
||||
<form action="{{ request.resource_url(context, 'roles') }}" method="POST" class="action"> |
||||
<input type="hidden" name="csrf_token" value="{{get_csrf_token()}}"> |
||||
|
||||
<table class="table"> |
||||
<thead> |
||||
<th>Username</th> |
||||
<th>First Name</th> |
||||
<th>Last Name</th> |
||||
<th>Email</th> |
||||
<th>Role</th> |
||||
</thead> |
||||
|
||||
<tbody> |
||||
{% for account in accounts %} |
||||
<tr> |
||||
<td class="column-user"> |
||||
{{ account.user_name }} |
||||
</td> |
||||
<td>{{ account.first_name }} </td> |
||||
<td>{{ account.last_name }} </td> |
||||
<td>{{ account.email }} </td> |
||||
<td> |
||||
<select name="account-{{ account.id }}" class="select-role span2"> |
||||
{% for value, display in roles %} |
||||
<option value="{{ value }}" {{ 'selected="selected"' if value == account.role.name }}>{{ display }}</option> |
||||
{% endfor %} |
||||
</select> |
||||
</td> |
||||
</tr> |
||||
{% endfor %} |
||||
</tbody> |
||||
</table> |
||||
|
||||
<fieldset class="form-actions"> |
||||
<div class="right"> |
||||
<button name="change" type="submit" value="change" class="btn btn-large btn-danger">Change Role{{ 's' if accounts|length > 1 }}</button> |
||||
<button name="cancel" type="submit" value="cancel" class="btn btn-large">Cancel</button> |
||||
</div> |
||||
<div class="btn-group quick-action left" data-action="account"> |
||||
<a data-value="USER" href="#" class="btn btn-large btn-primary">Set all to User</a> |
||||
<a href="#" data-toggle="dropdown" class="btn btn-large btn-primary dropdown-toggle"><span class="caret"></span></a> |
||||
<ul class="dropdown-menu"> |
||||
<li><a data-value="INACTIVE" href="#">Set all to Inactive</a></li> |
||||
<li><a data-value="PURCHASER" href="#">Set all to Purchaser</a></li> |
||||
</ul> |
||||
</div> |
||||
</fieldset> |
||||
|
||||
</form> |
||||
|
||||
</div> |
||||
</div> |
||||
|
||||
</div> |
||||
|
||||
</div> |
||||
{% endblock content %} |
@ -0,0 +1,66 @@
@@ -0,0 +1,66 @@
|
||||
{% extends "ordr2:templates/layout.jinja2" %} |
||||
{% import 'ordr2:templates/macros.jinja2' as macros with context %} |
||||
|
||||
{% block subtitle %} Admin | Users | Confirm Delete {% endblock subtitle %} |
||||
|
||||
{% block content %} |
||||
<div class="content controls"> |
||||
|
||||
<div class="container-fluid"> |
||||
<div class="row-fluid"> |
||||
<div class="page-controls"> |
||||
<h1>Delete User{{ 's' if accounts|length > 1 }}</h1> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="row"> |
||||
<div class="span10"> |
||||
|
||||
<div class="action-header"> |
||||
<h3>The following user{{ 's' if accounts|length > 1 }} will be deleted:</h3> |
||||
</div> |
||||
|
||||
<form action="{{ request.resource_url(context, 'delete') }}" method="POST" class="action"> |
||||
<input type="hidden" name="csrf_token" value="{{get_csrf_token()}}"> |
||||
|
||||
<table class="table"> |
||||
<thead> |
||||
<th>Username</th> |
||||
<th>First Name</th> |
||||
<th>Last Name</th> |
||||
<th>Email</th> |
||||
<th>Role</th> |
||||
</thead> |
||||
|
||||
<tbody> |
||||
{% for account in accounts %} |
||||
<tr> |
||||
<td class="column-user"> |
||||
<input type="hidden" name="account" value="{{ account.id }}"> |
||||
{{ account.user_name }} |
||||
</td> |
||||
<td>{{ account.first_name }} </td> |
||||
<td>{{ account.last_name }} </td> |
||||
<td>{{ account.email }} </td> |
||||
<td>{{ account.role.value.capitalize() }} </td> |
||||
</tr> |
||||
{% endfor %} |
||||
</tbody> |
||||
</table> |
||||
|
||||
<fieldset class="form-actions"> |
||||
<div class="right"> |
||||
<button name="delete" type="submit" value="submit" class="btn btn-large btn-danger">Delete User{{ 's' if accounts|length > 1 }}</button> |
||||
<button name="cancel" type="submit" value="cancel" class="btn btn-large">Cancel</button> |
||||
</div> |
||||
</fieldset> |
||||
|
||||
</form> |
||||
|
||||
</div> |
||||
</div> |
||||
|
||||
</div> |
||||
|
||||
</div> |
||||
{% endblock content %} |
@ -0,0 +1,97 @@
@@ -0,0 +1,97 @@
|
||||
<form |
||||
tal:define="style style|field.widget.style; |
||||
css_class css_class|string:${field.widget.css_class or field.css_class or ''}; |
||||
item_template item_template|field.widget.item_template; |
||||
autocomplete autocomplete|field.autocomplete; |
||||
title title|field.title; |
||||
errormsg errormsg|field.errormsg; |
||||
description description|field.description; |
||||
buttons buttons|field.buttons; |
||||
use_ajax use_ajax|field.use_ajax; |
||||
ajax_options ajax_options|field.ajax_options; |
||||
formid formid|field.formid; |
||||
action action|field.action or None; |
||||
method method|field.method;" |
||||
tal:attributes="autocomplete autocomplete; |
||||
style style; |
||||
class css_class; |
||||
action action;" |
||||
id="${formid}" |
||||
method="${method}" |
||||
enctype="multipart/form-data" |
||||
accept-charset="utf-8" |
||||
i18n:domain="deform" |
||||
> |
||||
|
||||
<fieldset class="deform-form-fieldset"> |
||||
|
||||
<legend tal:condition="title">${title}</legend> |
||||
|
||||
<input type="hidden" name="_charset_" /> |
||||
<input type="hidden" name="__formid__" value="${formid}"/> |
||||
|
||||
<div class="alert alert-danger" tal:condition="field.error"> |
||||
<div class="error-msg-lbl" i18n:translate="" |
||||
>There was a problem with your submission</div> |
||||
<div class="error-msg-detail" i18n:translate="" |
||||
>Errors have been highlighted below</div> |
||||
<p class="error-msg">${field.errormsg}</p> |
||||
</div> |
||||
|
||||
<p class="section first" tal:condition="description"> |
||||
${description} |
||||
</p> |
||||
|
||||
<div tal:repeat="child field" |
||||
tal:replace="structure child.render_template(item_template)"/> |
||||
|
||||
<div class="form-actions deform-form-buttons"> |
||||
<tal:loop tal:repeat="button buttons"> |
||||
<button |
||||
tal:define="btn_disposition repeat.button.start and 'btn-primary' or 'btn-default'; |
||||
btn_icon button.icon|None" |
||||
tal:attributes="disabled button.disabled if button.disabled else None" |
||||
id="${formid+button.name}" |
||||
name="${button.name}" |
||||
type="${button.type}" |
||||
class="btn ${button.css_class or btn_disposition}" |
||||
value="${button.value}"> |
||||
<i tal:condition="btn_icon" class="${btn_icon}"> </i> |
||||
${button.title} |
||||
</button> |
||||
</tal:loop> |
||||
</div> |
||||
|
||||
</fieldset> |
||||
|
||||
<script type="text/javascript" tal:condition="use_ajax"> |
||||
deform.addCallback( |
||||
'${formid}', |
||||
function(oid) { |
||||
var target = '#' + oid; |
||||
var options = { |
||||
target: target, |
||||
replaceTarget: true, |
||||
success: function() { |
||||
deform.processCallbacks(); |
||||
deform.focusFirstInput(target); |
||||
}, |
||||
beforeSerialize: function() { |
||||
// See http://bit.ly/1agBs9Z (hack to fix tinymce-related ajax bug) |
||||
if ('tinymce' in window) { |
||||
$(tinymce.get()).each( |
||||
function(i, el) { |
||||
var content = el.getContent(); |
||||
var editor_input = document.getElementById(el.id); |
||||
editor_input.value = content; |
||||
}); |
||||
} |
||||
} |
||||
}; |
||||
var extra_options = ${ajax_options} || {}; |
||||
$('#' + oid).ajaxForm($.extend(options, extra_options)); |
||||
} |
||||
); |
||||
</script> |
||||
|
||||
</form> |
@ -0,0 +1,51 @@
@@ -0,0 +1,51 @@
|
||||
<div 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;" |
||||
class="control-group ${field.error and 'error' or ''} ${field.widget.item_css_class or ''} ${field.default_item_css_class()}" |
||||
title="${description}" |
||||
id="item-${oid}" |
||||
tal:omit-tag="structural" |
||||
i18n:domain="deform"> |
||||
|
||||
<label for="${oid}" |
||||
class="control-label ${required and 'required' or ''}" |
||||
tal:condition="not structural" |
||||
id="req-${oid}" |
||||
> |
||||
${title} |
||||
</label> |
||||
|
||||
<div class="controls"> |
||||
|
||||
<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> |
||||
|
||||
<p class="help-inline" |
||||
tal:define="errstr 'error-%s' % field.oid" |
||||
tal:repeat="msg field.error.messages()" |
||||
i18n:translate="" |
||||
tal:attributes="id repeat.msg.index==0 and errstr or |
||||
('%s-%s' % (errstr, repeat.msg.index))" |
||||
tal:condition="field.error and not field.widget.hidden and not field.typ.__class__.__name__=='Mapping'"> |
||||
${msg} |
||||
</p> |
||||
|
||||
<p tal:condition="field.description and not field.widget.hidden" |
||||
class="help-inline" > |
||||
${field.description} |
||||
</p> |
||||
</div> |
||||
</div> |
@ -0,0 +1,36 @@
@@ -0,0 +1,36 @@
|
||||
<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;" |
||||
i18n:domain="deform" |
||||
class="moneyinput"> |
||||
|
||||
${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> |
||||
|
@ -0,0 +1,36 @@
@@ -0,0 +1,36 @@
|
||||
<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;" |
||||
i18n:domain="deform" |
||||
class="moneyinput"> |
||||
|
||||
${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> |
||||
|
@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
|
||||
<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;" |
||||
class="${field.error and 'error' or ''} ${field.widget.item_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> |
@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
|
||||
<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;" |
||||
class="${field.error and 'error' or ''} ${field.widget.item_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> |
@ -0,0 +1,71 @@
@@ -0,0 +1,71 @@
|
||||
<tal:def tal:define="title title|field.title; |
||||
description description|field.description; |
||||
errormsg errormsg|field.errormsg; |
||||
item_template item_template|field.widget.item_template; |
||||
request field.schema.bindings['request']" |
||||
i18n:domain="deform"> |
||||
|
||||
<div class="panel panel-default" title="${description}"> |
||||
<div class="panel-heading">${title}</div> |
||||
<div class="panel-body"> |
||||
|
||||
<div tal:condition="errormsg" |
||||
class="clearfix alert alert-danger"> |
||||
<p i18n:translate=""> |
||||
There was a problem with this section |
||||
</p> |
||||
<p>${errormsg}</p> |
||||
</div> |
||||
|
||||
<div tal:condition="description"> |
||||
${description} |
||||
</div> |
||||
|
||||
${field.start_mapping()} |
||||
|
||||
<div tal:repeat="child field.children" |
||||
tal:replace="structure child.render_template(item_template)" > |
||||
</div> |
||||
|
||||
<div class="control-group"> |
||||
<label class="control-label"> Placed </label> |
||||
<div class="controls"> |
||||
<p class="form-control-static"> |
||||
${request.context.model.placed} |
||||
</p> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="control-group"> |
||||
<label class="control-label"> Approved </label> |
||||
<div class="controls"> |
||||
<p class="form-control-static"> |
||||
${request.context.model.approved} |
||||
</p> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="control-group"> |
||||
<label class="control-label"> Ordered </label> |
||||
<div class="controls"> |
||||
<p class="form-control-static"> |
||||
${request.context.model.ordered} |
||||
</p> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="control-group"> |
||||
<label class="control-label"> Completed </label> |
||||
<div class="controls"> |
||||
<p class="form-control-static"> |
||||
${request.context.model.completed} |
||||
</p> |
||||
</div> |
||||
</div> |
||||
|
||||
${field.end_mapping()} |
||||
|
||||
</div> |
||||
</div> |
||||
|
||||
</tal:def> |
@ -0,0 +1,42 @@
@@ -0,0 +1,42 @@
|
||||
<div tal:define=" |
||||
name name|field.name; |
||||
oid oid|field.oid; |
||||
style style|field.widget.style; |
||||
size size|field.widget.size; |
||||
css_class css_class|field.widget.css_class; |
||||
unicode unicode|str; |
||||
optgroup_class optgroup_class|field.widget.optgroup_class; |
||||
multiple multiple|field.widget.multiple;" |
||||
tal:omit-tag=""> |
||||
|
||||
<input type="hidden" name="__start__" value="${name}:sequence" |
||||
tal:condition="multiple" /> |
||||
<select tal:attributes=" |
||||
name name; |
||||
id oid; |
||||
class string: form-control ${css_class or ''}; |
||||
multiple multiple; |
||||
size size; |
||||
style style;" |
||||
readonly="readonly"> |
||||
<tal:loop tal:repeat="item values"> |
||||
<optgroup tal:condition="isinstance(item, optgroup_class)" |
||||
tal:attributes="label item.label"> |
||||
<option tal:repeat="(value, description) item.options" |
||||
tal:attributes=" |
||||
selected python:field.widget.get_select_value(cstruct, value); |
||||
class css_class; |
||||
label field.widget.long_label_generator and description; |
||||
value value" |
||||
tal:content="field.widget.long_label_generator and field.widget.long_label_generator(item.label, description) or description"/> |
||||
</optgroup> |
||||
<option tal:condition="not isinstance(item, optgroup_class)" |
||||
tal:attributes=" |
||||
selected python:field.widget.get_select_value(cstruct, item[0]); |
||||
class css_class; |
||||
value item[0]">${item[1]}</option> |
||||
</tal:loop> |
||||
</select> |
||||
<input type="hidden" name="__end__" value="${name}:sequence" |
||||
tal:condition="multiple" /> |
||||
</div> |
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
<span tal:define="name name|field.name; |
||||
css_class css_class|field.widget.css_class; |
||||
oid oid|field.oid; |
||||
mask mask|field.widget.mask|None; |
||||
mask_placeholder mask_placeholder|field.widget.mask_placeholder|'_'; |
||||
style style|field.widget.style; |
||||
" |
||||
tal:omit-tag=""> |
||||
<input type="text" name="${name}" value="${cstruct}" |
||||
tal:attributes="class string: form-control ${css_class or ''}; |
||||
style style" |
||||
id="${oid}" |
||||
readonly="readonly"/> |
||||
<script tal:condition="mask" type="text/javascript"> |
||||
deform.addCallback( |
||||
'${oid}', |
||||
function (oid) { |
||||
$("#" + oid).mask("${mask}", |
||||
{placeholder:"${mask_placeholder}"}); |
||||
}); |
||||
</script> |
||||
</span> |
@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html> |
||||
<html> |
||||
<head> |
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> |
||||
<title>ordr Account Activation</title> |
||||
<link href='http://fonts.googleapis.com/css?family=Anton&subset=latin,latin-ext' rel='stylesheet' type='text/css'> |
||||
<link rel="stylesheet" href="{{request.static_url('ordr2:static/css/email.css')}}" type="text/css" media="screen"> |
||||
</head> |
||||
<body> |
||||
<h1>Hi there!</h1> |
||||
<p> |
||||
Your account {{ user.user_name }} has been activated. |
||||
<a href="{{ request.resource_url(request.root) }}">Log in and start ordering.</a> |
||||
</p> |
||||
<p class="signature"> |
||||
Regards, |
||||
<br/> |
||||
<span class="brand">ordr</span> |
||||
</p> |
||||
<p class="footprint"> |
||||
<small>Please don't respont to this email! This is an automatically generated notification by the system.</small> |
||||
<a href="http://distractedbysquirrels.com/" target="_blank" title="This software was originally written by Sebastian Sebald." class="icon-dbs"></a> |
||||
</p> |
||||
</body> |
||||
</html> |
@ -0,0 +1,36 @@
@@ -0,0 +1,36 @@
|
||||
<!DOCTYPE html> |
||||
<html> |
||||
<head> |
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> |
||||
<title>ordr Notification</title> |
||||
<link href='http://fonts.googleapis.com/css?family=Anton&subset=latin,latin-ext' rel='stylesheet' type='text/css'> |
||||
<link rel="stylesheet" href="{{request.static_url('ordr2:static/css/email.css')}}" type="text/css" media="screen"> |
||||
</head> |
||||
<body> |
||||
<h1>Hi there!</h1> |
||||
<p> |
||||
Your purchase of the following item |
||||
{% if data.status.name == 'ORDERED' %} |
||||
has <u>been orderd</u>: |
||||
{% else %} |
||||
has <u>arrived</u>: |
||||
{% endif %} |
||||
<strong>{{ data }}</strong> |
||||
</p> |
||||
<p> |
||||
If you want to check details about the purchase go here: <a href="{{ request.resource_url(request.root, 'orders', data.id) }}">{{ request.resource_url(request.root, 'orders', data.id) }}</a> |
||||
</p> |
||||
<p> |
||||
|
||||
</p> |
||||
<p class="signature"> |
||||
Regards, |
||||
<br/> |
||||
<span class="brand">ordr</span> |
||||
</p> |
||||
<p class="footprint"> |
||||
<small>Please don't respont to this email! This is an automatically generated notification by the system.</small> |
||||
<a href="http://distractedbysquirrels.com/" target="_blank" title="This software was originally written by Sebastian Sebald." class="icon-dbs"></a> |
||||
</p> |
||||
</body> |
||||
</html> |
@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html> |
||||
<html> |
||||
<head> |
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> |
||||
<title>ordr Password Reset</title> |
||||
<link href='http://fonts.googleapis.com/css?family=Anton&subset=latin,latin-ext' rel='stylesheet' type='text/css'> |
||||
<link rel="stylesheet" href="{{request.static_url('ordr2:static/css/email.css')}}" type="text/css" media="screen"> |
||||
</head> |
||||
<body> |
||||
<h1>Hi there!</h1> |
||||
<p> |
||||
If you forgot your password, you can set a new one by |
||||
<a href="{{ request.resource_url(request.root, 'account', 'reset', data) }}">clicking this link.</a> |
||||
</p> |
||||
<p class="signature"> |
||||
Regards, |
||||
<br/> |
||||
<span class="brand">ordr</span> |
||||
</p> |
||||
<p class="footprint"> |
||||
<small>Please don't respont to this email! This is an automatically generated notification by the system.</small> |
||||
<a href="http://distractedbysquirrels.com/" target="_blank" title="This software was originally written by Sebastian Sebald." class="icon-dbs"></a> |
||||
</p> |
||||
</body> |
||||
</html> |
@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
|
||||
{% extends "ordr2:templates/layout.jinja2" %} |
||||
|
||||
{% block subtitle %} Whoops! {% endblock subtitle %} |
||||
|
||||
{% block content %} |
||||
<div class="content"> |
||||
<div class="container"> |
||||
<div class="row"> |
||||
|
||||
<div class="span12"> |
||||
<hgroup id="access-denied"> |
||||
<h1>Please try again.</h1> |
||||
<p class="info">There was a problem with your form submission.</p> |
||||
<p class="info">Maybe it took you too long to fill out the form.</p> |
||||
</hgroup> |
||||
</div> |
||||
|
||||
</div> |
||||
</div> |
||||
</div> |
||||
{% endblock content %} |
@ -0,0 +1,65 @@
@@ -0,0 +1,65 @@
|
||||
<!DOCTYPE html> |
||||
<html lang="en"> |
||||
<head> |
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> |
||||
|
||||
<title>Ordr | Whoops!</title> |
||||
|
||||
<link href="{{request.static_url('ordr2:static/img/favicon.ico')}}" type="image/x-icon" rel="shortcut icon"> |
||||
|
||||
<link href='http://fonts.googleapis.com/css?family=Anton&subset=latin,latin-ext' rel='stylesheet' type='text/css'> |
||||
|
||||
<link rel="stylesheet" href="{{request.static_url('ordr2:static/css/bootstrap.css')}}" type="text/css" media="screen"> |
||||
<link rel="stylesheet" href="{{request.static_url('ordr2:static/css/bootstrap-responsive.css')}}" type="text/css" media="screen"> |
||||
<link rel="stylesheet" href="{{request.static_url('ordr2:static/css/style.css')}}" type="text/css" media="screen" /> |
||||
<link rel="stylesheet" href="{{request.static_url('deform:static/css/form.css')}}" type="text/css" media="screen" /> |
||||
<!--[if !IE 7]> |
||||
<style type="text/css"> |
||||
#wrap {display:table;height:100%} |
||||
</style> |
||||
<![endif]--> |
||||
<script src="{{request.static_url('deform:static/scripts/jquery-2.0.3.min.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 class="{{ ''.join(request.traversed)}} {{request.view_name}}"> |
||||
<header class="navbar navbar-fixed-top"> |
||||
<div class="navbar-inner"> |
||||
<div class="container-fluid"> |
||||
<a href="{{request.resource_url(request.root)}}" class="brand">ordr</a> |
||||
</div> |
||||
</div> |
||||
</header> |
||||
|
||||
<div class="content"> |
||||
<div class="container"> |
||||
<div class="row"> |
||||
|
||||
<div class="span12"> |
||||
<hgroup id="access-denied"> |
||||
<h1>Whoops!</h1> |
||||
<p class="info">This really shouldn't happen - You encountered a bug.</p> |
||||
</hgroup> |
||||
</div> |
||||
|
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
<footer> |
||||
|
||||
<div class="copy"> |
||||
<a class="icon-dbs" title="This software was orignially written by Sebastian Sebald." target="_blank" href="http://distractedbysquirrels.com/"></a> |
||||
</div> |
||||
|
||||
</footer> |
||||
<!--<script src="{{request.static_url('ordr2:static/js/bootstrap-transition.js')}}"></script>--> |
||||
<script src="{{request.static_url('ordr2:static/js/bootstrap-dropdown.js')}}"></script> |
||||
<script src="{{request.static_url('ordr2:static/js/bootstrap-modal.js')}}"></script> |
||||
<script src="{{request.static_url('ordr2:static/js/bootstrap-alert.js')}}"></script> |
||||
<script src="{{request.static_url('ordr2:static/js/bootstrap-tooltip.js')}}"></script> |
||||
<script src="{{request.static_url('ordr2:static/js/bootstrap-typeahead.js')}}"></script> |
||||
<script src="{{request.static_url('ordr2:static/js/bootstrap-collapse.js')}}"></script> |
||||
<script src="{{request.static_url('ordr2:static/js/functions.js')}}"></script> |
||||
</body> |
||||
</html> |
@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
|
||||
{% extends "ordr2:templates/layout.jinja2" %} |
||||
|
||||
{% block subtitle %} Access Denied {% endblock subtitle %} |
||||
|
||||
{% block content %} |
||||
<div class="content"> |
||||
<div class="container"> |
||||
<div class="row"> |
||||
|
||||
<div class="span12"> |
||||
<hgroup id="access-denied"> |
||||
<h1>Access Denied!!!</h1> |
||||
<p class="info">You do not have the permission to access this page.</p> |
||||
</hgroup> |
||||
</div> |
||||
|
||||
</div> |
||||
</div> |
||||
</div> |
||||
{% endblock content %} |
@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
|
||||
{% extends "ordr2:templates/layout.jinja2" %} |
||||
|
||||
{% block subtitle %} Not Found {% endblock subtitle %} |
||||
|
||||
{% block content %} |
||||
<div class="content"> |
||||
<div class="container"> |
||||
<div class="row"> |
||||
|
||||
<div class="span12"> |
||||
<hgroup id="access-denied"> |
||||
<h1>These are not the droids<br>you are looking for</h1> |
||||
<p class="info">Whatever you wanted to see was not found on this server.</p> |
||||
</hgroup> |
||||
</div> |
||||
|
||||
</div> |
||||
</div> |
||||
</div> |
||||
{% endblock content %} |
@ -1,64 +1,85 @@
@@ -1,64 +1,85 @@
|
||||
<!DOCTYPE html> |
||||
<html lang="{{request.locale_name}}"> |
||||
<head> |
||||
<meta charset="utf-8"> |
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
<meta name="description" content="pyramid web application"> |
||||
<meta name="author" content="Pylons Project"> |
||||
<link rel="shortcut icon" href="{{request.static_url('ordr2:static/pyramid-16x16.png')}}"> |
||||
<html lang="en"> |
||||
<head> |
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> |
||||
|
||||
<title>Cookiecutter Alchemy project for the Pyramid Web Framework</title> |
||||
<title>Ordr | {% block subtitle %} Subtitle {% endblock subtitle %}</title> |
||||
|
||||
<!-- Bootstrap core CSS --> |
||||
<link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet"> |
||||
<link href="{{request.static_url('ordr2:static/img/favicon.ico')}}" type="image/x-icon" rel="shortcut icon"> |
||||
|
||||
<!-- Custom styles for this scaffold --> |
||||
<link href="{{request.static_url('ordr2:static/theme.css')}}" rel="stylesheet"> |
||||
<link href='https://fonts.googleapis.com/css?family=Anton&subset=latin,latin-ext' rel='stylesheet' type='text/css'> |
||||
|
||||
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries --> |
||||
<!--[if lt IE 9]> |
||||
<script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script> |
||||
<script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script> |
||||
<![endif]--> |
||||
</head> |
||||
|
||||
<body> |
||||
|
||||
<div class="starter-template"> |
||||
<div class="container"> |
||||
<div class="row"> |
||||
<div class="col-md-2"> |
||||
<img class="logo img-responsive" src="{{request.static_url('ordr2:static/pyramid.png') }}" alt="pyramid web framework"> |
||||
</div> |
||||
<div class="col-md-10"> |
||||
{% block content %} |
||||
<p>No content</p> |
||||
{% endblock content %} |
||||
</div> |
||||
</div> |
||||
<div class="row"> |
||||
<div class="links"> |
||||
<ul> |
||||
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li> |
||||
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li> |
||||
<li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li> |
||||
</ul> |
||||
</div> |
||||
</div> |
||||
<div class="row"> |
||||
<div class="copyright"> |
||||
Copyright © Pylons Project |
||||
</div> |
||||
<link rel="stylesheet" href="{{request.static_url('ordr2:static/css/bootstrap.css')}}" type="text/css" media="screen"> |
||||
<link rel="stylesheet" href="{{request.static_url('ordr2:static/css/bootstrap-responsive.css')}}" type="text/css" media="screen"> |
||||
<link rel="stylesheet" href="{{request.static_url('ordr2:static/css/style.css')}}" type="text/css" media="screen" /> |
||||
<link rel="stylesheet" href="{{request.static_url('deform:static/css/form.css')}}" type="text/css" media="screen" /> |
||||
<!--[if !IE 7]> |
||||
<style type="text/css"> |
||||
#wrap {display:table;height:100%} |
||||
</style> |
||||
<![endif]--> |
||||
<script src="{{request.static_url('deform:static/scripts/jquery-2.0.3.min.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 class="{{ ''.join(request.traversed)}} {{request.view_name}}"> |
||||
<header class="navbar navbar-fixed-top"> |
||||
<div class="navbar-inner"> |
||||
<div class="container-fluid"> |
||||
<a href="{{request.resource_url(request.root)}}" class="brand">ordr</a> |
||||
{% if request.user %} |
||||
<ul class="nav"> |
||||
<li {% if context.nav_highlight == 'orders' %} class="active" {% endif %}><a href="{{request.resource_url(request.root, 'orders')}}">Orders</a></li> |
||||
<li {% if context.nav_highlight == 'faq' %} class="active" {% endif %}><a href="{{request.resource_url(request.root, 'faq')}}">FAQs</a></li> |
||||
{% if request.user.role.name == 'ADMIN' %} |
||||
<li {% if context.nav_highlight == 'admin' %} class="active" {% endif %}><a href="{{request.resource_url(request.root, 'admin')}}">Admin</a></li> |
||||
{% endif %} |
||||
</ul> |
||||
<ul class="nav pull-right"> |
||||
<li class="dropdown" id="user-options"> |
||||
<a data-toggle="dropdown" class="dropdown-toggle" href="#">Logged in as <span class="user-name">{{request.user.user_name}}</span></a> |
||||
<ul class="dropdown-menu"> |
||||
<li><a href="{{request.resource_url(request.root, 'account', 'settings')}}">Settings</a></li> |
||||
<li><a href="https://git.cpi.imtek.uni-freiburg.de/holgi/ordr2/issues">Submit an Issue</a></li> |
||||
<li class="divider"></li> |
||||
<li><a href="{{request.resource_url(request.root, 'account', 'logout')}}">Logout</a></li> |
||||
</ul> |
||||
</li> |
||||
</ul> |
||||
{% else %} |
||||
<ul class="nav"> |
||||
<li {% if context.nav_highlight == 'register' %} class="active" {% endif %}><a href="{{request.resource_url(request.root, 'account', 'register')}}">Register</a></li> |
||||
<li {% if context.nav_highlight == 'about' %} class="active" {% endif %}><a href="{{request.resource_url(request.root, 'about')}}">About</a></li> |
||||
</ul> |
||||
<form action="{{request.resource_url(request.root, 'account', 'login')}}" method="post" class="navbar-form pull-right"> |
||||
<input type="hidden" name="csrf_token" value="{{get_csrf_token()}}"> |
||||
<input name="username" type="text" placeholder="Username" class="input-small"> |
||||
<input name="password" type="password" placeholder="Password" class="input-small"> |
||||
<button type="submit" class="btn">Log in</button> |
||||
</form> |
||||
{% endif %} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</header> |
||||
|
||||
{% block content %} |
||||
<p>No content</p> |
||||
{% endblock content %} |
||||
|
||||
<footer> |
||||
|
||||
<div class="copy"> |
||||
<a class="icon-dbs" title="This software was orignially written by Sebastian Sebald." target="_blank" href="http://distractedbysquirrels.com/"></a> |
||||
</div> |
||||
|
||||
<!-- Bootstrap core JavaScript |
||||
================================================== --> |
||||
<!-- Placed at the end of the document so the pages load faster --> |
||||
<script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script> |
||||
<script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script> |
||||
</body> |
||||
</footer> |
||||
<!--<script src="{{request.static_url('ordr2:static/js/bootstrap-transition.js')}}"></script>--> |
||||
<script src="{{request.static_url('ordr2:static/js/bootstrap-dropdown.js')}}"></script> |
||||
<script src="{{request.static_url('ordr2:static/js/bootstrap-modal.js')}}"></script> |
||||
<script src="{{request.static_url('ordr2:static/js/bootstrap-alert.js')}}"></script> |
||||
<script src="{{request.static_url('ordr2:static/js/bootstrap-tooltip.js')}}"></script> |
||||
<script src="{{request.static_url('ordr2:static/js/bootstrap-typeahead.js')}}"></script> |
||||
<script src="{{request.static_url('ordr2:static/js/bootstrap-collapse.js')}}"></script> |
||||
<script src="{{request.static_url('ordr2:static/js/functions.js')}}"></script> |
||||
</body> |
||||
</html> |
||||
|
@ -1,8 +0,0 @@
@@ -1,8 +0,0 @@
|
||||
{% extends "layout.jinja2" %} |
||||
|
||||
{% block content %} |
||||
<div class="content"> |
||||
<h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy project</span></h1> |
||||
<p class="lead">Welcome to <span class="font-normal">Ordr2</span>, a Pyramid application generated by<br><span class="font-normal">Cookiecutter</span>.</p> |
||||
</div> |
||||
{% endblock content %} |
@ -0,0 +1,66 @@
@@ -0,0 +1,66 @@
|
||||
{% extends "ordr2:templates/layout.jinja2" %} |
||||
{% import 'ordr2:templates/macros.jinja2' as macros with context %} |
||||
|
||||
{% block subtitle %} Orders | Confirm Delete {% endblock subtitle %} |
||||
|
||||
{% block content %} |
||||
<div class="content controls"> |
||||
|
||||
<div class="container-fluid"> |
||||
<div class="row-fluid"> |
||||
<div class="page-controls"> |
||||
<h1>Delete Order{{ 's' if orders|length > 1 }}</h1> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="row"> |
||||
<div class="span10"> |
||||
|
||||
<div class="action-header"> |
||||
<h3>The following order{{ 's' if orders|length > 1 }} will be deleted:</h3> |
||||
</div> |
||||
|
||||
<form action="{{ request.resource_url(context, 'delete') }}" method="POST" class="action"> |
||||
<input type="hidden" name="csrf_token" value="{{get_csrf_token()}}"> |
||||
|
||||
<table class="table"> |
||||
<thead> |
||||
<th>Date Created</th> |
||||
<th>CAS / Description</th> |
||||
<th>Vendor</th> |
||||
<th>Placed by</th> |
||||
<th>Status</th> |
||||
</thead> |
||||
|
||||
<tbody> |
||||
{% for order in orders %} |
||||
<tr> |
||||
<td> |
||||
<input type="hidden" name="order" value="{{ order.id }}"> |
||||
{{ order.created_date.strftime('%Y-%m-%d %H:%M') }} |
||||
</td> |
||||
<td>{{ order.cas_description }} </td> |
||||
<td>{{ order.vendor }} </td> |
||||
<td>{{ order.created_by }} </td> |
||||
<td>{{ macros.colored_status(order.status) }} </td> |
||||
</tr> |
||||
{% endfor %} |
||||
</tbody> |
||||
</table> |
||||
|
||||
<fieldset class="form-actions"> |
||||
<div class="right"> |
||||
<button name="delete" type="submit" value="submit" class="btn btn-large btn-danger">Delete Order{{ 's' if orders|length > 1 }}</button> |
||||
<button name="cancel" type="submit" value="cancel" class="btn btn-large">Cancel</button> |
||||
</div> |
||||
</fieldset> |
||||
|
||||
</form> |
||||
|
||||
</div> |
||||
</div> |
||||
|
||||
</div> |
||||
|
||||
</div> |
||||
{% endblock content %} |
@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
|
||||
{% extends "ordr2:templates/layout.jinja2" %} |
||||
{% import 'ordr2:templates/macros.jinja2' as macros with context %} |
||||
|
||||
{% block subtitle %} Order | {{ context.model.cas_description }} {% endblock subtitle %} |
||||
|
||||
{% block content %} |
||||
<div class="content controls"> |
||||
|
||||
<div class="container-fluid"> |
||||
<div class="row-fluid"> |
||||
<div class="page-controls"> |
||||
<h1>Edit Order: {{ context.model.cas_description }}</h1> |
||||
</div> |
||||
</div> |
||||
<div class="row edit-order"> |
||||
<div class="span8"> |
||||
{{ macros.flash_messages() }} |
||||
{{form.render()|safe}} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
</div> |
||||
{% endblock content %} |
@ -0,0 +1,77 @@
@@ -0,0 +1,77 @@
|
||||
{% extends "ordr2:templates/layout.jinja2" %} |
||||
{% import 'ordr2:templates/macros.jinja2' as macros with context %} |
||||
|
||||
{% block subtitle %} Oders | Change Status {% endblock subtitle %} |
||||
|
||||
{% block content %} |
||||
<div class="content controls"> |
||||
|
||||
<div class="container-fluid"> |
||||
<div class="row-fluid"> |
||||
<div class="page-controls"> |
||||
<h1>Change Status of Order{{ 's' if orders|length > 1 }}</h1> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="row"> |
||||
<div class="span10"> |
||||
|
||||
<div class="action-header"> |
||||
<h3>The status of the following order{{ 's' if orders|length > 1 }} will be changed:</h3> |
||||
</div> |
||||
|
||||
<form action="{{ request.resource_url(context, 'stati') }}" method="POST" class="action"> |
||||
<input type="hidden" name="csrf_token" value="{{get_csrf_token()}}"> |
||||
|
||||
<table class="table"> |
||||
<thead> |
||||
<th>Date Created</th> |
||||
<th>CAS / Description</th> |
||||
<th>Vendor</th> |
||||
<th>Placed by</th> |
||||
<th>Status</th> |
||||
</thead> |
||||
|
||||
<tbody> |
||||
{% for order in orders %} |
||||
<tr> |
||||
<td>{{ order.created_date.strftime('%Y-%m-%d %H:%M') }}</td> |
||||
<td>{{ order.cas_description }} </td> |
||||
<td>{{ order.vendor }} </td> |
||||
<td>{{ order.created_by }} </td> |
||||
<td> |
||||
<select name="order-{{ order.id }}" class="select-status span2"> |
||||
{% for value, display in stati %} |
||||
<option value="{{ value }}" {{ 'selected="selected"' if value == order.status.name }}>{{ display }}</option> |
||||
{% endfor %} |
||||
</select> |
||||
</td> |
||||
</tr> |
||||
{% endfor %} |
||||
</tbody> |
||||
</table> |
||||
|
||||
<fieldset class="form-actions"> |
||||
<div class="right"> |
||||
<button name="change" type="submit" value="change" class="btn btn-large btn-danger">Change {{ 'Status' if orders|length > 1 else 'Stati'}}</button> |
||||
<button name="cancel" type="submit" value="cancel" class="btn btn-large">Cancel</button> |
||||
</div> |
||||
<div class="btn-group quick-action left" data-action="order"> |
||||
<a data-value="APPROVAL" href="#" class="btn btn-large btn-primary">Set all to Approval</a> |
||||
<a href="#" data-toggle="dropdown" class="btn btn-large btn-primary dropdown-toggle"><span class="caret"></span></a> |
||||
<ul class="dropdown-menu"> |
||||
<li><a data-value="ORDERED" href="#">Set all to Ordered</a></li> |
||||
<li><a data-value="COMPLETED" href="#">Set all to Completed</a></li> |
||||
</ul> |
||||
</div> |
||||
</fieldset> |
||||
|
||||
</form> |
||||
|
||||
</div> |
||||
</div> |
||||
|
||||
</div> |
||||
|
||||
</div> |
||||
{% endblock content %} |
@ -0,0 +1,194 @@
@@ -0,0 +1,194 @@
|
||||
{% extends "ordr2:templates/layout.jinja2" %} |
||||
{% import 'ordr2:templates/macros.jinja2' as macros with context %} |
||||
|
||||
{% block subtitle %} Orders {% endblock subtitle %} |
||||
|
||||
{% block content %} |
||||
<div class="content controls"> |
||||
<div class="container-fluid controls"> |
||||
|
||||
<div class="row-fluid"> |
||||
<div class="span2"> |
||||
<div class="page-controls"> |
||||
<h1> |
||||
Orders |
||||
</h1> |
||||
</div> |
||||
{{ macros.filter_box('All Orders', 'status', stati, {'user': None, 'search':None} ) }} |
||||
{{ macros.filter_box('My Orders', 'status', stati, {'user': request.user.user_name, 'search':None} ) }} |
||||
</div> |
||||
|
||||
<div class="span10"> |
||||
<form action="{{ request.resource_url(context, 'actions', query=context.query_params()) }}" method="POST"> |
||||
<input type="hidden" name="csrf_token" value="{{get_csrf_token()}}"> |
||||
<div class="page-controls"> |
||||
<div class="input-append search"> |
||||
<input type="search" name="search" size="30" placeholder="Search" value="{{ context.filters.get('search', None) or '' }}"> |
||||
<label class="add-on"> |
||||
<button type="submit" class="search" name="action" value="search">Search</button> |
||||
</label> |
||||
</div> |
||||
<div class="actions"> |
||||
{% if request.has_permission('create', context) %} |
||||
<a href="{{ request.resource_url(context, 'splash') }}" rel="tooltip" data-original-title="New" class="btn-flat single"><i class="add"></i></a> |
||||
{% endif %} |
||||
{% if request.has_permission('edit', context) or request.has_permission('delete', context) %} |
||||
<div class="btn-group marking-needed"> |
||||
{% if request.has_permission('edit', context) %} |
||||
<button rel="tooltip" data-original-title="Change Status" value="status" type="submit" name="action" class="btn-flat"><i class="clip"></i></button> |
||||
{% endif %} |
||||
{% if request.has_permission('delete', context) %} |
||||
<button rel="tooltip" data-original-title="Delete" value="delete" type="submit" name="action" class="btn-flat"><i class="trash"></i></button> |
||||
{% endif %} |
||||
</div> |
||||
{% endif %} |
||||
|
||||
<a href="#modal-display" rel="tooltip" data-original-title="Display Options" class="btn-flat single" data-toggle="modal"><i class="eye"></i></a> |
||||
|
||||
<button rel="tooltip" data-original-title="Download View" value="export" type="submit" name="action" class="btn-flat"><i class="download"></i></button> |
||||
</div> |
||||
</div> |
||||
|
||||
{{ macros.flash_messages() }} |
||||
|
||||
{% if orders %} |
||||
{{ macros.show_or_hide_columns('orders') }} |
||||
|
||||
<table class="table"> |
||||
<thead> |
||||
{% if request.has_permission('edit', context) or request.has_permission('delete', context) %} |
||||
<th class="center"> |
||||
<input type="checkbox" value="all" name="mark_all" id="mark_all"> |
||||
</th> |
||||
{% endif %} |
||||
{{ macros.sortable_table_header('Date Created', 'created') }} |
||||
{{ macros.sortable_table_header('CAS / Description', 'cas') }} |
||||
{{ macros.sortable_table_header('Vendor', 'vendor') }} |
||||
{{ macros.sortable_table_header('Catalog Nr.', 'catalog') }} |
||||
{{ macros.sortable_table_header('Unit Price', 'price') }} |
||||
{{ macros.sortable_table_header('Quantity', 'amount') }} |
||||
{{ macros.sortable_table_header('Total Price', 'total') }} |
||||
{{ macros.sortable_table_header('Account', 'account') }} |
||||
{{ macros.sortable_table_header('Category', 'category') }} |
||||
{{ macros.sortable_table_header('Status', 'status') }} |
||||
{{ macros.sortable_table_header('Ordered By', 'user') }} |
||||
<th>Actions</th> |
||||
</thead> |
||||
|
||||
<tbody> |
||||
{% for order in orders %} |
||||
<tr> |
||||
{% if request.has_permission('edit', context) or request.has_permission('delete', context) %} |
||||
<td class="center"> |
||||
<input type="checkbox" name="marked" value="{{ order.model.id }}"> |
||||
</td> |
||||
{% endif %} |
||||
<td class="column-created">{{ order.model.created_date.strftime('%Y-%m-%d %H:%M') }}</td> |
||||
<td class="column-cas">{{ order.model.cas_description }}</td> |
||||
<td class="column-vendor">{{ order.model.vendor }}</td> |
||||
<td class="column-catalog">{{ order.model.catalog_nr }}</td> |
||||
<td class="column-price">{{ '%.2f'|format(order.model.unit_price) }} {{ order.model.currency }}</td> |
||||
<td class="column-amount">{{ order.model.amount }}</td> |
||||
<td class="column-total">{{ '%.2f'|format(order.model.total_price) }} {{ order.model.currency }}</td> |
||||
<td class="column-account">{{ order.model.account }}</td> |
||||
<td class="column-category">{{ order.model.category.value|capitalize }}</td> |
||||
<td class="column-status">{{ macros.colored_status(order.model.status) }}</td> |
||||
<td class="column-user"> |
||||
<a href="{{ request.resource_url(context, query={'user': order.model.created_by}) }}" title="click to view all orders from user">{{ order.model.created_by }}</a> |
||||
</td> |
||||
<td> |
||||
{% if request.has_permission('edit', order) %} |
||||
<a href="{{ request.resource_url(order, 'edit') }}" class="action edit" title="Edit Order">edit</a> |
||||
{% else %} |
||||
<a href="{{ request.resource_url(order) }}" class="action eye" title="View Order">edit</a> |
||||
{% endif %} |
||||
{% if request.has_permission('delete', order) %} |
||||
<a href="{{ request.resource_url(order, 'delete') }}" class="action delete" title="Delete Order">delete</a> |
||||
{% endif %} |
||||
</td> |
||||
</tr> |
||||
{% endfor %} |
||||
</tbody> |
||||
</table> |
||||
|
||||
{{ macros.pagination() }} |
||||
{% else %} |
||||
<div class="alert alert-block alert-error"> |
||||
<h4 class="alert-heading">Oh snap! Nothing to display!</h4> |
||||
<p>Your query didn't return any data.</p> |
||||
</div> |
||||
{% endif %} |
||||
</form> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div id="modal-display" class="modal hide fade"> |
||||
<form action="{{ request.resource_url(context, 'changeview', query=context.query_params()) }}" method="POST" class="checkslist"> |
||||
<input type="hidden" name="csrf_token" value="{{get_csrf_token()}}"> |
||||
{% set display = request.session.get('display', dict()) %} |
||||
{% set settings = display.get('orders', dict()) %} |
||||
<div class="modal-header"> |
||||
<a href="#" class="close" data-dismiss="modal">×</a> |
||||
<h3>Display Options</h3> |
||||
</div> |
||||
<div class="modal-body"> |
||||
<p class="help-block"><span class="label notice">Notice</span> If the displayed information is too cluttered, deselect some fields below. This will temporaly remove them from your view and should help you stay on top of things.</p> |
||||
<div class="checklist"> |
||||
<fieldset class="left"> |
||||
<label class="checkbox"> |
||||
<input type="checkbox" value="created" name="display" checked="checked" disabled="disabled"> |
||||
Date Created |
||||
</label> |
||||
<label class="checkbox"> |
||||
<input type="checkbox" value="cas" name="display" {{ 'checked="checked"' if settings.get('cas') }}> |
||||
CAS / Description |
||||
</label> |
||||
<label class="checkbox"> |
||||
<input type="checkbox" value="vendor" name="display" {{ 'checked="checked"' if settings.get('vendor') }}> |
||||
Vendor |
||||
</label> |
||||
<label class="checkbox"> |
||||
<input type="checkbox" value="catalog" name="display" {{ 'checked="checked"' if settings.get('catalog') }}> |
||||
Catalog Number |
||||
</label> |
||||
<label class="checkbox"> |
||||
<input type="checkbox" value="price" name="display" {{ 'checked="checked"' if settings.get('price') }}> |
||||
Unit Price |
||||
</label> |
||||
<label class="checkbox"> |
||||
<input type="checkbox" value="amount" name="display" {{ 'checked="checked"' if settings.get('amount') }}> |
||||
Quantity |
||||
</label> |
||||
</fieldset> |
||||
<fieldset class="right"> |
||||
<label class="checkbox"> |
||||
<input type="checkbox" value="total" name="display" {{ 'checked="checked"' if settings.get('total') }}> |
||||
Total Price |
||||
</label> |
||||
<label class="checkbox"> |
||||
<input type="checkbox" value="account" name="display" {{ 'checked="checked"' if settings.get('account') }}> |
||||
Account |
||||
</label> |
||||
<label class="checkbox"> |
||||
<input type="checkbox" value="category" name="display" checked="checked" disabled="disabled"> |
||||
Category |
||||
</label> |
||||
<label class="checkbox"> |
||||
<input type="checkbox" value="status" name="display" checked="checked" disabled="disabled"> |
||||
Work Status |
||||
</label> |
||||
<label class="checkbox"> |
||||
<input type="checkbox" value="user" name="display" checked="checked" disabled="disabled"> |
||||
Ordered by |
||||
</label> |
||||
</fieldset> |
||||
</div> |
||||
</div> |
||||
<div class="modal-footer"> |
||||
<button class="btn btn-primary" type="submit">Apply Changes</button> |
||||
<a data-dismiss="modal" class="btn" href="#">Close</a> |
||||
</div> |
||||
</form> |
||||
</div> |
||||
</div> |
||||
{% endblock content %} |
@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
|
||||
{% extends "ordr2:templates/layout.jinja2" %} |
||||
{% import 'ordr2:templates/macros.jinja2' as macros with context %} |
||||
|
||||
{% block subtitle %} Order | Create New Order {% endblock subtitle %} |
||||
|
||||
{% block content %} |
||||
<div class="content controls"> |
||||
|
||||
<div class="container-fluid"> |
||||
<div class="row-fluid"> |
||||
<div class="page-controls"> |
||||
<h1>Create New Order</h1> |
||||
</div> |
||||
</div> |
||||
<div class="row edit-order"> |
||||
<div class="span8"> |
||||
{{ macros.flash_messages() }} |
||||
{{form.render()|safe}} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
</div> |
||||
{% endblock content %} |
@ -0,0 +1,69 @@
@@ -0,0 +1,69 @@
|
||||
{% extends "ordr2:templates/layout.jinja2" %} |
||||
{% import 'ordr2:templates/macros.jinja2' as macros with context %} |
||||
|
||||
{% block subtitle %} Order | Place Order {% endblock subtitle %} |
||||
|
||||
{% block content %} |
||||
<div class="content controls"> |
||||
|
||||
<div class="container-fluid"> |
||||
|
||||
<div class="page-controls"> |
||||
<h1>Place an Order</h1> |
||||
</div> |
||||
|
||||
<div class="row"> |
||||
<div class="span10"> |
||||
|
||||
<div class="alert alert-block alert-info"> |
||||
<h3 class="alert-heading">You can choose from a number of options to place a new order.</h3> |
||||
<ol> |
||||
<li>Place a custom order (empty, not prepopulated order form)</li> |
||||
<li>Use the search field to find a consumable and use a prepopulated order form.</li> |
||||
<li>Choose one of the consumables from a list to use a prepopulated order form.</li> |
||||
</ol> |
||||
</div> |
||||
|
||||
{{ macros.flash_messages() }} |
||||
|
||||
<div class="bordered"> |
||||
<form action="{{ request.resource_url(context, 'splash')}}" method="POST" class="form-inline right" autocomplete="off"> |
||||
<input type="hidden" name="csrf_token" value="{{get_csrf_token()}}"> |
||||
<input type="search" data-provide="typeahead" data-source='{{ consumable_names|tojson }}' placeholder="Search Consumables" size="50" name="search"> |
||||
<button class="btn" type="submit" name="search_consumable">Create Order</button> |
||||
</form> |
||||
<a class="btn" href="{{ request.resource_url(context, 'new') }}">Place a custom order</a> |
||||
</div> |
||||
|
||||
|
||||
<div id="common-consumables-accordion" class="accordion"> |
||||
{% for category, items in consumables.items() %} |
||||
<div class="accordion-group"> |
||||
<div class="accordion-heading"> |
||||
<a href="#{{ category.name }}" data-parent="#common-consumables-accordion" data-toggle="collapse"> |
||||
{{ category.value|capitalize }} |
||||
</a> |
||||
</div> |
||||
<div class="accordion-body collapse" id="{{ category.name }}"> |
||||
<div class="accordion-inner"> |
||||
<ul class="unstyled"> |
||||
{% for item in items %} |
||||
<li><a href="{{ request.resource_url(context, 'new', query={'consumable': item.id}) }}">{{ item.cas_description }}</a> <span>({{ item.package_size }})</span></li> |
||||
{% endfor %} |
||||
</ul> |
||||
</div> |
||||
</div> |
||||
|
||||
</div> |
||||
{% endfor %} |
||||
</div> |
||||
|
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
</div> |
||||
{% endblock content %} |
||||
|
||||
|
||||
|
@ -0,0 +1,156 @@
@@ -0,0 +1,156 @@
|
||||
{% extends "ordr2:templates/layout.jinja2" %} |
||||
{% import 'ordr2:templates/macros.jinja2' as macros with context %} |
||||
|
||||
{% block subtitle %} Order | View | {{ context.model.cas_desctiption }} {% endblock subtitle %} |
||||
|
||||
{% block content %} |
||||
|
||||
<div class="content controls"> |
||||
<div class="container-fluid"> |
||||
|
||||
<div class="row-fluid"> |
||||
<div class="page-controls"> |
||||
<h1> |
||||
View Order: {{ context.model.cas_desctiption }} |
||||
</h1> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="row edit-order"> |
||||
<div class="span8 form-horizontal form-like-display"> |
||||
<div class="controls"> |
||||
<div class="panel panel-default"> |
||||
<div class="panel-heading"> |
||||
Order Information |
||||
</div> |
||||
<div class="panel-body"> |
||||
<div class="control-group"> |
||||
<label class="control-label"> Status </label> |
||||
<div class="controls"> |
||||
{{ macros.colored_status(context.model.status) }} |
||||
</div> |
||||
</div> |
||||
<div class="control-group"> |
||||
<label class="control-label"> Placed </label> |
||||
<div class="controls"> |
||||
<p class="form-control-static">{{ context.model.placed }}</p> |
||||
</div> |
||||
</div> |
||||
<div class="control-group"> |
||||
<label class="control-label"> Approved </label> |
||||
<div class="controls"> |
||||
<p class="form-control-static">{{ context.model.approved }}</p> |
||||
</div> |
||||
</div> |
||||
<div class="control-group"> |
||||
<label class="control-label"> Ordered </label> |
||||
<div class="controls"> |
||||
<p class="form-control-static">{{ context.model.ordered }}</p> |
||||
</div> |
||||
</div> |
||||
<div class="control-group"> |
||||
<label class="control-label"> Completed </label> |
||||
<div class="controls"> |
||||
<p class="form-control-static">{{ context.model.completed }}</p> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="controls"> |
||||
<div class="panel panel-default"> |
||||
<div class="panel-heading"> |
||||
Item Information |
||||
</div> |
||||
<div class="panel-body"> |
||||
<div class="control-group" > |
||||
<label class="control-label"> Cas Description </label> |
||||
<div class="controls"> |
||||
<p class="form-control-static">{{ context.model.cas_description }}</p> |
||||
</div> |
||||
</div> |
||||
<div class="control-group" > |
||||
<label class="control-label"> Category </label> |
||||
<div class="controls"> |
||||
<p class="form-control-static">{{ context.model.category.value|capitalize }}</p> |
||||
</div> |
||||
</div> |
||||
<div class="control-group" > |
||||
<label class="control-label"> Catalog Nr </label> |
||||
<div class="controls"> |
||||
<p class="form-control-static">{{ context.model.catalog_nr }}</p> |
||||
</div> |
||||
</div> |
||||
<div class="control-group" > |
||||
<label class="control-label"> Vendor </label> |
||||
<div class="controls"> |
||||
<p class="form-control-static">{{ context.model.vendor }}</p> |
||||
</div> |
||||
</div> |
||||
<div class="control-group" > |
||||
<label class="control-label"> Package Size </label> |
||||
<div class="controls"> |
||||
<p class="form-control-static">{{ context.model.package_size }}</p> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="controls"> |
||||
<div class="panel panel-default"> |
||||
<div class="panel-heading"> |
||||
Pricing |
||||
</div> |
||||
<div class="panel-body"> |
||||
<div class="control-group" > |
||||
<label class="control-label"> Unit Price </label> |
||||
<div class="controls"> |
||||
<p class="form-control-static">{{ context.model.unit_price }} {{ context.model.currency }}</p> |
||||
</div> |
||||
</div> |
||||
<div class="control-group" > |
||||
<label class="control-label"> Quantity </label> |
||||
<div class="controls"> |
||||
<p class="form-control-static">{{ context.model.amount }}</p> |
||||
</div> |
||||
</div> |
||||
<div class="control-group" > |
||||
<label class="control-label"> Total Price </label> |
||||
<div class="controls"> |
||||
<p class="form-control-static">{{ context.model.total_price }} {{ context.model.currency }}</p> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="controls"> |
||||
<div class="panel panel-default"> |
||||
<div class="panel-heading"> |
||||
Optional Information |
||||
</div> |
||||
<div class="panel-body"> |
||||
<div class="control-group" > |
||||
<label class="control-label "> Account </label> |
||||
<div class="controls"> |
||||
<p class="form-control-static">{{ context.model.account }}</p> |
||||
</div> |
||||
</div> |
||||
<div class="control-group" > |
||||
<label class="control-label "> Comment </label> |
||||
<div class="controls"> |
||||
<p class="form-control-static">{{ context.model.comment.replace('\n', '<br>') }}</p> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="form-actions deform-form-buttons"> |
||||
<a href="{{ request.resource_url(context.__parent__, 'new', query={'reorder':context.model.id}) }}" class="btn btn-success"> Reorder </a> |
||||
<a href="{{ context.__parent__.url() }}" class="btn btn-default"> cancel </a> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
{% endblock content %} |
@ -0,0 +1,76 @@
@@ -0,0 +1,76 @@
|
||||
{% extends "ordr2:templates/layout.jinja2" %} |
||||
|
||||
{% block subtitle %} FAQ {% endblock subtitle %} |
||||
|
||||
{% block content %} |
||||
<div class="content"> |
||||
<div class="container"> |
||||
<h1>Frequently Asked Questions</h1> |
||||
|
||||
<section> |
||||
<h2>Account</h2> |
||||
|
||||
<div class="well"> |
||||
<h3> |
||||
Why can't I log in? It always says: "You entered the wrong unsername and/or password." |
||||
</h3> |
||||
<p> |
||||
First of all, just because <em>ordr</em> tells you that you have entered a wrong username or password doesn't mean that's necesseraily true. Sound dumb? But actually this is a security measure, which helps against brood force attacks because the adversary doesn't know if the username he guessed was correct or not. |
||||
</p> |
||||
<p> |
||||
Usually there are two common reasons why you can't log in. There are: |
||||
</p> |
||||
<ol> |
||||
<li><em>Your account hasn't been activated by an admin yet.</em> – In this case ask a admin if he already has activated your account.</li> |
||||
<li><em>You mistyped your password or username.</em> – Just try it another time and make sure caps log isn't activated.</li> |
||||
</ol> |
||||
</div> |
||||
|
||||
<div class="well"> |
||||
<h3> |
||||
Why can't I choose my own username? |
||||
</h3> |
||||
<p> |
||||
We want to enforce a specific guideline for the username. Namely <em>first name</em> followed immediately by the <em>last name</em>. This way everyone can easily identify the person, who placed an order. So please enter your real name into the registration form. Otherwise your account may not been activated because no one knows who you are. |
||||
</p> |
||||
</div> |
||||
|
||||
<div class="well"> |
||||
<h3> |
||||
Why can I only edit my email after I registered? |
||||
</h3> |
||||
<p> |
||||
Your username depends on your first and last name. Changing the username is not allowed, therefore you are also not allowed to change your first and last name afterwards. Even admins can not change your first/last name and your username. |
||||
</p> |
||||
</div> |
||||
</section> |
||||
|
||||
<section> |
||||
<h2>Ordering</h2> |
||||
|
||||
<div class="well"> |
||||
<h3> |
||||
I have an consumable, which I am ordering regulary but isn't in the databse. Can I add it? |
||||
</h3> |
||||
<p> |
||||
Unfortunately you can not add consumables to the databse yourself. But ask an admin or purchaser. They're certainly to help you out. |
||||
</p> |
||||
</div> |
||||
</section> |
||||
|
||||
<section> |
||||
<h2>Miscellaneous</h2> |
||||
|
||||
<div class="well"> |
||||
<h3> |
||||
I have found a bug where can I report it? |
||||
</h3> |
||||
<p> |
||||
If you have found a bug or an issue with the software please use <a href="https://git.cpi.imtek.uni-freiburg.de/holgi/ordr2/issues">this page here</a>. |
||||
You need an <a href="https://wiki.cpi.imtek.uni-freiburg.de/CPIvServerDocumentation/GogsGitServer">gogs</a> account to submit an issue. Please be as specific as you can be. |
||||
</p> |
||||
</div> |
||||
</section> |
||||
</div> |
||||
</div> |
||||
{% endblock content %} |
@ -0,0 +1,42 @@
@@ -0,0 +1,42 @@
|
||||
{% extends "ordr2:templates/layout.jinja2" %} |
||||
|
||||
{% block subtitle %} Welcome {% endblock subtitle %} |
||||
|
||||
{% block content %} |
||||
<div class="content"> |
||||
<div class="container"> |
||||
|
||||
<div class="row"> |
||||
<div class="span12"> |
||||
<hgroup id="welcome"> |
||||
<h1>Welcome to <span class="brand">ordr</span>!</h1> |
||||
<p class="quote">An order management system to simplify your shopping for laboratory supplies.</p> |
||||
</hgroup> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="row"> |
||||
<div class="span12"> |
||||
<p> |
||||
<strong>What can order do for you?</strong> It will simplify the process of ordering laboratory supplies by using the power of the newest web technologies. Interested? Just follow the three steps below! |
||||
</p> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="row"> |
||||
<div class="span4"> |
||||
<h2>1. Register</h2> |
||||
<p>Registration is easy as 1-2-3. Just fill out the form on <?php echo anchor('account/register', 'this page');?> and as soon as an admin has activated your account the shopping can begin!</p> |
||||
</div> |
||||
<div class="span4"> |
||||
<h2>2. Place an Order</h2> |
||||
<p>A lot of the chemicals, supllies and so forth are already stored in the database, so you don't have to fill out the order form your self!</p> |
||||
</div> |
||||
<div class="span4"> |
||||
<h2>3. Get notified</h2> |
||||
<p>As soons as your purchase has arrived you will automatically get notified. Or you can use the orders overview to check what the working status is.</p> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
{% endblock content %} |
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
''' custom jinja2 tests and filters ''' |
||||
|
||||
def are_extras_active(context, extras): |
||||
''' checks if the filters are active in a PaginationResource ''' |
||||
if extras is None: |
||||
return True |
||||
return all(context.filters.get(k) == v for k, v in extras.items()) |
@ -1 +1,63 @@
@@ -1 +1,63 @@
|
||||
# package |
||||
''' views package |
||||
|
||||
some view helpers are defined here |
||||
''' |
||||
|
||||
from collections import namedtuple |
||||
|
||||
# a message for session.flash() |
||||
FlashMessage = namedtuple('FlashMessage', 'message description dismissable') |
||||
|
||||
|
||||
def flash(request, channel, message, description='', dismissable=True): |
||||
''' small wrapper around request.session.flash ''' |
||||
msg = FlashMessage(message, description, dismissable) |
||||
request.session.flash(msg, channel, allow_duplicate=False) |
||||
|
||||
|
||||
def update_column_display(request, section): |
||||
''' update the session values for which columns to display ''' |
||||
if section not in request.session['display']: |
||||
return |
||||
display_keys = request.session['display'][section].keys() |
||||
display = dict.fromkeys(display_keys, False) |
||||
for column in request.POST.values(): |
||||
if column in display: |
||||
display[column] = True |
||||
request.session['display'][section] = display |
||||
|
||||
|
||||
def set_display_defaults(request): |
||||
''' sets the coulumn display default ''' |
||||
defaults = { |
||||
'users': { |
||||
'first': True, |
||||
'last': True, |
||||
'email': True, |
||||
}, |
||||
'orders': { |
||||
'account': False, |
||||
'cas': True, |
||||
'catalog': False, |
||||
'vendor': True, |
||||
'price': False, |
||||
'amount': False, |
||||
'total': True, |
||||
} |
||||
} |
||||
request.session['display'] = defaults |
||||
|
||||
|
||||
def includeme(config): |
||||
''' adding request helpers and static views |
||||
|
||||
Activate this setup using ``config.include('ordr2.views')``. |
||||
''' |
||||
config.add_request_method(flash, 'flash') |
||||
|
||||
settings = config.get_settings() |
||||
age = int(settings.get('static_views.cache_max_age', 3600)) |
||||
|
||||
config.add_static_view('static', 'ordr2:static', cache_max_age=age) |
||||
config.add_static_view('deform', 'deform:static', cache_max_age=age) |
||||
|
||||
|
@ -0,0 +1,273 @@
@@ -0,0 +1,273 @@
|
||||
''' Account Registration and Settings ''' |
||||
|
||||
import deform |
||||
|
||||
from pyramid.httpexceptions import HTTPFound |
||||
from pyramid.renderers import render |
||||
from pyramid.security import remember, forget |
||||
from pyramid.view import view_config |
||||
|
||||
from ordr2.events import UserLogIn |
||||
from ordr2.models import User, Role |
||||
from ordr2.schemas.account import ( |
||||
ResetPasswordSchema, |
||||
RegistrationSchema, |
||||
SettingsSchema |
||||
) |
||||
|
||||
# below this password length a warning is displayed |
||||
MIN_PW_LENGTH = 12 |
||||
|
||||
# user log in and log out |
||||
|
||||
@view_config( |
||||
context='ordr2:resources.Account', |
||||
name='login', |
||||
permission='login', |
||||
request_method='GET', |
||||
renderer='ordr2:templates/account/login.jinja2' |
||||
) |
||||
def login_form(context, request): |
||||
''' display a login form ''' |
||||
return {} |
||||
|
||||
|
||||
@view_config( |
||||
context='ordr2:resources.Account', |
||||
name='login', |
||||
permission='login', |
||||
request_method='POST' |
||||
) |
||||
def login(context, request): |
||||
''' loging in a user ''' |
||||
username = request.POST.get('username') |
||||
password = request.POST.get('password') |
||||
|
||||
# Form validation is not done for login forms, |
||||
# either the data represents a user or not. |
||||
user = request.dbsession.query(User).filter_by(user_name=username).first() |
||||
if user is not None: |
||||
if user.is_active and user.check_password(password): |
||||
headers = remember(request, user.id) |
||||
event = UserLogIn(request, user) |
||||
request.registry.notify(event) |
||||
return HTTPFound( |
||||
request.resource_path(request.root, 'orders'), |
||||
headers=headers |
||||
) |
||||
|
||||
request.flash( |
||||
'error', |
||||
'Oh snap! You entered the wrong unsername and/or password.', |
||||
'''Please try it again. If you still can not log in make sure that |
||||
your account is activated and you haven't enabled caps lock on |
||||
your keyboard.''', |
||||
dismissable=False |
||||
) |
||||
return HTTPFound(request.resource_path(context, 'login')) |
||||
|
||||
|
||||
@view_config( |
||||
context='ordr2:resources.Account', |
||||
name='logout', |
||||
permission='logout' |
||||
) |
||||
def logout(context, request): |
||||
''' logout of a user ''' |
||||
if request.user: |
||||
pass |
||||
# request.session.flash(MSG_LOGOUT, 'success') |
||||
headers = forget(request) |
||||
return HTTPFound( |
||||
request.resource_path(request.root, 'about'), |
||||
headers=headers |
||||
) |
||||
|
||||
|
||||
# user registration |
||||
|
||||
@view_config( |
||||
context='ordr2:resources.Account', |
||||
name='register', |
||||
permission='register', |
||||
request_method='GET', |
||||
renderer='ordr2:templates/account/register.jinja2' |
||||
) |
||||
def registration_form(context, request): |
||||
''' display a registration form ''' |
||||
context.nav_highlight = 'register' |
||||
form = RegistrationSchema.as_form(request) |
||||
return {'form': form} |
||||
|
||||
|
||||
@view_config( |
||||
context='ordr2:resources.Account', |
||||
name='register', |
||||
permission='register', |
||||
request_method='POST', |
||||
renderer='ordr2:templates/account/register.jinja2' |
||||
) |
||||
def registration_form_processing(context, request): |
||||
''' process a submitted registration form ''' |
||||
|
||||
if 'Cancel' in request.POST: |
||||
return HTTPFound(request.resource_path(request.root)) |
||||
|
||||
form = RegistrationSchema.as_form(request) |
||||
data = request.POST.items() |
||||
try: |
||||
appstruct = form.validate(data) |
||||
except deform.ValidationFailure as e: |
||||
context.nav_highlight = 'register' |
||||
return {'form': form} |
||||
|
||||
# form validation successfull, create user |
||||
account = User( |
||||
user_name=appstruct['user_name'], |
||||
first_name=appstruct['first_name'], |
||||
last_name=appstruct['last_name'], |
||||
email=appstruct['email'], |
||||
role=Role.NEW |
||||
) |
||||
account.set_password(appstruct['password']) |
||||
request.dbsession.add(account) |
||||
|
||||
request.flash( |
||||
'success', |
||||
'Your account <em>{}</em> has been created.'.format(account.user_name), |
||||
dismissable=False |
||||
) |
||||
if len(appstruct['password']) < MIN_PW_LENGTH: |
||||
request.flash( |
||||
'warning', |
||||
'You should really consider using a longer password.', |
||||
dismissable=False |
||||
) |
||||
return HTTPFound(request.resource_path(context, 'registered')) |
||||
|
||||
|
||||
@view_config( |
||||
context='ordr2:resources.Account', |
||||
name='registered', |
||||
permission='register', |
||||
renderer='ordr2:templates/account/register_sucessful.jinja2' |
||||
) |
||||
def registration_sucessful(context, request): |
||||
''' registration was sucessfull ''' |
||||
return {} |
||||
|
||||
|
||||
# user settings |
||||
|
||||
@view_config( |
||||
context='ordr2:resources.Account', |
||||
name='settings', |
||||
permission='settings', |
||||
request_method='GET', |
||||
renderer='ordr2:templates/account/settings.jinja2' |
||||
) |
||||
def settings_form(context, request): |
||||
''' display the user settings form ''' |
||||
form = SettingsSchema.as_form(request) |
||||
|
||||
form_data = { |
||||
'general': { |
||||
'user_name': request.user.user_name, |
||||
'first_name': request.user.first_name, |
||||
'last_name': request.user.last_name, |
||||
'email': request.user.email, |
||||
'role': request.user.role.value.capitalize() |
||||
} |
||||
} |
||||
form.set_appstruct(form_data) |
||||
return {'form': form} |
||||
|
||||
|
||||
@view_config( |
||||
context='ordr2:resources.Account', |
||||
name='settings', |
||||
permission='settings', |
||||
request_method='POST', |
||||
renderer='ordr2:templates/account/settings.jinja2' |
||||
) |
||||
def settings_form_processing(context, request): |
||||
''' process the user settings form ''' |
||||
|
||||
if 'Cancel' in request.POST: |
||||
return HTTPFound(request.resource_url(request.root)) |
||||
|
||||
form = SettingsSchema.as_form(request) |
||||
data = request.POST.items() |
||||
try: |
||||
appstruct = form.validate(data) |
||||
except deform.ValidationFailure as e: |
||||
return {'form': form} |
||||
|
||||
# form validation sucessful, change settings |
||||
request.user.first_name = appstruct['general']['first_name'] |
||||
request.user.last_name = appstruct['general']['last_name'] |
||||
request.user.email = appstruct['general']['email'] |
||||
if appstruct['change_password']['new_password']: |
||||
request.user.set_password(appstruct['change_password']['new_password']) |
||||
if len(appstruct['change_password']['new_password']) < MIN_PW_LENGTH: |
||||
request.flash( |
||||
'warning', |
||||
'You should really consider using a longer password.' |
||||
) |
||||
|
||||
request.flash('success', 'Your account information has been updated.') |
||||
|
||||
return {'form': form} |
||||
|
||||
|
||||
# passwort reset links |
||||
|
||||
@view_config( |
||||
context='ordr2:resources.PasswordResetAccount', |
||||
permission='reset', |
||||
request_method='GET', |
||||
renderer='ordr2:templates/account/password_reset.jinja2' |
||||
) |
||||
def reset_password_form(context, request): |
||||
''' display the password reset form ''' |
||||
form = ResetPasswordSchema.as_form(request) |
||||
return {'form': form} |
||||
|
||||
|
||||
@view_config( |
||||
context='ordr2:resources.PasswordResetAccount', |
||||
permission='reset', |
||||
request_method='POST', |
||||
renderer='ordr2:templates/account/password_reset.jinja2' |
||||
) |
||||
def reset_password_form_processing(context, request): |
||||
''' process the password reset form ''' |
||||
|
||||
if 'Cancel' in request.POST: |
||||
return HTTPFound(request.resource_url(request.root)) |
||||
|
||||
form = ResetPasswordSchema.as_form(request) |
||||
data = request.POST.items() |
||||
try: |
||||
appstruct = form.validate(data) |
||||
except deform.ValidationFailure as e: |
||||
return {'form': form} |
||||
|
||||
# form validation sucessful, change password and remove reset token |
||||
context.model.set_password(appstruct['new_password']) |
||||
context.model.password_reset = '' |
||||
|
||||
request.flash( |
||||
'success', |
||||
'Password reset successful', |
||||
'Please Log In with your new password', |
||||
) |
||||
if len(appstruct['new_password']) < MIN_PW_LENGTH: |
||||
request.flash( |
||||
'warning', |
||||
'You should really consider using a longer password.' |
||||
) |
||||
|
||||
return HTTPFound(request.resource_url(request.root, 'account', 'login')) |
||||
|
||||
|
@ -0,0 +1,469 @@
@@ -0,0 +1,469 @@
|
||||
''' views for the admin section ''' |
||||
|
||||
import deform |
||||
|
||||
from pyramid.httpexceptions import HTTPFound |
||||
from pyramid.renderers import render |
||||
from pyramid.security import remember, forget |
||||
from pyramid.view import view_config |
||||
|
||||
from ordr2.events import AccountActivation, PasswordReset |
||||
from ordr2.models import Category, Consumable, User, Role |
||||
from ordr2.schemas.account import UserSchema |
||||
from ordr2.schemas.orders import ConsumableSchema |
||||
|
||||
from . import update_column_display |
||||
|
||||
# admin section |
||||
|
||||
@view_config( |
||||
context='ordr2:resources.Admin', |
||||
permission='view', |
||||
renderer='ordr2:templates/admin/admin_section.jinja2' |
||||
) |
||||
def admin_section(context, request): |
||||
''' display the admin section ''' |
||||
new_users = request.dbsession.query(User).filter_by(role=Role.NEW).count() |
||||
if new_users: |
||||
plural = 's' if new_users > 1 else '' |
||||
request.flash( |
||||
'info', |
||||
'{} new user{} have registered.'.format(new_users, plural), |
||||
'''Please <a href="{}">take a look at them</a> and confirm or |
||||
reject the registration by setting the role accordingly. This |
||||
message will disappear when all new registrations have been |
||||
processed.'''.format( |
||||
request.resource_url(context, 'users', query={'role': 'new'}) |
||||
) |
||||
) |
||||
return {} |
||||
|
||||
|
||||
# user list |
||||
|
||||
@view_config( |
||||
context='ordr2:resources.UserList', |
||||
permission='view', |
||||
renderer='ordr2:templates/admin/user_list.jinja2' |
||||
) |
||||
def user_list(context, request): |
||||
''' display the user list ''' |
||||
users = context.items() |
||||
roles = [(role.value.lower(), role.value.capitalize()) for role in Role] |
||||
return {'users':users, 'roles':roles} |
||||
|
||||
|
||||
@view_config( |
||||
context='ordr2:resources.UserList', |
||||
name = 'changeview', |
||||
permission='view', |
||||
request_method='POST' |
||||
) |
||||
def change_column_view(context, request): |
||||
''' changes the columns to display ''' |
||||
update_column_display(request, 'users') |
||||
return HTTPFound(context.url()) |
||||
|
||||
|
||||
@view_config( |
||||
context='ordr2:resources.UserList', |
||||
name='actions', |
||||
request_param='action=search', |
||||
permission='view', |
||||
request_method='POST' |
||||
) |
||||
def user_search(context, request): |
||||
''' process the search user form ''' |
||||
term = request.POST.get('search', '') |
||||
term = term.strip() |
||||
if term: |
||||
return HTTPFound(context.url(search=term, role=None, p=1)) |
||||
return HTTPFound(context.url()) |
||||
|
||||
|
||||
@view_config( |
||||
context='ordr2:resources.UserList', |
||||
name='actions', |
||||
request_param='action=delete', |
||||
permission='delete', |
||||
request_method='POST', |
||||
renderer='ordr2:templates/admin/users_delete.jinja2' |
||||
) |
||||
def delete_multiple_accounts_form(context, request): |
||||
''' show confirmation page for deleting users ''' |
||||
account_ids = [v for k, v in request.POST.items() if k == 'marked'] |
||||
accounts = request.dbsession.\ |
||||
query(User).\ |
||||
filter(User.id.in_(account_ids)).\ |
||||
order_by(User.user_name).\ |
||||
all() |
||||
if len(accounts) == 0: |
||||
return HTTPFound(context.url()) |
||||
return {'accounts': accounts} |
||||
|
||||
|
||||
@view_config( |
||||
context='ordr2:resources.UserList', |
||||
name='actions', |
||||
request_param='action=role', |
||||
permission='edit', |
||||
request_method='POST', |
||||
renderer='ordr2:templates/admin/users_change_roles.jinja2' |
||||
) |
||||
def edit_multiple_roles_form(context, request): |
||||
''' show form for editing multiple user roles ''' |
||||
account_ids = [v for k, v in request.POST.items() if k == 'marked'] |
||||
accounts = request.dbsession.\ |
||||
query(User).\ |
||||
filter(User.id.in_(account_ids)).\ |
||||
order_by(User.user_name).\ |
||||
all() |
||||
if len(accounts) == 0: |
||||
return HTTPFound(context.url()) |
||||
roles = [(role.name, role.value.capitalize()) for role in Role] |
||||
return {'accounts': accounts, 'roles': roles} |
||||
|
||||
|
||||
@view_config( |
||||
context='ordr2:resources.UserList', |
||||
name='roles', |
||||
permission='edit', |
||||
request_method='POST' |
||||
) |
||||
def edit_multiple_roles_form_processing(context, request): |
||||
''' form processing for editing multiple user roles ''' |
||||
|
||||
if 'change' in request.POST: |
||||
count = 0 |
||||
for key, value in request.POST.items(): |
||||
if not key.startswith('account-'): |
||||
continue |
||||
_, account_id = key.split('-', 1) |
||||
account = request.dbsession.query(User).get(account_id) |
||||
if account: |
||||
was_active = account.is_active |
||||
try: |
||||
account.role = Role[value] |
||||
except ValueError: |
||||
pass |
||||
if not was_active and account.is_active: |
||||
# user account was activated, notify user |
||||
event = AccountActivation(request, account) |
||||
request.registry.notify(event) |
||||
count += 1 |
||||
|
||||
if count == 1: |
||||
request.flash('success', 'One user account was updated') |
||||
elif count > 1: |
||||
msg = '{} user accounts were updated.'.format(count) |
||||
request.flash('success', msg) |
||||
|
||||
return HTTPFound(context.url()) |
||||
|
||||
|
||||
# editing one user account |
||||
|
||||
@view_config( |
||||
context='ordr2:resources.UserAccount', |
||||
permission='edit', |
||||
request_method='GET', |
||||
renderer='ordr2:templates/admin/user_edit.jinja2' |
||||
) |
||||
def user_account_form(context, request): |
||||
''' display the user edit form ''' |
||||
form = UserSchema.as_form(request) |
||||
form_data = { |
||||
'user_name': context.model.user_name, |
||||
'first_name': context.model.first_name, |
||||
'last_name': context.model.last_name, |
||||
'email': context.model.email, |
||||
'role': context.model.role.name |
||||
} |
||||
form.set_appstruct(form_data) |
||||
return {'form': form} |
||||
|
||||
|
||||
@view_config( |
||||
context='ordr2:resources.UserAccount', |
||||
permission='edit', |
||||
request_method='POST', |
||||
renderer='ordr2:templates/admin/user_edit.jinja2' |
||||
) |
||||
def user_account_form_processing(context, request): |
||||
''' process the user edit form ''' |
||||
|
||||
form = UserSchema.as_form(request) |
||||
data = request.POST.items() |
||||
|
||||
if 'delete' in request.POST: |
||||
# redirect to delete user confirmation page |
||||
return HTTPFound(request.resource_url(context, 'delete')) |
||||
|
||||
elif 'reset' in request.POST: |
||||
# create a password reset token and notify user |
||||
token = context.model.generate_password_token() |
||||
event = PasswordReset(request, context.model, token) |
||||
request.registry.notify(event) |
||||
msg = 'Password reset mail sent to {}.'.format(context.model.email) |
||||
request.flash('success', msg) |
||||
|
||||
elif 'save' in request.POST: |
||||
try: |
||||
appstruct = form.validate(data) |
||||
except deform.ValidationFailure as e: |
||||
return {'form': form} |
||||
|
||||
# form validation sucessful, change settings |
||||
was_active = context.model.is_active |
||||
context.model.first_name = appstruct['first_name'] |
||||
context.model.last_name = appstruct['last_name'] |
||||
context.model.email = appstruct['email'] |
||||
context.model.role = Role[appstruct['role']] |
||||
|
||||
if not was_active and context.model.is_active: |
||||
# user account was activated, notify user |
||||
event = AccountActivation(request, context.model) |
||||
request.registry.notify(event) |
||||
text = 'An activation email was sent to <em>{}</em>'.format( |
||||
appstruct['email'] |
||||
) |
||||
else: |
||||
text = '' |
||||
|
||||
msg = 'User account <em>{}</em> updated.'.format( |
||||
context.model.user_name |
||||
) |
||||
request.flash('success', msg, text) |
||||
|
||||
return HTTPFound(context.__parent__.url()) |
||||
|
||||
|
||||
@view_config( |
||||
context='ordr2:resources.UserAccount', |
||||
name='delete', |
||||
permission='delete', |
||||
request_method='GET', |
||||
renderer='ordr2:templates/admin/users_delete.jinja2' |
||||
) |
||||
def user_delete_form(context, request): |
||||
''' delete user confirmation page ''' |
||||
return {'accounts': [context.model]} |
||||
|
||||
|
||||
@view_config( |
||||
context='ordr2:resources.UserList', |
||||
name='delete', |
||||
permission='delete', |
||||
request_method='POST' |
||||
) |
||||
@view_config( |
||||
context='ordr2:resources.UserAccount', |
||||
name='delete', |
||||
permission='delete', |
||||
request_method='POST' |
||||
) |
||||
def user_delete_form_processing(context, request): |
||||
''' delete one or multiple users after confirmation ''' |
||||
if 'delete' in request.POST: |
||||
account_ids = [v for k, v in request.POST.items() if k == 'account'] |
||||
accounts = request.dbsession.\ |
||||
query(User).\ |
||||
filter(User.id.in_(account_ids)).\ |
||||
all() |
||||
for account in accounts: |
||||
request.dbsession.delete(account) |
||||
|
||||
if len(accounts) == 1: |
||||
request.flash('success', 'One user account was deleted') |
||||
elif len(accounts) > 1: |
||||
msg = '{} user accounts were deleted.'.format(len(accounts)) |
||||
request.flash('success', msg) |
||||
|
||||
return HTTPFound(request.resource_url(request.root, 'admin', 'users')) |
||||
|
||||
|
||||
# consumables list |
||||
|
||||
@view_config( |
||||
context='ordr2:resources.ConsumableList', |
||||
permission='view', |
||||
renderer='ordr2:templates/admin/consumable_list.jinja2' |
||||
) |
||||
def consumable_list(context, request): |
||||
''' display the consumable list ''' |
||||
consumables = context.items() |
||||
categories = [(c.value.lower(), c.value.capitalize()) for c in Category] |
||||
return {'consumables': consumables, 'categories': categories} |
||||
|
||||
|
||||
@view_config( |
||||
context='ordr2:resources.ConsumableList', |
||||
name='actions', |
||||
request_param='action=search', |
||||
permission='view', |
||||
request_method='POST' |
||||
) |
||||
def consumable_search(context, request): |
||||
''' process the search consumable form ''' |
||||
term = request.POST.get('search', '') |
||||
term = term.strip() |
||||
if term: |
||||
return HTTPFound(context.url(search=term, category=None, p=1)) |
||||
return HTTPFound(context.url()) |
||||
|
||||
|
||||
# adding a consumable |
||||
|
||||
@view_config( |
||||
context='ordr2:resources.ConsumableList', |
||||
name='new', |
||||
permission='create', |
||||
request_method='GET', |
||||
renderer='ordr2:templates/admin/consumable_new.jinja2' |
||||
) |
||||
def consumable_new_form(context, request): |
||||
''' display the new consumable form ''' |
||||
form = ConsumableSchema.as_form(request, is_new_consumable=True) |
||||
return {'form': form} |
||||
|
||||
|
||||
@view_config( |
||||
context='ordr2:resources.ConsumableList', |
||||
name='new', |
||||
permission='create', |
||||
request_method='POST', |
||||
renderer='ordr2:templates/admin/consumable_new.jinja2' |
||||
) |
||||
def consumable_new_form_processing(context, request): |
||||
''' process the new consumable form ''' |
||||
|
||||
form = ConsumableSchema.as_form(request, is_new_consumable=True) |
||||
data = request.POST.items() |
||||
if 'save' in request.POST: |
||||
try: |
||||
appstruct = form.validate(data) |
||||
except deform.ValidationFailure as e: |
||||
return {'form': form} |
||||
|
||||
# form validation sucessful, change consumable |
||||
consumable = Consumable( |
||||
cas_description=appstruct['cas_description'], |
||||
category=Category[appstruct['category']], |
||||
vendor=appstruct['vendor'], |
||||
catalog_nr=appstruct['catalog_nr'], |
||||
package_size=appstruct['package_size'], |
||||
unit_price=appstruct['unit_price']['amount'], |
||||
currency=appstruct['unit_price']['currency'], |
||||
comment=appstruct['comment'] |
||||
) |
||||
request.dbsession.add(consumable) |
||||
|
||||
msg = 'Consumable <em>{!s}</em> added.'.format(consumable) |
||||
request.flash('success', msg) |
||||
|
||||
return HTTPFound(context.url()) |
||||
|
||||
|
||||
# edit a consumable |
||||
|
||||
@view_config( |
||||
context='ordr2:resources.ConsumableResource', |
||||
permission='edit', |
||||
request_method='GET', |
||||
renderer='ordr2:templates/admin/consumable_edit.jinja2' |
||||
) |
||||
def consumable_edit_form(context, request): |
||||
''' display the consumable edit form ''' |
||||
form = ConsumableSchema.as_form(request, is_new_consumable=False) |
||||
form_data = { |
||||
'cas_description': context.model.cas_description, |
||||
'category': context.model.category.name, |
||||
'vendor': context.model.vendor, |
||||
'catalog_nr': context.model.catalog_nr, |
||||
'package_size': context.model.package_size, |
||||
'unit_price': { |
||||
'amount': context.model.unit_price, |
||||
'currency': context.model.currency |
||||
}, |
||||
'comment': context.model.comment |
||||
} |
||||
form.set_appstruct(form_data) |
||||
return {'form': form} |
||||
|
||||
|
||||
@view_config( |
||||
context='ordr2:resources.ConsumableResource', |
||||
permission='edit', |
||||
request_method='POST', |
||||
renderer='ordr2:templates/admin/consumable_edit.jinja2' |
||||
) |
||||
def consumable_edit_form_processing(context, request): |
||||
''' process the consumable edit form ''' |
||||
|
||||
form = ConsumableSchema.as_form(request, is_new_consumable=False) |
||||
data = request.POST.items() |
||||
if 'save' in request.POST: |
||||
try: |
||||
appstruct = form.validate(data) |
||||
except deform.ValidationFailure as e: |
||||
return {'form': form} |
||||
|
||||
# form validation sucessful, change consumable |
||||
context.model.cas_description = appstruct['cas_description'] |
||||
context.model.category = Category[appstruct['category']] |
||||
context.model.vendor = appstruct['vendor'] |
||||
context.model.catalog_nr = appstruct['catalog_nr'] |
||||
context.model.package_size = appstruct['package_size'] |
||||
context.model.unit_price = appstruct['unit_price']['amount'] |
||||
context.model.currency = appstruct['unit_price']['currency'] |
||||
context.model.comment = appstruct['comment'] |
||||
|
||||
msg = 'Consumable <em>{!s}</em> updated.'.format(context.model) |
||||
request.flash('success', msg) |
||||
|
||||
elif 'delete' in request.POST and context.model: |
||||
# redirect to delete consumable confirmation page |
||||
return HTTPFound(request.resource_url(context, 'delete')) |
||||
|
||||
return HTTPFound(context.__parent__.url()) |
||||
|
||||
|
||||
# delete consumable |
||||
|
||||
@view_config( |
||||
context='ordr2:resources.ConsumableResource', |
||||
name='delete', |
||||
permission='delete', |
||||
request_method='GET', |
||||
renderer='ordr2:templates/admin/consumable_delete.jinja2' |
||||
) |
||||
def consumable_delete_form(context, request): |
||||
''' delete consumable confirmation page ''' |
||||
return {'consumables': [context.model]} |
||||
|
||||
|
||||
@view_config( |
||||
context='ordr2:resources.ConsumableResource', |
||||
name='delete', |
||||
permission='delete', |
||||
request_method='POST' |
||||
) |
||||
def consumable_delete_form_processing(context, request): |
||||
''' delete consumable after confirmation ''' |
||||
if 'delete' in request.POST: |
||||
c_ids = [v for k, v in request.POST.items() if k == 'consumable'] |
||||
consumables = request.dbsession.\ |
||||
query(Consumable).\ |
||||
filter(Consumable.id.in_(c_ids)).\ |
||||
all() |
||||
for consumable in consumables: |
||||
request.dbsession.delete(consumable) |
||||
|
||||
if len(consumables) == 1: |
||||
request.flash('success', 'One consumable was deleted') |
||||
elif len(consumables) > 1: |
||||
msg = '{} consumables were deleted.'.format(len(accounts)) |
||||
request.flash('success', msg) |
||||
|
||||
return HTTPFound(context.__parent__.url()) |
||||
|
@ -1,33 +0,0 @@
@@ -1,33 +0,0 @@
|
||||
from pyramid.response import Response |
||||
from pyramid.view import view_config |
||||
|
||||
from sqlalchemy.exc import DBAPIError |
||||
|
||||
from ..models import MyModel |
||||
|
||||
|
||||
@view_config(context='ordr2.resources.Root', renderer='../templates/mytemplate.jinja2') |
||||
def my_view(context, request): |
||||
try: |
||||
query = request.dbsession.query(MyModel) |
||||
one = query.filter(MyModel.name == 'one').first() |
||||
except DBAPIError: |
||||
return Response(db_err_msg, content_type='text/plain', status=500) |
||||
return {'one': one, 'project': 'Ordr2'} |
||||
|
||||
|
||||
db_err_msg = '''\ |
||||
Pyramid is having a problem using your SQL database. The problem |
||||
might be caused by one of the following things: |
||||
|
||||
1. You may need to run the 'initialize_ordr2_db' script |
||||
to initialize your database tables. Check your virtual |
||||
environment's 'bin' directory for this script and try to run it. |
||||
|
||||
2. Your database server may not be running. Check that the |
||||
database server referred to by the 'sqlalchemy.url' setting in |
||||
your 'development.ini' file is running. |
||||
|
||||
After you fix the problem, please restart the Pyramid application to |
||||
try it again. |
||||
''' |
@ -0,0 +1,78 @@
@@ -0,0 +1,78 @@
|
||||
''' display error pages ''' |
||||
|
||||
import io |
||||
import pprint |
||||
import traceback |
||||
import sys |
||||
|
||||
|
||||
from pyramid.exceptions import BadCSRFToken |
||||
from pyramid.view import ( |
||||
notfound_view_config, |
||||
forbidden_view_config, |
||||
view_config |
||||
) |
||||
from pyramid_mailer.message import Message |
||||
|
||||
|
||||
|
||||
@notfound_view_config(renderer='ordr2:templates/errors/not_found.jinja2') |
||||
def notfound_view(context, request): |
||||
context.nav_highlight = 'errors' |
||||
request.response.status = 404 |
||||
return {} |
||||
|
||||
|
||||
@forbidden_view_config(renderer='ordr2:templates/errors/forbidden.jinja2') |
||||
def forbidden_view(context, request): |
||||
context.nav_highlight = 'errors' |
||||
request.response.status = 403 |
||||
return {} |
||||
|
||||
|
||||
@view_config( |
||||
context=BadCSRFToken, |
||||
renderer='ordr2:templates/errors/bad_csrf_token.jinja2' |
||||
) |
||||
def bad_csrf_view(context, request): |
||||
context.nav_highlight = 'errors' |
||||
request.response.status = 400 |
||||
return {} |
||||
|
||||
|
||||
@view_config( |
||||
context=Exception, |
||||
renderer='ordr2:templates/errors/exception.jinja2' |
||||
) |
||||
def exception_view(context, request): |
||||
|
||||
exc_type, exc_value, exc_traceback = sys.exc_info() |
||||
traceback_output = io.StringIO() |
||||
traceback.print_exc(file=traceback_output) |
||||
|
||||
request_output = io.StringIO() |
||||
pprint.pprint(request.__dict__, stream=request_output) |
||||
|
||||
settings = request.registry.settings |
||||
default_sender = settings['mail.default_sender'] |
||||
recipient = settings.get('admin_email', None) or default_sender |
||||
|
||||
body = '\n'.join([ |
||||
traceback_output.getvalue(), |
||||
'', |
||||
'', |
||||
'Request:', |
||||
request_output.getvalue() |
||||
]) |
||||
|
||||
message = Message( |
||||
subject='[ordr] Exception occured', |
||||
sender=default_sender, |
||||
recipients=[recipient], |
||||
body=body |
||||
) |
||||
request.mailer.send_immediately(message) |
||||
|
||||
context.nav_highlight = 'errors' |
||||
request.response.status = 500 |
||||
return {} |
@ -1,7 +0,0 @@
@@ -1,7 +0,0 @@
|
||||
from pyramid.view import notfound_view_config |
||||
|
||||
|
||||
@notfound_view_config(renderer='../templates/404.jinja2') |
||||
def notfound_view(context, request): |
||||
request.response.status = 404 |
||||
return {} |
@ -0,0 +1,579 @@
@@ -0,0 +1,579 @@
|
||||
''' views for creating and editing orders ''' |
||||
|
||||
import deform |
||||
import io |
||||
import xlsxwriter |
||||
|
||||
from collections import OrderedDict |
||||
from datetime import datetime |
||||
|
||||
from pyramid.httpexceptions import HTTPFound |
||||
from pyramid.renderers import render |
||||
from pyramid.response import FileIter |
||||
from pyramid.view import view_config |
||||
|
||||
from ordr2.events import OrderStatusChange |
||||
from ordr2.models import Category, Consumable, Order, OrderStatus, User |
||||
from ordr2.schemas.orders import NewOrderSchema, EditOrderSchema |
||||
|
||||
from . import update_column_display |
||||
|
||||
|
||||
# helper method |
||||
|
||||
def change_in_order_status(request, order, old): |
||||
''' notifies a user if a noteworthy change in a order occured ''' |
||||
noteworthy = False |
||||
|
||||
if order.status == OrderStatus.APPROVAL and order.status != old: |
||||
order.approval_date = datetime.utcnow() |
||||
order.approval_by = request.user.user_name |
||||
elif order.status == OrderStatus.ORDERED and order.status != old: |
||||
noteworthy = True |
||||
order.ordered_date = datetime.utcnow() |
||||
order.ordered_by = request.user.user_name |
||||
elif order.status == OrderStatus.COMPLETED and order.status != old: |
||||
noteworthy = True |
||||
order.completed_date = datetime.utcnow() |
||||
order.completed_by = request.user.user_name |
||||
|
||||
if noteworthy and order.created_by != request.user.user_name: |
||||
# only notify if the user who changed the order is not the one who |
||||
# created it in the first place |
||||
account = request.dbsession.\ |
||||
query(User).\ |
||||
filter_by(user_name=order.created_by).\ |
||||
first() |
||||
if account: |
||||
event = OrderStatusChange(request, account, order) |
||||
request.registry.notify(event) |
||||
|
||||
|
||||
@view_config( |
||||
context='ordr2:resources.OrderList', |
||||
name='view', |
||||
permission='view' |
||||
) |
||||
def old_list_redirect(context, request): |
||||
''' redirect the old /orders/view/ path to /orders/ ''' |
||||
return HTTPFound(context.url()) |
||||
|
||||
|
||||
# oder list |
||||
|
||||
@view_config( |
||||
context='ordr2:resources.OrderList', |
||||
permission='view', |
||||
renderer='ordr2:templates/orders/list.jinja2' |
||||
) |
||||
def order_list(context, request): |
||||
''' display the order list ''' |
||||
orders = context.items() |
||||
stati = [(s.value.lower(), s.value.capitalize()) for s in OrderStatus] |
||||
return {'orders':orders, 'stati':stati} |
||||
|
||||
|
||||
@view_config( |
||||
context='ordr2:resources.OrderList', |
||||
name = 'changeview', |
||||
permission='view', |
||||
request_method='POST' |
||||
) |
||||
def change_column_view(context, request): |
||||
''' changes the columns to display ''' |
||||
update_column_display(request, 'orders') |
||||
return HTTPFound(context.url()) |
||||
|
||||
|
||||
@view_config( |
||||
context='ordr2:resources.OrderList', |
||||
name='actions', |
||||
request_param='action=export', |
||||
permission='view', |
||||
request_method='POST', |
||||
renderer='ordr2:templates/orders/edit_multiple_stati.jinja2' |
||||
) |
||||
def download_view(context, request): |
||||
''' downloads the displayed order list as an excel file |
||||
|
||||
see https://xlsxwriter.readthedocs.io/example_http_server3.html ''' |
||||
# Create an in-memory output file for the new workbook. |
||||
output = io.BytesIO() |
||||
# Even though the final file will be in memory the module uses temp |
||||
# files during assembly for efficiency. To avoid this on servers that |
||||
# don't allow temp files, for example the Google APP Engine, set the |
||||
# 'in_memory' constructor option to True: |
||||
workbook = xlsxwriter.Workbook(output, {'in_memory': True}) |
||||
worksheet = workbook.add_worksheet() |
||||
|
||||
# formatting |
||||
bold = workbook.add_format({'bold': 1}) |
||||
# Add a number format for cells with money. |
||||
money_format = workbook.add_format({'num_format': '0.00'}) |
||||
# Add an Excel date format. |
||||
date_format = workbook.add_format({'num_format': 'yy-mm-dd hh:mm'}) |
||||
|
||||
# Write the column headers |
||||
headers = [ |
||||
'Placed On', 'CAS / Description', 'Vendor', 'Catalog Nr', |
||||
'Package Size', 'Unit Price', 'Quantity', 'Total Price', 'Currency', |
||||
'Account', 'Status', 'Category', 'Placed By' |
||||
] |
||||
for col, header in enumerate(headers): |
||||
worksheet.write(0, col, header, bold) |
||||
|
||||
for row, resource in enumerate(context.items()): |
||||
order = resource.model |
||||
worksheet.write(row + 1, 0, order.created_date, date_format) |
||||
worksheet.write_string(row + 1, 1, order.cas_description) |
||||
worksheet.write_string(row + 1, 2, order.vendor) |
||||
worksheet.write_string(row + 1, 3, order.catalog_nr) |
||||
worksheet.write_string(row + 1, 4, order.package_size) |
||||
worksheet.write(row + 1, 5, order.unit_price, money_format) |
||||
worksheet.write(row + 1, 6, order.amount) |
||||
worksheet.write(row + 1, 7, order.total_price, money_format) |
||||
worksheet.write_string(row + 1, 8, order.currency) |
||||
worksheet.write_string(row + 1, 9, order.account) |
||||
worksheet.write_string(row + 1, 10, order.status.value.capitalize()) |
||||
worksheet.write_string(row + 1, 11, order.category.value.capitalize()) |
||||
worksheet.write_string(row + 1, 12, order.created_by) |
||||
|
||||
# Close the workbook before streaming the data. |
||||
workbook.close() |
||||
# Rewind the buffer. |
||||
output.seek(0) |
||||
|
||||
response = request.response |
||||
response.app_iter = FileIter(output) |
||||
headers = response.headers |
||||
headers['Content-Type'] = 'application/download' |
||||
headers['Accept-Ranges'] = 'bite' |
||||
headers['Content-Disposition'] = 'attachment;filename=order-list.xlsx' |
||||
return response |
||||
|
||||
|
||||
@view_config( |
||||
context='ordr2:resources.OrderList', |
||||
name='actions', |
||||
request_param='action=search', |
||||
permission='view', |
||||
request_method='POST' |
||||
) |
||||
def search(context, request): |
||||
''' process the search order form ''' |
||||
term = request.POST.get('search', '') |
||||
term = term.strip() |
||||
if term: |
||||
return HTTPFound(context.url(search=term, user=None, status=None, p=1)) |
||||
return HTTPFound(context.url()) |
||||
|
||||
|
||||
@view_config( |
||||
context='ordr2:resources.OrderList', |
||||
name='actions', |
||||
request_param='action=status', |
||||
permission='edit', |
||||
request_method='POST', |
||||
renderer='ordr2:templates/orders/edit_multiple_stati.jinja2' |
||||
) |
||||
def edit_multiple_stati_form(context, request): |
||||
''' form for editing the stati of multiple orders ''' |
||||
order_ids = [v for k, v in request.POST.items() if k == 'marked'] |
||||
orders = request.dbsession.\ |
||||
query(Order).\ |
||||
filter(Order.id.in_(order_ids)).\ |
||||
order_by(Order.created_date).\ |
||||
all() |
||||
if len(orders) == 0: |
||||
return HTTPFound(context.url()) |
||||
stati = [(s.name, s.value.capitalize()) for s in OrderStatus] |
||||
return {'orders': orders, 'stati': stati} |
||||
|
||||
|
||||
@view_config( |
||||
context='ordr2:resources.OrderList', |
||||
name='stati', |
||||
permission='edit', |
||||
request_method='POST' |
||||
) |
||||
def edit_multiple_stati_form_processing(context, request): |
||||
''' change the stati of multiple orders ''' |
||||
|
||||
if 'change' in request.POST: |
||||
count = 0 |
||||
for key, value in request.POST.items(): |
||||
if not key.startswith('order-'): |
||||
continue |
||||
_, order_id = key.split('-', 1) |
||||
order = request.dbsession.query(Order).get(order_id) |
||||
if order: |
||||
old_status = order.status |
||||
try: |
||||
order.status = OrderStatus[value] |
||||
except ValueError: |
||||
pass |
||||
if old_status != order.status: |
||||
change_in_order_status(request, order, old_status) |
||||
count += 1 |
||||
|
||||
if count == 1: |
||||
request.flash('success', 'One order was updated') |
||||
elif count > 1: |
||||
msg = '{} orders were updated.'.format(count) |
||||
request.flash('success', msg) |
||||
|
||||
return HTTPFound(context.url()) |
||||
|
||||
|
||||
@view_config( |
||||
context='ordr2:resources.OrderList', |
||||
name='actions', |
||||
request_param='action=delete', |
||||
permission='delete', |
||||
request_method='POST', |
||||
renderer='ordr2:templates/orders/delete.jinja2' |
||||
) |
||||
def delete_multiple_orders_form(context, request): |
||||
''' show confirmation page for deleting multiple orders ''' |
||||
order_ids = [v for k, v in request.POST.items() if k == 'marked'] |
||||
orders = request.dbsession.\ |
||||
query(Order).\ |
||||
filter(Order.id.in_(order_ids)).\ |
||||
order_by(Order.created_date).\ |
||||
all() |
||||
if len(orders) == 0: |
||||
return HTTPFound(context.url()) |
||||
return {'orders': orders} |
||||
|
||||
|
||||
@view_config( |
||||
context='ordr2:resources.OrderList', |
||||
name='delete', |
||||
permission='delete', |
||||
request_method='POST' |
||||
) |
||||
@view_config( |
||||
context='ordr2:resources.OrderResource', |
||||
name='delete', |
||||
permission='delete', |
||||
request_method='POST' |
||||
) |
||||
def order_delete_form_processing(context, request): |
||||
''' delete one or multiple orders after confirmation ''' |
||||
if 'delete' in request.POST: |
||||
order_ids = [v for k, v in request.POST.items() if k == 'order'] |
||||
orders = request.dbsession.\ |
||||
query(Order).\ |
||||
filter(Order.id.in_(order_ids)).\ |
||||
all() |
||||
for order in orders: |
||||
request.dbsession.delete(order) |
||||
|
||||
if len(orders) == 1: |
||||
request.flash('success', 'One order was deleted') |
||||
elif len(orders) > 1: |
||||
msg = '{} orders were deleted.'.format(len(orders)) |
||||
request.flash('success', msg) |
||||
|
||||
return HTTPFound(request.resource_url(request.root, 'orders')) |
||||
|
||||
|
||||
# Single Order views |
||||
|
||||
@view_config( |
||||
context='ordr2:resources.OrderResource', |
||||
permission='view', |
||||
request_method='GET', |
||||
renderer='ordr2:templates/orders/view.jinja2' |
||||
) |
||||
def order_view(context, request): |
||||
''' show the order information ''' |
||||
return {} |
||||
|
||||
|
||||
@view_config( |
||||
context='ordr2:resources.OrderResource', |
||||
name='delete', |
||||
permission='delete', |
||||
request_method='GET', |
||||
renderer='ordr2:templates/orders/delete.jinja2' |
||||
) |
||||
def order_delete_form(context, request): |
||||
''' show the confirmation page for deleting one order ''' |
||||
return {'orders': [context.model]} |
||||
|
||||
|
||||
@view_config( |
||||
context='ordr2:resources.OrderResource', |
||||
name='edit', |
||||
permission='edit', |
||||
request_method='GET', |
||||
renderer='ordr2:templates/orders/edit.jinja2' |
||||
) |
||||
def order_edit_form(context, request): |
||||
''' show the edit order form ''' |
||||
form = EditOrderSchema.as_form(request) |
||||
order = context.model |
||||
info = { |
||||
'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 = { |
||||
'order_information': info, |
||||
'item_information': item, |
||||
'pricing': pricing, |
||||
'optional_information': optional |
||||
} |
||||
form.set_appstruct(form_data) |
||||
return {'form': form} |
||||
|
||||
|
||||
@view_config( |
||||
context='ordr2:resources.OrderResource', |
||||
name='edit', |
||||
permission='edit', |
||||
request_method='POST', |
||||
renderer='ordr2:templates/orders/edit.jinja2' |
||||
) |
||||
def order_edit_form_processing(context, request): |
||||
''' process the edit order form ''' |
||||
|
||||
form = EditOrderSchema.as_form(request) |
||||
data = request.POST.items() |
||||
|
||||
if 'save' in request.POST: |
||||
try: |
||||
appstruct = form.validate(data) |
||||
except deform.ValidationFailure as e: |
||||
return {'form': form} |
||||
|
||||
# form validation sucessful, change order |
||||
order = context.model |
||||
info = appstruct['order_information'] |
||||
item = appstruct['item_information'] |
||||
pricing = appstruct['pricing'] |
||||
optional = appstruct['optional_information'] |
||||
old_status = order.status |
||||
|
||||
order.status = OrderStatus[info['status']] |
||||
|
||||
order.cas_description = item['cas_description'] |
||||
order.category = item['category'] |
||||
order.vendor = item['vendor'] |
||||
order.catalog_nr = item['catalog_nr'] |
||||
order.package_size = item['package_size'] |
||||
|
||||
order.unit_price = pricing['unit_price']['amount'] |
||||
order.currency = pricing['unit_price']['currency'] |
||||
order.amount = pricing['quantity'] |
||||
order.total_price = order.unit_price * order.amount |
||||
|
||||
order.account = optional['account'] |
||||
order.comment = optional['comment'] |
||||
|
||||
if old_status != order.status: |
||||
change_in_order_status(request, order, old_status) |
||||
|
||||
msg = 'Order <em>{!s}</em> updated.'.format(context.model) |
||||
request.flash('success', msg) |
||||
|
||||
elif 'delete' in request.POST and context.model: |
||||
# redirect to delete order confirmation page |
||||
return HTTPFound(request.resource_url(context, 'delete')) |
||||
|
||||
elif 'reorder' in request.POST and context.model: |
||||
# redirect to create new order form |
||||
return HTTPFound( |
||||
request.resource_url( |
||||
context.__parent__, |
||||
'new', |
||||
query={'reorder': context.model.id} |
||||
) |
||||
) |
||||
|
||||
return HTTPFound(context.__parent__.url()) |
||||
|
||||
|
||||
@view_config( |
||||
context='ordr2:resources.OrderList', |
||||
name='new', |
||||
permission='create', |
||||
request_method='GET', |
||||
renderer='ordr2:templates/orders/new.jinja2' |
||||
) |
||||
def order_new_form(context, request): |
||||
''' create a new order ''' |
||||
form = NewOrderSchema.as_form(request) |
||||
|
||||
# check if the form should be prefilled with some data, |
||||
# either from selecting a consumable or from reordering an item |
||||
consumable_id = request.GET.get('consumable', None) |
||||
order_id = request.GET.get('reorder', None) |
||||
prefill = None |
||||
|
||||
if order_id: |
||||
prefill = request.dbsession.query(Order).get(order_id) |
||||
elif consumable_id: |
||||
prefill = request.dbsession.query(Consumable).get(consumable_id) |
||||
|
||||
# prefill the form data |
||||
if prefill: |
||||
# some fields depend on if the prefill was a reorder or consumable |
||||
quantity = prefill.amount if order_id else 1 |
||||
total_price = prefill.total_price if order_id else prefill.unit_price |
||||
account = prefill.account if order_id else '' |
||||
item = { |
||||
'cas_description': prefill.cas_description, |
||||
'category': prefill.category.name, |
||||
'vendor': prefill.vendor, |
||||
'catalog_nr': prefill.catalog_nr, |
||||
'package_size': prefill.package_size |
||||
} |
||||
pricing = { |
||||
'unit_price': { |
||||
'amount': '%.2f' % prefill.unit_price, |
||||
'currency': prefill.currency |
||||
}, |
||||
'quantity': quantity, |
||||
'total_price': { |
||||
'amount': '%.2f' % total_price, |
||||
'currency': prefill.currency |
||||
}, |
||||
} |
||||
optional = { |
||||
'account': account, |
||||
'comment': prefill.comment |
||||
} |
||||
|
||||
form_data = { |
||||
'item_information': item, |
||||
'pricing': pricing, |
||||
'optional_information': optional |
||||
} |
||||
form.set_appstruct(form_data) |
||||
|
||||
return {'form': form} |
||||
|
||||
|
||||
@view_config( |
||||
context='ordr2:resources.OrderList', |
||||
name='splash', |
||||
permission='create', |
||||
request_method='GET', |
||||
renderer='ordr2:templates/orders/splash.jinja2' |
||||
) |
||||
def order_splash(context, request): |
||||
''' splash screen for a new order where consumables can be selected ''' |
||||
structured = OrderedDict() |
||||
for cat in Category: |
||||
structured[cat] = [] |
||||
available = request.dbsession.\ |
||||
query(Consumable).\ |
||||
order_by(Consumable.cas_description).\ |
||||
all() |
||||
names = [] |
||||
for consumable in available: |
||||
names.append(consumable.cas_description) |
||||
structured[consumable.category].append(consumable) |
||||
return {'consumable_names': names, 'consumables': structured} |
||||
|
||||
|
||||
@view_config( |
||||
context='ordr2:resources.OrderList', |
||||
name='splash', |
||||
permission='create', |
||||
request_method='POST', |
||||
renderer='ordr2:templates/orders/splash.jinja2' |
||||
) |
||||
def order_splash_processing(context, request): |
||||
''' process the splash screen selection ''' |
||||
name = request.POST.get('search') |
||||
consumable = request.dbsession.\ |
||||
query(Consumable).\ |
||||
filter_by(cas_description=name).\ |
||||
first() |
||||
if consumable: |
||||
return HTTPFound( |
||||
request.resource_url( |
||||
context, |
||||
'new', |
||||
query={'consumable': consumable.id} |
||||
) |
||||
) |
||||
|
||||
request.flash('error', 'No consumable selected') |
||||
return HTTPFound(request.resource_url(context, 'splash')) |
||||
|
||||
|
||||
@view_config( |
||||
context='ordr2:resources.OrderList', |
||||
name='new', |
||||
permission='create', |
||||
request_method='POST', |
||||
renderer='ordr2:templates/orders/new.jinja2' |
||||
) |
||||
def order_new_form_processing(context, request): |
||||
''' process the new order form ''' |
||||
|
||||
form = NewOrderSchema.as_form(request) |
||||
data = request.POST.items() |
||||
|
||||
if 'save' in request.POST: |
||||
|
||||
try: |
||||
appstruct = form.validate(data) |
||||
except deform.ValidationFailure as e: |
||||
return {'form': form} |
||||
|
||||
# form validation sucessful, change order |
||||
order = Order( |
||||
status=OrderStatus.OPEN, |
||||
created_by=request.user.user_name |
||||
) |
||||
item = appstruct['item_information'] |
||||
pricing = appstruct['pricing'] |
||||
optional = appstruct['optional_information'] |
||||
|
||||
order.cas_description = item['cas_description'] |
||||
order.category = item['category'] |
||||
order.vendor = item['vendor'] |
||||
order.catalog_nr = item['catalog_nr'] |
||||
order.package_size = item['package_size'] |
||||
|
||||
order.unit_price = pricing['unit_price']['amount'] |
||||
order.currency = pricing['unit_price']['currency'] |
||||
order.amount = pricing['quantity'] |
||||
order.total_price = order.unit_price * order.amount |
||||
|
||||
order.account = optional['account'] |
||||
order.comment = optional['comment'] |
||||
|
||||
request.dbsession.add(order) |
||||
|
||||
msg = 'Order <em>{!s}</em> created.'.format(context.model) |
||||
request.flash('success', msg) |
||||
|
||||
return HTTPFound(context.url()) |
||||
|
@ -0,0 +1,36 @@
@@ -0,0 +1,36 @@
|
||||
''' views for static pages ''' |
||||
|
||||
from pyramid.httpexceptions import HTTPFound |
||||
from pyramid.view import view_config |
||||
|
||||
|
||||
@view_config( |
||||
context='ordr2.resources.Root', |
||||
permission='view' |
||||
) |
||||
def welcome(context, request): |
||||
next = 'orders' if request.user else 'about' |
||||
redirect_to = request.resource_url(context, next) |
||||
return HTTPFound(redirect_to) |
||||
|
||||
|
||||
@view_config( |
||||
context='ordr2.resources.Root', |
||||
name='about', |
||||
permission='view', |
||||
renderer='ordr2:templates/pages/welcome.jinja2' |
||||
) |
||||
def about(context, request): |
||||
context.nav_highlight = 'about' |
||||
return {} |
||||
|
||||
|
||||
@view_config( |
||||
context='ordr2.resources.Root', |
||||
name='faq', |
||||
permission='view', |
||||
renderer='ordr2:templates/pages/faq.jinja2' |
||||
) |
||||
def faqs(context, request): |
||||
context.nav_highlight = 'faq' |
||||
return {} |
Some files were not shown because too many files have changed in this diff Show More
Reference in new issue