Skip to content

Commit

Permalink
Merge branch 'release/1.3.0-alpha.2' into testing
Browse files Browse the repository at this point in the history
* release/1.3.0-alpha.2:
  Bump version.
  Upgrade to Pyramid 1.6 and Pillow 3.1.0.
  Convert 2 more instance methods to static methds.
  Add missing docstrings, convert instance to static methods where possible.
  Pass a complete copy of os.environ to the subocmmands for installing packages in the scaffold testing virtualenvs.
  - Add custom traverser doing single query traversal. - Use SQLAlchemy's baked query to cache query construction (not execution!) where possible. - Properly configure caching and use pip-accel on Travis CI (build times < 50%). - Bugfix: kotti.events._update_children_paths could fail (at least under Python 2.6 with SQLite) under unclear conditions.
  Fix wrong assertion in test_format_currency that was probably introduced because of a former bug / missing feature in Babel.
  Update requirements.
  Get rid of more browser doctests.  Also fix 2 bugs  found during conversion to webtest:
  Update the copyright year
  Update the year in the footer.
  Upgrade requirements.
  Update SQLAlchemy and alembic requirements.
  Use iterators instead of materializing the list.
  Avoid consecutive if-statements.
  Covert instance and class methods to static methods where possible.
  Fix some issues reported by quantifiedco.de.  Add a .checkignore to skip test analysis in the future.
  Bump version.
  • Loading branch information
disko committed Jan 5, 2016
2 parents 9147d6d + dd50c83 commit c31e2ce
Show file tree
Hide file tree
Showing 50 changed files with 1,521 additions and 1,280 deletions.
2 changes: 2 additions & 0 deletions .checkignore
@@ -0,0 +1,2 @@
kotti/testing.py
kotti/tests/*
14 changes: 9 additions & 5 deletions .travis.yml
@@ -1,6 +1,11 @@
language: python
sudo: false
cache: pip
cache:
directories:
- $HOME/.cache/pip
- $HOME/.pip-accel
before_cache:
- rm -f $HOME/.cache/pip/log/debug.log
python:
- "2.6"
- "2.7"
Expand All @@ -9,13 +14,12 @@ env:
- KOTTI_TEST_DB_STRING=mysql+oursql://root@localhost:3306/kotti_testing
- KOTTI_TEST_DB_STRING=sqlite://
install:
- travis_retry pip install "pip==1.3.1" # fix issue with fanstatic==1.0a
- travis_retry pip install "setuptools>=17.1" # fix issue with mock==1.3.0
- travis_retry pip install -e . -r requirements.txt --use-mirrors
- travis_retry pip install --cache-dir $HOME/.cache/pip -U pip setuptools pip-accel
- travis_retry pip-accel install psycopg2 oursql python-coveralls codecov
- travis_retry pip-accel install -e . -r requirements.txt
- pip uninstall -y Kotti
- python setup.py develop
- python setup.py dev
- travis_retry pip install psycopg2 oursql python-coveralls codecov
before_script:
- psql -c 'create database kotti_testing;' -U postgres
- mysql -e 'create database kotti_testing;'
Expand Down
22 changes: 22 additions & 0 deletions CHANGES.txt
@@ -1,6 +1,28 @@
Change History
==============

1.3.0-alpha.2 - 2016-01-05
--------------------------

**This is a alpha release. Blindly upgrading your production environments will
make the universe collapse!**

- Add a custom traverser, which gets all nodes in a single DB query. For deeply
nested trees this results in drastic perfor,ance improvements. See
http://kotti.readthedocs.org/en/master/api/kotti.traversal.html for details.

- Bugfix: copy and paste of file nodes wouldn't create a new depot file, but
instead lead to multiple references to a single file which would cause
undesired results when one of them was deleted later.

- Bugfix: local 'role:owner' was not set when a new node was created by copy
and paste.

- Bugfix: kotti.events._update_children_paths could fail under unclear
conditions (at least under Python 2.6 with SQLite).

- Get rid of more browser doctests (converted to webtest).

1.3.0-alpha.1 - 2015-12-22
--------------------------

Expand Down
2 changes: 1 addition & 1 deletion COPYRIGHT.txt
@@ -1,2 +1,2 @@
Copyright (c) 2010-2015 Daniel Nouri and Contributors.
Copyright (c) 2010-2016 Daniel Nouri and Contributors.
All Rights Reserved
2 changes: 2 additions & 0 deletions MANIFEST.in
Expand Up @@ -5,3 +5,5 @@ recursive-include kotti/static *
recursive-include kotti/templates *
recursive-include kotti/tests *
recursive-include kotti *.zcml
recursive-exclude * __pycache__
recursive-exclude * *.pyc
1 change: 1 addition & 0 deletions docs/api/index.rst
Expand Up @@ -23,6 +23,7 @@ API Documentation
kotti.sqla
kotti.testing
kotti.tests
kotti.traversal
kotti.util
kotti.views/index
kotti.workflow
Expand Down
8 changes: 8 additions & 0 deletions docs/api/kotti.traversal.rst
@@ -0,0 +1,8 @@
.. _api-kotti.traversal:

kotti.traversal
---------------

.. automodule:: kotti.traversal
:members:
:member-order: bysource
2 changes: 1 addition & 1 deletion docs/conf.py
Expand Up @@ -50,7 +50,7 @@
# -- Options for HTML output ---------------------------------------------------

# on_rtd is whether we are on readthedocs.org
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
on_rtd = os.environ.get('READTHEDOCS') == 'True'

if not on_rtd: # only import and set the theme if we're building docs locally
import sphinx_rtd_theme
Expand Down
7 changes: 7 additions & 0 deletions kotti/__init__.py
Expand Up @@ -63,6 +63,7 @@ def none_factory(**kwargs): # pragma: no cover
]),
'kotti.base_includes': ' '.join([
'kotti',
'kotti.traversal',
'kotti.filedepot',
'kotti.events',
'kotti.sanitizers',
Expand Down Expand Up @@ -251,6 +252,12 @@ def base_configure(global_config, **settings):


def includeme(config):
""" Pyramid includeme hook.
:param config: app config
:type config: :class:`pyramid.config.Configurator`
"""

import kotti.views.util

settings = config.get_settings()
Expand Down
9 changes: 5 additions & 4 deletions kotti/events.py
Expand Up @@ -226,12 +226,13 @@ def set_owner(event):
"""

obj, request = event.object, event.request
if request is not None and isinstance(obj, Node) and obj.owner is None:
if request is not None and isinstance(obj, Node):
userid = request.authenticated_userid
if userid is not None:
userid = unicode(userid)
# Set owner metadata:
obj.owner = userid
if obj.owner is None:
obj.owner = userid
# Add owner role for userid if it's not inherited already:
if u'role:owner' not in list_groups(userid, obj):
groups = list_groups_raw(userid, obj) | set([u'role:owner'])
Expand Down Expand Up @@ -314,7 +315,7 @@ def reset_content_owner(event):
def _update_children_paths(old_parent_path, new_parent_path):
for child in DBSession.query(Node).options(
load_only('path', 'type')).filter(
Node.path.startswith(old_parent_path)):
Node.path.startswith(old_parent_path)).order_by(Node.path):
if child.path == new_parent_path:
# The child is the node itself and has already be renamed.
# Nothing to do!
Expand Down Expand Up @@ -385,7 +386,7 @@ def _all_children(item, _all=None):
def _set_path_for_new_parent(target, value, oldvalue, initiator):
"""Triggered whenever the Node's 'parent' attribute is set.
"""
if value is None:
if value is None or value == oldvalue:
# The parent is about to be set to 'None', so skip.
return

Expand Down
44 changes: 21 additions & 23 deletions kotti/filedepot.py
Expand Up @@ -2,7 +2,6 @@
import mimetypes
import uuid
from datetime import datetime
from time import time

import rfc6266
from depot.fields.sqlalchemy import _SQLAMutationTracker
Expand Down Expand Up @@ -69,6 +68,8 @@ class DBStoredFile(Base):
_cursor = 0
_data = _marker

public_url = None

def __init__(self, file_id, filename=None, content_type=None,
last_modified=None, content_length=None, **kwds):
self.file_id = file_id
Expand Down Expand Up @@ -100,23 +101,27 @@ def read(self, n=-1):

return result

def close(self, *args, **kwargs):
@staticmethod
def close(*args, **kwargs):
"""Implement :meth:`StoredFile.close`.
:class:`DBStoredFile` never closes.
"""
return

def closed(self):
@staticmethod
def closed():
"""Implement :meth:`StoredFile.closed`.
"""
return False

def writable(self):
@staticmethod
def writable():
"""Implement :meth:`StoredFile.writable`.
"""
return False

def seekable(self):
@staticmethod
def seekable():
"""Implement :meth:`StoredFile.seekable`.
"""
return True
Expand Down Expand Up @@ -160,17 +165,6 @@ def name(self):
"""
return self.filename

@property
def public_url(self):
""" Integration with :class:`depot.middleware.DepotMiddleware`
When supported by the storage this will provide the
public url to which the file content can be accessed.
In case this returns ``None`` it means that the file can
only be served by the :class:`depot.middleware.DepotMiddleware` itself.
"""
return None

@classmethod
def __declare_last__(cls):
""" Executed by SQLAlchemy as part of mapper configuration
Expand Down Expand Up @@ -203,7 +197,8 @@ class DBFileStorage(FileStorage):
Uses `kotti.filedepot.DBStoredFile` to store blob data in an SQL database.
"""

def get(self, file_id):
@staticmethod
def get(file_id):
"""Returns the file given by the file_id
:param file_id: the unique id associated to the file
Expand Down Expand Up @@ -311,10 +306,12 @@ def exists(self, file_or_id):
return bool(
DBSession.query(DBStoredFile).filter_by(file_id=file_id).count())

def list(self, *args):
@staticmethod
def list(*args):
raise NotImplementedError("list() method is unimplemented.")

def _get_file_id(self, file_or_id):
@staticmethod
def _get_file_id(file_or_id):
if hasattr(file_or_id, 'file_id'):
return file_or_id.file_id
return file_or_id
Expand Down Expand Up @@ -438,7 +435,8 @@ def __init__(self, f, request, disposition='attachment',
f.filename, disposition=disposition,
filename_compat=unidecode(f.filename))

def _get_type_and_encoding(self, content_encoding, content_type, f):
@staticmethod
def _get_type_and_encoding(content_encoding, content_type, f):
content_type = content_type or getattr(f, 'content_type', None)
if content_type is None:
content_type, content_encoding = \
Expand All @@ -451,9 +449,9 @@ def _get_type_and_encoding(self, content_encoding, content_type, f):
content_type = str(content_type)
return content_encoding, content_type

@classmethod
def generate_etag(self, f):
return '"%s-%s"' % (f.last_modified, f.content_length)
@staticmethod
def generate_etag(f):
return '"{0}-{1}"'.format(f.last_modified, f.content_length)


def uploaded_file_response(self, uploaded_file, disposition='inline'):
Expand Down
6 changes: 3 additions & 3 deletions kotti/message.py
Expand Up @@ -56,9 +56,9 @@ def validate_token(user, token, valid_hrs=24):
seconds = float(token.split(':')[1])
except (IndexError, ValueError):
return False
if token == make_token(user, seconds):
if time.time() - seconds < 60 * 60 * valid_hrs:
return True
if token == make_token(user, seconds) \
and time.time() - seconds < 60 * 60 * valid_hrs:
return True
return False


Expand Down
6 changes: 4 additions & 2 deletions kotti/migrate.py
Expand Up @@ -91,13 +91,15 @@ def run_env(self, fn, **kw):
version_table=self.version_table, **kw):
self.script_dir.run_env()

def _make_config(self, location):
@staticmethod
def _make_config(location):
cfg = Config()
cfg.set_main_option("script_location", location)
cfg.set_main_option("sqlalchemy.url", get_settings()['sqlalchemy.url'])
return cfg

def _make_script_dir(self, alembic_cfg):
@staticmethod
def _make_script_dir(alembic_cfg):
script_dir = ScriptDirectory.from_config(alembic_cfg)
script_dir.__class__ = ScriptDirectoryWithDefaultEnvPy # O_o
return script_dir
Expand Down

0 comments on commit c31e2ce

Please sign in to comment.