Skip to content

Commit

Permalink
Merge branch 'master' into 3196-common-url_for
Browse files Browse the repository at this point in the history
  • Loading branch information
amercader committed Sep 15, 2016
2 parents 23f9c53 + d73326a commit 63693ae
Show file tree
Hide file tree
Showing 7 changed files with 785 additions and 150 deletions.
2 changes: 1 addition & 1 deletion ckan/i18n/zh_CN/LC_MESSAGES/ckan.po
Expand Up @@ -959,7 +959,7 @@ msgstr[0] "{years}年前"

#: ckan/lib/formatters.py:146
msgid "{month} {day}, {year}, {hour:02}:{min:02} ({timezone})"
msgstr "{} {日},{年},{时}:{:02}({时区})"
msgstr "{month} {day}, {year}, {hour:02}:{min:02} ({timezone})"

#: ckan/lib/formatters.py:151
msgid "{month} {day}, {year}"
Expand Down
746 changes: 669 additions & 77 deletions ckan/public/base/vendor/moment-with-locales.js

Large diffs are not rendered by default.

20 changes: 6 additions & 14 deletions ckanext/reclineview/plugin.py
Expand Up @@ -58,6 +58,7 @@ class ReclineViewBase(p.SingletonPlugin):
'''
p.implements(p.IConfigurer, inherit=True)
p.implements(p.IResourceView, inherit=True)
p.implements(p.ITemplateHelpers, inherit=True)

def update_config(self, config):
'''
Expand All @@ -80,14 +81,17 @@ def setup_template_variables(self, context, data_dict):
def view_template(self, context, data_dict):
return 'recline_view.html'

def get_helpers(self):
return {
'get_map_config': get_mapview_config
}


class ReclineView(ReclineViewBase):
'''
This extension views resources using a Recline MultiView.
'''

p.implements(p.ITemplateHelpers, inherit=True)

def info(self):
return {'name': 'recline_view',
'title': 'Data Explorer',
Expand All @@ -109,11 +113,6 @@ def can_view(self, data_dict):
else:
return False

def get_helpers(self):
return {
'get_map_config': get_mapview_config
}


class ReclineGridView(ReclineViewBase):
'''
Expand Down Expand Up @@ -190,8 +189,6 @@ class ReclineMapView(ReclineViewBase):
This extension views resources using a Recline map.
'''

p.implements(p.ITemplateHelpers, inherit=True)

map_field_types = [{'value': 'lat_long',
'text': 'Latitude / Longitude fields'},
{'value': 'geojson', 'text': 'GeoJSON'}]
Expand Down Expand Up @@ -252,8 +249,3 @@ def setup_template_variables(self, context, data_dict):

def form_template(self, context, data_dict):
return 'recline_map_form.html'

def get_helpers(self):
return {
'get_mapview_config': get_mapview_config
}
2 changes: 1 addition & 1 deletion ckanext/reclineview/theme/templates/recline_view.html
Expand Up @@ -2,7 +2,7 @@

{% block page %}

{% set map_config = h.get_map_config() or h.get_mapview_config() %}
{% set map_config = h.get_map_config() %}
<div data-module="recline_view"
data-module-site_url="{{ h.dump_json(h.url('/', locale='default', qualified=true)) }}"
data-module-resource = "{{ h.dump_json(resource_json) }}";
Expand Down
161 changes: 104 additions & 57 deletions doc/contributing/architecture.rst
Expand Up @@ -6,30 +6,82 @@ This section documents our CKAN-specific coding standards, which are guidelines
for writing code that is consistent with the intended design and architecture
of CKAN.

.. image:: /images/architecture.png
:alt: CKAN architecture diagram

----------------------------------------
Encapsulate SQLAlchemy in ``ckan.model``
----------------------------------------

Ideally SQLAlchemy should only be used within ``ckan.model`` and not from other
packages such as ``ckan.logic``. For example instead of using an SQLAlchemy
query from the logic package to retrieve a particular user from the database,
we add a ``get()`` method to ``ckan.model.user.User``::
------
Routes
------

@classmethod
def get(cls, user_id):
query = ...
.
.
.
return query.first()
Routes define the connection between CKAN URLs and views that process
requests and provide responses.

Now we can call this method from the logic package.
Default routes are defined in ``ckan.config.routing`` and extended with the
:class:`ckan.plugins.interfaces.IRoutes` plugin interface.

.. FIXME: talk about flask
-----
Views
-----

Views process requests by reading and updating data with action
function and return a response by rendering Jinja2 templates.
CKAN views are defined in ``ckan.controllers`` and templates in
``ckan.templates``.

Views and templates may use ``logic.check_access`` or
:func:`ckan.lib.helpers.check_access` to hide links or render
helpful errors but action functions, not views, are responsible for
actually enforcing permissions checking.

Plugins define new views by adding or updating routes. For adding
templates or helper functions from a plugin see
:doc:`../theming/index` and
:ref:`custom template helper functions`.


Template helper functions
#########################

Template helper functions are used for code that is reused frequently or
code that is too complicated to be included in the templates themselves.

Template helpers should never perform expensive queries or update data.

``ckan.lib.helpers`` contains helper functions that can be used from
``ckan.controllers`` or from templates. When developing for ckan core, only use
the helper functions found in ``ckan.lib.helpers.__allowed_functions__``.


Always go through the action functions
######################################

Whenever some code, for example in ``ckan.lib`` or ``ckan.controllers``, wants
to get, create, update or delete an object from CKAN's model it should do so by
calling a function from the ``ckan.logic.action`` package, and *not* by
accessing ``ckan.model`` directly.


Use ``get_action()``
####################

Don't call ``logic.action`` functions directly, instead use ``get_action()``.
This allows plugins to override action functions using the ``IActions`` plugin
interface. For example::

ckan.logic.get_action('group_activity_list')(...)

Instead of ::

ckan.logic.action.get.group_activity_list(...)

Views and templates may check authorization to avoid rendering

-----------------------------------
Don't pass ORM objects to templates
-----------------------------------
###################################

Don't pass SQLAlchemy ORM objects (e.g. :py:class:`ckan.model.User` objects)
to templates (for example by adding them to :py:data:`c`, passing them to
Expand All @@ -39,25 +91,27 @@ from template helper functions, etc.)
Using ORM objects in the templates often creates SQLAlchemy "detached instance"
errors that cause 500 Server Errors and can be difficult to debug.

Instead, ORM objects should be dictized and their dictionary forms should be
passed to templates. Controllers can dictize ORM objects using the funtions in
:py:mod:`ckan.lib.dictization`, but they should probably just get dictionaries
from :py:mod:`ckan.logic.action` functions instead.

-----
Logic
-----

--------------------------------------
Always go through the action functions
--------------------------------------
Logic includes action functions, auth functions, background tasks
and business logic.

Whenever some code, for example in ``ckan.lib`` or ``ckan.controllers``, wants
to get, create, update or delete an object from CKAN's model it should do so by
calling a function from the ``ckan.logic.action`` package, and *not* by
accessing ``ckan.model`` directly.
Action functions have a uniform interface accepting a dictionary of simple
strings lists, dictionaries or files (wrapped in a ``cgi.FieldStorage``
objects). They return simple dictionaries or raise one of a small number of
exceptions including ``ckan.logic.NotAuthorized``, ``ckan.logic.NotFound``
and ``ckan.logic.ValidationError``.

Plugins override action functions with the
:class:`ckan.plugins.interfaces.IActions` interface and auth functions
with the :class:`ckan.plugins.interfaces.IAuthFunctions` interface.


---------------------------------------
Action functions are exposed in the API
---------------------------------------
#######################################

The functions in ``ckan.logic.action`` are exposed to the world as the
:doc:`/api/index`. The API URL for an action function is automatically generated
Expand Down Expand Up @@ -92,24 +146,8 @@ care should be taken to:
_get_or_bust = logic.get_or_bust


--------------------
Use ``get_action()``
--------------------

Don't call ``logic.action`` functions directly, instead use ``get_action()``.
This allows plugins to override action functions using the ``IActions`` plugin
interface. For example::

ckan.logic.get_action('group_activity_list_html')(...)

Instead of ::

ckan.logic.action.get.group_activity_list_html(...)


-------------------------------------
Auth functions and ``check_access()``
-------------------------------------
#####################################

Each action function defined in ``ckan.logic.action`` should use its own
corresponding auth function defined in ``ckan.logic.auth``. Instead of calling
Expand All @@ -127,9 +165,8 @@ an authorization error in their browser (or will receive one in their response
from the API).


-----------------------
``logic.get_or_bust()``
-----------------------
#######################

The ``data_dict`` parameter of logic action functions may be user provided, so
required files may be invalid or absent. Naive Code like::
Expand All @@ -146,9 +183,8 @@ which will raise ``ValidationError`` if ``"id"`` is not in ``data_dict``. The
response and an error message explaining the problem.


------------------------------------
Validation and ``ckan.logic.schema``
------------------------------------
####################################

Logic action functions can use schema defined in ``ckan.logic.schema`` to
validate the contents of the ``data_dict`` parameters that users pass to them.
Expand All @@ -166,13 +202,24 @@ is the validation code from the ``user_create()`` action function::
raise ValidationError(errors)


--------------------------------------
Controller & template helper functions
--------------------------------------
------
Models
------

``ckan.lib.helpers`` contains helper functions that can be used from
``ckan.controllers`` or from templates. When developing for ckan core, only use
the helper functions found in ``ckan.lib.helpers.__allowed_functions__``.
Ideally SQLAlchemy should only be used within ``ckan.model`` and not from other
packages such as ``ckan.logic``. For example instead of using an SQLAlchemy
query from the logic package to retrieve a particular user from the database,
we add a ``get()`` method to ``ckan.model.user.User``::

@classmethod
def get(cls, user_id):
query = ...
.
.
.
return query.first()

Now we can call this method from the logic package.


-----------
Expand Down
Binary file added doc/images/architecture.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions doc/images/architecture.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 63693ae

Please sign in to comment.