mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Update document for the develpers as per current implementation
This commit is contained in:
parent
e27e39a8f3
commit
7f8ebb01d8
@ -14,6 +14,12 @@ ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
|
||||
|
||||
# It is possible that, it fails to build this (if not running from the
|
||||
# development environment).
|
||||
code_snippet:
|
||||
@echo "Generating code-snippet.rst for some of the important classes..."
|
||||
-@python build_code_snippet.py
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@ -36,95 +42,95 @@ help:
|
||||
clean:
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
|
||||
html:
|
||||
html: code_snippet
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
dirhtml: code_snippet
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml:
|
||||
singlehtml: code_snippet
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle:
|
||||
pickle: code_snippet
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
json: code_snippet
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
htmlhelp: code_snippet
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
qthelp: code_snippet
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pgAdminIII.qhcp"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pgAdminIV.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pgAdminIII.qhc"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pgAdminIV.qhc"
|
||||
|
||||
devhelp:
|
||||
devhelp: code_snippet
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/pgAdminIII"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pgAdminIII"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/pgAdminIV"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pgAdminIV"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
epub: code_snippet
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex:
|
||||
latex: code_snippet
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
latexpdf:
|
||||
latexpdf: code_snippet
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
make -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
text:
|
||||
text: code_snippet
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
man:
|
||||
man: code_snippet
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
changes:
|
||||
changes: code_snippet
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
linkcheck: code_snippet
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
doctest: code_snippet
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
||||
|
71
docs/en_US/build_code_snippet.py
Normal file
71
docs/en_US/build_code_snippet.py
Normal file
@ -0,0 +1,71 @@
|
||||
import os
|
||||
import sys
|
||||
import inspect
|
||||
|
||||
root = os.path.realpath(
|
||||
os.path.dirname(
|
||||
os.path.realpath(__file__)
|
||||
) + '{0}..{0}..{0}web'.format(os.sep)
|
||||
)
|
||||
|
||||
if sys.path[0] != root:
|
||||
sys.path.insert(0, root)
|
||||
|
||||
from pgadmin.utils import PgAdminModule
|
||||
|
||||
target = open('code-snippet.rst', 'w')
|
||||
target.truncate()
|
||||
|
||||
target.write("Code Snippet\n")
|
||||
target.write("------------\n\n")
|
||||
target.write("""
|
||||
This document contains code for some of the important classes, listed as
|
||||
below:\n\n""")
|
||||
|
||||
for m in [
|
||||
'PgAdminModule', 'NodeView',
|
||||
'BaseDriver', 'BaseConnection'
|
||||
]:
|
||||
target.write("* {0}_\n".format(m))
|
||||
|
||||
def print_code(outstream, name, module, info=None):
|
||||
l = len(name)
|
||||
|
||||
outstream.write("\n\n.. _{0}:\n\n{0}\n".format(name))
|
||||
|
||||
idx = 0
|
||||
while idx < l:
|
||||
idx += 1
|
||||
outstream.write("*")
|
||||
|
||||
if info:
|
||||
outstream.write("\n\n{0}".format(info))
|
||||
|
||||
outstream.write("\n\n.. code-block:: python\n\n")
|
||||
|
||||
for line in inspect.getsourcelines(module)[0]:
|
||||
if line.strip():
|
||||
outstream.write(" {0}".format(line))
|
||||
else:
|
||||
outstream.write("{0}".format(line))
|
||||
|
||||
|
||||
print_code(
|
||||
target, "PgAdminModule", PgAdminModule,
|
||||
"""
|
||||
PgAdminModule is inherted from Flask.Blueprint module.
|
||||
This module defines a set of methods, properties and attributes, that every module should implement.
|
||||
""")
|
||||
|
||||
from pgadmin.browser.utils import NodeView
|
||||
print_code(
|
||||
target, "NodeView", NodeView,
|
||||
"""
|
||||
NodeView class helps exposing basic REST APIs for different operations used by
|
||||
pgAdmin Browser. The basic idea has been taken from the `Flask's MethodView
|
||||
<http://flask.pocoo.org/docs/0.10/api/#flask.views.MethodView>`_ class. Because
|
||||
- we need a lot more operations (not, just CRUD), we can not use it directly.""")
|
||||
|
||||
from pgadmin.utils.driver.abstract import BaseDriver, BaseConnection
|
||||
print_code(target, "BaseDriver", BaseDriver)
|
||||
print_code(target, "BaseConnection", BaseConnection)
|
@ -9,8 +9,8 @@ a web application, which is written in C++ using the QT framework.
|
||||
Runtime
|
||||
-------
|
||||
|
||||
The runtime is essentially a Python webserver and browser in a box. Found in the
|
||||
**/runtime** directory in the source tree, it is a relatively simple QT
|
||||
The runtime is essentially a Python webserver and browser in a box. Found in the
|
||||
**/runtime** directory in the source tree, it is a relatively simple QT
|
||||
application that is most easily modified using the **QT Creator** application.
|
||||
|
||||
Web Application
|
||||
@ -27,23 +27,23 @@ Configuration
|
||||
The core application configuration is found in **config.py**. This file includes
|
||||
all configurable settings for the application, along with descriptions of their
|
||||
use. It is essential that various settings are configured prior to deployent on
|
||||
a web server; these can be overriden in **config_local.py** to avoid modifying
|
||||
a web server; these can be overriden in **config_local.py** to avoid modifying
|
||||
the main configuration file.
|
||||
|
||||
User Settings
|
||||
*************
|
||||
|
||||
When running in desktop mode, pgAdmin has a single, default user account that is
|
||||
used for the desktop user. When running in server mode, there may be unlimited
|
||||
used for the desktop user. When running in server mode, there may be unlimited
|
||||
users who are required to login prior to using the application. pgAdmin utilised
|
||||
the **Flask-Security** module to manage application security and users, and
|
||||
the **Flask-Security** module to manage application security and users, and
|
||||
provides options for self-service password reset and password changes etc.
|
||||
|
||||
Whether in desktop or server mode, each user's settings are stored in a SQLite
|
||||
database which is also used to store the user accounts. This is initially
|
||||
database which is also used to store the user accounts. This is initially
|
||||
created using the **setup.py** script which will create the database file and
|
||||
schema within it, and add the first user account (with administrative
|
||||
privileges) and a default server group for them. A **settings** table is also
|
||||
schema within it, and add the first user account (with administrative
|
||||
privileges) and a default server group for them. A **settings** table is also
|
||||
used to store user configuration settings in a key-value fashion. Although not
|
||||
required, setting keys (or names) are typically formatted using forward slashes
|
||||
to artificially namespace values, much like the pgAdmin 3 settings files on Linux
|
||||
@ -56,67 +56,131 @@ particularly with regard to desktop vs. server mode.
|
||||
pgAdmin Core
|
||||
************
|
||||
|
||||
The heart of pgAdmin is the **pgadmin** package. This contains the globally
|
||||
The heart of pgAdmin is the **pgadmin** package. This contains the globally
|
||||
available HTML templates used by the Jinja engine, as well as any global static
|
||||
files such as images, Javascript and CSS files that are used in multiple modules.
|
||||
|
||||
The work of the package is handled in it's constructor, **__init__.py**. This
|
||||
is responsible for setting up logging and authentication, dynamically loading
|
||||
is responsible for setting up logging and authentication, dynamically loading
|
||||
other modules, and a few other tasks.
|
||||
|
||||
Modules
|
||||
*******
|
||||
|
||||
Units of functionality are added to pgAdmin through the addition of modules. Theses
|
||||
are Python packages that implement Flask Blueprints, and provide various hook
|
||||
points for other modules to utilise (primarily the default module - the browser).
|
||||
Units of functionality are added to pgAdmin through the addition of modules.
|
||||
Theses are Python object instance of classes, inherits the
|
||||
PgAdminModule class (a Flask Blueprint implementation), found in
|
||||
**web/pgadmin/utils.py**. It provide various hook points for other modules
|
||||
to utilise (primarily the default module - the browser).
|
||||
|
||||
To be recognised as a module, a Python package must be created. This must:
|
||||
|
||||
1) Be placed within the **web/pgadmin/** directory, and
|
||||
2) Contain a Python module called **views**, and
|
||||
3) Contain within the views module, a **blueprint** variable representing the
|
||||
Flask Blueprint
|
||||
|
||||
2) Implements pgadmin.utils.PgAdminModule class
|
||||
3) An instance variable (generally - named **blueprint**) representing that
|
||||
particular class in that package.
|
||||
|
||||
Each module may define a **template** and **static** directory for the Blueprint
|
||||
that it implements. To avoid name collisions, templates should be stored under
|
||||
a directory within the specified template directory, named after the module itself.
|
||||
For example, the **browser** module stores it's templates in
|
||||
For example, the **browser** module stores it's templates in
|
||||
**web/pgadmin/browser/templates/browser/**. This does not apply to static files
|
||||
which may omit the second module name.
|
||||
|
||||
In addition to defining the Blueprint, the **views** module is typically
|
||||
responsible for defining all the views that will be rendered in response to
|
||||
client requests. These must include appropriate route and security decorators.
|
||||
In addition to defining the Blueprint, the **views** module is typically
|
||||
responsible for defining all the views that will be rendered in response to
|
||||
client requests, we must provide a REST API url(s) for these views. These must
|
||||
include appropriate route and security decorators. Take a look at the NodeView
|
||||
class, which uses the same approach as Flask's MethodView, it can be found in
|
||||
**web/pgadmin/browser/utils.py**. This specific class is used by browser nodes
|
||||
for creating REST API url(s) for different operation on them. i.e. list, create,
|
||||
update, delete, fetch children, get
|
||||
statistics/reversed SQL/dependencies/dependents list for that node, etc. We can
|
||||
use the same class for other purpose too. You just need to inherit that class,
|
||||
and overload the member variables operations, parent_ids, ids, node_type, and
|
||||
then register it as node view with PgAdminModule instance.
|
||||
|
||||
Most pgAdmin modules will also implement a **hooks** Python module. This is
|
||||
responsible for providing hook points to integrate the module into the rest of
|
||||
the application - for example, a hook might tell the caller what CSS files need
|
||||
to be included on the rendered page, or what menu options to include and what
|
||||
they should do. Hook points need not exist if they are not required. It is the
|
||||
responsiblity of the caller to ensure they are present before attempting to
|
||||
utilise them.
|
||||
Most pgAdmin modules will also implement the **hooks** provided by the
|
||||
PgAdminModule class. This is responsible for providing hook points to integrate
|
||||
the module into the rest of the application - for example, a hook might tell
|
||||
the caller what CSS files need to be included on the rendered page, or what menu
|
||||
options to include and what they should do. Hook points need not exist if they
|
||||
are not required. It is the responsiblity of the caller to ensure they are
|
||||
present before attempting to utilise them.
|
||||
|
||||
Hooks currently implemented are:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def register_submodules(app):
|
||||
"""Register any child module or node blueprints"""
|
||||
|
||||
def get_file_menu_items():
|
||||
def get_edit_menu_items():
|
||||
def get_tools_menu_items():
|
||||
def get_management_menu_items():
|
||||
def get_help_menu_items():
|
||||
"""Return a (set) of dicts of menu items, with name, priority, URL, target and onclick code."""
|
||||
|
||||
def get_scripts():
|
||||
"""Return a list of script URLs to include in the rendered page header"""
|
||||
class MyModule(PgAdminModule):
|
||||
"""
|
||||
This is class implements the pgadmin.utils.PgAdminModule, and
|
||||
implements the hooks
|
||||
"""
|
||||
|
||||
...
|
||||
|
||||
def get_own_stylesheets(self):
|
||||
"""
|
||||
Returns:
|
||||
list: the stylesheets used by this module, not including any
|
||||
stylesheet needed by the submodules.
|
||||
"""
|
||||
return [url_for('static', 'css/mymodule.css')]
|
||||
|
||||
def get_own_javascripts(self):
|
||||
"""
|
||||
Returns:
|
||||
list of dict:
|
||||
- contains the name (representation for this javascript
|
||||
module), path (url for it without .js suffix), deps (array of
|
||||
dependents), exports window object by the javascript module,
|
||||
and when (would you like to load this javascript), etc
|
||||
information for this module, not including any script needed
|
||||
by submodules.
|
||||
"""
|
||||
return [
|
||||
{
|
||||
'name': 'pgadmin.extension.mymodule',
|
||||
'path': url_for('static', filename='js/mymodule'),
|
||||
'exports': None,
|
||||
'when': 'server'
|
||||
}
|
||||
]
|
||||
|
||||
def get_own_menuitems(self):
|
||||
"""
|
||||
Returns:
|
||||
dict: the menuitems for this module, not including
|
||||
any needed from the submodules.
|
||||
"""
|
||||
return {
|
||||
'help_items': [
|
||||
MenuItem(
|
||||
name='mnu_mymodule_help',
|
||||
priority=999,
|
||||
# We need to create javascript, which registers itself
|
||||
# as module
|
||||
module="pgAdmin.MyModule",
|
||||
callback='about_show',
|
||||
icon='fa fa-info-circle',
|
||||
label=gettext('About MyModule'
|
||||
)
|
||||
]
|
||||
}
|
||||
def get_panels(self):
|
||||
"""
|
||||
Returns:
|
||||
list: a list of panel objects to add implemented in javascript
|
||||
module
|
||||
"""
|
||||
return []
|
||||
...
|
||||
|
||||
|
||||
|
||||
blueprint = MyModule('mymodule', __name__, static_url_path='/static')
|
||||
|
||||
def get_stylesheets():
|
||||
"""Return a list of stylesheet URLs to include in the rendered page header"""
|
||||
|
||||
pgAdmin Modules may include any additional Python modules that are required to
|
||||
fulfill their purpose, as required. They may also reference other dynamically
|
||||
loaded modules, but must use the defined hook points and fail gracefully in the
|
||||
@ -125,40 +189,24 @@ event that a particular module is not present.
|
||||
Nodes
|
||||
*****
|
||||
|
||||
Nodes are very similar to modules, but implement individual nodes on the browser
|
||||
treeview. To be recognised as a module, a Python package must be created. This
|
||||
must:
|
||||
Nodes are very similar to modules, it represents an individual node or,
|
||||
collection object on the browser treeview. To recognised as a node module, a
|
||||
Python package (along with javascript modules) must be created. This must:
|
||||
|
||||
1) Be placed within the **web/pgadmin/browser/** directory, and
|
||||
2) Contain a Python module called **views**, and
|
||||
3) Contain within the views module, a **blueprint** variable representing the
|
||||
Flask Blueprint
|
||||
|
||||
The hook points currently defined for nodes are:
|
||||
2) Implements the BrowserPluginModule, and registers the node view, which
|
||||
exposes required the REST APIs
|
||||
3) An instance of the class object
|
||||
|
||||
.. code-block:: python
|
||||
Front End
|
||||
*********
|
||||
|
||||
def register_submodules(app):
|
||||
"""Register any child node blueprints"""
|
||||
|
||||
def get_file_menu_items():
|
||||
"""Return a (set) of dicts of menu items, with name, priority, URL, target and onclick code."""
|
||||
|
||||
def get_context_menu_items():
|
||||
"""Return a (set) of dicts of content menu items with name, label, priority and JS"""
|
||||
|
||||
def get_create_menu_items():
|
||||
"""Return a (set) of dicts of create menu items, with a Javascript array of
|
||||
object types on which the option should appear, name, label. priority and
|
||||
the function name (no parens) to call on click."""
|
||||
|
||||
def get_standard_menu_items():
|
||||
"""Return a (set) of dicts of standard menu items (drop/rename), with
|
||||
object type, action, priority and the function name (no parens) to call
|
||||
on click."""
|
||||
|
||||
def get_script_snippets():
|
||||
"""Return the script snippets needed to handle treeview node operations."""
|
||||
|
||||
def get_css_snippets():
|
||||
"""Return the CSS needed to display the treeview node image."""
|
||||
pgAdmin uses javascript extensively for the front-end implementation. It uses
|
||||
require.js to allow the lazy loading (or, say load only when required),
|
||||
bootstrap for UI look and feel, Backbone for data manipulation of a node,
|
||||
Backform for generating properties/create dialog for selected node. We have
|
||||
divided each module in small chunks as much as possible. Not all javascript
|
||||
modules are required to be loaded (i.e. loading a javascript module for
|
||||
database will make sense only when a server node is loaded competely.) Please
|
||||
look at the the javascript files node.js, browser.js, menu.js, panel.js, etc for
|
||||
better understanding of the code.
|
||||
|
484
docs/en_US/code-snippet.rst
Normal file
484
docs/en_US/code-snippet.rst
Normal file
@ -0,0 +1,484 @@
|
||||
Code Snippet
|
||||
------------
|
||||
|
||||
|
||||
This document contains code for some of the important classes, listed as
|
||||
below:
|
||||
|
||||
* PgAdminModule_
|
||||
* NodeView_
|
||||
* BaseDriver_
|
||||
* BaseConnection_
|
||||
|
||||
|
||||
.. _PgAdminModule:
|
||||
|
||||
PgAdminModule
|
||||
*************
|
||||
|
||||
|
||||
PgAdminModule is inherted from Flask.Blueprint module.
|
||||
This module defines a set of methods, properties and attributes, that every module should implement.
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class PgAdminModule(Blueprint):
|
||||
"""
|
||||
Base class for every PgAdmin Module.
|
||||
|
||||
This class defines a set of method and attributes that
|
||||
every module should implement.
|
||||
"""
|
||||
|
||||
def __init__(self, name, import_name, **kwargs):
|
||||
kwargs.setdefault('url_prefix', '/' + name)
|
||||
kwargs.setdefault('template_folder', 'templates')
|
||||
kwargs.setdefault('static_folder', 'static')
|
||||
self.submodules = []
|
||||
super(PgAdminModule, self).__init__(name, import_name, **kwargs)
|
||||
|
||||
def register(self, app, options, first_registration=False):
|
||||
"""
|
||||
Override the default register function to automagically register
|
||||
sub-modules at once.
|
||||
"""
|
||||
if first_registration:
|
||||
self.submodules = list(app.find_submodules(self.import_name))
|
||||
super(PgAdminModule, self).register(app, options, first_registration)
|
||||
for module in self.submodules:
|
||||
app.register_blueprint(module)
|
||||
|
||||
def get_own_stylesheets(self):
|
||||
"""
|
||||
Returns:
|
||||
list: the stylesheets used by this module, not including any
|
||||
stylesheet needed by the submodules.
|
||||
"""
|
||||
return []
|
||||
|
||||
def get_own_javascripts(self):
|
||||
"""
|
||||
Returns:
|
||||
list: the javascripts used by this module, not including
|
||||
any script needed by the submodules.
|
||||
"""
|
||||
return []
|
||||
|
||||
def get_own_menuitems(self):
|
||||
"""
|
||||
Returns:
|
||||
dict: the menuitems for this module, not including
|
||||
any needed from the submodules.
|
||||
"""
|
||||
return defaultdict(list)
|
||||
|
||||
def get_panels(self):
|
||||
"""
|
||||
Returns:
|
||||
list: a list of panel objects to add
|
||||
"""
|
||||
return []
|
||||
|
||||
@property
|
||||
def stylesheets(self):
|
||||
stylesheets = self.get_own_stylesheets()
|
||||
for module in self.submodules:
|
||||
stylesheets.extend(module.stylesheets)
|
||||
return stylesheets
|
||||
|
||||
@property
|
||||
def javascripts(self):
|
||||
javascripts = self.get_own_javascripts()
|
||||
for module in self.submodules:
|
||||
javascripts.extend(module.javascripts)
|
||||
return javascripts
|
||||
|
||||
@property
|
||||
def menu_items(self):
|
||||
menu_items = self.get_own_menuitems()
|
||||
for module in self.submodules:
|
||||
for key, value in module.menu_items.items():
|
||||
menu_items[key].extend(value)
|
||||
menu_items = {key: sorted(values, key=attrgetter('priority'))
|
||||
for key, values in menu_items.items()}
|
||||
return menu_items
|
||||
|
||||
|
||||
.. _NodeView:
|
||||
|
||||
NodeView
|
||||
********
|
||||
|
||||
|
||||
NodeView class helps exposing basic REST APIs for different operations used by
|
||||
pgAdmin Browser. The basic idea has been taken from the `Flask's MethodView
|
||||
<http://flask.pocoo.org/docs/0.10/api/#flask.views.MethodView>`_ class. Because
|
||||
- we need a lot more operations (not, just CRUD), we can not use it directly.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class NodeView(with_metaclass(MethodViewType, View)):
|
||||
"""
|
||||
A PostgreSQL Object has so many operaions/functions apart from CRUD
|
||||
(Create, Read, Update, Delete):
|
||||
i.e.
|
||||
- Reversed Engineered SQL
|
||||
- Modified Query for parameter while editing object attributes
|
||||
i.e. ALTER TABLE ...
|
||||
- Statistics of the objects
|
||||
- List of dependents
|
||||
- List of dependencies
|
||||
- Listing of the children object types for the certain node
|
||||
It will used by the browser tree to get the children nodes
|
||||
|
||||
This class can be inherited to achieve the diffrent routes for each of the
|
||||
object types/collections.
|
||||
|
||||
OPERATION | URL | Method
|
||||
---------------+------------------------+--------
|
||||
List | /obj/[Parent URL]/ | GET
|
||||
Properties | /obj/[Parent URL]/id | GET
|
||||
Create | /obj/[Parent URL]/ | POST
|
||||
Delete | /obj/[Parent URL]/id | DELETE
|
||||
Update | /obj/[Parent URL]/id | PUT
|
||||
|
||||
SQL (Reversed | /sql/[Parent URL]/id | GET
|
||||
Engineering) |
|
||||
SQL (Modified | /sql/[Parent URL]/id | POST
|
||||
Properties) |
|
||||
|
||||
Statistics | /stats/[Parent URL]/id | GET
|
||||
Dependencies | /deps/[Parent URL]/id | GET
|
||||
Dependents | /deps/[Parent URL]/id | POST
|
||||
|
||||
Children Nodes | /nodes/[Parent URL]/id | GET
|
||||
|
||||
NOTE:
|
||||
Parent URL can be seen as the path to identify the particular node.
|
||||
|
||||
i.e.
|
||||
In order to identify the TABLE object, we need server -> database -> schema
|
||||
information.
|
||||
"""
|
||||
operations = dict({
|
||||
'obj': [
|
||||
{'get': 'properties', 'delete': 'delete', 'put': 'update'},
|
||||
{'get': 'list', 'post': 'create'}
|
||||
],
|
||||
'nodes': [{'get': 'nodes'}],
|
||||
'sql': [{'get': 'sql', 'post': 'modified_sql'}],
|
||||
'stats': [{'get': 'statistics'}],
|
||||
'deps': [{'get': 'dependencies', 'post': 'dependents'}],
|
||||
'module.js': [{}, {}, {'get': 'module_js'}]
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def generate_ops(cls):
|
||||
cmds = []
|
||||
for op in cls.operations:
|
||||
idx = 0
|
||||
for ops in cls.operations[op]:
|
||||
meths = []
|
||||
for meth in ops:
|
||||
meths.append(meth.upper())
|
||||
if len(meths) > 0:
|
||||
cmds.append({
|
||||
'cmd': op, 'req': (idx == 0),
|
||||
'with_id': (idx != 2), 'methods': meths
|
||||
})
|
||||
idx += 1
|
||||
|
||||
return cmds
|
||||
|
||||
# Inherited class needs to modify these parameters
|
||||
node_type = None
|
||||
# This must be an array object with attributes (type and id)
|
||||
parent_ids = []
|
||||
# This must be an array object with attributes (type and id)
|
||||
ids = []
|
||||
|
||||
@classmethod
|
||||
def get_node_urls(cls):
|
||||
assert cls.node_type is not None, \
|
||||
"Please set the node_type for this class ({0})".format(
|
||||
str(cls.__class__.__name__))
|
||||
common_url = '/'
|
||||
for p in cls.parent_ids:
|
||||
common_url += '<{0}:{1}>/'.format(str(p['type']), str(p['id']))
|
||||
|
||||
id_url = None
|
||||
for p in cls.ids:
|
||||
id_url = '{0}<{1}:{2}>'.format(common_url if not id_url else id_url,
|
||||
p['type'], p['id'])
|
||||
|
||||
return id_url, common_url
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.cmd = kwargs['cmd']
|
||||
|
||||
# Check the existance of all the required arguments from parent_ids
|
||||
# and return combination of has parent arguments, and has id arguments
|
||||
def check_args(self, **kwargs):
|
||||
has_id = has_args = True
|
||||
for p in self.parent_ids:
|
||||
if p['id'] not in kwargs:
|
||||
has_args = False
|
||||
break
|
||||
|
||||
for p in self.ids:
|
||||
if p['id'] not in kwargs:
|
||||
has_id = False
|
||||
break
|
||||
|
||||
return has_args, has_id and has_args
|
||||
|
||||
def dispatch_request(self, *args, **kwargs):
|
||||
meth = flask.request.method.lower()
|
||||
if meth == 'head':
|
||||
meth = 'get'
|
||||
|
||||
assert self.cmd in self.operations, \
|
||||
"Unimplemented Command ({0}) for {1}".format(
|
||||
self.cmd,
|
||||
str(self.__class__.__name__)
|
||||
)
|
||||
|
||||
has_args, has_id = self.check_args(**kwargs)
|
||||
|
||||
assert (self.cmd in self.operations and
|
||||
(has_id and len(self.operations[self.cmd]) > 0 and
|
||||
meth in self.operations[self.cmd][0]) or
|
||||
(not has_id and len(self.operations[self.cmd]) > 1 and
|
||||
meth in self.operations[self.cmd][1]) or
|
||||
(len(self.operations[self.cmd]) > 2 and
|
||||
meth in self.operations[self.cmd][2])), \
|
||||
"Unimplemented method ({0}) for command ({1}), which {2} an id".format(
|
||||
meth, self.cmd,
|
||||
'requires' if has_id else 'does not require'
|
||||
)
|
||||
|
||||
meth = self.operations[self.cmd][0][meth] if has_id else \
|
||||
self.operations[self.cmd][1][meth] if has_args and \
|
||||
meth in self.operations[self.cmd][1] else \
|
||||
self.operations[self.cmd][2][meth]
|
||||
|
||||
method = getattr(self, meth, None)
|
||||
|
||||
if method is None:
|
||||
return make_json_response(
|
||||
status=406,
|
||||
success=0,
|
||||
errormsg=gettext(
|
||||
"Unimplemented method ({0}) for this url ({1})".format(
|
||||
meth, flask.request.path)
|
||||
)
|
||||
)
|
||||
|
||||
return method(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def register_node_view(cls, blueprint):
|
||||
cls.blueprint = blueprint
|
||||
id_url, url = cls.get_node_urls()
|
||||
|
||||
commands = cls.generate_ops()
|
||||
|
||||
for c in commands:
|
||||
if c['with_id']:
|
||||
blueprint.add_url_rule(
|
||||
'/{0}{1}'.format(
|
||||
c['cmd'], id_url if c['req'] else url
|
||||
),
|
||||
view_func=cls.as_view(
|
||||
'{0}{1}'.format(
|
||||
c['cmd'], '_id' if c['req'] else ''
|
||||
),
|
||||
cmd=c['cmd']
|
||||
),
|
||||
methods=c['methods']
|
||||
)
|
||||
else:
|
||||
blueprint.add_url_rule(
|
||||
'/{0}'.format(c['cmd']),
|
||||
view_func=cls.as_view(
|
||||
'{0}'.format(c['cmd']), cmd=c['cmd']
|
||||
),
|
||||
methods=c['methods']
|
||||
)
|
||||
|
||||
def module_js(self, **kwargs):
|
||||
"""
|
||||
This property defines (if javascript) exists for this node.
|
||||
Override this property for your own logic.
|
||||
"""
|
||||
return flask.make_response(
|
||||
flask.render_template(
|
||||
"{0}/{0}.js".format(self.node_type)
|
||||
),
|
||||
200, {'Content-Type': 'application/x-javascript'}
|
||||
)
|
||||
|
||||
def nodes(self, *args, **kwargs):
|
||||
"""Build a list of treeview nodes from the child nodes."""
|
||||
nodes = []
|
||||
|
||||
for module in self.blueprint.submodules:
|
||||
nodes.extend(module.get_nodes(*args, **kwargs))
|
||||
|
||||
return make_json_response(data=nodes)
|
||||
|
||||
|
||||
.. _BaseDriver:
|
||||
|
||||
BaseDriver
|
||||
**********
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class BaseDriver(object):
|
||||
"""
|
||||
class BaseDriver(object):
|
||||
|
||||
This is a base class for different server types.
|
||||
Inherit this class to implement different type of database driver
|
||||
implementation.
|
||||
|
||||
(For PostgreSQL/Postgres Plus Advanced Server, we will be using psycopg2)
|
||||
|
||||
Abstract Properties:
|
||||
-------- ----------
|
||||
* Version (string):
|
||||
Current version string for the database server
|
||||
|
||||
Abstract Methods:
|
||||
-------- -------
|
||||
* get_connection(*args, **kwargs)
|
||||
- It should return a Connection class object, which may/may not be
|
||||
connected to the database server.
|
||||
|
||||
* release_connection(*args, **kwargs)
|
||||
- Implement the connection release logic
|
||||
|
||||
* gc()
|
||||
- Implement this function to release the connections assigned in the
|
||||
session, which has not been pinged from more than the idle timeout
|
||||
configuration.
|
||||
"""
|
||||
__metaclass__ = DriverRegistry
|
||||
|
||||
@abstractproperty
|
||||
def Version(cls):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_connection(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def release_connection(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def gc(self):
|
||||
pass
|
||||
|
||||
|
||||
.. _BaseConnection:
|
||||
|
||||
BaseConnection
|
||||
**************
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class BaseConnection(object):
|
||||
"""
|
||||
class BaseConnection(object)
|
||||
|
||||
It is a base class for database connection. A different connection
|
||||
drive must implement this to expose abstract methods for this server.
|
||||
|
||||
General idea is to create a wrapper around the actaul driver
|
||||
implementation. It will be instantiated by the driver factory
|
||||
basically. And, they should not be instantiated directly.
|
||||
|
||||
|
||||
Abstract Methods:
|
||||
-------- -------
|
||||
* connect(**kwargs)
|
||||
- Define this method to connect the server using that particular driver
|
||||
implementation.
|
||||
|
||||
* execute_scalar(query, params)
|
||||
- Implement this method to execute the given query and returns single
|
||||
datum result.
|
||||
|
||||
* execute_2darray(query, params)
|
||||
- Implement this method to execute the given query and returns the result
|
||||
as a 2 dimentional array.
|
||||
|
||||
* execute_dict(query, params)
|
||||
- Implement this method to execute the given query and returns the result
|
||||
as an array of dict (column name -> value) format.
|
||||
|
||||
* connected()
|
||||
- Implement this method to get the status of the connection. It should
|
||||
return True for connected, otherwise False
|
||||
|
||||
* reset()
|
||||
- Implement this method to reconnect the database server (if possible)
|
||||
|
||||
* transaction_status()
|
||||
- Implement this method to get the transaction status for this
|
||||
connection. Range of return values different for each driver type.
|
||||
|
||||
* ping()
|
||||
- Implement this method to ping the server. There are times, a connection
|
||||
has been lost, but - the connection driver does not know about it. This
|
||||
can be helpful to figure out the actual reason for query failure.
|
||||
|
||||
* _release()
|
||||
- Implement this method to release the connection object. This should not
|
||||
be directly called using the connection object itself.
|
||||
|
||||
NOTE: Please use BaseDriver.release_connection(...) for releasing the
|
||||
connection object for better memory management, and connection pool
|
||||
management.
|
||||
"""
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
@abstractmethod
|
||||
def connect(self, **kwargs):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def execute_scalar(self, query, params=None):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def execute_2darray(self, query, params=None):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def execute_dict(self, query, params=None):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def connected(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def reset(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def transaction_status(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def ping(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _release(self):
|
||||
pass
|
@ -91,7 +91,7 @@ pygments_style = 'sphinx'
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'default'
|
||||
html_theme = 'classic'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
|
@ -74,6 +74,7 @@ learn how pgAdmin works, and how to develop improvements and new features.
|
||||
|
||||
coding-standards
|
||||
code-overview
|
||||
code-snippet
|
||||
submitting-patches
|
||||
translations
|
||||
|
||||
@ -94,4 +95,4 @@ pgAdmin is released under the
|
||||
liberal Open Source licence similar to BSD or MIT, and approved by the Open
|
||||
Source Initiative. The copyright for the project source code, website and
|
||||
documentation is attributed to the
|
||||
`pgAdmin Development Team <http://www.pgadmin.org/development/team.php>`_.
|
||||
`pgAdmin Development Team <http://www.pgadmin.org/development/team.php>`_.
|
||||
|
Loading…
Reference in New Issue
Block a user