1
0
Fork 0
mirror of https://github.com/yweber/lodel2.git synced 2025-12-28 03:36:55 +01:00

Merge branch 'newlodel' of git.labocleo.org:lodel2 into newlodel

This commit is contained in:
m.orban 2016-07-06 14:10:42 +02:00
commit 0ffbe0845a
52 changed files with 735 additions and 163 deletions

View file

@ -20,3 +20,7 @@ Instance operations :
make dyncode # Leapi dynamic code creation ( in leapi_dyncode.py in lodel2 instance root dir)
make init_db # Call migration handlers to tell them to init all needed databases. (note : this target has dyncode as dependencie)
make list_hooks # List all the hooks registered
Instance loader uppdate :
If the install/loader.py is updated you can update instance's loader.py using
scripts/create_instance.sh -u INSTANCE_PATH

View file

@ -87,6 +87,7 @@ person.new_field( 'firstname',
'fre': 'Prénom',
},
data_handler = 'varchar',
group = base_group,
)
person.new_field( 'lastname',
display_name = {
@ -94,6 +95,7 @@ person.new_field( 'lastname',
'fre': 'Nom de famille',
},
data_handler = 'varchar',
group = base_group,
)
person.new_field( 'fullname',
display_name = {
@ -115,6 +117,7 @@ person.new_field( 'alias',
allowed_classes = [person],
default = None,
nullable = True,
group = base_group,
)
@ -324,14 +327,16 @@ index_name = index_abstract.new_field(
display_name = {
'eng': 'name',
'fre': 'nom'},
data_handler = 'varchar')
data_handler = 'varchar',
group = index_group)
index_value = index_abstract.new_field(
'value',
display_name = {
'eng': 'value',
'fre': 'valeur'},
data_handler = 'varchar')
data_handler = 'varchar',
group = index_group)
text.new_field( 'indexes',
display_name = {
@ -341,7 +346,8 @@ text.new_field( 'indexes',
back_reference = ('Indexabs', 'texts'),
allowed_classes = [index_abstract],
default = None,
nullable = True)
nullable = True,
group = index_group)
index_abstract.new_field( 'texts',
display_name = {
@ -349,7 +355,8 @@ index_abstract.new_field( 'texts',
'fre': 'Texte contenant cette index'},
data_handler = 'list',
back_reference = ('Text', 'indexes'),
allowed_classes = [text])
allowed_classes = [text],
group = index_group)
index_theme = em.new_class(
'indexTheme',
@ -364,7 +371,8 @@ index_theme_theme = index_abstract.new_field(
'theme',
display_name = {
'eng': 'theme'},
data_handler = 'varchar')
data_handler = 'varchar',
group = index_group)
#em.save('xmlfile', filename = 'examples/em_test.xml')
pickle_file = 'examples/em_test.pickle'

Binary file not shown.

View file

@ -10,7 +10,7 @@ filename = -
context = True
[lodel2.editorialmodel]
groups =
groups = base_group, editorial_abstract, editorial_person
emfile = editorial_model.pickle
dyncode = leapi_dyncode.py

View file

@ -14,7 +14,6 @@ except ImportError:
print("Unable to load lodel module. exiting...")
exit(1)
#
# Loading settings
#
@ -29,16 +28,19 @@ from lodel.plugin import core_hooks
def start():
#Load plugins
from lodel import logger
from lodel.plugin import Plugin
logger.debug("Loader.start() called")
Plugin.load_all()
LodelHook.call_hook('lodel2_bootstraped', '__main__', None)
if __name__ == '__main__':
start()
start()
if Settings.runtest:
start()
import unittest
import tests
loader = unittest.TestLoader()
@ -51,7 +53,11 @@ if __name__ == '__main__':
runner.run(suite)
exit()
import lodel
import leapi_dyncode as dyncode
lodel.dyncode = dyncode
LodelHook.call_hook('lodel2_loader_main', '__main__', None)
#Run interative python
import code
print("""

View file

@ -1,5 +1,7 @@
#-*- coding: utf-8 -*-
dyncode = None
##@page lodel2_start Lodel2 boot mechanism
#
# @par Lodel2 boot sequence

View file

@ -7,6 +7,7 @@ import hashlib
from lodel.utils.mlstring import MlString
from lodel.settings import Settings
from lodel.editorial_model.exceptions import *
from lodel.leapi.leobject import CLASS_ID_FIELDNAME
@ -84,6 +85,12 @@ class EmClass(EmComponent):
##@brief Stores EmFields instances indexed by field uid
self.__fields = dict()
self.group = group
if group is None:
warnings.warn("NO GROUP FOR EMCLASS %s" % uid)
else:
group.add_components([self])
#Adding common field
if not self.abstract:
self.new_field(
@ -95,8 +102,9 @@ class EmClass(EmComponent):
'eng': "Allow to create instance of the good class when\
fetching arbitrary datas from DB"},
data_handler = 'LeobjectSubclassIdentifier',
internal = True)
internal = True,
group = group)
##@brief Property that represent a dict of all fields (the EmField defined in this class and all its parents)
# @todo use Settings.editorialmodel.groups to determine wich fields should be returned
@property
@ -136,6 +144,16 @@ class EmClass(EmComponent):
return list(fields.values()) if uid is None else fields[uid]
except KeyError:
raise EditorialModelError("No such EmField '%s'" % uid)
##@brief Keep in __fields only fields contained in active groups
def _set_active_fields(self, active_groups):
if not Settings.editorialmodel.editormode:
active_fields = []
for grp_name, agrp in active_groups.items():
active_fields += [ emc for emc in agrp.components()
if isinstance(emc, EmField)]
self.__fields = { fname:fdh for fname, fdh in self.__fields.items()
if fdh in active_fields }
##@brief Add a field to the EmClass
# @param emfield EmField : an EmField instance
@ -143,6 +161,7 @@ class EmClass(EmComponent):
# @throw EditorialModelException if an EmField with same uid allready in this EmClass (overwritting allowed from parents)
# @todo End the override checks (needs methods in data_handlers)
def add_field(self, emfield):
assert_edit()
if emfield.uid in self.__fields:
raise EditorialModelError("Duplicated uid '%s' for EmField in this class ( %s )" % (emfield.uid, self))
# Incomplete field override check
@ -151,7 +170,6 @@ class EmClass(EmComponent):
if not emfield.data_handler_instance.can_override(parent_field.data_handler_instance):
raise AttributeError("'%s' field override a parent field, but data_handles are not compatible" % emfield.uid)
self.__fields[emfield.uid] = emfield
emfield._emclass = self
return emfield
##@brief Create a new EmField and add it to the EmClass
@ -159,7 +177,8 @@ class EmClass(EmComponent):
# @param uid str : the EmField uniq id
# @param **field_kwargs : EmField constructor parameters ( see @ref EmField.__init__() )
def new_field(self, uid, data_handler, **field_kwargs):
return self.add_field(EmField(uid, data_handler, **field_kwargs))
assert_edit()
return self.add_field(EmField(uid, data_handler, self, **field_kwargs))
def d_hash(self):
m = hashlib.md5()
@ -196,7 +215,7 @@ class EmField(EmComponent):
# @param help_text MlString|str|dict : help text
# @param group EmGroup :
# @param **handler_kwargs : data handler arguments
def __init__(self, uid, data_handler, display_name = None, help_text = None, group = None, **handler_kwargs):
def __init__(self, uid, data_handler, em_class = None, display_name = None, help_text = None, group = None, **handler_kwargs):
from lodel.leapi.datahandlers.base_classes import DataHandler
super().__init__(uid, display_name, help_text, group)
##@brief The data handler name
@ -208,7 +227,13 @@ class EmField(EmComponent):
##@brief Stores data handler instanciation options
self.data_handler_options = handler_kwargs
##@brief Stores the emclass that contains this field (set by EmClass.add_field() method)
self._emclass = None
self._emclass = em_class
if self._emclass is None:
warnings.warn("No EmClass for field %s" %uid)
if group is None:
warnings.warn("No EmGroup for field %s" % uid)
else:
group.add_components([self])
##@brief Returns data_handler_name attribute
def get_data_handler_name(self):
@ -315,10 +340,13 @@ class EmGroup(object):
##@brief Add components in a group
# @param components list : EmComponent instances list
def add_components(self, components):
assert_edit()
for component in components:
if isinstance(component, EmField):
if component._emclass is None:
warnings.warn("Adding an orphan EmField to an EmGroup")
msg = "Adding an orphan EmField '%s' to EmGroup '%s'"
msg %= (component, self)
warnings.warn(msg)
elif not isinstance(component, EmClass):
raise EditorialModelError("Expecting components to be a list of EmComponent, but %s found in the list" % type(component))
self.__components |= set(components)
@ -326,6 +354,7 @@ class EmGroup(object):
##@brief Add a dependencie
# @param em_group EmGroup|iterable : an EmGroup instance or list of instance
def add_dependencie(self, grp):
assert_edit()
try:
for group in grp:
self.add_dependencie(group)
@ -343,6 +372,7 @@ class EmGroup(object):
# @param em_group EmGroup|iterable : an EmGroup instance or list of instance
# Useless ???
def add_applicant(self, grp):
assert_edit()
try:
for group in grp:
self.add_applicant(group)

View file

@ -3,3 +3,12 @@
class EditorialModelError(Exception):
pass
def assert_edit():
try:
from lodel import Settings
except ImportError: #Very dirty, but don't know how to fix the tests
return
if not Settings.editorialmodel.editormode:
raise EditorialModelError("EM is readonly : editormode is OFF")

View file

@ -5,7 +5,7 @@ import importlib
import copy
from lodel.utils.mlstring import MlString
from lodel.logger import logger
from lodel import logger
from lodel.settings import Settings
from lodel.settings.utils import SettingsError
@ -128,19 +128,28 @@ class EditorialModel(object):
#EditorialModel.__active_classes attibutes
def __set_actives(self):
if Settings.editorialmodel.editormode:
logger.warning("All EM groups active because editormode in ON")
# all groups & classes actives because we are in editor mode
self.__active_groups = self.__groups
self.__active_classes = self.__classes
else:
#determine groups first
self.__active_groups = dict()
self.__active_classes = dict()
for agrp in Settings.editorialmodel.groups:
if agrp not in self.__groups:
raise SettingsError('Invalid group found in settings : %s' % agrp)
logger.debug("Set group '%s' as active" % agrp)
grp = self.__groups[agrp]
self.__active_groups[grp.uid] = grp
for acls in grp.components():
for acls in [cls for cls in grp.components() if isinstance(cls, EmClass)]:
self.__active_classes[acls.uid] = acls
if len(self.__active_groups) == 0:
raise RuntimeError("No groups activated, abording...")
if len(self.__active_classes) == 0:
raise RuntimeError("No active class found. Abording")
for clsname, acls in self.__active_classes.items():
acls._set_active_fields(self.__active_groups)
##@brief EmField getter
# @param uid str : An EmField uid represented by "CLASSUID.FIELDUID"
@ -167,7 +176,7 @@ class EditorialModel(object):
# @param emclass EmClass : the EmClass instance to add
# @return emclass
def add_class(self, emclass):
self.raise_if_ro()
assert_edit()
if not isinstance(emclass, EmClass):
raise ValueError("<class EmClass> expected but got %s " % type(emclass))
if emclass.uid in self.classes():
@ -179,7 +188,7 @@ class EditorialModel(object):
# @param emgroup EmGroup : the EmGroup instance to add
# @return emgroup
def add_group(self, emgroup):
self.raise_if_ro()
assert_edit()
if not isinstance(emgroup, EmGroup):
raise ValueError("<class EmGroup> expected but got %s" % type(emgroup))
if emgroup.uid in self.groups():
@ -192,7 +201,7 @@ class EditorialModel(object):
#@param **kwargs : EmClass constructor options (
# see @ref lodel.editorial_model.component.EmClass.__init__() )
def new_class(self, uid, **kwargs):
self.raise_if_ro()
assert_edit()
return self.add_class(EmClass(uid, **kwargs))
##@brief Add a new EmGroup to the editorial model
@ -200,14 +209,14 @@ class EditorialModel(object):
#@param *kwargs : EmGroup constructor keywords arguments (
# see @ref lodel.editorial_model.component.EmGroup.__init__() )
def new_group(self, uid, **kwargs):
self.raise_if_ro()
assert_edit()
return self.add_group(EmGroup(uid, **kwargs))
##@brief Save a model
# @param translator module : The translator module to use
# @param **translator_args
def save(self, translator, **translator_kwargs):
self.raise_if_ro()
assert_edit()
if isinstance(translator, str):
translator = self.translator_from_name(translator)
return translator.save(self, **translator_kwargs)

View file

@ -316,7 +316,7 @@ def load_class_xml(model, elem):
fields = elem.find('fields')
for field in fields:
emfield = load_field_xml(model, field)
emfield = load_field_xml(model, field, emclass)
l_emfields = emclass.fields()
incls = False
for emf in l_emfields:
@ -329,10 +329,11 @@ def load_class_xml(model, elem):
return emclass
##@brief Creates a EmField from a xml description
# @param elem : the element which represents the EmField
# @param model : the model which will contain the new field
# @return a new EmField object
def load_field_xml(model, elem):
#@param elem : the element which represents the EmField
#@param model : the model which will contain the new field
#@param emclass EmClass : the EmClass of the field
#@return a new EmField object
def load_field_xml(model, elem, emclass):
uid = elem.find('uid').text
if elem.find('display_name').text is None:
name = None
@ -354,16 +355,13 @@ def load_field_xml(model, elem):
group = None
dhdl = elem.find('datahandler_name')
dhdl_opts = {}
if dhdl.text is not None:
dhdl_opts = elem.find('datahandler_options')
if dhdl_opts is not None:
dhdl_options = load_dhdl_options_xml(model, dhdl_opts)
emfield = EmField(uid, dhdl.text, name, help_text, group, **dhdl_options)
else:
emfield = EmField(uid, dhdl.text, name, help_text, group)
else:
emfield = EmField(uid, dhdl.text, name, help_text, group)
emfield = EmField(
uid, dhdl.text, emclass, name, help_text, group, **dhdl_options)
return emfield

View file

@ -110,8 +110,7 @@ class DataHandler(object):
return data_handler.default
elif data_handler is not None and data_handler.nullable:
return None
return RuntimeError("Unable to construct data for field %s", fname)
return cur_value
##@brief Check datas consistency
# @param emcomponent EmComponent : An EmComponent child class instance
@ -184,6 +183,7 @@ class DataField(DataHandler):
# References are fields that stores a reference to another
# editorial object
class Reference(DataHandler):
base_type="ref"
##@brief Instanciation
# @param allowed_classes list | None : list of allowed em classes if None no restriction
@ -249,19 +249,22 @@ class SingleRef(Reference):
##@brief This class represent a data_handler for multiple references to another object
#
# The fields using this data handlers are like SingleRef but can store multiple references in one field
# @note SQL implementation could be tricky
# @note for the moment split on ',' chars
class MultipleRef(Reference):
##
# @param max_item int | None : indicate the maximum number of item referenced by this field, None mean no limit
def __init__(self, max_item = None, **kwargs):
self.max_item = max_item
super().__init__(**kwargs)
def _check_data_value(self, value):
value = value.split(',')
if self.max_item is not None:
if self.max_item < len(value):
return None, FieldValidationError("To many items")
return value, None
## @brief Class designed to handle datas access will fieldtypes are constructing datas
#

View file

@ -72,9 +72,6 @@ class UniqID(Integer):
kwargs['internal'] = 'automatic'
super(self.__class__, self).__init__(primary_key = True, **kwargs)
def _check_data_value(self, value):
return value, None
def construct_data(self, emcomponent, fname, datas, cur_value):
if cur_value is None:
#Ask datasource to provide a new uniqID
@ -114,3 +111,6 @@ class Concat(FormatString):
super().__init__(
format_string = format_string, field_list = field_list, **kwargs)
class Password(Varchar):
help = 'Handle passwords'
pass

View file

@ -20,10 +20,9 @@ class List(MultipleRef):
# @param value *
# @return tuple(value, exception)
def _check_data_value(self, value):
val, expt = super()._check_data_value()
val, expt = super()._check_data_value(value)
if not isinstance(expt, Exception):
val = list(val)
val, expt = super()._check_data_value(value.values())
return val, expt
@ -41,10 +40,9 @@ class Set(MultipleRef):
# @param value *
# @return tuple(value, exception)
def _check_data_value(self, value):
val, expt = super()._check_data_value()
val, expt = super()._check_data_value(value)
if not isinstance(expt, Exception):
val = set(val)
val, expt = super()._check_data_value(value.values())
val = tuple(set(val))
return val, expt
@ -62,9 +60,9 @@ class Map(MultipleRef):
# @param value *
# @return tuple(value, exception)
def _check_data_value(self, value):
val, expt = super()._check_data_value(value)
if not isinstance(value, dict):
return None, FieldValidationError("Values for dict fields should be dict")
val, expt = super()._check_data_value(value.values())
return (
None if isinstance(expt, Exception) else value,
expt)

View file

@ -4,6 +4,7 @@ import functools
#from lodel.editorial_model.components import *
from lodel.leapi.leobject import LeObject
from lodel.leapi.datahandlers.base_classes import DataHandler
from lodel import logger
##@brief Generate python module code from a given model
# @param model lodel.editorial_model.model.EditorialModel
@ -112,6 +113,7 @@ def generate_classes(model):
bootstrap = ""
# Generating field list for LeObjects generated from EmClass
for em_class in get_classes(model):
logger.info("Generating a dynamic class for %s" % em_class.uid)
uid = list() # List of fieldnames that are part of the EmClass primary key
parents = list() # List of parents EmClass
# Determine pk

View file

@ -180,8 +180,11 @@ class LeObject(object):
cls.__name__))
##@return A dict with fieldname as key and datahandler as instance
@classmethod
def fields(cls):
return copy.copy(cls._fields)
def fields(cls, include_ro = False):
if include_ro:
return copy.copy(cls._fields)
else:
return {fname:cls._fields[fname] for fname in cls._fields if not cls._fields[fname].is_internal()}
##@brief Return the list of parents classes
#
@ -223,7 +226,7 @@ class LeObject(object):
uid_handlers = set( cls._fields[name] for name in cls._uid )
for pcls in cls.hierarch()[1:]:
puid_handlers = set(cls._fields[name] for name in pcls._uid)
if set(pcls._uid) != set(pcls._uid) \
if set(pcls._uid) != set(prev._uid) \
or puid_handlers != uid_handlers:
break
prev = pcls
@ -242,14 +245,24 @@ class LeObject(object):
ro_ds, rw_ds = cls._datasource_name
#Read only datasource initialisation
cls._ro_datasource = cls._init_datasource(ro_ds, True)
log_msg = "Read only datasource %s initialized for LeObject %s"
log_msg %= (ro_ds, cls.__name__)
logger.debug(log_msg)
if cls._ro_datasource is None:
log_msg = "No read only datasource set for LeObject %s"
log_msg %= cls.__name__
logger.debug(log_msg)
else:
log_msg = "Read only datasource '%s' initialized for LeObject %s"
log_msg %= (ro_ds, cls.__name__)
logger.debug(log_msg)
#Read write datasource initialisation
cls._rw_datasource = cls._init_datasource(rw_ds, False)
log_msg = "Read&write only datasource %s initialized for LeObject %s"
log_msg %= (rw_ds, cls.__name__)
logger.debug(log_msg)
if cls._ro_datasource is None:
log_msg = "No read/write datasource set for LeObject %s"
log_msg %= cls.__name__
logger.debug(log_msg)
else:
log_msg = "Read/write datasource '%s' initialized for LeObject %s"
log_msg %= (ro_ds, cls.__name__)
logger.debug(log_msg)
##@brief Replace the _datasource attribute value by a datasource instance
@ -586,7 +599,6 @@ construction and consitency when datas are not complete\n")
query_filter = list()
for uid in uids:
query_filter.append((uid, '=', self.data(uid)))
try:
query = LeUpdateQuery(self.__class__, query_filter)
except Exception as err:
@ -648,9 +660,7 @@ construction and consitency when datas are not complete\n")
#@return a list of items (lists of (fieldname, fieldvalue))
@classmethod
def get(cls, query_filters, field_list=None, order=None, group=None, limit=None, offset=0):
if field_list is None:
field_list = cls.fieldnames(True)
else:
if field_list is not None:
for uid in [ uidname
for uidname in cls.uid_fieldname()
if uidname not in field_list ]:

View file

@ -188,7 +188,9 @@ class LeFilteredQuery(LeQuery):
other_ds_filters[cur_ds].append(
((rfield, ref_dict), op, value))
#deduplication of std filters
filters_orig = list(set(filters_orig))
if not isinstance(filters_orig, set):
filters_orig = set(filters_orig)
filters_orig = list(filters_orig)
# Sets _query_filter attribute of self query
self._query_filter = (filters_orig, result_rel_filters)
@ -290,6 +292,9 @@ field name" % fieldname)
err_l[field] = ret
continue
field_datahandler = self._target_class.field(field)
if isinstance(field_datahandler, Exception):
err_l[field] = error
continue
if ref_field is not None and not field_datahandler.is_reference():
# inconsistency
err_l[field] = NameError( "The field '%s' in %s is not \
@ -330,10 +335,12 @@ field to use for the relational filter"
else:
rel_filters.append((ret, operator, value))
else:
# Casting value given datahandler
value, error = field_datahandler._check_data_value(value)
res_filters.append((field,operator, value))
if len(err_l) > 0:
raise LeApiDataCheckError(
raise LeApiDataCheckErrors(
"Error while preparing filters : ",
err_l)
return (res_filters, rel_filters)
@ -517,7 +524,7 @@ target to LeUpdateQuery constructor"
if target_class.initialized:
self.__leobject_instance_datas = target.datas(True)
else:
query_filters = [(target._uid[0], '=', str(target.uid()))]
query_filters = [(target._uid[0], '=', target.uid())]
super().__init__(target_class, query_filters)
@ -552,7 +559,7 @@ target to LeUpdateQuery constructor"
res_data.update(datas)
res_datas = self._target_class.prepare_datas(
res_data, True, True)
filters = [(uid_name, '=', str(res_data[uid_name]))]
filters = [(uid_name, '=', res_data[uid_name])]
res = self._rw_datasource.update(
self._target_class, filters, [],
res_datas)
@ -658,17 +665,18 @@ class LeGetQuery(LeFilteredQuery):
# @throw LeApiQueryError if unknown field given
def set_field_list(self, field_list):
err_l = dict()
for fieldname in field_list:
ret = self._check_field(self._target_class, fieldname)
if isinstance(ret, Exception):
msg = "No field named '%s' in %s"
msg %= (fieldname, self._target_class.__name__)
expt = NameError(msg)
err_l[fieldname] = expt
if len(err_l) > 0:
msg = "Error while setting field_list in a get query"
raise LeApiQueryErrors(msg = msg, exceptions = err_l)
self._field_list = list(set(field_list))
if field_list is not None:
for fieldname in field_list:
ret = self._check_field(self._target_class, fieldname)
if isinstance(ret, Exception):
msg = "No field named '%s' in %s"
msg %= (fieldname, self._target_class.__name__)
expt = NameError(msg)
err_l[fieldname] = expt
if len(err_l) > 0:
msg = "Error while setting field_list in a get query"
raise LeApiQueryErrors(msg = msg, exceptions = err_l)
self._field_list = list(set(field_list))
##@brief Execute the get query
def execute(self, datas = None):
@ -678,9 +686,10 @@ class LeGetQuery(LeFilteredQuery):
# @returns a list containing the item(s)
def _query(self, datas = None):
# select datas corresponding to query_filter
fl = list(self._field_list) if self._field_list is not None else None
l_datas=self._ro_datasource.select(
target = self._target_class,
field_list = list(self._field_list),
field_list = fl,
filters = self._query_filter[0],
relational_filters = self._query_filter[1],
order = self._order,

View file

@ -7,6 +7,7 @@ import copy
from importlib.machinery import SourceFileLoader, SourcelessFileLoader
import plugins
from .exceptions import *
## @package lodel.plugins Lodel2 plugins management
#
@ -25,8 +26,6 @@ LOADER_FILENAME_VARNAME = '__loader__'
PLUGIN_DEPS_VARNAME = '__plugin_deps__'
ACTIVATE_METHOD_NAME = '_activate'
class PluginError(Exception):
pass
##@brief Handle plugins
#
@ -119,7 +118,14 @@ class Plugin(object):
#@throw PluginError if the filename was not valid
def _import_from_init_var(self, varname):
# Read varname
filename = getattr(self.module, varname)
try:
filename = getattr(self.module, varname)
except AttributeError:
msg = "Malformed plugin {plugin}. No {varname} found in __init__.py"
msg = msg.format(
plugin = self.name,
varname = LOADER_FILENAME_VARNAME)
raise PluginError(msg)
#Path are not allowed
if filename != os.path.basename(filename):
msg = "Invalid {varname} content : '{fname}' for plugin {name}"
@ -213,12 +219,8 @@ class Plugin(object):
#Loading the plugin
try:
self.__loader_module = self._import_from_init_var(LOADER_FILENAME_VARNAME)
except AttributeError:
msg = "Malformed plugin {plugin}. No {varname} found in __init__.py"
msg = msg.format(
plugin = self.name,
varname = LOADER_FILENAME_VARNAME)
raise PluginError(msg)
except PluginError as e:
raise e
except ImportError as e:
msg = "Broken plugin {plugin} : {expt}"
msg = msg.format(

View file

@ -4,7 +4,7 @@ from lodel.settings.validator import SettingValidator
CONFSPEC = {
'lodel2.datasource.mongodb_datasource.*':{
'read_only': (True, SettingValidator('bool')),
'read_only': (False, SettingValidator('bool')),
'host': ('localhost', SettingValidator('host')),
'port': (None, SettingValidator('string')),
'db_name':('lodel', SettingValidator('string')),

View file

@ -13,8 +13,8 @@ from lodel import logger
from lodel.leapi.leobject import CLASS_ID_FIELDNAME
from . import utils
from .utils import object_collection_name,\
MONGODB_SORT_OPERATORS_MAP, connection_string
from .utils import object_collection_name, collection_name, \
MONGODB_SORT_OPERATORS_MAP, connection_string, mongo_fieldname
class MongoDbDataSourceError(Exception):
pass
@ -75,7 +75,7 @@ class MongoDbDatasource(object):
#@warning multiple UID broken by this method
#@return an integer
def new_numeric_id(self, emcomp):
target = emcomp.uid_source()
target = emcomp #.uid_source()
tuid = target._uid[0] # Multiple UID broken here
results = self.select(
target, field_list = [tuid], filters = [],
@ -130,16 +130,26 @@ class MongoDbDatasource(object):
query_filters = self.__process_filters(
target, filters, relational_filters)
query_result_ordering = None
if order is not None:
query_result_ordering = utils.parse_query_order(order)
results_field_list = None if len(field_list) == 0 else field_list
limit = limit if limit is not None else 0
if group is None:
if field_list is None:
field_list = dict()
else:
f_list=dict()
for fl in field_list:
f_list[fl] = 1
field_list = f_list
field_list['_id'] = 0
cursor = collection.find(
filter=query_filters, projection=results_field_list,
skip=offset, limit=limit, sort=query_result_ordering)
spec = query_filters,
fields=field_list,
skip=offset,
limit=limit if limit != None else 0,
sort=query_result_ordering)
else:
pipeline = list()
unwinding_list = list()
@ -156,7 +166,7 @@ class MongoDbDatasource(object):
sorting_list.extends(query_result_ordering)
pipeline.append({'$match': query_filters})
if results_field_list is not None:
if field_list is not None:
pipeline.append({
'$project': SON([{field_name: 1}
for field_name in field_list])})
@ -180,7 +190,7 @@ class MongoDbDatasource(object):
#@param relational_filters list : List of relational filters
#@return int : number of deleted records
def delete(self, target, filters, relational_filters):
if target.is_asbtract():
if target.is_abstract():
#Deletion with abstract LeObject as target (reccursiv calls)
return self.__act_on_abstract(target, filters,
relational_filters, self.delete)
@ -197,15 +207,15 @@ class MongoDbDatasource(object):
#@param upd_datas dict : datas to update (new values)
#@return int : Number of updated records
def update(self, target, filters, relational_filters, upd_datas):
if target.is_asbtract():
if target.is_abstract():
#Update using abstract LeObject as target (reccursiv calls)
return self.__act_on_abstract(target, filters,
relational_filters, self.update, upd_datas = upd_datas)
#Non abstract beahavior
mongo_filters = self.__process_filters(
target, filters, relational_filters)
res = self.__collection(target).update_many(mongo_filters, upd_datas)
return res.modified_count()
res = self.__collection(target).update(mongo_filters, upd_datas)
return res['n']
## @brief Inserts a record in a given collection
# @param target Emclass : class of the object to insert
@ -252,7 +262,8 @@ class MongoDbDatasource(object):
fname, op, val))
del(new_filters[i])
new_filters.append(
(CLASS_ID_FIELDNAME, '=', target_child.__name__))
(CLASS_ID_FIELDNAME, '=',
collection_name(target_child.__name__)))
result += act(
target = target_child,
filters = new_filters,
@ -348,7 +359,7 @@ class MongoDbDatasource(object):
if '$in' in res[fname]:
#WARNING we allready have a IN on this field, doing dedup
#from result
deduped = set(res[fname]['$in']) & subq
deduped = set(res[fname]['$in']) & subq_results
if len(deduped) == 0:
del(res[fname]['$in'])
else:
@ -419,8 +430,8 @@ class MongoDbDatasource(object):
#here we are filling a dict with leobject as index but
#we are doing a UNIQ on collection name
cur_collname = object_collection_name(leobject)
if cur_collname not in collnames:
leo_collname[cur_collame] = leobject
if cur_collname not in leo_collname:
leo_collname[cur_collname] = leobject
rfilters[fname][leobject] = dict()
#Fecthing the collection's representative leobject
repr_leo = leo_collname[cur_collname]
@ -436,17 +447,33 @@ class MongoDbDatasource(object):
@classmethod
def __filters2mongo(cls, filters):
res = dict()
eq_fieldname = [] #Stores field with equal comparison OP
for fieldname, op, value in filters:
oop = op
ovalue = value
op, value = cls.__op_value_conv(op, value)
if op == '=':
eq_fieldname.append(fieldname)
if fieldname in res:
logger.warning("Dropping previous condition. Overwritten \
by an equality filter")
res[fieldname] = value
continue
if fieldname in eq_fieldname:
logger.warning("Dropping condition : '%s %s %s'" % (
fieldname, op, value))
continue
if fieldname not in res:
res[fieldname] = dict()
if op in res[fieldname]:
logger.warning("Dropping condition : '%s %s %s'" % (
fieldname, op, value))
else:
res[fieldname][op] = value
if op not in cls.lodel2mongo_op_map:
raise ValueError("Invalid operator : '%s'" % op)
new_op = cls.lodel2mongo_op_map[op]
res[fieldname][new_op] = value
return res

View file

@ -69,6 +69,9 @@ def connect(host, port, db_name, username, password):
def object_collection_name(class_object):
return class_object.__name__
def collection_name(class_name):
return class_name
## @brief Determine a collection field name given a lodel2 fieldname
# @note For the moment this method only return the argument but EVERYWHERE
# in the datasource we should use this method to gather proper fieldnames

View file

@ -8,6 +8,9 @@ CONFSPEC = {
SettingValidator('dummy')),
'listen_port': ( '9090',
SettingValidator('int')),
'virtualenv': ('',
SettingValidator('path')),
'uwsgicmd': ('uwsgi_python3', SettingValidator('dummy')),
},
'lodel2.webui.sessions': {
'directory': ( '/tmp/lodel2_session',

View file

@ -0,0 +1,51 @@
#-*- coding: utf-8 -*-
from werkzeug.wrappers import Response
class HttpException(Exception):
STATUS_STR = {
4:{
400: 'Bad request',
401: 'Unauthorized',
402: 'Payment required',
403: 'Forbidden',
404: 'Not found',
418: 'I\'m a teapot', #RFC 2324
},
5:{
500: 'Internal server error',
501: 'Not implemented',
},
}
def __init__(self, status_code = 500, tpl = 'error.html', custom = None):
self.status_code = status_code
self.tpl = tpl
self.custom = custom
def render(self, request):
from .interface.template.loader import TemplateLoader
loader = TemplateLoader()
tpl_vars = {
'status_code': self.status_code,
'status_str': self.status_str(self.status_code),
'custom': self.custom }
response = Response(
loader.render_to_response(self.tpl, template_vars = tpl_vars),
mimetype = 'text/html')
response.status_code = self.status_code
return response
@staticmethod
def status_str(status_code):
status_fam = status_code / 100
if status_fam not in HttpException.STATUS_STR or \
status_code not in HttpException.STATUS_STR[status_fam]:
return 'Unknown'
else:
return HttpException.STATUS_STR[status_fam][status_code]

View file

@ -1,2 +1,5 @@
from .base import *
from .admin import *
from .document import *
from .listing import *

View file

@ -1,5 +1,135 @@
from ...exceptions import *
from .base import get_response
from lodel.leapi.exceptions import *
from lodel import logger
import leapi_dyncode as dyncode
import warnings
from lodel import logger
def index_admin(request):
return get_response('admin/admin.html')
def admin_update(request):
msg=''
if request.method == 'POST':
error = None
datas = list()
classname = request.form['classname']
uid = request.form['uid']
try:
target_leo = dyncode.Object.name2class(classname)
except LeApiError:
classname = None
if classname is None or target_leo.is_abstract():
raise HttpException(400)
fieldnames = target_leo.fieldnames()
fields = dict()
for in_put, in_value in request.form.items():
if in_put != 'classname' and in_put != 'uid':
fields[in_put[12:]] = in_value
obj = (target_leo.get(('lodel_id = %s' % (uid))))[0]
inserted = obj.update(fields)
if inserted==1:
msg = 'Successfully updated';
else:
msg = 'Oops something wrong happened...object not saved'
return get_response('admin/admin_edit.html', target=target_leo, lodel_id = uid, msg = msg)
test_valid = 'lodel_id' in request.GET \
and len(request.GET['lodel_id']) == 1
if test_valid:
try:
lodel_id = int(request.GET['lodel_id'][0])
except (ValueError, TypeError):
test_valid = False
if not test_valid:
raise HttpException(400)
else:
obj = dyncode.Object.get(['lodel_id = %d' % lodel_id])
if len(obj) == 0:
raise HttpException(404)
if 'classname' in request.GET:
classname = request.GET['classname']
if len(classname) > 1:
raise HttpException(400)
classname = classname[0]
try:
target_leo = dyncode.Object.name2class(classname)
except LeApiError:
classname = None
return get_response('admin/admin_edit.html', target=target_leo, lodel_id =lodel_id)
def admin_create(request):
classname = None
if request.method == 'POST':
error = None
datas = list()
classname = request.form['classname']
try:
target_leo = dyncode.Object.name2class(classname)
except LeApiError:
classname = None
if classname is None or target_leo.is_abstract():
raise HttpException(400)
fieldnames = target_leo.fieldnames()
fields = dict()
for in_put, in_value in request.form.items():
if in_put != 'classname':
fields[in_put[12:]] = in_value
new_uid = target_leo.insert(fields)
if not new_uid is None:
msg = 'Successfull creation';
else:
msg = 'Oops something wrong happened...object not saved'
return get_response('admin/admin_create.html', target=target_leo, msg = msg)
if 'classname' in request.GET:
classname = request.GET['classname']
if len(classname) > 1:
raise HttpException(400)
classname = classname[0]
try:
target_leo = dyncode.Object.name2class(classname)
except LeApiError:
classname = None
msg = None
if 'msg' in request.GET:
msg = request.GET['msg']
if classname is None or target_leo.is_abstract():
raise HttpException(400)
return get_response('admin/admin_create.html', target=target_leo)
def admin_classes(request):
return get_response('admin/list_classes_admin.html', my_classes = dyncode.dynclasses)
def admin_class(request):
if 'classname' in request.GET:
classname = request.GET['classname']
if len(classname) > 1:
raise HttpException(400)
classname = classname[0]
try:
target_leo = dyncode.Object.name2class(classname)
except LeApiError:
classname = None
if classname is None or target_leo.is_abstract():
raise HttpException(400)
return get_response('admin/show_class_admin.html', target=target_leo)
def admin(request):
return get_response('admin/admin.html')

View file

@ -5,13 +5,27 @@ from ..template.loader import TemplateLoader
# This module contains the web UI controllers that will be called from the web ui class
def get_response(tpl, mimetype='text/html', status_code=200):
##@brief Render a template and return a respone
#@param tpl str : template relativ path
#@param tpl_vars : templates variables (obsolete)
#@param mimetype
#@param status_code
#@param **kwargs : new version of tpl_vars
#@return a response...
def get_response(tpl='empty.html', tpl_vars={}, mimetype='text/html', status_code=200, **kwargs):
tpl_vars.update(kwargs)
loader = TemplateLoader()
response = Response(loader.render_to_response(tpl), mimetype=mimetype)
response = Response(loader.render_to_response(tpl, template_vars=tpl_vars), mimetype=mimetype)
response.status_code = status_code
return response
## @brief gets the html template corresponding to a given component type
# @param type str : name of the component type
# @param params dict : extra parameters to customize the template
def get_component_html(type='text', params={}):
params['type'] = type
template_loader = TemplateLoader()
return template_loader.render_to_html(template_file='components/components.html', template_vars=params)
def index(request):
return get_response('index/index.html')
@ -22,10 +36,14 @@ def not_found(request):
def test(request):
return get_response('test.html')
if 'id' not in request.url_args:
id = None
else:
id = request.url_args['id']
def list_classes(request):
# TODO Add the method to get the classes list
return get_response('list_classes.html')
template_vars = {
'id': id,
'params': request.GET
}
return get_response('test.html', tpl_vars=template_vars)

View file

@ -0,0 +1,6 @@
from .base import get_response
def show_document(request):
template_vars = {'id': request.url_args['id']}
return get_response('documents/show.html', tpl_vars=template_vars)

View file

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from .base import get_response
import leapi_dyncode as dyncode
def list_classes(request):
template_vars = {'my_classes': dyncode.dynclasses}
return get_response('listing/list_classes.html', tpl_vars=template_vars)
def show_class(request):
template_vars = {
'params': request.GET
}
return get_response('listing/show_class.html', tpl_vars=template_vars)
def show_object(request):
template_vars = {
'params': request.GET
}
return get_response('listing/show_object.html', tpl_vars=template_vars)

View file

@ -3,19 +3,15 @@ import re
from .controllers import *
from .urls import urls
from ..main import root_url
from lodel.settings import Settings
def format_url_rule(url_rule):
if url_rule == '^$':
return "^%s$" % Settings.sitename
formatted_rule = ''
if url_rule.startswith('^'):
formatted_rule += "^"
formatted_rule += "%s/%s" % (Settings.sitename, url_rule)
return formatted_rule
res = url_rule.replace('^', '^'+root_url())
else:
res = root_url()+'.*'+url_rule
return res
def get_controller(request):
@ -26,9 +22,10 @@ def get_controller(request):
# Returning the right controller to call
for regex, callback in url_rules:
match = re.search(regex, request.PATH)
if match is not None:
request.url_args = match.groups()
p = re.compile(regex)
m = p.search(request.PATH)
if m is not None:
request.url_args = m.groupdict()
return callback
return not_found

View file

@ -1,3 +1,8 @@
# -*- coding: utf-8 -*-
# Lodel 2 templates API : loaded by default
class Test(object):
def ok(self):
return 'ok'

View file

@ -2,9 +2,12 @@
import jinja2
import os
import settings
from lodel.settings import Settings
import leapi_dyncode
from .api import api_lodel_templates
from .exceptions.not_allowed_custom_api_key_error import NotAllowedCustomAPIKeyError
from ...main import root_url
from ...main import PLUGIN_PATH
TEMPLATE_PATH = os.path.realpath(os.path.join(PLUGIN_PATH, 'templates/'))
@ -39,6 +42,11 @@ class TemplateLoader(object):
# lodel2 default api is loaded
# TODO change this if needed
template.globals['lodel'] = api_lodel_templates
template.globals['leapi'] = leapi_dyncode
template.globals['settings'] = Settings
template.globals['url'] = lambda sufix='': root_url()\
+ ('' if sufix.startswith('/') else '/')\
+ sufix
# Extra modules are loaded
if template_extra is not None:

View file

@ -1,9 +1,19 @@
# -*- coding: utf-8 -*-
from .controllers import *
urls = (
(r'^$', index),
(r'admin/?$', admin),
(r'admin/(.+)$', admin),
(r'test/(.+)$', test),
(r'test/?$', test)
(r'^/?$', index),
(r'^/admin/?$', admin),
(r'^/admin/create$', admin_create),
(r'^/admin/update$', admin_update),
(r'^/admin/classes_admin$', admin_classes),
(r'^/admin/class_admin$', admin_class),
(r'/test/(?P<id>.*)$', test),
(r'^/test/?$', test),
#(r'/show/(?P<id>.*)$', show_document),
(r'^/list_classes', list_classes),
#(r'^/show_object/(.+)$', show_object),
(r'^/show_object?$', show_object),
#(r'^/show_class/(.+)$', show_class),
(r'^/show_class?$', show_class)
)

View file

@ -6,12 +6,25 @@ from lodel.settings import Settings
PLUGIN_PATH = os.path.dirname(__file__)
##@brief Return the root url of the instance
#@warning no trailing slash
def root_url():
return Settings.sitename
##@brief uwsgi startup demo
@LodelHook('lodel2_loader_main')
def uwsgi_fork(hook_name, caller, payload):
from lodel.plugin.plugins import Plugin
Plugin.from_name('users')
if Settings.webui.standalone:
cmd='uwsgi_python3 --http-socket {addr}:{port} --module plugins.webui.run'
cmd='{uwsgi} --http-socket {addr}:{port} --module plugins.webui.run'
cmd = cmd.format(
addr = Settings.webui.listen_address,
port = Settings.webui.listen_port)
port = Settings.webui.listen_port,
uwsgi= Settings.webui.uwsgicmd)
if Settings.webui.virtualenv != '':
cmd += " --virtualenv %s" % Settings.webui.virtualenv
exit(os.system(cmd))

View file

@ -3,11 +3,14 @@ import loader # Lodel2 loader
import os
from werkzeug.contrib.sessions import FilesystemSessionStore
from werkzeug.wrappers import Response
from lodel.settings import Settings
from .interface.router import get_controller
from .interface.lodelrequest import LodelRequest
from .exceptions import *
from lodel.utils.datetime import get_utc_timestamp
from lodel.plugin.hooks import LodelHook
SESSION_FILES_BASE_DIR = Settings.webui.sessions.directory
SESSION_FILES_TEMPLATE = Settings.webui.sessions.file_template
@ -15,6 +18,8 @@ SESSION_EXPIRATION_LIMIT = Settings.webui.sessions.expiration
session_store = FilesystemSessionStore(path=SESSION_FILES_BASE_DIR, filename_template=SESSION_FILES_TEMPLATE)
#Starting instance
loader.start()
# TODO déplacer dans un module "sessions.py"
def delete_old_session_files(timestamp_now):
@ -53,11 +58,23 @@ def application(env, start_response):
request.session = session_store.new()
request.session['user_context'] = None
request.session['last_accessed'] = current_timestamp
controller = get_controller(request)
response = controller(request)
try:
controller = get_controller(request)
response = controller(request)
except HttpException as e:
try:
response = e.render(request)
except Exception as eb:
res = Response()
res.status_code = 500
return res
if request.session.should_save:
session_store.save(request.session)
response.set_cookie('sid', request.session.sid)
return response(env, start_response)
res = response(env, start_response)
LodelHook.call_hook('lodel2_session_end', __file__, None)
return res

View file

@ -1,3 +1,10 @@
{% extends "base_backend.html" %}
{% block title %}Lodel 2 - ADMIN{% endblock %}
{% block content %}ADMIN{% endblock %}
{% block title %}- Index{% endblock %}
{% block body %}
<h1>{{settings.sitename}} administration</h1>
{{url('admin')}}
<ul>
<li><a href="classes_admin">List of Classes</a></li>
</ul>
{% endblock %}

View file

@ -0,0 +1,20 @@
{% extends "base_backend.html" %}
{% import "admin/editable_component.html" as edit %}
{% block title %}- Creating a new {{target.__name__}}{% endblock %}
{% block body %}
{% if msg is not none %}
{% block msg %} <p style="color:red; font-size:20pt; font-weight:bold">{{ msg }}</p> {% endblock %}
{% endif %}
<h1>Creating a new {{target.__name__}}</h1>
<form action="" method ="post">
<input type="hidden" name="classname" id="classname" value="{{target.__name__}}" />
{% for fieldname, field in target.fields().items() %}
<div style="padding-bottom:15px;"> {{edit.input(fieldname, field) }}</div>
{% endfor %}
<p>&nbsp;</p>
<input type="submit" value="Save">
</form>
{% endblock %}

View file

@ -0,0 +1,19 @@
{% extends "base_backend.html" %}
{% import "admin/editable_component.html" as edit %}
{% set objects = target.get(('lodel_id = %s') % (lodel_id)) %}
{% set obj = objects.pop() %}
{% block title %}- Edit Object{% endblock %}
{% block body %}
{% if msg is not none %}
{% block msg %} <p style="color:red; font-size:20pt; font-weight:bold">{{ msg }}</p> {% endblock %}
{% endif %}
<h1>Lodel 2 - Edit Object {{ lodel_id }} of {{ target.__name__ }}</h1>
<form action="" method ="post">
<input type="hidden" name="uid" value="{{ lodel_id}}" >
<input type="hidden" name="classname" value={{ target.__name__ }} />
{% for fieldname, fieldvalue in obj.fields().items() %}
<div style="padding-bottom:15px;"> {{edit.input(fieldname, fieldvalue, obj.data(fieldname)) }} </div>
{% endfor %}
<input type="submit" value="Save">
</form>
{% endblock %}

View file

@ -0,0 +1,10 @@
{% macro input(fieldname, field, value='') -%}
<label for="field_input_{{fieldname}}">{{fieldname}}</label>
{% if field.base_type == 'bool' %}
<input id="field_input_{{fieldname}}" name="field_input_{{fieldname}}" type="checkbox" checked="{% if value %}checked{% endif %}" />
{% elif field.base_type == 'char' or field.base_type == 'int' %}
<input id="{{fieldname}}" name="field_input_{{fieldname}}" type="text" value="{{value}}" />
{% else %}
Unsupported base type "{{field.base_type}}" </br>
{% endif %}
{%- endmacro %}

View file

@ -2,14 +2,12 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>{% block title %}{% endblock %}</title>
<title>{{ settings.sitename }} Admin{% block title %}{% endblock %}</title>
{% block style %}{% endblock %}
{% block scripts %}{% endblock %}
</head>
<body>
<div id="content">
{% block content %}{% endblock %}
</div>
{% block body %}{% endblock %}
<script type="text/javascript">{% block javascript %}{% endblock %}</script>
</body>
</html>
</html>

View file

@ -0,0 +1,9 @@
{% macro input(name, value='', type='text') -%}
<input type="{{ type }}" value="{{ value }}" name="{{ name }}" id= "{{ name }}"/>
{%- endmacro %}
{% macro textarea(name, value='', rows=10, cols=40) -%}
<textarea name="{{ name }}" rows="{{ rows }}" cols="{{ cols }}">
{{ value|e }}
</textarea>
{%- endmacro %}

View file

@ -0,0 +1,5 @@
{% extends "base_backend.html" %}
{% block title %}Lodel 2 - Document {{ id }}{% endblock %}
{% block content %}
{{ leapi.Section.get(['lodel_id = %s' % id]) }}
{% endblock %}

View file

View file

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<title>{{status_code}} {{status_str}}</title>
</head>
<body>
{{status_code}} {{status_str}}
</body>
</html>

View file

@ -1,3 +1,9 @@
{% extends "base.html" %}
{% block title %}Lodel 2 - DASHBOARD{% endblock %}
{% block content %}DASHBOARD{% endblock %}
{% block content %}
DASHBOARD <br />
{{ lodel.Test().ok() }}
<ul>
<li><a href="list_classes">Tous les types</a></li>
</ul>
{% endblock %}

View file

@ -0,0 +1,16 @@
{% extends "base.html" %}
{% block title %}Lodel 2 - List of Classes{% endblock %}
{% block content %}
<h1>Lodel 2 - List of Classes</h1>
<ul>
{% for classe in my_classes %}
{% set abst = '' %}
{% if classe.is_abstract() %}
{% set abst = ' - Abstract class ' %}
{% else %}
{% set abst = ' - ' ~ classe.get(None)|length %}
{% endif %}
<li> <a href="show_class?name={{ classe.__name__ }}" target="_blank">{{ classe.__name__ }} </a>{{ abst }}</li>
{% endfor %}
</ul>
{% endblock %}

View file

@ -0,0 +1,30 @@
{% extends "base.html" %}
{% set my_classname = params['name'].pop() %}
{% block title %}Lodel 2 - Class {{ my_classname }} {% endblock %}
{% block content %}
<h1>Lodel 2 - Class {{ my_classname }} </h1>
{% set my_class = leapi.Object.name2class(my_classname) %}
{% if my_class.child_classes()|length >0 %}
<h2> Childs classes</h2>
<ul>
{% for child in my_class.child_classes() %}
{% if child.is_abstract() %}
{% set abst = ' - Abstract class ' %}
{% else %}
{% set abst = ' - ' ~ child.get(None)|length %}
{% endif %}
<li><a href="show_class?name={{ child.__name__ }}" target="_blank">{{ child.__name__ }}</a>{{ abst }}</li>
{% endfor %}
</ul>
{% endif %}
{% if not my_class.is_abstract() %}
{% set uid_f = my_class.uid_fieldname() %}
{% set objects = my_class.get(None) %}
<ul>
{% for obj in objects %}
<li><a href="show_object?classe={{ my_classname }}&id={{ obj.uid() }}" target="_blank">{{ obj.uid() }} </a></li>
{% endfor %}
</ul>
{% endif %}
{% endblock %}

View file

@ -0,0 +1,21 @@
{% extends "base.html" %}
{% import 'components/components.html' as components %}
{% set my_classname = params['classe'].pop() %}
{% set my_id = params['id'].pop() %}
{% set my_class = leapi.Object.name2class(my_classname) %}
{% set objects = my_class.get(('%s = %s') % ('lodel_id', my_id)) %}
{% set obj = objects.pop() %}
{% block title %}Lodel 2 - Object {{ my_id }} {% endblock %}
{% import "components/components.html" as components %}
{% block content %}
<h1>Lodel 2 - Object {{ my_id }} of the class {{ my_classname }}</h1>
<ul>
<!-- To get a component HTML code, it is necessary to call : components.<macro_name>(args) -->
{% for fieldname, fieldvalue in obj.datas().items() %}
{% if fieldvalue is not none %}
<li> {{ fieldname }} : {{ fieldvalue }} </li>
{% endif %}
{% endfor %}
</ul>
{% endblock %}

View file

@ -1,6 +1,16 @@
{% import "components/components.html" as components %}
<html>
<head></head>
<body>
{{ components.textarea('test', value='ceci est un test', rows=10, cols=20) }}<br/>
URL arg : id = {{ id }}<br />
GET values :<br />
<ul>
{% for argument_name, argument_value in params.items() %}
<li>{{argument_name}} = {{ argument_value }}</li>
{% endfor %}
</ul>
<form action="http://localhost:9090/admin?r=1&rand[]=7&rand[]=5" method="POST" enctype="multipart/form-data">
<input type="text" name="re[]" value="3"><br />
<input type="text" name="re[]" value="1"><br />

View file

@ -1,10 +1,18 @@
#!/bin/bash
usage() {
echo "Usage : $0 instance_name instance_dir [lodel_libdir]" 1>&2
echo -e "Usage : $0 instance_name (instance_dir|-u) [lodel_libdir]" 1>&2
echo -e "\n\tIf -u given as first argument update instance's loader.py" 1>&2
exit 1
}
cp_loader() {
cp -Rv $libdir/install/loader.py $instdir/
# Adding lib path to loader
sed -i -E "s#^(LODEL2_LIB_ABS_PATH = )None#\1'$libdir'#" "$loader"
}
if [ $# -lt 2 ]
then
echo "Not enough arguments" 1>&2
@ -12,6 +20,7 @@ then
fi
name="$1"
instdir="$2"
@ -21,6 +30,13 @@ libdir="${libdir:=$(realpath $(dirname $0)/..)}/"
loader="$instdir/loader.py"
conf="$instdir/conf.d/lodel2.ini"
if [ $1 = '-u' ]
then
#Update instance
cp_loader
exit 0
fi
if [ -e "$instdir" ]
then
echo "Abording... "$instdir" exists" 1>&2
@ -34,19 +50,15 @@ chmod 700 "$instdir/sessions"
#cp -Rv $libdir/install/* $instdir
cp -Rv $libdir/install/conf.d $instdir/
cp -Rv $libdir/install/loader.py $instdir/
cp -Rv $libdir/examples/em_test.pickle $instdir/editorial_model.pickle
ln -sv $libdir/install/Makefile $instdir/Makefile
ln -sv $libdir/install/lodel_admin.py $instdir/lodel_admin.py
ln -sv $libdir/plugins $instdir/plugins
# Adding lib path to loader
sed -i -E "s#^(LODEL2_LIB_ABS_PATH = )None#\1'$libdir'#" "$loader"
cp_loader
# Adding instance name to conf
sed -i -E "s#^sitename = noname#sitename = $name#" "$conf"
echo -e "\nInstance successfully created in $instdir"
echo -e "============================\n"
echo "Now you should edit files in '${instdir}/conf.d/' and then run : cd $instdir && make dyncode"

Binary file not shown.

View file

@ -71,7 +71,7 @@ class LeQueryDatasourceTestCase(unittest.TestCase):
self.assertEqual(call_args[0], cls)
self.assertEqual(
sorted(call_args[1]),
sorted([('lodel_id', '=', '1'), ('alias', '=', '2')]))
sorted([('lodel_id', '=', 1), ('alias', '=', '2')]))
self.assertEqual(call_args[2], [])
self.check_nocall(read = False, exclude = ['delete'])
self.check_nocall(read = True)
@ -87,7 +87,7 @@ class LeQueryDatasourceTestCase(unittest.TestCase):
query.execute()
self.mockwrite.delete.assert_called_once_with(
cls,
[('lodel_id', '=', '1')],
[('lodel_id', '=', 1)],
[(('alias', {cls: 'firstname'}), '=', 'foo')])
self.check_nocall(read = False, exclude = ['delete'])
self.check_nocall(read = True)

View file

@ -3,7 +3,7 @@ import unittest
import tests.loader_utils
from tests.leapi.query.utils import dyncode_module as dyncode
from lodel.leapi.exceptions import LeApiDataCheckError
from lodel.leapi.exceptions import *
from lodel.leapi.query import LeDeleteQuery, LeUpdateQuery, LeGetQuery
class LeFilteredQueryTestCase(unittest.TestCase):
@ -13,20 +13,20 @@ class LeFilteredQueryTestCase(unittest.TestCase):
def test_filters(self):
""" Testing FilteredQuery filters handling """
test_datas = [ ( 'lodel_id = 42',
( [('lodel_id','=','42')],
( [('lodel_id','=',42)],
[])),
( 'lodel_id <= 42',
( [('lodel_id','<=','42')],
( [('lodel_id','<=',42)],
[])),
( ['lodel_id <= 42'],
( [('lodel_id','<=','42')],
( [('lodel_id','<=',42)],
[])),
( ('lodel_id <= 42',),
( [('lodel_id','<=','42')],
( [('lodel_id','<=',42)],
[])),
( ['lodel_id <= 42','lodel_id >= 33'],
( [ ('lodel_id','<=','42'),
('lodel_id', '>=','33')],
( [ ('lodel_id','<=',42),
('lodel_id', '>=',33)],
[])),
]
for q_class in self.q_classes:
@ -53,7 +53,7 @@ class LeFilteredQueryTestCase(unittest.TestCase):
)
for invalid_filter in invalid_filters:
for q_class in self.q_classes:
with self.assertRaises( LeApiDataCheckError,
with self.assertRaises( LeApiDataCheckErrors,
msg="for filter '%s'" % (invalid_filter,)):
q_class(dyncode.Publication, invalid_filter)
@ -70,7 +70,7 @@ class LeFilteredQueryTestCase(unittest.TestCase):
'not in',
'like',
'not like']
values = ( '42',
values = ( 42,
'not in',
'in',
'like',

View file

@ -259,7 +259,7 @@ class LeObjectQueryMockTestCase(unittest.TestCase):
mock_init.assert_called_once_with(
dyncode.Person,
query_filters = ['lodel_id = 1'],
field_list = dyncode.Person.fieldnames(True),
field_list = None,
order = None, group = None, limit = None, offset = 0)
with patch.object(