Skip to content
This repository has been archived by the owner on Apr 23, 2018. It is now read-only.

Commit

Permalink
Merge branch 'master' of github.com:Kozea/pynuts
Browse files Browse the repository at this point in the history
  • Loading branch information
liZe committed Aug 26, 2014
2 parents ad45ed4 + 11b78a8 commit eba9295
Show file tree
Hide file tree
Showing 17 changed files with 164 additions and 100 deletions.
14 changes: 14 additions & 0 deletions docs/Changelog.rst
Expand Up @@ -3,6 +3,20 @@ Pynuts changelog

Here you can see the full list of changes between each Pynuts release.

Version 0.4.3
-------------

* Flask 0.10 compatibility
* API BREAK: Document update_content urls are now of the form '/_pynuts/resource/{{ document_class.__name__ }}/update_content' instead of '/_pynuts/update_content' to avoid conflicts


Version 0.4
-----------
* Simplification of crud functions
* Handle permissions on links with auth_url_for with request/link params in acl rights functions
* Rewrite of the action_url_for


Version 0.3
-----------
* The ``pynuts.Pynuts`` class no longer inherits from ``flask.Flask``.
Expand Down
5 changes: 3 additions & 2 deletions docs/Tutorial.rst
Expand Up @@ -29,7 +29,7 @@ First, import the ``Pynuts``, ``Flask`` and ``SQLAlchemy`` classes by calling::
Then, specify the Flask app configuration::

CONFIG = {
'CSRF_ENABLED': False,
'WTF_CSRF_ENABLED': False,
'SQLALCHEMY_DATABASE_URI': 'sqlite:////tmp/test.db'}
.. note::
Expand Down Expand Up @@ -111,7 +111,8 @@ It is used for representing the columns from the Employee class into HTML field

.. sourcecode:: python

from flask.ext.wtf import TextField, Required, IntegerField
from wtforms import TextField, IntegerField
from wtforms.validators import Required

from pynuts.view import BaseForm

Expand Down
2 changes: 1 addition & 1 deletion docs/example/advanced/config.py
@@ -1,2 +1,2 @@
CSRF_ENABLED = 'False'
WTF_CSRF_ENABLED = 'False'
LOL = 'lol'
8 changes: 4 additions & 4 deletions docs/example/advanced/view.py
@@ -1,7 +1,7 @@
from flask.ext.wtf import (TextField, IntegerField,
PasswordField, Required, QuerySelectField,
QuerySelectMultipleField)

from wtforms import TextField, IntegerField, PasswordField
from wtforms.ext.sqlalchemy.fields import (
QuerySelectField, QuerySelectMultipleField)
from wtforms.validators import Required
from pynuts.view import BaseForm

import database
Expand Down
2 changes: 1 addition & 1 deletion docs/example/complete/config.py
@@ -1,2 +1,2 @@
CSRF_ENABLED = 'False'
WTF_CSRF_ENABLED = 'False'
LOL = 'lol'
17 changes: 10 additions & 7 deletions docs/example/complete/view.py
@@ -1,7 +1,8 @@
from flask.ext.wtf import (TextField, IntegerField,
PasswordField, Required, QuerySelectField,
QuerySelectMultipleField, BooleanField)

from wtforms import (
TextField, IntegerField, PasswordField, BooleanField)
from wtforms.ext.sqlalchemy.fields import (
QuerySelectField, QuerySelectMultipleField)
from wtforms.validators import Required
import database
from application import nuts
from pynuts.fields import UploadField, ImageField
Expand All @@ -20,7 +21,7 @@ class EmployeeView(nuts.ModelView):
read_columns = ('person_id', 'name', 'firstname', 'fullname', 'company',
'resume', 'photo', 'driving_license')
update_columns = ('name', 'firstname', 'company', 'driving_license',
'resume', 'photo', 'order')
'resume', 'photo', 'order')

class Form(BaseForm):
person_id = IntegerField('ID')
Expand All @@ -30,10 +31,12 @@ class Form(BaseForm):
firstname = TextField(u'Firstname', validators=[Required()])
fullname = TextField('Employee name')
driving_license = BooleanField('Driving license')
resume = UploadField(label='resume',
resume = UploadField(
label='resume',
upload_set=UPLOAD_SETS['resumes'],
validators=[AllowedFile(), MaxSize(1)])
photo = ImageField(label='photo',
photo = ImageField(
label='photo',
upload_set=UPLOAD_SETS['images'],
validators=[AllowedFile(), MaxSize(1)])
company = QuerySelectField(
Expand Down
5 changes: 3 additions & 2 deletions docs/example/quickstart/employee.py
@@ -1,5 +1,6 @@
from flask import Flask
from flask.ext.wtf import TextField, IntegerField, Required
from wtforms import TextField, IntegerField
from wtforms.validators import Required
from flask_sqlalchemy import SQLAlchemy

from pynuts import Pynuts
Expand All @@ -8,7 +9,7 @@

# The application
CONFIG = {
'CSRF_ENABLED': False,
'WTF_CSRF_ENABLED': False,
'SQLALCHEMY_DATABASE_URI': 'sqlite:////tmp/test.db'}

app = Flask(__name__)
Expand Down
2 changes: 1 addition & 1 deletion docs/example/simple/application.py
Expand Up @@ -3,7 +3,7 @@
from pynuts import Pynuts

CONFIG = {
'CSRF_ENABLED': False,
'WTF_CSRF_ENABLED': False,
'SQLALCHEMY_DATABASE_URI': 'sqlite:////tmp/test.db'}

app = Flask(__name__)
Expand Down
4 changes: 2 additions & 2 deletions docs/example/simple/view.py
@@ -1,5 +1,5 @@
from flask.ext.wtf import TextField, Required, IntegerField

from wtforms import TextField, IntegerField
from wtforms.validators import Required
from pynuts.view import BaseForm

import database
Expand Down
27 changes: 25 additions & 2 deletions pynuts/__init__.py
@@ -1,8 +1,9 @@
"""__init__ file for Pynuts."""

__version__ = '0.4'
__version__ = '0.4.3.1'

import os
import sys
import flask
from werkzeug.utils import cached_property
from flask.ext.uploads import configure_uploads, patch_request_class
Expand Down Expand Up @@ -30,7 +31,7 @@ def __init__(self, app, authed_url_for=False):
# Pynuts default config
# Can be overwritten by setting
# these parameters in the application config
self.app.config.setdefault('CSRF_ENABLED', False)
self.app.config.setdefault('WTF_CSRF_ENABLED', False)
self.app.config.setdefault('UPLOADS_DEFAULT_DEST',
os.path.join(app.instance_path, 'uploads'))
self.app.config.setdefault('PYNUTS_DOCUMENT_REPOSITORY',
Expand All @@ -43,6 +44,10 @@ def __init__(self, app, authed_url_for=False):
# at the /_pynuts/static/<path:filename> URL
self.app.add_url_rule('/_pynuts/static/<path:filename>',
'_pynuts-static', static)
self.app.add_url_rule(
'/_pynuts/update_content', '_pynuts-update_content',
lambda: document.update_content(self),
methods=('POST',))

class Document(document.Document):
"""Document base class of the application."""
Expand Down Expand Up @@ -147,3 +152,21 @@ def static(filename):
"""
return flask.send_from_directory(
os.path.join(os.path.dirname(__file__), 'static'), filename)


def install_secret_key(app, filename='secret_key'):
"""Configure the SECRET_KEY from a file in the instance directory.
If the file does not exist, print instructions to create it from a shell
with a random key, then exit.
"""
filename = os.path.join(app.instance_path, filename)
try:
app.config['SECRET_KEY'] = open(filename, 'rb').read()
except IOError:
print 'Error: No secret key. Create it with:'
if not os.path.isdir(os.path.dirname(filename)):
print 'mkdir -p', os.path.dirname(filename)
print 'head -c 24 /dev/urandom >', filename
sys.exit(1)
129 changes: 69 additions & 60 deletions pynuts/document.py
Expand Up @@ -23,26 +23,27 @@ class InvalidId(ValueError):

class MetaDocument(type):
"""Metaclass for document classes."""
def __init__(mcs, name, bases, dict_):
if mcs.document_id_template:
def __init__(cls, name, bases, dict_):
if cls.document_id_template:
# TODO: find a better endpoint name than the name of the class
if not mcs.type_name:
mcs.type_name = mcs.__name__
mcs._pynuts.documents[mcs.type_name] = mcs
mcs._app.add_url_rule(
if not cls.type_name:
cls.type_name = cls.__name__
cls._pynuts.documents[cls.type_name] = cls
cls._app.add_url_rule(
'/_pynuts/resource/%s/<document_id>/'
'<version>/<path:filename>' % (
mcs.type_name),
endpoint='_pynuts-resource/' + mcs.type_name,
view_func=mcs.static_route)
mcs._app.add_url_rule(
'/_pynuts/update_content', '_pynuts-update_content',
mcs.update_content, methods=('POST',))
if mcs.model_path and not os.path.isabs(mcs.model_path):
mcs.model_path = os.path.join(
mcs._app.root_path, mcs.model_path)
mcs.document_id_template = unicode(mcs.document_id_template)
super(MetaDocument, mcs).__init__(name, bases, dict_)
'<version>/<path:filename>' % cls.type_name,
'_pynuts_resource_%s' % cls.type_name,
cls.static_route)
cls._app.add_url_rule(
'/_pynuts/resource/%s/update_content' % cls.type_name,
'_pynuts_resource_%s_update_content' % cls.type_name,
cls.update_content, methods=('POST',))

if cls.model_path and not os.path.isabs(cls.model_path):
cls.model_path = os.path.join(
cls._app.root_path, cls.model_path)
cls.document_id_template = unicode(cls.document_id_template)
super(MetaDocument, cls).__init__(name, bases, dict_)


class Document(object):
Expand Down Expand Up @@ -172,7 +173,7 @@ def from_data(cls, version=None, **kwargs):
def resource_url(self, filename):
"""Resource URL for the application."""
return url_for(
'_pynuts-resource/' + self.type_name,
'_pynuts_resource_%s' % self.type_name,
document_id=self.document_id,
filename=filename,
version=self.version)
Expand Down Expand Up @@ -207,7 +208,8 @@ def _generate_rest(self, part='index.rst.jinja2', archive=False,
:param part: part of the document to render
:param archive: return archive content if `True`
:param editable: if you use the 'Editable' pynuts'
ReST directive and if you need to render html with 'contenteditable="false"',
ReST directive and if you need to render html with
'contenteditable="false"',
set this parameter to 'False'. For more info see :ref:`api`
"""
part = 'index.rst' if archive else part
Expand All @@ -226,7 +228,7 @@ def generate_html(cls, part='index.rst.jinja2', archive=False,
part=part, archive=archive, editable=editable)

def _generate_html(self, part='index.rst.jinja2', archive=False,
editable=True):
editable=True):
"""Generate the HTML samples of the document.
The output is a dict corresponding to the different HTML samples as
Expand All @@ -235,7 +237,8 @@ def _generate_html(self, part='index.rst.jinja2', archive=False,
:param part: part of the document to render
:param archive: return archive content if `True`
:param editable: if you use the 'Editable' pynuts'
ReST directive and if you need to render html with 'contenteditable="false"',
ReST directive and if you need to render html with
'contenteditable="false"',
set this parameter to 'False'. For more info see :ref:`api`
.. seealso::
Expand Down Expand Up @@ -323,42 +326,7 @@ def archive(cls, part='index.rst.jinja2', version=None,

@classmethod
def update_content(cls):
"""Update the ReST document.
It is used by the javascript/AJAX save function.
It gets the request as JSON and update all the parts of the document
return document's information as JSON.
'See the save function<>_' for more details.
"""

contents = request.json['data']
author_name = request.json['author']
author_email = request.json['author_email']
message = request.json['message']

documents = {}
for values in contents:
key = (values['document_type'], values['document_id'])
if key in documents:
document = documents[key]
else:
cls = cls._pynuts.documents[values['document_type']]
document = cls(values['document_id'], values['version'])
documents[key] = document
document.git.write(values['part'],
values['content'].encode('utf-8'))
for document in documents.values():
document.git.commit(
author_name or 'Pynuts',
author_email or 'pynut@pynuts.org',
message or 'Edit %s' % document.document_id)
return jsonify(documents=[{
'document_type': document.type_name,
'document_id': document.document_id,
'version': document.version}
for document in documents.values()])
return update_content(cls._pynuts)

@classmethod
def create(cls, author_name=None, author_email=None, message=None,
Expand Down Expand Up @@ -452,7 +420,8 @@ def html(cls, template, part='index.rst.jinja2', version=None,
:param version: version of the document to render
:param archive: return archive content if `True`
:param editable: if you use the 'Editable' pynuts'
ReST directive and if you need to render html with 'contenteditable="false"',
ReST directive and if you need to render html with
'contenteditable="false"',
set this parameter to 'False'. For more info see :ref:`api`
"""
Expand All @@ -472,7 +441,8 @@ def view_html(cls, part='index.rst.jinja2', version=None, archive=False,
:param archive: set it to 'True' if you render an archive
:param html_part: the docutils publish part to render
:param editable: if you use the 'Editable' pynuts'
ReST directive and if you need to render html with 'contenteditable="false"',
ReST directive and if you need to render html with
'contenteditable="false"',
set this parameter to 'False'. For more info see :ref:`api`
"""
Expand Down Expand Up @@ -529,3 +499,42 @@ def write(self, content, author_name=None, author_email=None,
raise ConflictError
else:
return True


def update_content(pynuts):
"""Update the ReST document.
It is used by the javascript/AJAX save function.
It gets the request as JSON and update all the parts of the document
return document's information as JSON.
'See the save function<>_' for more details.
"""

contents = request.json['data']
author_name = request.json['author']
author_email = request.json['author_email']
message = request.json['message']

documents = {}
for values in contents:
key = (values['document_type'], values['document_id'])
if key in documents:
document = documents[key]
else:
cls = pynuts.documents[values['document_type']]
document = cls(values['document_id'], values['version'])
documents[key] = document
document.git.write(values['part'],
values['content'].encode('utf-8'))
for document in documents.values():
document.git.commit(
author_name or 'Pynuts',
author_email or 'pynut@pynuts.org',
message or 'Edit %s' % document.document_id)
return jsonify(documents=[{
'document_type': document.type_name,
'document_id': document.document_id,
'version': document.version}
for document in documents.values()])
2 changes: 1 addition & 1 deletion pynuts/fields.py
Expand Up @@ -2,7 +2,7 @@

from wtforms import StringField
from wtforms.widgets import html_params, HTMLString
from flask.ext.wtf import FileField
from flask_wtf.file import FileField


class Editable(object):
Expand Down

0 comments on commit eba9295

Please sign in to comment.