Skip to content

Commit

Permalink
Merge branch 'release/1.1.4' into testing
Browse files Browse the repository at this point in the history
* release/1.1.4:
  Bump version.
  Add compatibility with SQLAlchemy 1.0.  Also require SQLAlchemy 1.0.6 now.
  add test for slot rendering with HTTPForbidden
  prevent raising HTTPForbidden exception during slots rendering
  Upgrade upstream packages.
  Use the add_permission in the generated scaffold
  Generate development.ini with 0.0.0.0 as bind address; useful when developing on remote machines
  Bump version.
  • Loading branch information
disko committed Jun 26, 2015
2 parents da77fb6 + 049ff50 commit 802364e
Show file tree
Hide file tree
Showing 9 changed files with 97 additions and 60 deletions.
6 changes: 6 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
Change History
==============

1.1.4 - 2015-06-27
------------------

- Add compatibility with SQLAlchemy 1.0. Also require SQLAlchemy 1.0.6 now.
- Ignore HTTPForbidden exceptions during slot rendering

1.1.3 - 2015-06-17
------------------

Expand Down
94 changes: 53 additions & 41 deletions kotti/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -645,7 +645,59 @@ def __init__(self, body=u"", mime_type='text/html', **kwargs):
self.mime_type = mime_type


class File(Content):
class SaveDataMixin(object):
""" The classmethods must not be implemented on a class that inherits
from ``Base`` with ``SQLAlchemy>=1.0``, otherwise that class cannot be
subclassed further.
See http://stackoverflow.com/questions/30433960/how-to-use-declare-last-in-sqlalchemy-1-0 # noqa
"""

@classmethod
def __declare_last__(cls):
""" Unconfigure the event set in _SQLAMutationTracker,
we have _save_data """

mapper = cls._sa_class_manager.mapper
args = (mapper.attrs['data'], 'set', _SQLAMutationTracker._field_set)
if event.contains(*args):
event.remove(*args)

# Declaring the event on the class attribute instead of mapper property
# enables proper registration on its subclasses
event.listen(cls.data, 'set', cls._save_data, retval=True)

@classmethod
def _save_data(cls, target, value, oldvalue, initiator):
""" Refresh metadata and save the binary data to the data field.
:param target: The File instance
:type target: :class:`kotti.resources.File` or subclass
:param value: The container for binary data
:type value: A :class:`cgi.FieldStorage` instance
"""

if isinstance(value, bytes):
value = _to_fieldstorage(fp=StringIO(value),
filename=target.filename,
mimetype=target.mimetype,
size=len(value))

newvalue = _SQLAMutationTracker._field_set(
target, value, oldvalue, initiator)

if newvalue is None:
return

target.filename = newvalue.filename
target.mimetype = newvalue.content_type
target.size = newvalue.file.content_length

return newvalue


class File(Content, SaveDataMixin):
"""File adds some attributes to :class:`~kotti.resources.Content` that are
useful for storing binary data.
"""
Expand Down Expand Up @@ -706,46 +758,6 @@ def from_field_storage(cls, fs):

return cls(data=fs)

@classmethod
def __declare_last__(cls):
# Unconfigure the event set in _SQLAMutationTracker, we have _save_data
mapper = cls._sa_class_manager.mapper
args = (mapper.attrs['data'], 'set', _SQLAMutationTracker._field_set)
if event.contains(*args):
event.remove(*args)

# Declaring the event on the class attribute instead of mapper property
# enables proper registration on its subclasses
event.listen(cls.data, 'set', cls._save_data, retval=True)

@classmethod
def _save_data(cls, target, value, oldvalue, initiator):
""" Refresh metadata and save the binary data to the data field.
:param target: The File instance
:type target: :class:`kotti.resources.File` or subclass
:param value: The container for binary data
:type value: A :class:`cgi.FieldStorage` instance
"""

if isinstance(value, bytes):
value = _to_fieldstorage(fp=StringIO(value),
filename=target.filename,
mimetype=target.mimetype,
size=len(value))

newvalue = _SQLAMutationTracker._field_set(
target, value, oldvalue, initiator)

if newvalue is None:
return

target.filename = newvalue.filename
target.mimetype = newvalue.content_type
target.size = newvalue.file.content_length

return newvalue


class Image(File):
"""Image doesn't add anything to :class:`~kotti.resources.File`, but images
Expand Down
3 changes: 2 additions & 1 deletion kotti/scaffolds/package/+package+/views/edit.py_tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ class CustomContentSchema(ContentSchema):
title=_(u"Custom attribute"))


@view_config(name=CustomContent.type_info.add_view, permission='add',
@view_config(name=CustomContent.type_info.add_view,
permission=CustomContent.type_info.add_permission,
renderer='kotti:templates/edit/node.pt')
class CustomContentAddForm(AddFormView):
""" Form to add a new instance of CustomContent. """
Expand Down
2 changes: 1 addition & 1 deletion kotti/scaffolds/package/development.ini_tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pipeline =

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

[alembic]
Expand Down
7 changes: 4 additions & 3 deletions kotti/tests/test_filedepot.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,8 @@ def test_session_integration(self, db_session):
DepotManager._default_depot = 'default'
DepotManager._depots = {'default': DBFileStorage()}

file_id = DepotManager.get().create('content here', u'f.jpg', 'image/jpg')
file_id = DepotManager.get().create(
'content here', u'f.jpg', 'image/jpg')
fs = DepotManager.get().get(file_id)

db_session.add(fs)
Expand Down Expand Up @@ -185,8 +186,8 @@ def test_migrate_between_storages(self, db_session, root, no_filedepots):
import shutil

event.listen(db_session,
'before_commit',
_SQLAMutationTracker._session_committed)
'before_commit',
_SQLAMutationTracker._session_committed)

tmp_location = tempfile.mkdtemp()

Expand Down
16 changes: 16 additions & 0 deletions kotti/tests/test_util_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,22 @@ def special(context, request):
api = self.make()
assert api.slots.right == [u"Hello world!"]

def test_assign_to_slot_forbidden(self, config, db_session,
events):
from kotti.views.slots import assign_slot
from pyramid.exceptions import HTTPForbidden

def special(context, request):
return Response(u"Hello world!")
assign_slot('special', 'right')

config.add_view(special, name='special', permission='admin')
# the slot rendering must not fail if a HTTPForbidden exception
api = self.make()
with patch('kotti.views.slots.render_view') as render_view:
render_view.side_effect = HTTPForbidden()
assert api.slots.right == []

def test_assign_slot_bad_name(self):
from kotti.views.slots import assign_slot

Expand Down
3 changes: 2 additions & 1 deletion kotti/views/slots.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def view(request, context):
import urllib

from pyramid.exceptions import PredicateMismatch
from pyramid.exceptions import HTTPForbidden
from pyramid.request import Request
from pyramid.view import render_view

Expand Down Expand Up @@ -77,7 +78,7 @@ def _render_view_on_slot_event(view_name, event, params):

try:
result = render_view(context, view_request, view_name)
except PredicateMismatch:
except (PredicateMismatch, HTTPForbidden):
return None
else:
return result.decode('utf-8')
Expand Down
24 changes: 12 additions & 12 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
Kotti==1.1.3
Kotti==1.1.4
Babel==1.3
Beaker==1.7.0
Chameleon==2.22
FormEncode==1.3.0
Mako==1.0.1
MarkupSafe==0.23
PasteDeploy==1.5.2
Pillow==2.8.1
Pillow==2.8.2
Pygments==2.0.2
SQLAlchemy==0.9.9
Unidecode==0.04.17
SQLAlchemy==1.0.6
Unidecode==0.04.18
WebOb==1.4.1
alembic==0.7.6
appdirs==1.4.0
Expand All @@ -19,8 +19,8 @@ colander==1.0
deform==2.0a2
docopt==0.6.2
fanstatic==1.0a5
filedepot==0.0.4
html2text==2015.4.14
filedepot==0.0.6
html2text==2015.6.21
html5lib==1.0b6
iso8601==0.1.10
js.angular==1.1.4
Expand Down Expand Up @@ -49,26 +49,26 @@ py-bcrypt==0.4
pyramid==1.5.7
pyramid-beaker==0.8
pyramid-chameleon==0.3
pyramid-debugtoolbar==2.3
pyramid-debugtoolbar==2.4
pyramid-deform==0.2
pyramid-mailer==0.14
pyramid-mailer==0.14.1
pyramid-mako==1.0.2
pyramid-tm==0.11
pyramid-tm==0.12
pyramid-zcml==1.0.0
pytz==2015.2
pytz==2015.4
repoze.lru==0.6
repoze.sendmail==4.2
repoze.workflow==0.6.1
repoze.zcml==1.0b1
shutilwhich==1.1.0
six==1.9.0
transaction==1.4.3
transaction==1.4.4
translationstring==1.3
usersettings==1.0.7
venusian==1.0
waitress==0.8.9
wsgiref==0.1.2
zope.component==4.2.1
zope.component==4.2.2
zope.configuration==4.0.3
zope.deprecation==4.1.2
zope.event==4.0.3
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@
install_requires.append('ordereddict')

setup(name='Kotti',
version='1.1.3',
version='1.1.4',
description="A high-level, Pythonic web application framework based on Pyramid and SQLAlchemy. It includes an extensible Content Management System called the Kotti CMS.", # noqa
long_description='\n\n'.join([README, AUTHORS, CHANGES]),
classifiers=[
Expand Down

0 comments on commit 802364e

Please sign in to comment.