Browse Source

first drop

master
tr4ck3ur des JM2L 9 years ago
commit
fa4a9859c5
100 changed files with 12995 additions and 0 deletions
  1. +4
    -0
      CHANGES.txt
  2. +2
    -0
      MANIFEST.in
  3. +14
    -0
      README.txt
  4. +71
    -0
      development.ini
  5. +163
    -0
      jm2l/ExtWtforms.py
  6. +112
    -0
      jm2l/__init__.py
  7. +42
    -0
      jm2l/auth.py
  8. +151
    -0
      jm2l/captcha.py
  9. +498
    -0
      jm2l/forms.py
  10. +616
    -0
      jm2l/models.py
  11. +1
    -0
      jm2l/scripts/__init__.py
  12. +40
    -0
      jm2l/scripts/initializedb.py
  13. +25
    -0
      jm2l/security.py
  14. +60
    -0
      jm2l/static/404.html
  15. +470
    -0
      jm2l/static/css/bootstrap-theme.css
  16. +1
    -0
      jm2l/static/css/bootstrap-theme.css.map
  17. +5
    -0
      jm2l/static/css/bootstrap-theme.min.css
  18. +6332
    -0
      jm2l/static/css/bootstrap.css
  19. +1
    -0
      jm2l/static/css/bootstrap.css.map
  20. +5
    -0
      jm2l/static/css/bootstrap.min.css
  21. +22
    -0
      jm2l/static/css/main.css
  22. BIN
      jm2l/static/favicon.ico
  23. BIN
      jm2l/static/fonts/glyphicons-halflings-regular.eot
  24. +229
    -0
      jm2l/static/fonts/glyphicons-halflings-regular.svg
  25. BIN
      jm2l/static/fonts/glyphicons-halflings-regular.ttf
  26. BIN
      jm2l/static/fonts/glyphicons-halflings-regular.woff
  27. +15
    -0
      jm2l/static/humans.txt
  28. BIN
      jm2l/static/img/2006/logo.png
  29. BIN
      jm2l/static/img/Base-icon.png
  30. BIN
      jm2l/static/img/Calc-icon.png
  31. BIN
      jm2l/static/img/Draw-icon.png
  32. BIN
      jm2l/static/img/Impress-icon.png
  33. BIN
      jm2l/static/img/PDF_Thumb_Stamp.png
  34. BIN
      jm2l/static/img/Writer-icon.png
  35. BIN
      jm2l/static/img/asker.png
  36. BIN
      jm2l/static/img/echange.png
  37. BIN
      jm2l/static/img/echange.xcf
  38. BIN
      jm2l/static/img/glyphicons-halflings-white.png
  39. BIN
      jm2l/static/img/glyphicons-halflings.png
  40. BIN
      jm2l/static/img/loading.gif
  41. BIN
      jm2l/static/img/no-image-thumb.jpg
  42. BIN
      jm2l/static/img/personne.jpg
  43. BIN
      jm2l/static/img/progressbar.gif
  44. BIN
      jm2l/static/img/provider.png
  45. BIN
      jm2l/static/img/select2-spinner.gif
  46. BIN
      jm2l/static/img/select2.png
  47. BIN
      jm2l/static/img/select2x2.png
  48. +90
    -0
      jm2l/static/index.html
  49. +1
    -0
      jm2l/static/js/main.js
  50. +24
    -0
      jm2l/static/js/plugins.js
  51. +2320
    -0
      jm2l/static/js/vendor-dev/bootstrap.js
  52. +7
    -0
      jm2l/static/js/vendor-dev/bootstrap.min.js
  53. +944
    -0
      jm2l/static/js/vendor-dev/ckeditor/ckeditor.js
  54. +42
    -0
      jm2l/static/js/vendor-dev/ckeditor/config.js
  55. +42
    -0
      jm2l/static/js/vendor-dev/ckeditor/config.js~
  56. +134
    -0
      jm2l/static/js/vendor-dev/ckeditor/contents.css
  57. +5
    -0
      jm2l/static/js/vendor-dev/ckeditor/lang/en.js
  58. +5
    -0
      jm2l/static/js/vendor-dev/ckeditor/lang/fr.js
  59. +10
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/a11yhelp.js
  60. +25
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/_translationstatus.txt
  61. +11
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/af.js
  62. +11
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/ar.js
  63. +11
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/bg.js
  64. +13
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/ca.js
  65. +13
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/cs.js
  66. +11
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/cy.js
  67. +11
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/da.js
  68. +13
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/de.js
  69. +13
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/el.js
  70. +11
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/en-gb.js
  71. +11
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/en.js
  72. +13
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/eo.js
  73. +12
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/es.js
  74. +11
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/et.js
  75. +11
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/fa.js
  76. +12
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/fi.js
  77. +12
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/fr-ca.js
  78. +13
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/fr.js
  79. +12
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/gl.js
  80. +11
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/gu.js
  81. +11
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/he.js
  82. +11
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/hi.js
  83. +11
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/hr.js
  84. +13
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/hu.js
  85. +11
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/id.js
  86. +13
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/it.js
  87. +9
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/ja.js
  88. +11
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/km.js
  89. +11
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/ko.js
  90. +12
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/ku.js
  91. +11
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/lt.js
  92. +13
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/lv.js
  93. +11
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/mk.js
  94. +11
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/mn.js
  95. +12
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/nb.js
  96. +12
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/nl.js
  97. +11
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/no.js
  98. +13
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/pl.js
  99. +12
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/pt-br.js
  100. +12
    -0
      jm2l/static/js/vendor-dev/ckeditor/plugins/a11yhelp/dialogs/lang/pt.js

+ 4
- 0
CHANGES.txt View File

@@ -0,0 +1,4 @@
0.0
---

- Initial version

+ 2
- 0
MANIFEST.in View File

@@ -0,0 +1,2 @@
include *.txt *.ini *.cfg *.rst
recursive-include jm2l *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml

+ 14
- 0
README.txt View File

@@ -0,0 +1,14 @@
JM2L README
==================

Getting Started
---------------

- cd <directory containing this file>

- $VENV/bin/python setup.py develop

- $VENV/bin/initialize_JM2L_db development.ini

- $VENV/bin/pserve development.ini


+ 71
- 0
development.ini View File

@@ -0,0 +1,71 @@
###
# app configuration
# http://docs.pylonsproject.org/projects/pyramid/en/1.5-branch/narr/environment.html
###

[app:main]
use = egg:JM2L

pyramid.reload_templates = true
pyramid.debug_authorization = false
pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.default_locale_name = en
pyramid.includes =
pyramid_debugtoolbar
pyramid_tm

sqlalchemy.url = sqlite:///%(here)s/JM2L.sqlite

# By default, the toolbar only appears for clients from IP addresses
# '127.0.0.1' and '::1'.
# debugtoolbar.hosts = 127.0.0.1 ::1

###
# wsgi server configuration
###

[server:main]
use = egg:waitress#main
host = 0.0.0.0
port = 8080

###
# logging configuration
# http://docs.pylonsproject.org/projects/pyramid/en/1.5-branch/narr/logging.html
###

[loggers]
keys = root, jm2l, sqlalchemy

[handlers]
keys = console

[formatters]
keys = generic

[logger_root]
level = INFO
handlers = console

[logger_jm2l]
level = DEBUG
handlers =
qualname = jm2l

[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
# "level = INFO" logs SQL queries.
# "level = DEBUG" logs SQL queries and results.
# "level = WARN" logs neither. (Recommended for production systems.)

[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic

[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s

+ 163
- 0
jm2l/ExtWtforms.py View File

@@ -0,0 +1,163 @@
try:
from html import escape
except ImportError:
from cgi import escape

#from wtforms import widgets
from wtforms.widgets import HTMLString, html_params
from wtforms.fields.core import Field
from wtforms.compat import text_type, izip

class MySelect(object):
"""
Renders a select field.

If `multiple` is True, then the `size` property should be specified on
rendering to make the field useful.

The field must provide an `iter_choices()` method which the widget will
call on rendering; this method must yield tuples of
`(value, label, selected)`.
"""
def __init__(self, multiple=False):
self.multiple = multiple

def __call__(self, field, **kwargs):
kwargs.setdefault('id', field.id)
if self.multiple:
kwargs['multiple'] = True
html = ['<select %s>' % html_params(name=field.name, **kwargs)]
last_group = None
for group, val, label, selected in field.iter_choices():
if group is None:
html.append(self.render_option(val, label, selected))
elif last_group != group:
html.append(self.render_optgroup(last_group, group))
html.append(self.render_option(val, label, selected))
last_group=group
else:
html.append(self.render_option(val, label, selected))
if last_group:
html.append(self.render_optgroup(last_group, None))
html.append('</select>')
return HTMLString(''.join(html))

@classmethod
def render_option(cls, value, label, selected, **kwargs):
if value is True:
# Handle the special case of a 'True' value.
value = text_type(value)

options = dict(kwargs, value=value)
if selected:
options['selected'] = True
return HTMLString('<option %s>%s</option>' % (html_params(**options), escape(text_type(label), quote=False)))

@classmethod
def render_optgroup(cls, previous_label, label, **kwargs):
options = dict(kwargs)
if previous_label is None:
return HTMLString('<optgroup %s label="%s">' % (html_params(**options), escape(text_type(label), quote=False)))
elif label is None:
return HTMLString('</optgroup>')
else:
return HTMLString('</optgroup><optgroup %s label="%s">' % (html_params(**options), escape(text_type(label), quote=False)))


class MyOption(object):
"""
Renders the individual option from a select field.

This is just a convenience for various custom rendering situations, and an
option by itself does not constitute an entire field.
"""
def __call__(self, field, **kwargs):
return MySelect.render_option(field._value(), field.label.text, field.checked, **kwargs)


class MySelectFieldBase(Field):
#option_widget = widgets.Option()
option_widget = MyOption()

"""
Base class for fields which can be iterated to produce options.

This isn't a field, but an abstract base class for fields which want to
provide this functionality.
"""
def __init__(self, label=None, validators=None, option_widget=None, **kwargs):
super(MySelectFieldBase, self).__init__(label, validators, **kwargs)

if option_widget is not None:
self.option_widget = option_widget

def iter_choices(self):
"""
Provides data for choice widget rendering. Must return a sequence or
iterable of (value, label, selected) tuples.
"""
raise NotImplementedError()

def __iter__(self):
opts = dict(widget=self.option_widget, _name=self.name, _form=None, _meta=self.meta)
for i, (value, label, checked) in enumerate(self.iter_choices()):
opt = self._Option(label=label, id='%s-%d' % (self.id, i), **opts)
opt.process(None, value)
opt.checked = checked
yield opt

class _Option(Field):
checked = False

def _value(self):
return text_type(self.data)


class MySelectField(MySelectFieldBase):
#widget = widgets.Select()
widget = MySelect()

def __init__(self, label=None, validators=None, coerce=text_type, choices=None, **kwargs):
super(MySelectField, self).__init__(label, validators, **kwargs)
self.coerce = coerce
self.choices = choices

def iter_choices(self):
for choiceA, choiceB in self.choices:
if isinstance(choiceB, (tuple, list)):
# We should consider choiceA as an optgroup label
group_label = choiceA
for value, label in choiceB:
yield (group_label, value, label, self.coerce(value) == self.data)
else:
value, label = choiceA, choiceB
# Not an optgroup, let's fallback to classic usage
yield (None, value, label, self.coerce(value) == self.data)

def process_data(self, value):
try:
self.data = self.coerce(value)
except (ValueError, TypeError):
self.data = None

def process_formdata(self, valuelist):
if valuelist:
try:
self.data = self.coerce(valuelist[0])
except ValueError:
raise ValueError(self.gettext('Invalid Choice: could not coerce'))

def pre_validate(self, form):
for choiceA, choiceB in self.choices:
if isinstance(choiceB, (tuple, list)):
for value, label in choiceB:
if self.data == value:
break
if self.data == value:
break
else:
value, label = choiceA, choiceB
if self.data == value:
break
else:
raise ValueError(self.gettext('Not a valid choice'))

+ 112
- 0
jm2l/__init__.py View File

@@ -0,0 +1,112 @@
from pyramid.authentication import AuthTktAuthenticationPolicy
from pyramid.authorization import ACLAuthorizationPolicy
from pyramid.config import Configurator
from pyramid.renderers import JSON, JSONP
from pyramid.session import SignedCookieSessionFactory
from sqlalchemy import engine_from_config

from .models import DBSession, get_user
from .security import EntryFactory, groupfinder
import locale

def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
locale.setlocale(locale.LC_ALL, "fr_FR.UTF-8")
engine = engine_from_config(settings, 'sqlalchemy.')
DBSession.configure(bind=engine)
my_session_factory = SignedCookieSessionFactory('itsaseekreet')
authentication_policy = AuthTktAuthenticationPolicy('somesecret',
callback=groupfinder, hashalg='sha512', debug=True)
authorization_policy = ACLAuthorizationPolicy()
config = Configurator(settings=settings,
root_factory='.security.RootFactory',
authentication_policy=authentication_policy,
authorization_policy=authorization_policy
)
config.add_renderer('json', JSON(indent=4))
config.add_renderer('jsonp', JSONP(param_name='callback'))
config.set_session_factory(my_session_factory)
config.add_request_method(get_user, 'user', reify=True)
config.add_static_view('static', 'static', cache_max_age=3600)
config.add_static_view('img', 'static/img', cache_max_age=3600)
config.add_static_view('upload', 'upload', cache_max_age=3600)
config.add_route('tester', '/tester')

# ICal Routes
config.add_route('progr_iCal', '/{year:\d+}/JM2L.ics')
# JSON Routes
config.add_route('users_json', '/json-users')
config.add_route('tiers_json', '/json-tiers')
config.add_route('progr_json', '/{year:\d+}/le-prog-json')
config.add_route('timeline_json', '/{year:\d+}/timeline-json')
# Session setting Routes
config.add_route('year', '/year/{year:\d+}')
# HTML Routes - Staff
config.add_route('list_task', '/Staff')
config.add_route('handle_task', '/Staff/tasks{sep:/*}{task_id:(\d+)?}')
config.add_route('action_task', '/Staff/{action:(\w+)}/{task_id:(\d+)}')

# HTML Routes - Public
config.add_route('home', '/')
config.add_route('presse', '/{year:\d+}/dossier-de-presse')
config.add_route('edit_presse', '/{year:\d+}/dossier-de-presse/edit')
config.add_route('programme', '/{year:\d+}/le-programme')
config.add_route('plan', 'nous-rejoindre')
config.add_route('participer', 'participer-l-evenement')
config.add_route('captcha', '/captcha')
## Events
config.add_route('event', '/event/{year:\d+}/{event_id:([\w-]+)?}')
config.add_route('link_event', '/MesJM2L/{year:\d+}/{intervention:\w+}/link')
config.add_route('edit_event', '/MesJM2L/{year:\d+}/{intervention:\w+}{sep:/*}{event_id:([\w-]+)?}')
## Entities
config.add_route('entities', '/entities') #{sep:/*}{Nature:\w+?}')
config.add_route('add_entity', '/entity')
config.add_route('show_entity', '/entity/{tiers_type:(\w+)}/{entity_id:([\w-]+)?}')
config.add_route('edit_entity', '/entity/{tiers_type:(\w+)}/{entity_id:([\w-]+)}/edit')
config.add_route('edit_entity_cat', '/categorie/entity')
## Users
config.add_route('show_user', '/user/{user_slug:([\w-]+)?}')
# HTML Routes - Logged
#config.add_route('profil', 'MesJM2L')
config.add_route('jm2l', '/MesJM2L')
config.add_route('modal', '/{year:\d+}/modal/{modtype:\w+}/{id:(\d+)}')
# Handle exchanges
config.add_route('exchange', '/{year:\d+}/exchange/{modtype:\w+}/{id:(\d+)}/{action:\w+}')

# Handle authentication
config.add_route('register', '/register')
config.add_route('auth', '/sign/{action}')
config.add_route('bymail', '/sign/jm2l/{hash}')
# Handle Multimedia and Uploads
config.add_route('media_uploadform', '/test2')
config.add_route('media_view', '/image/{media_table:\w+}/{uid:\d+}/{name:.+}')
config.add_route('media_upload', '/uploader/{media_table:\w+}/{uid:\d+}/proceed{sep:/*}{name:.*}')

# To Trash routes
config.add_route('test', '/test')
config.add_route('test2', '/toast{sep:/*}{uid:(\d+)?}')

#config.add_route('link_user_entity', '/entity/{uid:(\d+)}/{year:\d+}/user/{user_id:(\d+)}')
#config.add_route('link_role_entity', '/entity/{uid:(\d+)}/{year:\d+}/role/{role_id:(\d+)}')

config.add_route('IntAdd', '/IntAdd/{modtype:\w+}')
config.add_route('IntProp', '/IntProp/{modtype:\w+}')

config.add_route('blog', '/blog/{id:\d+}/{slug}')
config.add_route('blog_action', '/blog/{action}',
factory='jm2l.security.EntryFactory')
config.scan()
return config.make_wsgi_app()

+ 42
- 0
jm2l/auth.py View File

@@ -0,0 +1,42 @@

from pyramid.view import view_config
from pyramid.security import remember, forget
from pyramid.httpexceptions import HTTPFound
from .models import User

@view_config(route_name='auth', match_param="action=login", renderer="jm2l:templates/login.mako")
def login(request):
return {}

@view_config(route_name='bymail', renderer="string")
def bymail(request):
myhash = request.matchdict.get('hash', "")
user = User.by_hash(myhash)
if user:
headers = remember(request, user.uid)
return HTTPFound(location=request.route_url('jm2l'),
headers=headers)
else:
headers = forget(request)
return HTTPFound(location=request.route_url('auth', action='login'),
headers=headers)

@view_config(route_name='auth', match_param="action=in", renderer="string",
request_method="POST")
@view_config(route_name='auth', match_param="action=out", renderer="string")
def sign_in_out(request):
username = request.POST.get('username')
if username:
user = User.by_name(username)
if user and user.verify_password(request.POST.get('password')):
headers = remember(request, user.uid)
return HTTPFound(location=request.route_url('jm2l'),
headers=headers)
else:
headers = forget(request)
else:
headers = forget(request)
return HTTPFound(location=request.route_url('home'),
headers=headers)



+ 151
- 0
jm2l/captcha.py View File

@@ -0,0 +1,151 @@
# Stolen from tgcaptcha/plugins
# http://code.google.com/p/tgcaptcha/source/browse/trunk/tgcaptcha/plugins/image/vanasco_dowty/captcha.py

import random
from PIL import Image, ImageDraw, ImageFont, ImageFilter
import StringIO
import math
from pyramid.view import view_config
from words import TabMots
from pyramid.response import Response

class Captcha_Img(object):
def __init__( self, width, height):
self.width = width
self.height = height
self._layers = [
_PyCaptcha_SineWarp(amplitudeRange = (4, 8) , periodRange=(0.65,0.73) ),
]
def getImg(self):
"""Get a PIL image representing this CAPTCHA test, creating it if necessary"""
if not self._image:
self._image = self.render()
return self._image

def render(self):
"""Render this CAPTCHA, returning a PIL image"""
size = (self.width,self.height)
#img = Image.new("RGB", size )
img = self._image
for layer in self._layers:
img = layer.render( img ) or img
self._image = img
return self._image

class _PyCaptcha_WarpBase(object):
"""Abstract base class for image warping. Subclasses define a
function that maps points in the output image to points in the input image.
This warping engine runs a grid of points through this transform and uses
PIL's mesh transform to warp the image.
"""
filtering = Image.BILINEAR
resolution = 40

def get_transform(self, image):
"""Return a transformation function, subclasses should override this"""
return lambda x, y: (x, y)

def render(self, image):
r = self.resolution
xPoints = image.size[0] / r + 2
yPoints = image.size[1] / r + 2
f = self.get_transform(image)

# Create a list of arrays with transformed points
xRows = []
yRows = []
for j in xrange(yPoints):
xRow = []
yRow = []
for i in xrange(xPoints):
x, y = f(i*r, j*r)

# Clamp the edges so we don't get black undefined areas
x = max(0, min(image.size[0]-1, x))
y = max(0, min(image.size[1]-1, y))

xRow.append(x)
yRow.append(y)
xRows.append(xRow)
yRows.append(yRow)

# Create the mesh list, with a transformation for
# each square between points on the grid
mesh = []
for j in xrange(yPoints-1):
for i in xrange(xPoints-1):
mesh.append((
# Destination rectangle
(i*r, j*r,
(i+1)*r, (j+1)*r),
# Source quadrilateral
(xRows[j ][i ], yRows[j ][i ],
xRows[j+1][i ], yRows[j+1][i ],
xRows[j+1][i+1], yRows[j+1][i+1],
xRows[j ][i+1], yRows[j ][i+1]),
))

return image.transform(image.size, Image.MESH, mesh, self.filtering)

class _PyCaptcha_SineWarp(_PyCaptcha_WarpBase):
"""Warp the image using a random composition of sine waves"""

def __init__(self,
amplitudeRange = (1,1),#(2, 6),
periodRange = (1,1)#(0.65, 0.73),
):
self.amplitude = random.uniform(*amplitudeRange)
self.period = random.uniform(*periodRange)
self.offset = (random.uniform(0, math.pi * 2 / self.period),
random.uniform(0, math.pi * 2 / self.period))
def get_transform(self, image):
return (lambda x, y,
a = self.amplitude,
p = self.period,
o = self.offset:
(math.sin( (y+o[0])*p )*a + x,
math.sin( (x+o[1])*p )*a + y))

@view_config(route_name='captcha')
def DoCaptcha(request):
ImgSize = (230,100)
WorkImg = Image.new( 'RGBA', ImgSize, (255, 255, 255, 0) )
Xmax, Ymax = WorkImg.size
# Write something on it
draw = ImageDraw.Draw(WorkImg)

# use a truetype font
#font = ImageFont.truetype("/var/lib/defoma/gs.d/dirs/fonts/LiberationMono-Regular.ttf", 40)
# use it
font = ImageFont.truetype("jm2l/static/LiberationMono-Regular.ttf",40)
# Re-position
# Choose a word for captcha
text = random.choice(TabMots)
Xt, Yt = font.getsize(text)
OrX, OrY = (ImgSize[0]-Xt)/2, (ImgSize[1]-Yt)/2
draw.text((OrX, OrY), text, font=font, fill="#000000")
# Apply a Blur
# WorkImg=WorkImg.filter(ImageFilter.BLUR)
# Apply a DETAIL
WorkImg=WorkImg.filter(ImageFilter.DETAIL)
# randomize parameters for perspective
ax, ay = (random.uniform(0.9,1.2) , random.uniform(0.9,1.2))
tx, ty = (random.uniform(0,0.0003),random.uniform(0,0.0003))
bx, by = (random.uniform(0.5,0.8),random.uniform(0,0.2))
# Apply perspective to Captcha
WorkImg= WorkImg.transform(ImgSize, Image.PERSPECTIVE, (ax, bx, -25, by, ay, -10, tx, ty))
# Apply SinWarp to Captcha
tr = Captcha_Img(Xmax, Ymax)
tr._image = WorkImg
WorkImg = tr.render()
# Apply a Smooth on it
WorkImg=WorkImg.filter(random.choice([ImageFilter.SMOOTH, ImageFilter.SMOOTH_MORE]))
# Save Result
request.session['Captcha'] = text
#session.save()
ImgHandle = StringIO.StringIO()
WorkImg.save(ImgHandle,'png')
ImgHandle.seek(0)
return Response(app_iter=ImgHandle, content_type = 'image/png')

+ 498
- 0
jm2l/forms.py View File

@@ -0,0 +1,498 @@
# -*- coding: utf8 -*-
from wtforms import Form, BooleanField, TextField, TextAreaField, SelectField, SubmitField, validators, FieldList
#import .ExtWforms
from .ExtWtforms import MySelectField
from wtforms import HiddenField, DecimalField, DateTimeField, FormField, FileField, DateField
from wtforms.validators import ValidationError
from datetime import datetime
strip_filter = lambda x: x.strip() if x else None
from wtforms.csrf.session import SessionCSRF
from datetime import timedelta

class MyBaseForm(Form):
class Meta:
csrf = True
csrf_class = SessionCSRF
csrf_secret = b'lJDQtOAMC2qe89doIn8u3Mch_DgeLSKO'
csrf_time_limit = timedelta(minutes=20)

class BlogCreateForm(MyBaseForm):
title = TextField('Entry title', [validators.Length(min=1, max=255)],
filters=[strip_filter])
body = TextAreaField('Entry body', [validators.Length(min=1)],
filters=[strip_filter])

class BlogUpdateForm(BlogCreateForm):
id = HiddenField()
PLACE_TYPE = [('Aeroport', u'Aรฉroport'), ('Gare','Gare'), ('JM2L','JM2L'),
('Hotel',u'Hรดtel'), ('Habitant','Habitant'),
('Restaurant','Restaurant'), ('Autres','Autres')]

TIERS_ROLE = [('Exposant','Exposant'), ('Sponsor','Sponsor'),
('Donateur','Donateur')]

EVENT_TYPE = ['Stand', 'Table ronde', 'Atelier', 'Concert', 'Conference', 'Repas']

CONF_DURATION = [ (15,u'Lighting talk ( 5 min)'),
(30,u'Confรฉrence (20 min)'),
(60,u'Confรฉrence (50 min)'),
(90,u'Confรฉrence (75 min)'),]
ATELIER_DURATION = [ (15,u'Lighting talk ( 5 min)'),
(30,u'Confรฉrence (20 min)'),
(60,u'Confรฉrence (50 min)'),
(90,u'Confรฉrence (75 min)'),]


class StaffArea(MyBaseForm):
uid = HiddenField()
name = TextField(u'Pรดle')
description = TextAreaField('Description', [validators.optional(), validators.Length(max=1000000)],
filters=[strip_filter]
)

class StaffTasks(MyBaseForm):
name = TextField(u'Nom de la tรขche')
area_uid = SelectField(u'Pรดle concernรฉ', coerce=int )
due_date = DateField(u'Date prรฉvue', format='%d/%m/%Y')
closed_by = SelectField(u'Assignรฉ ร ', coerce=int )
description = TextAreaField('Description', [validators.optional(), validators.Length(max=1000000)],
filters=[strip_filter])

class EditStaffTasks(StaffTasks):
uid = HiddenField()

class DossPresse(MyBaseForm):
year_uid = HiddenField()
doss_presse = TextAreaField('Dossier de Presse', [validators.optional(), validators.Length(max=1000000)],
filters=[strip_filter])

class TiersMember(MyBaseForm):
class Meta:
csrf = False

year_uid = SelectField(u'Annรฉe', coerce=int, choices=zip(range(2006,2016),range(2006,2016)))
user_uid = TextField(u'user')
role = TextField(u'Role')
class TiersChoice(MyBaseForm):
class Meta:
csrf = False

year_uid = HiddenField()
user_uid = HiddenField()
tiers_uid = TextField(u'Entitรฉ')
role = TextField(u'Role')

class AddIntervenant(MyBaseForm):
class Meta:
csrf = False
event_uid = HiddenField()
nom = TextField(u'Nom', [validators.Length(max=80)],
filters=[strip_filter],
description = u"Les espaces sont autorisรฉs, la ponctuation n'est " +
u"pas autorisรฉe ร  l'exception des points, traits d'union, " +
u"apostrophes et tirets bas."
)
prenom = TextField(u'Prรฉnom', [validators.Length(max=80)],
filters=[strip_filter],
description = u"Les espaces sont autorisรฉs, la ponctuation n'est " +
u"pas autorisรฉe ร  l'exception des points, traits d'union, " +
u"apostrophes et tirets bas."
)
email = TextField(u'Email', [validators.required(),
validators.length(max=10),
validators.Email(message='Ceci ne ressemble pas ร  une adresse valide')],
description=u"Afin d'รฉviter la duplication d'information et les doublons inutile, "+
u"pensez d'abord ร  lui demander de confirmer le mail qu'il a utilisรฉ lors de "+
u"son inscription sur le site.")
add = SubmitField('Ajouter des intervenants')

class InterventionForm(MyBaseForm):
event_type = HiddenField()
for_year = HiddenField()
start_time = HiddenField()
end_time = HiddenField()
name = TextField(u'Le nom de votre ',
[validators.DataRequired(u'Vous devez spรฉcifier un nom pour votre intรฉrvention'),
validators.Length(min=1, max=80, message='entre 1 et 80 car')],
filters=[strip_filter])
description = TextAreaField(u'Dรฉcrivez ici quelques dรฉtails ร  propos de votre intervention ',
[validators.Optional(), validators.Length(max=1000000)],
filters=[strip_filter]
)

class ConfCreateForm(InterventionForm):
salle_uid = SelectField(u'Salle', coerce=int,
description=u"Choisissez ici la salle en fonction "
u"du nombres de personnes potentiellement intรฉressรฉ par votre intervention "+
u"l'organisation se rรฉserve le droit de changer la salle (avec votre accord)."
)
start_sel = SelectField(u'Dรฉbut', coerce=int,
description=u"C'est une heure indicative correspondant au mieux ร  vos prรฉfรฉrences "+
u"personnelles. Vous pouvez prendre un crรฉneau horaire dรฉjร  rรฉservรฉ si vous avez des contraintes "
u"particuliรจres. L'รฉquipe des JM2L mettra ร  disposition plus de salle si nรฉcessaire. En cas de conflit,"+
u"l'organisation se rรฉserve le droit de changer la salle et l'heure avec votre accord."
)
duration = SelectField(u'Durรฉe', coerce=int,
description=u"Prรฉcisez ici la durรฉe de votre intervention"
)


class ConfUpdateForm(ConfCreateForm):
uid = HiddenField()

class PlaceCreateForm(MyBaseForm):

place_type = SelectField('Type', choices=PLACE_TYPE)
display_name = TextField(u'Nom affichรฉ', [validators.Length(min=1, max=20)],
filters=[strip_filter])
name = TextField('Nom Complet', [validators.Length(min=1, max=80)],
filters=[strip_filter])
gps_coord = TextField(u'Coordonnรฉes GPS', [validators.Length(max=30)],
filters=[strip_filter])
adresse = TextAreaField('Adresse', [validators.Length(max=100)],
filters=[strip_filter])
codePostal = TextField('Code Postal', [validators.Length(max=5)],
filters=[strip_filter])
ville = TextField('Ville', [validators.Length(max=40)],
filters=[strip_filter])
website = TextField('Site Web', [validators.Length(max=100)],
filters=[strip_filter])
description = TextAreaField('Description',
filters=[strip_filter])
created_by = HiddenField()
class PlaceUpdateForm(PlaceCreateForm):
place_id = HiddenField()


def captcha_check(form, field):
if form.meta.csrf_context.get('Captcha')!=field.data:
raise ValidationError(u"la vรฉrification captcha est invalide.")



class UserRegisterForm(MyBaseForm):
nom = TextField(u'Nom', [
validators.Length(max=80, message=u"80 car. maximum"),
validators.required(message=u"Ce champ est obligatoire") ],
filters=[strip_filter]
)
prenom = TextField(u'Prรฉnom', [
validators.Length(max=80, message=u"80 car. maximum"),
validators.required(message=u"Ce champ est obligatoire"),
validators.Length(max=80)], filters=[strip_filter]
)
mail = TextField(u'Adresse รฉlectronique', [
validators.required(message=u"Ce champ est obligatoire"),
validators.Email(message=u"Essayez aussi avec une adresse e-mail valide"),
validators.Length(max=100)],
filters=[strip_filter],
description = u"Une adresse e-mail valide." +
u"Cette adresse ne sera pas rendue publique, "+
u"et ne sera pas divulguรฉ ร  des tiers."
)
captcha = TextField(u'Captcha', [validators.Length(max=8), captcha_check],
filters=[strip_filter]
)
class ProfilForm(MyBaseForm):
id = HiddenField()
user_id = HiddenField()
nom = TextField(u'Nom', [validators.Length(max=80)],
filters=[strip_filter],
description = u"Les espaces sont autorisรฉs, la ponctuation n'est " +
u"pas autorisรฉe ร  l'exception des points, traits d'union, " +
u"apostrophes et tirets bas."
)
prenom = TextField(u'Prรฉnom', [validators.Length(max=80)],
filters=[strip_filter],
description = u"Les espaces sont autorisรฉs, la ponctuation n'est " +
u"pas autorisรฉe ร  l'exception des points, traits d'union, " +
u"apostrophes et tirets bas."
)
pseudo = TextField(u'Pseudo', [validators.Length(max=80)],
filters=[strip_filter],
description = "Votre pseudo d'usage sur la toile."
)
mail = TextField(u'Adresse รฉlectronique', [validators.optional(), validators.Email(), validators.Length(max=100)],
filters=[strip_filter],
description = u"Une adresse e-mail valide. Tous les messages de ce systรจme" +
u"seront envoyรฉs ร  cette adresse. Cette adresse ne sera pas rendue publique,"+
u"et ne sera utilisรฉe que si vous dรฉsirez obtenir un nouveau mot de passe ou" +
u"recevoir personnellement certaines nouvelles ou avertissements."
)
phone = TextField(u'Mobile', [validators.optional(), validators.Length(max=10),
validators.Regexp("\d+", message=u"Le numรฉro de tรฉlรฉphone mobile ne doit contenir que des chiffres")],
filters=[strip_filter],
description = u"Un numรฉro de mobile valide. Afin de pouvoir rester en" +
u"contact avec les personne de l'organisation, et pour vos รฉchanges. " +
u"Ce numรฉro ne sera pas publiรฉ, et ne sera utilisรฉ que si " +
u"vous dรฉsirez recevoir personnellement certaines nouvelles ou alertes."
)
website = TextField(u'Site web', [validators.optional(), validators.URL(), validators.Length(max=100)],
filters=[strip_filter],
description = "Renseignez ici votre site Web."
)

gpg_key = TextAreaField(u'Ma clรฉ GPG',
[validators.optional(), validators.Length(max=9000)],
filters=[strip_filter],
description = u"Vous pouvez insรฉrer votre clรฉ GPG publique pour " +
u"รฉchanger des donnรฉes sรฉcurisรฉes."
)
soc_link = TextAreaField('Mes autres identifiants',
[validators.optional(), validators.Length(max=1000000)],
filters=[strip_filter],
description = u"Vous pouvez insรฉrer ici d'autres identifiants " +
u"permettant aux autres de vous retrouver sur la toile (IRC, jabber, rรฉseaux sociaux etc)."
)

bio = TextAreaField('Biographie', [validators.optional(), validators.Length(max=1000000)],
filters=[strip_filter]
)
tiersship = FieldList(FormField(TiersChoice))


class DateStartConfidenceForm(MyBaseForm):
ConfidenceLevel = [
("0",u"exactement ร "),
("1",u"approximativement ร "),
("2",u"ร  peu prรจs (5 ร  15 min) vers"),
("3",u"ร  une vache prรจs (1h) vers")
]
DayChoice = [("4","Jeudi"), ("5","Vendredi"), ("6","Samedi"), ("0","Dimanche"), ("1","Lundi")]
Day = SelectField(u'Jour', choices=DayChoice )
Confidence = SelectField(u'Confiance', choices=ConfidenceLevel)
Hour = TextField(u'Heure', [validators.Length(max=5,
message=u"doit faire au maximum 5 caractรจres"),
validators.Regexp("\d+:\d+",
message=u"doit รชtre sous la forme HH:MM")],
filters=[strip_filter])
start_time = HiddenField()
class ItineraireForm(Form):
start_place = SelectField(u'En partant de', coerce=int)
arrival_place = SelectField(u'et ร  destination de', coerce=int)
itin_id = HiddenField()
class AddItineraireForm(Form):

itin = FormField(ItineraireForm)
distance = DecimalField(u'Distance', [validators.Length(min=1, max=4)],
filters=[strip_filter])
duration = DateTimeField(u'Durรฉe', [validators.Length(min=4, max=5)],
filters=[strip_filter])
price = DecimalField(u'Prix approx.', [validators.Length(min=1, max=5)],
filters=[strip_filter])
tr_pied = BooleanField(u'ร  pied')
tr_velo = BooleanField(u'ร  vรฉlo')
tr_moto = BooleanField(u'ร  moto')
tr_voiture = BooleanField(u'en voiture')
tr_taxi = BooleanField(u'en taxi')
tr_bus = BooleanField(u'en bus')
tr_avion = BooleanField(u'en avion')
description = TextAreaField(u'Description de l\'itinรฉraire')

class AddMember(MyBaseForm):
tiers_uid = HiddenField()
nom = TextField(u'Nom', [validators.Length(max=80)],
filters=[strip_filter],
description = u"Les espaces sont autorisรฉs, la ponctuation n'est " +
u"pas autorisรฉe ร  l'exception des points, traits d'union, " +
u"apostrophes et tirets bas."
)
prenom = TextField(u'Prรฉnom', [validators.Length(max=80)],
filters=[strip_filter],
description = u"Les espaces sont autorisรฉs, la ponctuation n'est " +
u"pas autorisรฉe ร  l'exception des points, traits d'union, " +
u"apostrophes et tirets bas."
)
email = TextField(u'Email', [validators.required(),
validators.length(max=10),
validators.Email(message='Ceci ne ressemble pas ร  une adresse valide')],
description=u"Afin d'รฉviter la duplication d'information et les doublons inutile, "+
u"pensez d'abord ร  lui demander de confirmer le mail qu'il a utilisรฉ lors de "+
u"son inscription sur le site.")
add = SubmitField('Ajouter des membres')

class TiersForm(MyBaseForm):
name = TextField(u'Nom', [validators.Length(max=100)],
filters=[strip_filter],
description = u"Les espaces sont autorisรฉs, la ponctuation n'est " +
u"pas autorisรฉe ร  l'exception des points, traits d'union, " +
u"apostrophes et tirets bas."
)

tiers_type = MySelectField('Nature', coerce=int)
website = TextField(u'Site web', [validators.optional(), validators.URL(), validators.Length(max=100)],
filters=[strip_filter],
description = "Renseignez ici le site Web."
)
description = TextAreaField('Descriptif',
[validators.optional(), validators.Length(max=1000000)],
filters=[strip_filter],
description = u"Vous pouvez insรฉrer les dรฉtails"
)
membership = FieldList(FormField(TiersMember))

class UpdateTiersForm(TiersForm):
uid = HiddenField()
tiers_id = HiddenField()


class ExchCateg(MyBaseForm):
exch_type = HiddenField()
exch_subtype = TextField(u'Catรฉgorie', [validators.Length(max=80)],
filters=[strip_filter],
description = "Le nom de la categorie"
)
description = TextAreaField('Description',
filters=[strip_filter])

class UpdateExchangeForm(MyBaseForm):
exch_id = HiddenField()

class AskCForm(ItineraireForm):
ConfidenceLevel = [
("0",u"exactement ร "),
("1",u"approximativement ร "),
("2",u"ร  peu prรจs (5 ร  15 min) vers"),
("3",u"ร  une vache prรจs (1h) vers")
]
DayChoice = [("4","Jeudi"), ("5","Vendredi"), ("6","Samedi"), ("0","Dimanche"), ("1","Lundi")]
Day_start = SelectField(u'Jour', choices=DayChoice )
Confidence = SelectField(u'Confiance', choices=ConfidenceLevel)
Hour_start = TextField(u'Heure', [validators.Length(max=5,
message=u"doit faire au maximum 5 caractรจres"),
validators.Regexp("\d+:\d+",
message=u"doit รชtre sous la forme HH:MM")],
filters=[strip_filter])
start_time = HiddenField()
start_place = SelectField(u'En partant de', coerce=int)
arrival_place = SelectField(u'et ร  destination de', coerce=int)
itin_id = HiddenField()

class AskHForm(MyBaseForm):
DayChoice = [("4",u"Jeudi ร  Vendredi"), ("5",u"Vendredi ร  Samedi"), ("6",u"Samedi ร  Dimanche"), ("0",u"Dimanche ร  Lundi")]
Day_start = SelectField(u'Pour la nuit de', choices=DayChoice )
start_time = HiddenField()
description = TextAreaField(u'Description de vos contraintes รฉventuelles', filters=[strip_filter],
description = u"Dรฉcrivez ici vos souhaits et รฉventuellement "
+ u"les contraintes ร  prendre en compte. N'hรฉsitez pas ร  donner des dรฉtails."
)
class AskMForm(MyBaseForm):
DayChoice = [("4","Jeudi"), ("5","Vendredi"), ("6","Samedi"), ("0","Dimanche"), ("1","Lundi")]
Day_start = SelectField(u"ร  partir de", choices=DayChoice )
Hour_start = TextField(u'vers', [validators.Length(max=5,
message=u"doit faire au maximum 5 caractรจres"),
validators.Regexp("\d+:\d+",
message=u"doit รชtre sous la forme HH:MM")],
filters=[strip_filter])
start_time = HiddenField()
Day_end = SelectField(u"Jusqu'ร ", choices=DayChoice )
Hour_end = TextField(u'vers', [validators.Length(max=5,
message=u"doit faire au maximum 5 caractรจres"),
validators.Regexp("\d+:\d+",
message=u"doit รชtre sous la forme HH:MM")],
filters=[strip_filter])
end_time = HiddenField()
exch_categ = SelectField(u'Catรฉgorie de matรฉriel', coerce=int,
description = u"Choisissez une catรฉgorie de bien"
)
description = TextAreaField(u'Description du bien', filters=[strip_filter],
description = u"Dรฉcrivez ici les biens que vous souhaitez"
+ u"รฉchanger. N'hรฉsitez pas ร  donner des dรฉtails."
)
class PropCForm(ItineraireForm):
ConfidenceLevel = [
("0",u"exactement ร "),
("1",u"approximativement ร "),
("2",u"ร  peu prรจs (5 ร  15 min) vers"),
("3",u"ร  une vache prรจs (1h) vers")
]
DayChoice = [("4","Jeudi"), ("5","Vendredi"), ("6","Samedi"), ("0","Dimanche"), ("1","Lundi")]
Day_start = SelectField(u'Jour', choices=DayChoice )
Confidence = SelectField(u'Confiance', choices=ConfidenceLevel)
Hour_start = TextField(u'Heure', [validators.Length(max=5,
message=u"doit faire au maximum 5 caractรจres"),
validators.Regexp("\d+:\d+",
message=u"doit รชtre sous la forme HH:MM")],
filters=[strip_filter])
start_time = HiddenField()
start_place = SelectField(u'En partant de', coerce=int)
arrival_place = SelectField(u'et ร  destination de', coerce=int)
itin_id = HiddenField()
class PropHForm(MyBaseForm):
DayChoice = [("4",u"Jeudi ร  Vendredi"), ("5",u"Vendredi ร  Samedi"), ("6",u"Samedi ร  Dimanche"), ("0",u"Dimanche ร  Lundi")]
Day_start = SelectField(u'Pour la nuit de', choices=DayChoice )
start_time = HiddenField()
exch_categ = SelectField(u'Type de couchage', coerce=int,
description = u"Indiquez ici le type de couchage proposรฉ")
description = TextAreaField(u'Quelques mots autour du logement que vous proposez', filters=[strip_filter],
description = u"Dรฉcrivez ici quelques dรฉtails sur le logement que vous souhaitez "
+ u"proposer, les contraintes ร  prendre en compte. N'hรฉsitez pas ร  donner des dรฉtails."
)
place_id = SelectField(u'Emplacement', coerce=int,
description = u"Indiquez ici une des adresses que vous avez proposรฉ")

class PropMForm(MyBaseForm):
DayChoice = [("4","Jeudi"), ("5","Vendredi"), ("6","Samedi"), ("0","Dimanche"), ("1","Lundi")]
Day_start = SelectField(u"ร  partir de", choices=DayChoice )
Hour_start = TextField(u'vers', [validators.Length(max=5,
message=u"doit faire au maximum 5 caractรจres"),
validators.Regexp("\d+:\d+",
message=u"doit รชtre sous la forme HH:MM")],
filters=[strip_filter])
start_time = HiddenField()
Day_end = SelectField(u"Jusqu'a ", choices=DayChoice )
Hour_end = TextField(u'vers', [validators.Length(max=5,
message=u"doit faire au maximum 5 caractรจres"),
validators.Regexp("\d+:\d+",
message=u"doit รชtre sous la forme HH:MM")],
filters=[strip_filter])
end_time = HiddenField()
exch_categ = SelectField(u'Catรฉgorie de matรฉriel', coerce=int,
description = u"Choisissez une catรฉgorie de bien matรฉriel"
)
description = TextAreaField(u'Ajoutez quelques mots autour du matรฉriel que vous proposez', filters=[strip_filter],
description = u"Dรฉcrivez ici quelques dรฉtails sur le matรฉriel que vous souhaitez "
+ u"proposer. N'hรฉsitez pas ร  donner des dรฉtails."
)

class UpdateAskCForm(AskCForm, UpdateExchangeForm):
pass

class UpdateAskHForm(AskHForm, UpdateExchangeForm):
pass

class UpdateAskMForm(AskMForm, UpdateExchangeForm):
pass

class UpdatePropCForm(PropCForm, UpdateExchangeForm):
pass
class UpdatePropHForm(PropHForm, UpdateExchangeForm):
pass

class UpdatePropMForm(PropMForm, UpdateExchangeForm):
pass

+ 616
- 0
jm2l/models.py View File

@@ -0,0 +1,616 @@
# -*- coding: utf8 -*-
import datetime
import sqlalchemy as sa
import hashlib
from pyramid.security import unauthenticated_userid
from sqlalchemy.orm import relationship, backref
from sqlalchemy import func
from sqlalchemy import or_
from sqlalchemy import (
Column,
Integer,
Text,
Unicode,
UnicodeText,
DateTime,
Enum,
Boolean,
ForeignKey
)

from slugify import slugify
from webhelpers.text import urlify
from webhelpers.paginate import PageURL_WebOb, Page
from webhelpers.date import time_ago_in_words
from collections import namedtuple


from sqlalchemy.ext.declarative import declarative_base

from sqlalchemy.orm import (
scoped_session,
sessionmaker,
relation
)

from zope.sqlalchemy import ZopeTransactionExtension

DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
Base = declarative_base()

CurrentYear = 2015

class TasksArea(Base):
__tablename__ = 'staff_tasks_area'
uid = Column(Integer, primary_key=True)
name = Column(Unicode(80))
description = Column(UnicodeText)

class Tasks(Base):
__tablename__ = 'staff_tasks'
uid = Column(Integer, primary_key=True)
area_uid = Column(Integer, ForeignKey('staff_tasks_area.uid') )
due_date = Column(DateTime, default=None)
closed_by = Column(Integer, ForeignKey('users.uid') )
closed_date = Column(DateTime, default=None)
closed = Column(Integer, default=0)
name = Column(Unicode(80))
description = Column(UnicodeText)

@classmethod
def by_id(cls, id):
return DBSession.query(cls).filter(cls.uid == id).first()

class User_Event(Base):
""" Crรฉer le lien entre la personne et l' รฉvenement en fonction de l'annรฉe"""
__tablename__ = 'user_event_link'
uid = Column(Integer, primary_key=True)
event_uid = Column(Integer, ForeignKey('events.uid') )
#, primary_key=True)
#
user_uid = Column(Integer, ForeignKey('users.uid') )
#, primary_key=True)
#
year_uid = Column(Integer, ForeignKey('jm2l_year.year_uid'), default=CurrentYear)
role = Column(Unicode(80))
# Define some relation
#user = relationship('User', backref=backref("events_assoc") )
#event = relationship('events', backref=backref("users_assoc") )

class JM2L_Year(Base):
__tablename__ = 'jm2l_year'
year_uid = Column(Integer, primary_key=True)
description = Column(UnicodeText)
doss_presse = Column(UnicodeText)
state = Column(Enum('Archived', 'Cancelled', 'Ongoing'))
start_time = Column(DateTime, default=datetime.datetime.utcnow)
end_time = Column(DateTime, default=datetime.datetime.utcnow)
created = Column(DateTime, default=datetime.datetime.utcnow)
last_change = Column(DateTime, default=datetime.datetime.utcnow)
@property
def AvailableTimeSlots(self, TimeStep=30):
Available = self.end_time - self.start_time
NbMinutes = Available.total_seconds()/60
NbSteps = NbMinutes/TimeStep
# Create the range of date each 30min
date_list = [self.start_time + datetime.timedelta(minutes=TimeStep*x) for x in range(0, int(NbSteps))]
# Remove out of range datetime
# Remove hours > 19h
date_list = filter(lambda x:x.hour < 19, date_list)
# Remove hours < 10h
date_list = filter(lambda x:x.hour >= 10, date_list)
# Remove 12h < hours < 13h
date_list = filter(lambda x: x.hour<12 or x.hour>=13, date_list)
return date_list

class User(Base):
__tablename__ = 'users'
uid = Column(Integer, primary_key=True)
user_id = Column(Integer)
nom = Column(Unicode(80))
prenom = Column(Unicode(80))
pseudo = Column(Unicode(80))
slug = Column(Unicode(164))
mail = Column(Unicode(100))
password = Column(Unicode(100), nullable=False)
fonction = Column(Unicode(80))
website = Column(Unicode(100))
phone = Column(Unicode(10))
created = Column(DateTime, default=datetime.datetime.utcnow)
last_logged = Column(DateTime, default=datetime.datetime.utcnow)
last_change = Column(DateTime, default=datetime.datetime.utcnow)
active = Column(Integer, default=1)
bio = Column(UnicodeText)
gpg_key = Column(UnicodeText)
soc_link = Column(UnicodeText)
Staff = Column(Integer, default=0)
# relations
tiers = relationship('Tiers', secondary='user_tiers_link' )
events = relationship('Event', secondary='user_event_link' )
tiersship = relationship('User_Tiers', backref="matching_users")

@classmethod
def by_id(cls, id):
return DBSession.query(cls).filter(cls.uid == id).first()

@classmethod
def by_slug(cls, slug):
return DBSession.query(cls).filter(cls.slug == slug).first()

@classmethod
def by_user_id(cls, user_id):
return DBSession.query(cls).filter(cls.user_id == user_id).first()
@classmethod
def by_name(cls, name):
return DBSession.query(cls).filter(cls.nom == name).first()

@classmethod
def by_hash(cls, tsthash):
for u in DBSession.query(cls):
print u.nom, u.my_hash
if u.my_hash==tsthash:
return u
return None

@property
def my_hash(self):
m = hashlib.sha1()
m.update("Nobody inspects ")
if self.nom:
m.update(unicode.encode(self.nom,'utf8'))
if self.pseudo:
m.update(unicode.encode(self.pseudo,'utf8'))
if self.prenom:
m.update(unicode.encode(self.prenom,'utf8'))
m.update(" the spammish repetition")
return m.hexdigest()
@property
def Photos(self):
return DBSession.query(Media.filename) \
.filter(Media.media_table=='users') \
.filter(Media.media_type=='Image') \
.filter(Media.link_id == self.user_id).all()

@property
def PhotosLinks(self):
from .upload import MediaPath
return MediaPath().get_list('users', self.uid)

@property
def PhotosThumb(self):
from .upload import MediaPath
return MediaPath().get_thumb('users', self.uid)
def verify_password(self, password):
return self.password == password

class TiersOpt(Base):
__tablename__ = 'tiers_opt'
uid = Column(Integer, primary_key=True)
entity_type = Column(Unicode(80), nullable=False)
entity_subtype = Column(Unicode(80))
entity_role = Column(Unicode(80))

@property
def slug_entity_type(self):
return slugify(self.entity_type)
@property
def slug_entity_subtype(self):
return slugify(self.entity_subtype)
@classmethod
def get_entity_type(cls):
return DBSession.query(cls, func.count(Tiers.ent_type).label('count'))\
.outerjoin(Tiers)\
.group_by(cls.entity_type).all()
@classmethod
def get_entity_sub_type(cls, entity_type):
return DBSession.query(cls, func.count(Tiers.ent_type).label('count'))\
.outerjoin(Tiers)\
.filter(cls.entity_type == entity_type)\
.group_by(cls.entity_subtype).all()
@classmethod
def by_id(cls, id):
return DBSession.query(cls).filter(cls.uid == id).first()
class Tiers(Base):
__tablename__ = 'tiers'
uid = Column(Integer, primary_key=True)
tiers_id = Column(Integer)
name = Column(Unicode(100), nullable=False)
slug = Column(Unicode(100))
description = Column(UnicodeText)
website = Column(Unicode(100))
tiers_type = Column(Integer, ForeignKey('tiers_opt.uid'), default=1)
created = Column(DateTime, default=datetime.datetime.utcnow)
last_change = Column(DateTime, default=datetime.datetime.utcnow)
# relations
ent_type = relationship('TiersOpt')
#members = relationship('User', secondary='user_tiers_link' )
members = relationship(User,
secondary='user_tiers_link',
backref=backref('associate', uselist=False),
lazy='dynamic')
creator_id = Column(Integer)
membership = relationship('User_Tiers', backref="matching_tiers")
roles = relationship('Tiers', secondary='role_tiers_link' )
@classmethod
def by_id(cls, id):
return DBSession.query(cls).filter(cls.uid == id).first()
@classmethod
def by_slug(cls, slug):
return DBSession.query(cls).filter(cls.slug == slug).first()

@property
def get_entity_type(self):
return DBSession.query(TiersOpt)\
.filter(TiersOpt.uid == self.tiers_type).first()

@property
def logo(self):
return DBSession.query(Media) \
.filter(Media.media_table == 'tiers') \
.filter(Media.media_type == 'Image') \
.filter(Media.link_id == self.uid)
@property
def PhotosLinks(self):
from .upload import MediaPath
return MediaPath().get_list('tiers', self.uid)

@property
def ThumbLinks(self):
from .upload import MediaPath
return MediaPath().get_thumb('tiers', self.uid)

class Role_Tiers(Base):
""" Crรฉer le lien entre le tiers et son rรดle dans l'รฉvenement en fonction de l'annรฉe"""
__tablename__ = 'role_tiers_link'
uid_role = Column(Integer, primary_key=True)
year_uid = Column(Integer, ForeignKey('jm2l_year.year_uid'), default=CurrentYear)
tiers_uid = Column(Integer, ForeignKey('tiers.uid'))
tiers = relationship(Tiers, backref=backref("roles_assoc") )
tiers_role = Column(Enum('Exposant', 'Sponsor', 'Donateur'))

class User_Tiers(Base):
""" Crรฉer le lien entre la personne et le tiers en fonction de l'annรฉe"""
__tablename__ = 'user_tiers_link'
uid_tiers = Column(Integer, primary_key=True)
year_uid = Column(Integer, ForeignKey('jm2l_year.year_uid'), default=CurrentYear)
tiers_uid = Column(Integer, ForeignKey('tiers.uid'))
tiers = relationship(Tiers, backref=backref("users_assoc") )
user_uid = Column(Integer, ForeignKey('users.uid'))
user = relationship(User, backref=backref("tiers_assoc") )
role = Column(Unicode(80))

class Media(Base):
__tablename__ = 'medias'
media_id = Column(Integer, primary_key=True)
for_year = Column(Integer, ForeignKey('jm2l_year.year_uid'))
media_table = Column(Enum('users', 'tiers', 'place', 'salle', 'RIB', 'Justif', 'event' ))
media_type = Column(Enum('Image', 'Video', 'Pres', 'Document'))
link_id = Column(Integer)
mime_type = Column(Unicode(20))
size = Column(Integer)
width = Column(Integer)
height = Column(Integer)
length = Column(Integer)
filename = Column(UnicodeText)
created = Column(DateTime, default=datetime.datetime.utcnow)
@property
def get_path(self):
return '/upload/%s/%s/%s' % (self.media_type, self.media_table, self.filename)

class SallePhy(Base):
""" Reprรฉsente une salle dans les locaux """
__tablename__ = 'phy_salle'
uid = Column(Integer, primary_key=True)
name = Column(Unicode(40)) # Numรฉro de salle vu de polytech
slug = Column(Unicode(40))
description = Column(UnicodeText) # Description du matรฉriel disponible
nb_places = Column(Integer, default=0) # Nombre de places assises
@classmethod
def by_id(cls, uid):
return DBSession.query(cls).filter(cls.uid == uid).first()
class Salles(Base):
__tablename__ = 'salle'
salle_id = Column(Integer, primary_key=True)
phy_salle_id = Column(Integer, ForeignKey('phy_salle.uid'))
year_uid = Column(Integer, ForeignKey('jm2l_year.year_uid'), default=CurrentYear)
name = Column(Unicode(40))
place_type = Column(Enum('Conference', 'Stand', 'Ateliers', 'Autres'))
description = Column(UnicodeText) # Description du matรฉriel disponible
created = Column(DateTime, default=datetime.datetime.utcnow)
last_change = Column(DateTime, default=datetime.datetime.utcnow)

@classmethod
def by_id(cls, uid):
return DBSession.query(cls).filter(cls.salle_id == uid).first()

class Place(Base):
__tablename__ = 'place'
place_id = Column(Integer, primary_key=True)
usage = Column(Boolean, default=False) # By Default / Extended
place_type = Column(Enum('Aeroport', 'Gare', 'JM2L', \
'Hotel', 'Habitant', 'Restaurant', 'Autres'))
display_name = Column(Unicode(20))
name = Column(Unicode(80))
slug = Column(Unicode(80))
specific = Column(Unicode(80)) # eg Terminal 2
gps_coord = Column(Unicode(30))
adresse = Column(Unicode(100))
codePostal = Column(Unicode(5))
ville = Column(Unicode(40))
website = Column(Unicode(100))
description = Column(UnicodeText)
created_by = Column(Integer, ForeignKey('users.user_id'))
created = Column(DateTime, default=datetime.datetime.utcnow)
last_change = Column(DateTime, default=datetime.datetime.utcnow)

@classmethod
def by_id(cls, uid):
return DBSession.query(cls).filter(cls.place_id == uid).first()
@classmethod
def get_list(cls, All=False):
if All:
return DBSession.query(cls).all()
else:
return DBSession.query(cls).filter(cls.usage==True).all()

class Itineraire(Base):
__tablename__ = 'itineraire'
itin_id = Column(Integer, primary_key=True)
start_place = Column(Integer, ForeignKey('place.place_id')) # Place link
arrival_place = Column(Integer, ForeignKey('place.place_id')) # Place link
distance = Column(Integer)
duration = Column(Integer)
price = Column(Integer)
tr_pied = Column(Boolean, default=False)
tr_velo = Column(Boolean, default=False)
tr_moto = Column(Boolean, default=False)
tr_voiture = Column(Boolean, default=False)
tr_bus = Column(Boolean, default=False)
tr_taxi = Column(Boolean, default=False)
tr_avion = Column(Boolean, default=False)
description = Column(UnicodeText)
created_by = Column(Integer, ForeignKey('users.user_id')) # User link
created = Column(DateTime, default=datetime.datetime.utcnow)
last_change = Column(DateTime, default=datetime.datetime.utcnow)
# relations
start = relationship(Place, foreign_keys=[start_place])
arrival = relationship(Place, foreign_keys=[arrival_place])

class Exchange_Cat(Base):
__tablename__ = 'exchange_category'
cat_id = Column(Integer, primary_key=True)
exch_type = Column(Enum('H', 'C', 'M')) # Heberg, Co-voit, Materiel
exch_subtype = Column(Unicode(80))
description = Column(UnicodeText)

class Exchange(Base):
__tablename__ = 'exchanges'
exch_id = Column(Integer, primary_key=True)
for_year = Column(Integer, ForeignKey('jm2l_year.year_uid')) # link JM2L_Year
exch_done = Column(Boolean, default=False)
exch_state = Column(Enum('Ask', 'Proposal'))
exch_type = Column(Enum('H', 'C', 'M')) # Heberg, Co-Voit, Materiel
exch_categ = Column(Integer, ForeignKey('exchange_category.cat_id')) # Exchange_Cat link
# Users
asker_id = Column(Integer, ForeignKey('users.uid')) # User link
provider_id = Column(Integer, ForeignKey('users.uid')) # User link
start_time = Column(DateTime, default=datetime.datetime.utcnow)
end_time = Column(DateTime, default=datetime.datetime.utcnow)
# Co-voiturage
itin_id = Column(Integer, ForeignKey('itineraire.itin_id')) # Itineraire link
# Hebergement
place_id = Column(Integer, ForeignKey('place.place_id')) # Place link
# Materiel
duration = Column(Integer)
description = Column(UnicodeText)
pictures = Column(Unicode(80))
created_by = Column(Integer) # User link
created = Column(DateTime, default=datetime.datetime.utcnow)
last_change = Column(DateTime, default=datetime.datetime.utcnow)
# relations
Category = relationship(Exchange_Cat, backref="exchanges")
Itin = relationship(Itineraire, backref="exchanged")
asker = relationship(User, foreign_keys=[asker_id], backref="asked")
provider = relationship(User, foreign_keys=[provider_id], backref="provided")
@classmethod
def by_id(cls, id):
return DBSession.query(cls).filter(cls.exch_id == id).first()

@classmethod
def get_counters(cls):
return DBSession.query(cls.exch_state, cls.exch_type, cls.exch_done, func.count(cls.exch_id))\
.filter(cls.for_year==2015)\
.group_by(cls.exch_state, cls.exch_type, cls.exch_done)

@classmethod
def get_my_counters(cls, uid):
return DBSession.query(cls.exch_state, cls.exch_type, cls.exch_done, func.count(cls.exch_id))\
.filter(cls.for_year==2015)\
.filter( or_(cls.asker_id==uid, cls.provider_id==uid) )\
.group_by(cls.exch_state, cls.exch_type, cls.exch_done)
@classmethod
def get_overview(cls, uid):
# Build a Dic with all exchange to save database access
DicResult= {}
for extype in ['F','C','H','M']:
DicResult[extype] = {}
for exstate in ['Ask','Proposal','Missing','Agree']:
DicResult[extype][exstate]=[]
DicResult[extype]['Counters']={'AllAsk':0, 'AllProp':0, 'AllAgree':0}
Query = DBSession.query(cls)\
.filter(cls.for_year==2015)\
.order_by(cls.start_time).all()
for item in Query:
if item.exch_done:
DicResult[item.exch_type]['Counters']['AllAgree']+=1
if item.exch_state=='Ask':
DicResult[item.exch_type]['Counters']['AllAsk']+=1
if item.exch_state=='Proposal':
DicResult[item.exch_type]['Counters']['AllProp']+=1
if item.asker_id==uid or item.provider_id==uid:
if item.asker_id==uid and item.exch_state=='Ask':
DicResult[item.exch_type]['Ask'].append(item)
if item.provider_id==uid and item.exch_state=='Ask':
DicResult[item.exch_type]['Proposal'].append(item)
if item.asker_id==uid and item.exch_state=='Proposal':
DicResult[item.exch_type]['Ask'].append(item)
if item.provider_id==uid and item.exch_state=='Proposal':
DicResult[item.exch_type]['Proposal'].append(item)
if item.exch_done:
DicResult[item.exch_type]['Agree'].append(item)
else:
DicResult[item.exch_type]['Missing'].append(item)
return DicResult
@classmethod
def get_pub_list(cls, exch_type):
return DBSession.query(cls).filter(cls.for_year==2015 and exch_state in ['Ask','Proposal'])\
.filter(cls.exch_type=='%s' % exch_type)\
.filter(cls.exch_done==False)\
.all()
@classmethod
def get_my_list(cls, uid, exch_type):
DicResult = {}
DicResult['Ask']=DBSession.query(cls)\
.filter(cls.for_year==2015)\
.filter( or_(cls.asker_id==uid, cls.provider_id==uid) )\
.filter(cls.exch_type=='%s' % exch_type)\
.filter(cls.exch_state=='Ask')\
.order_by(cls.start_time).all()
DicResult['Proposal']=DBSession.query(cls)\
.filter(cls.for_year==2015)\
.filter( or_(cls.asker_id==uid, cls.provider_id==uid) )\
.filter(cls.exch_type=='%s' % exch_type)\
.filter(cls.exch_state=='Proposal')\
.order_by(cls.start_time).all()
return DicResult
class Sejour(Base):
__tablename__ = 'sejour'
sej_id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('users.user_id')) # User link
for_year = Column(Integer, ForeignKey('jm2l_year.year_uid')) # link JM2L_Year
arrival_time = Column(DateTime)
arrival_place = Column(Integer, ForeignKey('place.place_id')) # Place link
depart_time = Column(DateTime)
depart_place = Column(Integer, ForeignKey('place.place_id')) # Place link
created = Column(DateTime, default=datetime.datetime.utcnow)
last_change = Column(DateTime, default=datetime.datetime.utcnow)

class Event(Base):
__tablename__ = 'events'
uid = Column(Integer, primary_key=True)
salle_uid = Column(Integer, ForeignKey('salle.salle_id'))
event_uid = Column(Integer)
for_year = Column(Integer, ForeignKey('jm2l_year.year_uid')) # link JM2L_Year
name = Column(Unicode(100), nullable=False)
slug = Column(Unicode(100))
event_type = Column(Enum('Stand', 'Table ronde', 'Atelier', 'Concert', 'Conference', 'Repas'))
start_time = Column(DateTime, default=datetime.datetime.utcnow)
end_time = Column(DateTime, default=datetime.datetime.utcnow)
description = Column(UnicodeText)
created = Column(DateTime, default=datetime.datetime.utcnow)
last_change = Column(DateTime, default=datetime.datetime.utcnow)
intervenants = relationship(User,
secondary='user_event_link',
backref=backref('participate', uselist=False),
lazy='dynamic')
interventions = relationship(User_Event, backref="matching_events")
Salle = relationship(Salles, backref='allevents')
@classmethod
def by_id(cls, uid):
return DBSession.query(cls)\
.filter(cls.uid == uid).first()


@classmethod
def by_slug(cls, slug, year=None):
if not year is None:
return DBSession.query(cls)\
.filter(cls.for_year==year)\
.filter(cls.slug == slug).first()
else:
return DBSession.query(cls)\
.filter(cls.slug == slug).first()

@property
def video(self):
return DBSession.query(Media) \
.filter(Media.media_table == 'event') \
.filter(Media.media_type == 'Video') \
.filter(Media.link_id == self.uid)

@property
def presentation(self):
return DBSession.query(Media) \
.filter(Media.media_table == 'event') \
.filter(Media.media_type == 'Pres') \
.filter(Media.link_id == self.uid)
@property
def created_in_words(self):
return time_ago_in_words(self.created)
class Entry(Base):
__tablename__ = 'entries'
id = Column(Integer, primary_key=True)
active = Column(Integer, default=True)
title = Column(Unicode(255), unique=True, nullable=False)
body = Column(UnicodeText, default=u'')
created = Column(DateTime, default=datetime.datetime.utcnow)
edited = Column(DateTime, default=datetime.datetime.utcnow)
@classmethod
def all(cls):
return DBSession.query(Entry).order_by(sa.desc(Entry.created))
@classmethod
def by_id(cls, id):
return DBSession.query(Entry).filter(Entry.id == id).first()
@property
def slug(self):
return urlify(self.title)
@property
def created_in_words(self):
return time_ago_in_words(self.created)
@classmethod
def get_paginator(cls, request, page=1):
page_url = PageURL_WebOb(request)
return Page(Entry.all(), page, url=page_url, items_per_page=5)

#class Seances(Base):
# __tablename__ = 'seances'
def get_user(request):
# the below line is just an example, use your own method of
# accessing a database connection here (this could even be another
# request property such as request.db, implemented using this same
# pattern).
userid = unauthenticated_userid(request)
if userid is not None:
# this should return None if the user doesn't exist
# in the database
return DBSession.query(User).filter(User.uid==userid).first()

+ 1
- 0
jm2l/scripts/__init__.py View File

@@ -0,0 +1 @@
# package

+ 40
- 0
jm2l/scripts/initializedb.py View File

@@ -0,0 +1,40 @@
# -*- coding: utf8 -*-
import os
import sys
import transaction
import time
import lxml.etree as ET
from datetime import datetime
from sqlalchemy import engine_from_config
from sqlalchemy import create_engine
import unicodedata
import urllib
#ย Usefull tools
from slugify import slugify
from sqlite3 import dbapi2 as sqlite
from os import path

from pyramid.paster import (
get_appsettings,
setup_logging,
)
from jm2l.models import *

def usage(argv):
cmd = os.path.basename(argv[0])
print('usage: %s <config_uri>\n'
'(example: "%s development.ini")' % (cmd, cmd))
sys.exit(1)


def main(argv=sys.argv):
if len(argv) != 2:
usage(argv)
config_uri = "development.ini"
setup_logging(config_uri)
settings = get_appsettings(config_uri)
engine = engine_from_config(settings, 'sqlalchemy.')
DBSession.configure(bind=engine)
Base.metadata.create_all(engine)

+ 25
- 0
jm2l/security.py View File

@@ -0,0 +1,25 @@
# -*- coding: utf8 -*-
from pyramid.security import Allow, Everyone, Authenticated

USERS = { 1:'editor',
'editor':'editor',
'viewer':'viewer'}
GROUPS = {'editor':['group:editors'], 1:['group:editors']}

def groupfinder(userid, request):
if userid in USERS:
return GROUPS.get(userid, [])

class EntryFactory(object):
__acl__ = [(Allow, Everyone, 'view'),
(Allow, Authenticated, 'create'),
(Allow, Authenticated, 'edit'), ]
def __init__(self, request):
pass
class RootFactory(object):
__acl__ = [ (Allow, Everyone, 'view'),
(Allow, 'group:editors', 'edit') ]
def __init__(self, request):
pass

+ 60
- 0
jm2l/static/404.html View File

@@ -0,0 +1,60 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Page Not Found</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>

* {
line-height: 1.2;
margin: 0;
}

html {
color: #888;
display: table;
font-family: sans-serif;
height: 100%;
text-align: center;
width: 100%;
}

body {
display: table-cell;
vertical-align: middle;
margin: 2em auto;
}

h1 {
color: #555;
font-size: 2em;
font-weight: 400;
}

p {
margin: 0 auto;
width: 280px;
}

@media only screen and (max-width: 280px) {

body, p {
width: 95%;
}

h1 {
font-size: 1.5em;
margin: 0 0 0.3em;
}

}

</style>
</head>
<body>
<h1>Page Not Found</h1>
<p>Sorry, but the page you were trying to view does not exist.</p>
</body>
</html>
<!-- IE needs 512+ bytes: http://blogs.msdn.com/b/ieinternals/archive/2010/08/19/http-error-pages-in-internet-explorer.aspx -->

+ 470
- 0
jm2l/static/css/bootstrap-theme.css View File

@@ -0,0 +1,470 @@
/*!
* Bootstrap v3.3.1 (http://getbootstrap.com)
* Copyright 2011-2014 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/

.btn-default,
.btn-primary,
.btn-success,
.btn-info,
.btn-warning,
.btn-danger {
text-shadow: 0 -1px 0 rgba(0, 0, 0, .2);
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
}
.btn-default:active,
.btn-primary:active,
.btn-success:active,
.btn-info:active,
.btn-warning:active,
.btn-danger:active,
.btn-default.active,
.btn-primary.active,
.btn-success.active,
.btn-info.active,
.btn-warning.active,
.btn-danger.active {
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
}
.btn-default .badge,
.btn-primary .badge,
.btn-success .badge,
.btn-info .badge,
.btn-warning .badge,
.btn-danger .badge {
text-shadow: none;
}
.btn:active,
.btn.active {
background-image: none;
}
.btn-default {
text-shadow: 0 1px 0 #fff;
background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%);
background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0));
background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #dbdbdb;
border-color: #ccc;
}
.btn-default:hover,
.btn-default:focus {
background-color: #e0e0e0;
background-position: 0 -15px;
}
.btn-default:active,
.btn-default.active {
background-color: #e0e0e0;
border-color: #dbdbdb;
}
.btn-default:disabled,
.btn-default[disabled] {
background-color: #e0e0e0;
background-image: none;
}
.btn-primary {
background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88));
background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #245580;
}
.btn-primary:hover,
.btn-primary:focus {
background-color: #265a88;
background-position: 0 -15px;
}
.btn-primary:active,
.btn-primary.active {
background-color: #265a88;
border-color: #245580;
}
.btn-primary:disabled,
.btn-primary[disabled] {
background-color: #265a88;
background-image: none;
}
.btn-success {
background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);
background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641));
background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #3e8f3e;
}
.btn-success:hover,
.btn-success:focus {
background-color: #419641;
background-position: 0 -15px;
}
.btn-success:active,
.btn-success.active {
background-color: #419641;
border-color: #3e8f3e;
}
.btn-success:disabled,
.btn-success[disabled] {
background-color: #419641;
background-image: none;
}
.btn-info {
background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2));
background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #28a4c9;
}
.btn-info:hover,
.btn-info:focus {
background-color: #2aabd2;
background-position: 0 -15px;
}
.btn-info:active,
.btn-info.active {
background-color: #2aabd2;
border-color: #28a4c9;
}
.btn-info:disabled,
.btn-info[disabled] {
background-color: #2aabd2;
background-image: none;
}
.btn-warning {
background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316));
background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #e38d13;
}
.btn-warning:hover,
.btn-warning:focus {
background-color: #eb9316;
background-position: 0 -15px;
}
.btn-warning:active,
.btn-warning.active {
background-color: #eb9316;
border-color: #e38d13;
}
.btn-warning:disabled,
.btn-warning[disabled] {
background-color: #eb9316;
background-image: none;
}
.btn-danger {
background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a));
background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #b92c28;
}
.btn-danger:hover,
.btn-danger:focus {
background-color: #c12e2a;
background-position: 0 -15px;
}
.btn-danger:active,
.btn-danger.active {
background-color: #c12e2a;
border-color: #b92c28;
}
.btn-danger:disabled,
.btn-danger[disabled] {
background-color: #c12e2a;
background-image: none;
}
.thumbnail,
.img-thumbnail {
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);