Commit 20f4869a by Owo Sugiana

Bug fixed manifest

1 parent 1001ca9c
include *.txt *.ini *.cfg *.rst
recursive-include web_starter *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2
recursive-include web_starter *.csv *.png *.css *.pt *.mako *.pot *.mo *.po
......@@ -64,6 +64,7 @@ def run_migrations_online():
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
......
"""alter users date fields with time zone
Revision ID: 074b33635316
Revises:
Revises:
Create Date: 2018-10-11 12:00:48.568483
"""
......@@ -17,13 +17,16 @@ depends_on = None
def upgrade():
op.alter_column('users', 'last_login_date',
op.alter_column(
'users', 'last_login_date',
type_=sa.DateTime(timezone=True),
existing_type=sa.DateTime(timezone=False))
op.alter_column('users', 'registered_date',
op.alter_column(
'users', 'registered_date',
type_=sa.DateTime(timezone=True),
existing_type=sa.DateTime(timezone=False))
op.alter_column('users', 'security_code_date',
op.alter_column(
'users', 'security_code_date',
type_=sa.DateTime(timezone=True),
existing_type=sa.DateTime(timezone=False))
......
......@@ -32,7 +32,6 @@ requires = [
'deform',
'pyramid_beaker',
'pyramid_mailer',
# 'opensipkd-jsonrpc @ git+https://git.opensipkd.com/sugiana/opensipkd-jsonrpc',
]
tests_require = [
......@@ -69,7 +68,8 @@ setup(
'main = web_starter:main',
],
'console_scripts': [
'initialize_web_starter_db = web_starter.scripts.initialize_db:main',
'initialize_web_starter_db = '
'web_starter.scripts.initialize_db:main',
'send_email = web_starter.scripts.send_email:main',
]
},
......
......@@ -9,12 +9,14 @@ from ziggurat_foundations.models.base import BaseModel
from ziggurat_foundations.models.external_identity import ExternalIdentityMixin
from ziggurat_foundations.models.group import GroupMixin
from ziggurat_foundations.models.group_permission import GroupPermissionMixin
from ziggurat_foundations.models.group_resource_permission import GroupResourcePermissionMixin
from ziggurat_foundations.models.group_resource_permission import \
GroupResourcePermissionMixin
from ziggurat_foundations.models.resource import ResourceMixin
from ziggurat_foundations.models.user import UserMixin
from ziggurat_foundations.models.user_group import UserGroupMixin
from ziggurat_foundations.models.user_permission import UserPermissionMixin
from ziggurat_foundations.models.user_resource_permission import UserResourcePermissionMixin
from ziggurat_foundations.models.user_resource_permission import \
UserResourcePermissionMixin
from ziggurat_foundations import ziggurat_model_init
from . import (
Base,
......@@ -27,13 +29,16 @@ from . import (
ziggurat_foundations.models.DBSession = DBSession
# optional for folks who pass request.db to model methods
# Base is sqlalchemy's Base = declarative_base() from your project
class Group(GroupMixin, Base, CommonModel):
pass
class GroupPermission(GroupPermissionMixin, Base):
pass
class UserGroup(UserGroupMixin, Base):
pass
......@@ -45,6 +50,7 @@ class GroupResourcePermission(GroupResourcePermissionMixin, Base):
"resource_id",
"perm_name"),)
class Resource(ResourceMixin, Base):
# ... your own properties....
......@@ -61,35 +67,39 @@ class Resource(ResourceMixin, Base):
ALL_PERMISSIONS,), ])
return acls
class UserPermission(UserPermissionMixin, Base):
pass
class UserResourcePermission(UserResourcePermissionMixin, Base):
pass
class User(UserMixin, Base, CommonModel):
# ... your own properties....
pass
class ExternalIdentity(ExternalIdentityMixin, Base):
pass
# you can define multiple resource derived models to build a complex
# application like CMS, forum or other permission based solution
#class Entry(Resource):
# """
# Resource of `entry` type
# """
# class Entry(Resource):
# """
# Resource of `entry` type
# """
# __tablename__ = 'entries'
# __mapper_args__ = {'polymorphic_identity': 'entry'}
# __tablename__ = 'entries'
# __mapper_args__ = {'polymorphic_identity': 'entry'}
# resource_id = sa.Column(sa.Integer(),
# sa.ForeignKey('resources.resource_id',
# onupdate='CASCADE',
# ondelete='CASCADE', ),
# primary_key=True, )
# resource_id = sa.Column(sa.Integer(),
# sa.ForeignKey('resources.resource_id',
# onupdate='CASCADE',
# ondelete='CASCADE', ),
# primary_key=True, )
# ... your own properties....
# some_property = sa.Column(sa.UnicodeText())
......@@ -105,7 +115,7 @@ class RootFactory:
self.__acl__.append((Allow, acl_name, gp.perm_name))
#ziggurat_model_init(User, Group, UserGroup, GroupPermission, passwordmanager=None)
ziggurat_model_init(User, Group, UserGroup, GroupPermission, UserPermission,
UserResourcePermission, GroupResourcePermission, Resource,
ExternalIdentity, passwordmanager=None)
ziggurat_model_init(
User, Group, UserGroup, GroupPermission, UserPermission,
UserResourcePermission, GroupResourcePermission, Resource,
ExternalIdentity, passwordmanager=None)
......@@ -62,14 +62,14 @@ def read_file(filename):
def alembic_run(ini_file, url):
bin_path = os.path.split(sys.executable)[0]
alembic_bin = os.path.join(bin_path, 'alembic')
command = (alembic_bin, 'upgrade', 'head')
alembic_bin = os.path.join(bin_path, 'alembic')
command = (alembic_bin, 'upgrade', 'head')
s = read_file(ini_file)
s = s.replace('{db_url}', url)
f = open('alembic.ini', 'w')
f.write(s)
f.close()
subprocess.call(command)
subprocess.call(command)
os.remove('alembic.ini')
......@@ -81,7 +81,7 @@ def get_file(filename):
def ask_password(name):
localizer = MyLocalizer()
data = dict(name=name)
data = dict(name=name)
t_msg1 = _(
'ask-password-1', default='Enter new password for ${name}: ',
mapping=data)
......@@ -105,13 +105,13 @@ def restore_csv(table, filename):
q = DBSession.query(table)
if q.first():
return
with get_file(filename) as f:
with get_file(filename) as f:
reader = csv.DictReader(f)
for cf in reader:
row = table()
for fieldname in cf:
val = cf[fieldname]
if not val:
if not val:
continue
setattr(row, fieldname, val)
DBSession.add(row)
......@@ -119,7 +119,7 @@ def restore_csv(table, filename):
def append_csv(table, filename, keys):
with get_file(filename) as f:
with get_file(filename) as f:
reader = csv.DictReader(f)
filter_ = dict()
for cf in reader:
......@@ -132,7 +132,7 @@ def append_csv(table, filename, keys):
row = table()
for fieldname in cf:
val = cf[fieldname]
if not val:
if not val:
continue
setattr(row, fieldname, val)
DBSession.add(row)
......
import calendar
import calendar
from datetime import (
date,
datetime,
......@@ -12,50 +12,56 @@ def get_timezone():
return pytz.timezone(settings['timezone'])
def create_datetime(year, month, day, hour=0, minute=7, second=0,
microsecond=0):
tz = get_timezone()
return datetime(year, month, day, hour, minute, second,
microsecond, tzinfo=tz)
def create_datetime(
year, month, day, hour=0, minute=7, second=0, microsecond=0):
tz = get_timezone()
return datetime(
year, month, day, hour, minute, second, microsecond, tzinfo=tz)
def create_date(year, month, day):
def create_date(year, month, day):
return create_datetime(year, month, day)
def as_timezone(tz_date):
localtz = get_timezone()
if not tz_date.tzinfo:
tz_date = create_datetime(tz_date.year, tz_date.month, tz_date.day,
tz_date.hour, tz_date.minute, tz_date.second,
tz_date.microsecond)
return tz_date.astimezone(localtz)
return tz_date.astimezone(localtz)
def create_now():
tz = get_timezone()
return datetime.now(tz)
def date_from_str(value):
separator = None
value = value.split()[0] # dd-mm-yyyy HH:MM:SS
value = value.split()[0] # dd-mm-yyyy HH:MM:SS
for s in ['-', '/']:
if value.find(s) > -1:
separator = s
break
break
if separator:
t = map(lambda x: int(x), value.split(separator))
y, m, d = t[2], t[1], t[0]
if d > 999: # yyyy-mm-dd
if d > 999: # yyyy-mm-dd
y, d = d, y
else:
y, m, d = int(value[:4]), int(value[4:6]), int(value[6:])
return date(y, m, d)
return date(y, m, d)
def dmy(tgl):
return tgl.strftime('%d-%m-%Y')
def dmyhms(t):
return t.strftime('%d-%m-%Y %H:%M:%S')
def next_month(year, month):
if month == 12:
month = 1
......@@ -63,7 +69,8 @@ def next_month(year, month):
else:
month += 1
return year, month
def best_date(year, month, day):
try:
return date(year, month, day)
......@@ -71,6 +78,7 @@ def best_date(year, month, day):
last_day = calendar.monthrange(year, month)[1]
return date(year, month, last_day)
def next_month_day(year, month, day):
year, month = next_month(year, month)
return best_date(year, month, day)
......@@ -22,18 +22,18 @@ from ..models.ziggurat import (
_ = TranslationStringFactory('user')
########
########
# List #
########
########
@view_config(
route_name='group', renderer='templates/group/list.pt',
permission='user-edit')
def view_list(request):
q = DBSession.query(Group).order_by(Group.group_name)
return dict(groups=q, title=_('Groups'))
#######
#######
# Add #
#######
def clean_name(s):
......@@ -51,8 +51,7 @@ class GroupNameValidator:
group_name = clean_name(value)
if self.group and self.group.group_name.lower() == group_name.lower():
return
q = DBSession.query(Group).\
filter(Group.group_name.ilike(group_name))
q = DBSession.query(Group).filter(Group.group_name.ilike(group_name))
found = q.first()
if not found:
return
......@@ -92,12 +91,13 @@ class AddSchema(colander.Schema):
class EditSchema(AddSchema):
id = colander.SchemaNode(
colander.String(), widget=HiddenWidget(readonly=True))
PERMISSIONS = [
('edit-user', _('User management'))
]
def get_form(request, class_form, group=None):
schema = class_form()
schema = schema.bind(permission_list=PERMISSIONS, group=group)
......@@ -118,7 +118,7 @@ def insert(values):
gp.group_id = group.id
gp.perm_name = perm_name
DBSession.add(gp)
return group
return group
@view_config(
......@@ -147,6 +147,7 @@ def view_add(request):
request.session.flash(ts)
return HTTPFound(location=request.route_url('group'))
########
# Edit #
########
......@@ -175,7 +176,7 @@ def update(group, values):
gp.perm_name = perm_name
DBSession.add(gp)
@view_config(
route_name='group-edit', renderer='templates/group/edit.pt',
permission='user-edit')
......@@ -202,14 +203,16 @@ def view_edit(request):
return resp
update(group, dict(c.items()))
data = dict(group_name=group.group_name)
ts = _('group-updated', default='${group_name} group profile updated', mapping=data)
ts = _(
'group-updated', default='${group_name} group profile updated',
mapping=data)
request.session.flash(ts)
return HTTPFound(location=request.route_url('group'))
##########
# Delete #
##########
##########
@view_config(
route_name='group-delete', renderer='templates/group/delete.pt',
permission='user-edit')
......
......@@ -46,9 +46,9 @@ class Login(colander.Schema):
def login_validator(form, value):
user = form.user
if not user or \
not user.status or \
not user.user_password or \
not UserService.check_password(user, value['password']):
not user.status or \
not user.user_password or \
not UserService.check_password(user, value['password']):
raise colander.Invalid(form, _('Login failed'))
......@@ -70,13 +70,13 @@ def get_user_by_identity(request):
one_hour = timedelta(1/24)
two_minutes = timedelta(1/24/60)
def login_by_code(request):
q = DBSession.query(User).filter_by(security_code=request.GET['code'])
user = q.first()
if not user or \
create_now() - user.security_code_date > one_hour:
create_now() - user.security_code_date > one_hour:
return HTTPFound(location=request.route_url('login-by-code-failed'))
user.security_code = None
DBSession.add(user)
......@@ -103,7 +103,7 @@ def view_login(request):
schema = Login(validator=login_validator)
btn_submit = Button('submit', _('Submit'))
form = Form(schema, buttons=(btn_submit,))
if 'submit' not in request.POST:
if 'submit' not in request.POST:
resp['form'] = form.render()
return resp
controls = request.POST.items()
......@@ -119,11 +119,13 @@ def view_login(request):
@view_config(route_name='logout')
def view_logout(request):
headers = forget(request)
return HTTPFound(location = request.route_url('home'),
headers = headers)
return HTTPFound(
location=request.route_url('home'), headers=headers)
@view_config(route_name='login-by-code-failed', renderer='templates/login-by-code-failed.pt')
@view_config(
route_name='login-by-code-failed',
renderer='templates/login-by-code-failed.pt')
def view_login_by_code_failed(request):
return dict(title='Login by code failed')
......@@ -143,7 +145,7 @@ class ChangePassword(colander.Schema):
def password_validator(form, value):
if value['new_password'] != value['retype_password']:
raise colander.Invalid(form, _('Retype mismatch'))
@view_config(
route_name='change-password', renderer='templates/change-password.pt',
......@@ -170,12 +172,13 @@ def view_change_password(request):
DBSession.add(request.user)
return HTTPFound(location=request.route_url('change-password-done'))
@view_config(
route_name='change-password-done',
renderer='templates/change-password-done.pt', permission='view')
def view_change_password_done(request):
return dict(title=_('Change password'))
##################
# Reset password #
......@@ -185,7 +188,7 @@ class ResetPassword(colander.Schema):
colander.String(), title=_('Email'),
description=_(
'email-reset-password',
default='Enter your email address and we will send you '\
default='Enter your email address and we will send you '
'a link to reset your password.')
)
......@@ -201,7 +204,7 @@ def security_code_age(user):
def send_email_security_code(
request, user, time_remain, subject, body_msg_id, body_default_file):
request, user, time_remain, subject, body_msg_id, body_default_file):
settings = get_settings()
up = urlparse(request.url)
url = '{}://{}/login?code={}'.format(
......@@ -234,7 +237,8 @@ def regenerate_security_code(user):
return one_hour
@view_config(route_name='reset-password', renderer='templates/reset-password.pt')
@view_config(
route_name='reset-password', renderer='templates/reset-password.pt')
def view_reset_password(request):
if authenticated_userid(request):
return HTTPFound(location=request.route_url('home'))
......@@ -242,7 +246,7 @@ def view_reset_password(request):
schema = ResetPassword(validator=reset_password_validator)
btn_submit = Button('submit', _('Send password reset email'))
form = Form(schema, buttons=(btn_submit,))
if 'submit' in request.POST:
if 'submit' in request.POST:
controls = request.POST.items()
identity = request.POST.get('email')
q = DBSession.query(User).filter_by(email=identity)
......@@ -262,7 +266,7 @@ def view_reset_password(request):
@view_config(
route_name='reset-password-sent', renderer='templates/reset-password-sent.pt')
route_name='reset-password-sent',
renderer='templates/reset-password-sent.pt')
def view_reset_password_sent(request):
return dict(title=_('Reset password'))
......@@ -42,7 +42,7 @@ from .login import (
_ = TranslationStringFactory('user')
########
########
# List #
########
@colander.deferred
......@@ -71,7 +71,7 @@ my_templates = os.path.join(here, 'templates', 'user')
search_path = [my_templates, deform_templates]
my_renderer = ZPTRendererFactory(search_path)
def get_filter_form():
group_list = get_group_list()
group_list = [('', '')] + group_list
......@@ -85,8 +85,8 @@ def get_filter_form():
def query_filter(q, p):
if 'group' in p:
q = q.filter(
User.id==UserGroup.user_id,
UserGroup.group_id==p['group'])
User.id == UserGroup.user_id,
UserGroup.group_id == p['group'])
if 'name' in p:
pattern = '%{}%'.format(p['name'])
q = q.filter(
......@@ -136,9 +136,9 @@ def view_list(request):
q_user = query_filter(q_user, p)
resp['users'] = q_user.order_by(User.email)
return resp
#######
#######
# Add #
#######
@colander.deferred
......@@ -184,6 +184,7 @@ class EmailValidator(colander.Email, Validator):
REGEX_ONLY_CONTAIN = re.compile('([a-z0-9-]*)')
REGEX_BEGIN_END_ALPHANUMERIC = re.compile('^[a-z0-9]+(?:[-][a-z0-9]+)*$')
class UsernameValidator(Validator):
def __call__(self, node, value):
username = value.lower()
......@@ -227,7 +228,8 @@ class AddSchema(colander.Schema):
email = colander.SchemaNode(
colander.String(), title=_('Email'),
validator=deferred_email_validator)
user_name = colander.SchemaNode(colander.String(), title=_('Username'),
user_name = colander.SchemaNode(
colander.String(), title=_('Username'),
validator=deferred_username_validator)
groups = colander.SchemaNode(
colander.Set(), widget=deferred_group, title=_('Group'))
......@@ -239,19 +241,19 @@ class EditSchema(AddSchema):
widget=HiddenWidget(readonly=True))
status = colander.SchemaNode(
colander.String(), widget=deferred_status, title=_('Status'))
class MyEditSchema(AddSchema):
id = colander.SchemaNode(
colander.String(), missing=colander.drop,
widget=HiddenWidget(readonly=True))
def get_form(request, class_form, user=None):
status_list = (
(1, _('Active')),
(0, _('Inactive')))
group_list = get_group_list()
group_list = get_group_list()
schema = class_form()
schema = schema.bind(
status_list=status_list, group_list=group_list, user=user)
......@@ -282,7 +284,7 @@ def insert(request, values):
remain = regenerate_security_code(user)
DBSession.add(user)
DBSession.flush()
for gid in values['groups']:
for gid in values['groups']:
ug = UserGroup(user_id=user.id, group_id=gid)
DBSession.add(ug)
add_member_count(gid)
......@@ -306,7 +308,7 @@ def view_add(request):
except ValidationFailure:
resp['form'] = form.render()
return resp
user, remain = insert(request, dict(c.items()))
user, remain = insert(request, dict(c.items()))
send_email_security_code(
request, user, remain, 'Welcome new user', 'email-new-user',
'email-new-user.tpl')
......@@ -346,7 +348,7 @@ def update(request, user, values):
for gid in unused:
reduce_member_count(gid)
new = values['groups'] - existing
for gid in new:
for gid in new:
ug = UserGroup(user_id=user.id, group_id=gid)
DBSession.add(ug)
add_member_count(gid)
......@@ -387,7 +389,7 @@ def view_edit(request):
##########
# Delete #
##########
##########
@view_config(
route_name='user-delete', renderer='templates/user/delete.pt',
permission='user-edit')
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!