1 |
ERP systems involve many different attribute names which |
2 |
can be used in different contexts. For example, a resource |
3 |
may have a default global price. A path may define a default price |
4 |
for specific source. |
5 |
|
6 |
ERP5 uses PropertySheets in order to provide an explicit data model |
7 |
which is used independently from interfaces. The role of interfaces |
8 |
is to define common explicit behaviour which can be implemented through |
9 |
by different classes. The role of property sheets is |
10 |
to define common **attribute sets** which can be stored on instances |
11 |
of different documents. |
12 |
|
13 |
PropertySheets also hold explicit information about of the ERP data structure. |
14 |
For example, some documents may not hold themselves pricing information |
15 |
but rather acquire it through the order or the sourcing contract they depend from. |
16 |
|
17 |
ERP5 implements this feature through **explicit acquisition**. The value |
18 |
of attributes can be obtained by looking up the value of that attribute |
19 |
on another related document or by calling a method on another related document. |
20 |
|
21 |
Explicit acquisition requires to define relations between documents. The general |
22 |
model adopted by ERP5 is a **categorization** model which is equivalent to |
23 |
a DACG model (Direct Acyclic Colored Graph). Thanks to Zope acquisition, this model |
24 |
is theoretically equivalent to a relationnal model. |
25 |
|
26 |
We will explain bellow how those three concepts (attribute sets, categorization and |
27 |
explicit acquisition) relate and how to define new Property Sheets in ERP5. |
28 |
|
29 |
Attribute Sets |
30 |
|
31 |
ERP5 uses Attribute Sets based on an approach which was originally developped |
32 |
by Max M. for its Zope Easy Product (http://www.zope.org/Members/maxm/HowTo/easyProduct). |
33 |
This approach consists in defining a set of attributes in the following way:: |
34 |
|
35 |
class Organisation: |
36 |
""" |
37 |
Organisation properties and categories |
38 |
""" |
39 |
|
40 |
_properties = ( |
41 |
{ 'id' : 'corporate_name', |
42 |
'description' : 'The official name of this organisation', |
43 |
'type' : 'string', |
44 |
'mode' : 'w' }, |
45 |
{ 'id' : 'social_capital', |
46 |
'description' : 'The social capital of this organisation', |
47 |
'type' : 'int', |
48 |
'mode' : 'w' }, |
49 |
{ 'id' : 'social_capital_currency', |
50 |
'description' : "The currency in which the social capital is" |
51 |
"expressed", |
52 |
'type' : 'string', |
53 |
'mode' : 'w' }, |
54 |
) |
55 |
|
56 |
The class *Organisation* is simply a place holder for an class attribute |
57 |
called *_properties* which holds a list of dictionnaries, each of which |
58 |
describes the *id*, the *purpose* and the *type* of an attribute. In the |
59 |
above example, the attribute *corporate_name* is of type *string*, is |
60 |
in *write mode* which means it can be read and modified and is described |
61 |
as *The official name of this organisation*. |
62 |
|
63 |
Attribute sets allow to group related attributes in a common *package* |
64 |
and reuse them to define ERP5 classes. For example, the *Organisation* class |
65 |
in ERP5 is defined as:: |
66 |
|
67 |
class Organisation(Entity, MetaNode, XMLObject): |
68 |
""" |
69 |
An Organisation object holds the information about |
70 |
an organisation (ex. a division in a company, a company, |
71 |
a service in a public administration). |
72 |
|
73 |
Organisation objects can contain Coordinate objects |
74 |
(ex. Telephone, Url) as well a documents of various types. |
75 |
|
76 |
Organisation objects can be synchronized accross multiple |
77 |
sites. |
78 |
|
79 |
Organisation objects inherit from the MetaNode base class |
80 |
(one of the 5 base classes in the ERP5 universal business model) |
81 |
""" |
82 |
|
83 |
meta_type = 'ERP5 Organisation' |
84 |
portal_type = 'Organisation' |
85 |
isPortalContent = 1 |
86 |
isRADContent = 1 |
87 |
|
88 |
# Declarative security |
89 |
security = ClassSecurityInfo() |
90 |
security.declareObjectProtected(ERP5Permissions.View) |
91 |
|
92 |
# Declarative properties |
93 |
property_sheets = ( PropertySheet.Base |
94 |
, PropertySheet.XMLObject |
95 |
, PropertySheet.CategoryCore |
96 |
, PropertySheet.DublinCore |
97 |
, PropertySheet.Organisation |
98 |
) |
99 |
|
100 |
# Factory Type Information |
101 |
factory_type_information = \ |
102 |
{ 'id' : portal_type |
103 |
, 'meta_type' : meta_type |
104 |
, 'description' : """\ |
105 |
An Organisation object holds the information about |
106 |
an organisation (ex. a division in a company, a company, |
107 |
a service in a public administration).""" |
108 |
, 'icon' : 'organisation_icon.gif' |
109 |
, 'product' : 'ERP5' |
110 |
, 'factory' : 'addOrganisation' |
111 |
, 'immediate_view' : 'organisation_edit' |
112 |
, 'actions' : |
113 |
( { 'id' : 'view' |
114 |
, 'name' : 'View' |
115 |
, 'category' : 'object_view' |
116 |
, 'action' : 'organisation_edit' |
117 |
, 'permissions' : ( |
118 |
ERP5Permissions.View, ) |
119 |
} |
120 |
, { 'id' : 'print' |
121 |
, 'name' : 'Print' |
122 |
, 'category' : 'object_print' |
123 |
, 'action' : 'organisation_print' |
124 |
, 'permissions' : ( |
125 |
ERP5Permissions.View, ) |
126 |
} |
127 |
, { 'id' : 'metadata' |
128 |
, 'name' : 'Metadata' |
129 |
, 'category' : 'object_edit' |
130 |
, 'action' : 'metadata_edit' |
131 |
, 'permissions' : ( |
132 |
ERP5Permissions.View, ) |
133 |
} |
134 |
, { 'id' : 'translate' |
135 |
, 'name' : 'Translate' |
136 |
, 'category' : 'object_action' |
137 |
, 'action' : 'translation_template_view' |
138 |
, 'permissions' : ( |
139 |
ERP5Permissions.TranslateContent, ) |
140 |
} |
141 |
) |
142 |
} |
143 |
|
144 |
InitializeClass(Organisation) |
145 |
|
146 |
The reader should take notice of two ERP5 specific features in the |
147 |
class definition: |
148 |
|
149 |
- the attribute *isRADContent* is set to 1, which is used by ERP5 |
150 |
to determine that a given class definition should be considered |
151 |
as an **ERP5 RAD Class** or not. The meaning of *RAD* is *Rapid |
152 |
Application Development*. Classes which set the |
153 |
the attribute *isRADContent* to 1 will automatically benefit from |
154 |
ERP5 RAD features (ex. automatic generation of accessors, automatic |
155 |
generation of default values, etc.) |
156 |
|
157 |
- the attribute *property_sheets* contains a list of PropertySheets. This |
158 |
list defines the default attributes for all instances of the *Organisation* |
159 |
class |
160 |
|
161 |
During the initialization of an **ERP5 RAD Class**, the following |
162 |
class attributes and methods will be automatically generated: |
163 |
|
164 |
- a class attribute holding a **default value**. In |
165 |
the example of the *Organisation* class, a class attribute |
166 |
named *corporate_name* is created and set to |
167 |
the default string (''). |
168 |
|
169 |
- a default **getter** accessor method if no such accessor |
170 |
is already defined by the class. In |
171 |
the example of the *Organisation* class, a method |
172 |
named *getCorporateName* is created, which returns |
173 |
the value of the *corporate_name* instance attribute. |
174 |
|
175 |
- a default **setter** accessor method if no such accessor |
176 |
is already defined by the class. In |
177 |
the example of the *Organisation* class, a method |
178 |
named *setCorporateName* is created, which changes |
179 |
the value of the *corporate_name* document attribute |
180 |
and reindexes the document. Another method |
181 |
named *_setCorporateName* is created to change |
182 |
the value of the *corporate_name* document attribute |
183 |
without reindexing it. |
184 |
|
185 |
Besides *getters* and *setters*, all ERP5 documents |
186 |
which are implemented by an **ERP5 RAD Class** can |
187 |
be modified with the **edit** method. For example:: |
188 |
|
189 |
document.edit(corporate_name='ACME') |
190 |
|
191 |
is equivalent to:: |
192 |
|
193 |
document.setCorporateName('ACME') |
194 |
|
195 |
The use of *edit* allows to reindex documents only once. |
196 |
For example:: |
197 |
|
198 |
document.edit(corporate_name='ACME',social_capital=30000) |
199 |
|
200 |
is equivalent to:: |
201 |
|
202 |
document._setCorporateName('ACME') |
203 |
document.setSocialCapital(30000) |
204 |
|
205 |
Default attribute values and types are defined in the Utils.py |
206 |
file at the root of the ERP5 Product:: |
207 |
|
208 |
defaults = { |
209 |
'float' : 0.0, |
210 |
'int' : 0, |
211 |
'long' : 0L, |
212 |
'date' : DateTime(), |
213 |
'string' : '', |
214 |
'text' : '', |
215 |
'boolean' : 0, |
216 |
'lines' : [], |
217 |
'tokens' : [], |
218 |
'selection' : [], |
219 |
'multiple selection' : [], |
220 |
} |
221 |
|
222 |
**The use of getters and setters in ERP5 is not only recommended |
223 |
but is considered as COMPULSORY** since they provide a functional |
224 |
approach to content access and allow a more consistent implementation |
225 |
of complex interactions between documents. **NEVER ACCESS DIRECTLY |
226 |
A DOCUMENT ATTRIBUTE IN ERP5 OUTSIDE A PRIVATE METHOD. |
227 |
ONLY USE GETTERS AND SETTERS** |
228 |
|
229 |
*Implementation*: functions createDefaultAccessors and |
230 |
|
231 |
Categories |
232 |
|
233 |
|
234 |
First of all, a certain number |
235 |
of base categories is defined. Such categories can are identified by a string such |
236 |
as: region, client, skill, function, etc. Each category can hold subcategories, |
237 |
sub-subcategories, etc. For example, regions allow to categorize geography in EPR5:: |
238 |
|
239 |
region/americas |
240 |
region/americas/central |
241 |
region/americas/north |
242 |
region/americas/south |
243 |
region/americas/south/brazil |
244 |
region/europe |
245 |
region/europe/central |
246 |
region/europe/east |
247 |
region/europe/north |
248 |
region/europe/west |
249 |
region/europe/west/france |
250 |
region/europe/west/netherlands |
251 |
|
252 |
(we only included the countries where people contribute to ERP5 currently - if you |
253 |
contribute to ERP5 and your country is not list, let us know) |
254 |
|
255 |
Each ERP5 document holds a *categories* attribute which contains a tuple |
256 |
of category paths storever as a tuple of lines. Categories are considered |
257 |
as a *slowly evolving* in an ERP5 system. |
258 |
|
259 |
Documents which are implemented as **ERP5 RAD Class** can also benefit |
260 |
from the automatic creation of accessors in the area of categories. For example, |
261 |
the *Organisation* property sheet includes references to some default |
262 |
base categories of ERP5:: |
263 |
|
264 |
class Organisation: |
265 |
""" |
266 |
Organisation properties and categories |
267 |
""" |
268 |
|
269 |
_properties = ( |
270 |
{ 'id' : 'corporate_name', |
271 |
'description' : 'The official name of this organisation', |
272 |
'type' : 'string', |
273 |
'mode' : 'w' }, |
274 |
{ 'id' : 'social_capital', |
275 |
'description' : 'The social capital of this organisation', |
276 |
'type' : 'int', |
277 |
'mode' : 'w' }, |
278 |
{ 'id' : 'social_capital_currency', |
279 |
'description' : "The currency in which the social capital is" |
280 |
"expressed", |
281 |
'type' : 'string', |
282 |
'mode' : 'w' }, |
283 |
) |
284 |
|
285 |
_categories = ( 'role', 'group', 'activity', 'skill', 'market_segment', |
286 |
'social_form', 'function' ) |
287 |
|
288 |
|
289 |
The *_categories* attributes holds a tuple of strings which defines |
290 |
which ids of base categories should be implemented by documents of |
291 |
type Organisation. Based on this definition, the following getter and |
292 |
setter methods are defined:: |
293 |
|
294 |
getRole getDefaultRole setRole _setRole |
295 |
getGroup getDefaultGroup setGroup _setGroup |
296 |
getActivity getDefaultActivity setActivity _setActivity |
297 |
getSkill getDefaultSkill setSkill _setSkill |
298 |
getMarketSegment getDefaultMarketSegment setMarketSegment _setMarketSegment |
299 |
getSocialForm getDefaultSocialForm setSocialForm _setSocialForm |
300 |
getFunction getDefaultFunction setFunction _setFunction |
301 |
|
302 |
|
303 |
Each of these getters and setters sets or returns a list of partial path values except |
304 |
for the *Default* category getter which returns the first value in of the base |
305 |
category membership of a document. |
306 |
|
307 |
Ordered Categories |
308 |
|
309 |
Categories are ordered. |
310 |
|
311 |
|
312 |
*Implementation*: all this is implemented by createCategoryAccessors |
313 |
in Utils.py |
314 |
|
315 |
|
316 |
Categories and relations |
317 |
|
318 |
Categories allow to implement document categorization but also relations. |
319 |
In ERP5, categories are managed by a portal tool names **portal_categories**. |
320 |
Because ERP5 is based on Zope and because provides implicit acquisition, |
321 |
if an EPR5 document has for example the following path:: |
322 |
|
323 |
/portal_root/organisation/3 |
324 |
|
325 |
Then, this object can also be accessed as:: |
326 |
|
327 |
/portal_root/portal_categories/client/organisation/3 |
328 |
|
329 |
This allows to define virtual categories in a base category |
330 |
without having to create many subcategories. For example, |
331 |
the category:: |
332 |
|
333 |
client/organisation/3 |
334 |
|
335 |
only exists through acquisition. Including in the list of category paths |
336 |
of a document a paths such as:: |
337 |
|
338 |
client/organisation/3 |
339 |
|
340 |
is equivalent to creating an **arc** of coulor *client* between |
341 |
that document and the document |
342 |
|
343 |
/portal_root/organisation/3 |
344 |
|
345 |
Since relaltional models are equivalent to graphs, we can see |
346 |
here that the ERP5 categorization model provides the same expressive |
347 |
power as a relational model. Base categories are the equivalent |
348 |
to a relation identifier. Arcs are created between a document |
349 |
and a virtual subcategory acquired through implicit acquisition. |
350 |
|
351 |
**All relations are therefore treated in ERP5 as categories** |
352 |
|
353 |
|
354 |
Explicit Relational Acquisition |
355 |
|
356 |
|
357 |
One of the purpose of property sheets is to create |
358 |
a complete documentation of all attributes which can be used |
359 |
in a complexe ERP5 system, and to make sure that no name conflict |
360 |
happens. We want the *getTitle* or the *getPrice* accessor to mean the same thing |
361 |
wherever it is used from. |
362 |
|
363 |
In some cases, some attribute values can be *acquired* by one |
364 |
document from another document. In the Zope environment, |
365 |
acquisition allows to get the value of an attribute of one |
366 |
document from the value of its parents. However, in ERP5, |
367 |
we have multiple hierarchies. We may want to get the telephone |
368 |
of a person based on the office he/she is working (which is a region |
369 |
based tree) while we want to get the name of his product line |
370 |
through the division he/she belongs to (this is a product line tree). |
371 |
|
372 |
Explicit Relational Acquisition provides a simple way to implement |
373 |
this feature. Let us look at the example bellow:: |
374 |
|
375 |
|
376 |
class SalesOpportunity: |
377 |
""" |
378 |
Sales Opportunity properties and categories |
379 |
""" |
380 |
|
381 |
_properties = ( |
382 |
{ 'id' : 'client_organisation', |
383 |
'description' : 'The organisation involved', |
384 |
'type' : 'string', |
385 |
'acquisition' : { |
386 |
'base_category' : ('client',), |
387 |
'portal_type' : ('Organisation',), |
388 |
'copy_value' : 0, |
389 |
'accessor_id' : 'getTitle', |
390 |
'depends' : None |
391 |
}, |
392 |
'mode' : 'w' }, |
393 |
{ 'id' : 'client_person', |
394 |
'description' : "The contact person involved", |
395 |
'type' : 'string', |
396 |
'acquisition' : { |
397 |
'base_category' : ('client',), |
398 |
'portal_type' : ('Person',), |
399 |
'copy_value' : 0, |
400 |
'accessor_id' : 'getTitle', |
401 |
'depends' : None |
402 |
}, |
403 |
'mode' : 'w' }, |
404 |
) |
405 |
|
406 |
_categories = ('role', 'group', 'activity', 'function', |
407 |
'social_form', 'skill', 'market_segment') |
408 |
|
409 |
|
410 |
The name of the client_person is acquired by look at documents |
411 |
of type *Person* in the *client* tree (or relation) and applying |
412 |
the getTitle method. The attribute can be calculated each time |
413 |
(copy_value is 0) or copied once for all (copy_value is 0). |
414 |
|
415 |
Accessors: |
416 |
|
417 |
_getReference |
418 |
_getReferencePath |
419 |
_getReferenceList |
420 |
|
421 |
|
422 |
FAQ |
423 |
|
424 |
Do I need to create an attribute for a category ? |
425 |
|
426 |
It depends : if some information can be in a different application, |
427 |
you need to holde the information somewhere, .... |
428 |
|
429 |
Possible scenario |
430 |
|
431 |
copy_value |
432 |
mask_value |
433 |
sync_value |
434 |
|
435 |
|
436 |
Future |
437 |
|
438 |
Atribute lookup should be put outside property sheet |
439 |
into a tool ? |