Commit 880e6be6 authored by Jérome Perrin's avatar Jérome Perrin

Merge remote-tracking branch 'nexedi/master' into fix/pickles

parents 53616d78 c45c2295
Pipeline #25402 failed with stage
in 0 seconds
erp5_full_text_mroonga_catalog
\ No newline at end of file
"""Check consistency of an accounting transaction.
This verifies the constraints defined in constraints and also "temporary" constraints,
such as "client is validated" or "accounting period is open" that are currently defined
in workflow script.
This is intentded to be used in custom scripts creating accounting transactions and
validating them.
"""
context.Base_checkConsistency()
accounting_workflow = context.getPortalObject().portal_workflow.accounting_workflow
accounting_workflow.script_validateTransactionLines(
{
'object': context,
'kwargs': {},
'transition': accounting_workflow.transition_deliver_action
})
......@@ -54,7 +54,7 @@
</item>
<item>
<key> <string>id</string> </key>
<value> <string>WebSite_getJSONSchema</string> </value>
<value> <string>AccountingTransaction_checkConsistency</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -4,18 +4,28 @@ If accounting_transaction_line_uid_list is not passed, this script assumes that
it's called on the context of an accounting transaction and it guess the grouping
of related accounting transactions using causality.
"""
import collections
from Products.ERP5Type.Utils import int2letter
# this dict associates (node, section, mirror_section, extra_grouping_parameter) to a list of
# accounting lines info (total_price, date and path)
lines_per_node = {}
portal = context.getPortalObject()
# this counter associates the lines to the count of entities that are
# impacted by this accounting line.
# Typically it is 1 if the line is only for source/destination like in
# "normal" invoices and it can be 2 for Internal Invoices.
# When we found a match that can be grouped, we decrement this counter for
# this line. If all lines of the group have a 0 count, we can set grouping
# reference. This is a way to set grouping reference when the group is only
# valid for one side, it has to be valid for both side.
non_grouped_section_count = collections.Counter()
allow_grouping_with_different_quantity = portal.portal_preferences.getPreference(
'preferred_grouping_with_different_quantities', 0)
portal = context.getPortalObject()
if portal.portal_preferences.getPreference(
'preferred_grouping_with_different_quantities', False):
return
accounting_transaction_line_value_list = []
if accounting_transaction_line_uid_list is None:
......@@ -25,18 +35,21 @@ if accounting_transaction_line_uid_list is None:
accounting_transaction.getUid() != context.getUid():
continue
for line in accounting_transaction.getMovementList(
portal.getPortalAccountingMovementTypeList()):
portal.getPortalAccountingMovementTypeList()):
if line.getGroupingReference():
continue
accounting_transaction_line_value_list.append(line)
else:
if accounting_transaction_line_uid_list:
accounting_transaction_line_value_list = [
brain.getObject() for brain in portal.portal_catalog(uid=accounting_transaction_line_uid_list)]
brain.getObject() for brain in portal.portal_catalog(
uid=accounting_transaction_line_uid_list)
]
for line in accounting_transaction_line_value_list:
accounting_transaction = line.getParentValue()
if accounting_transaction.AccountingTransaction_isSourceView():
# source
node_relative_url = line.getSource(portal_type='Account')
if node_relative_url:
section_relative_url = None
source_section = line.getSourceSectionValue(portal_type='Organisation')
if source_section is not None:
......@@ -44,51 +57,88 @@ for line in accounting_transaction_line_value_list:
source_section.Organisation_getMappingRelatedOrganisation()
section_relative_url = source_section.getRelativeUrl()
line_relative_url = line.getRelativeUrl()
non_grouped_section_count.update({line_relative_url: 1})
lines_per_node.setdefault(
(line.getSource(portal_type='Account'),
section_relative_url,
line.getDestinationSection(),
line.AccountingTransactionLine_getGroupingExtraParameterList(source=True),
), []).append(
dict(total_price=line.getSourceInventoriatedTotalAssetPrice() or 0,
date=line.getStartDate(),
path=line.getRelativeUrl()))
else:
(
node_relative_url,
section_relative_url,
line.getDestinationSection(),
line.AccountingTransactionLine_getGroupingExtraParameterList(
source=True),
), []).append(
dict(
total_price=line.getSourceInventoriatedTotalAssetPrice() or 0,
date=line.getStartDate(),
path=line_relative_url))
# destination
node_relative_url = line.getDestination(portal_type='Account')
if node_relative_url:
section_relative_url = None
destination_section = line.getDestinationSectionValue(
portal_type='Organisation')
portal_type='Organisation')
if destination_section is not None:
destination_section = \
destination_section.Organisation_getMappingRelatedOrganisation()
section_relative_url = destination_section.getRelativeUrl()
line_relative_url = line.getRelativeUrl()
non_grouped_section_count.update({line_relative_url: 1})
lines_per_node.setdefault(
(line.getDestination(portal_type='Account'),
section_relative_url,
line.getSourceSection(),
line.AccountingTransactionLine_getGroupingExtraParameterList(source=False),
), []).append(
dict(total_price=line.getDestinationInventoriatedTotalAssetPrice() or 0,
date=line.getStopDate(),
path=line.getRelativeUrl()))
(
node_relative_url,
section_relative_url,
line.getSourceSection(),
line.AccountingTransactionLine_getGroupingExtraParameterList(
source=False),
), []).append(
dict(
total_price=line.getDestinationInventoriatedTotalAssetPrice() or 0,
date=line.getStopDate(),
path=line_relative_url))
changed_line_list = []
for (node, section, mirror_section, _), line_info_list in lines_per_node.items():
if node is None:
continue
total_price = sum([l['total_price'] for l in line_info_list])
# get the currency rounding for this section
# We do two passes, a first pass to check if lines are groupable from both
# sides in case of internal invoices - then a second path to actually set
# grouping references on groups that were groupable from both sides.
for (
node,
section,
mirror_section,
_,
), line_info_list in list(lines_per_node.items()):
# get the currency rounding for this section, with a fallback that something that would
# allow grouping in case precision is not defined.
currency_precision = 5
if section:
default_currency = portal.restrictedTraverse(section).getPriceCurrencyValue()
default_currency = portal.restrictedTraverse(
section).getPriceCurrencyValue()
if default_currency is not None:
total_price = round(total_price, default_currency.getQuantityPrecision())
if total_price == 0 or allow_grouping_with_different_quantity:
currency_precision = default_currency.getQuantityPrecision()
total_price = round(
sum([l['total_price'] for l in line_info_list]), currency_precision)
if total_price == 0:
for line in line_info_list:
non_grouped_section_count.subtract({line['path']: 1})
else:
# this group is not valid, remove it for second path
del lines_per_node[node, section, mirror_section, _]
changed_line_set = set()
for (
node,
section,
mirror_section,
_,
), line_info_list in lines_per_node.items():
if sum(non_grouped_section_count[line['path']] for line in line_info_list) == 0:
# we should include mirror node in the id_group, but this would reset
# id generators and generate grouping references that were already used.
id_group = ('grouping_reference', node, section, mirror_section)
previous_default = context.portal_ids.getLastGeneratedId(id_group=id_group, default=0)
grouping_reference = portal.portal_ids.generateNewId(id_generator='uid',
id_group=id_group,
default=previous_default + 1)
previous_default = context.portal_ids.getLastGeneratedId(
id_group=id_group, default=0)
grouping_reference = portal.portal_ids.generateNewId(
id_generator='uid', id_group=id_group, default=previous_default + 1)
# convert from int to letters
string_reference = int2letter(grouping_reference)
......@@ -97,11 +147,14 @@ for (node, section, mirror_section, _), line_info_list in lines_per_node.items()
date = max([line['date'] for line in line_info_list])
for line in line_info_list:
if line['path'] in changed_line_set:
continue
line_obj = portal.restrictedTraverse(line['path'])
assert not line_obj.getGroupingReference(), line
line_obj.setGroupingReference(string_reference)
line_obj.setGroupingDate(date)
line_obj.reindexObject(activate_kw=dict(tag='accounting_grouping_reference'))
changed_line_list.append(line['path'])
line_obj.reindexObject(
activate_kw=dict(tag='accounting_grouping_reference'))
changed_line_set.add(line['path'])
return changed_line_list
return changed_line_set
......@@ -2,4 +2,4 @@
because only validated accounts are displayed in the cache.
"""
if sci['object'].getValidationState() == 'validated':
container.Account_flushAccountListCache(sci)
container.script_Account_flushAccountListCache(sci)
......@@ -28,7 +28,7 @@ transaction.Base_checkConsistency()
skip_period_validation = state_change['kwargs'].get(
'skip_period_validation', 0)
transition = state_change['transition']
if transition.id in ('plan_action', 'confirm_action') :
if transition.getReference() in ('plan_action', 'confirm_action') :
skip_period_validation = 1
source_section = transaction.getSourceSectionValue(
......
......@@ -13,7 +13,7 @@ section_portal_type_list = ['Person', 'Organisation']
invalid_state_list = ['invalidated', 'deleted']
# first of all, validate the transaction itself
container.validateTransaction(state_change)
container.script_validateTransaction(state_change)
# Check that all lines uses open accounts, and doesn't use invalid third
......
......@@ -42,15 +42,18 @@
<item>
<key> <string>guard_permission</string> </key>
<value>
<tuple>
<string>Modify portal content</string>
</tuple>
<tuple/>
</value>
</item>
<item>
<key> <string>guard_role</string> </key>
<value>
<tuple/>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
......
......@@ -48,6 +48,7 @@
<value>
<tuple>
<string>Assignor</string>
<string>Associate</string>
</tuple>
</value>
</item>
......
......@@ -40,15 +40,18 @@
<item>
<key> <string>guard_permission</string> </key>
<value>
<tuple>
<string>Modify portal content</string>
</tuple>
<tuple/>
</value>
</item>
<item>
<key> <string>guard_role</string> </key>
<value>
<tuple/>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
......
......@@ -50,6 +50,7 @@
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
</tuple>
</value>
</item>
......
......@@ -41,15 +41,17 @@
<item>
<key> <string>guard_permission</string> </key>
<value>
<tuple>
<string>Modify portal content</string>
</tuple>
<tuple/>
</value>
</item>
<item>
<key> <string>guard_role</string> </key>
<value>
<tuple/>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
</tuple>
</value>
</item>
<item>
......
......@@ -51,6 +51,7 @@
<tuple>
<string>Assignor</string>
<string>Assignee</string>
<string>Associate</string>
</tuple>
</value>
</item>
......
return sci.getPortal().portal_workflow.accounting_workflow.scripts[script.getId()](sci)
return sci.getPortal().portal_workflow.accounting_workflow[script.getId()](sci)
state_change['object'].AccountingTransaction_setDefaultMirrorAccountList()
return container.setReferences(state_change)
return container.script_setReferences(state_change)
return sci.getPortal().portal_workflow.accounting_workflow.scripts[script.getId()](sci)
return sci.getPortal().portal_workflow.accounting_workflow[script.getId()](sci)
return state_change.getPortal().portal_workflow.accounting_workflow.scripts[script.getId()](state_change)
return state_change.getPortal().portal_workflow.accounting_workflow[script.getId()](state_change)
return state_change.getPortal().portal_workflow.accounting_workflow.scripts[script.getId()](state_change)
return state_change.getPortal().portal_workflow.accounting_workflow[script.getId()](state_change)
......@@ -7,4 +7,4 @@ if old_state.getId() == 'draft':
if internal_invoice.InternalInvoiceTransaction_getAuthenticatedUserSection() == internal_invoice.getDestinationSection():
raise ValidationFailed(translateString("Your entity should not be destination."))
return state_change.getPortal().portal_workflow.accounting_workflow.scripts[script.getId()](state_change)
return state_change.getPortal().portal_workflow.accounting_workflow[script.getId()](state_change)
......@@ -48,9 +48,10 @@
<key> <string>guard_role</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Owner</string>
</tuple>
</value>
</item>
......
......@@ -41,15 +41,18 @@
<item>
<key> <string>guard_permission</string> </key>
<value>
<tuple>
<string>Modify portal content</string>
</tuple>
<tuple/>
</value>
</item>
<item>
<key> <string>guard_role</string> </key>
<value>
<tuple/>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
......
......@@ -48,6 +48,7 @@
<value>
<tuple>
<string>Assignor</string>
<string>Associate</string>
</tuple>
</value>
</item>
......
......@@ -40,15 +40,18 @@
<item>
<key> <string>guard_permission</string> </key>
<value>
<tuple>
<string>Modify portal content</string>
</tuple>
<tuple/>
</value>
</item>
<item>
<key> <string>guard_role</string> </key>
<value>
<tuple/>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
......
......@@ -68,6 +68,7 @@
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
</tuple>
</value>
</item>
......
......@@ -59,15 +59,17 @@
<item>
<key> <string>guard_permission</string> </key>
<value>
<tuple>
<string>Modify portal content</string>
</tuple>
<tuple/>
</value>
</item>
<item>
<key> <string>guard_role</string> </key>
<value>
<tuple/>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
</tuple>
</value>
</item>
<item>
......
......@@ -51,6 +51,7 @@
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
</tuple>
</value>
</item>
......
erp5_full_text_mroonga_catalog
erp5_core_proxy_field_legacy
erp5_base
erp5_pdm
......
erp5_full_text_mroonga_catalog
erp5_core_proxy_field_legacy
erp5_base
erp5_pdm
......
erp5_full_text_mroonga_catalog
erp5_dummy_movement
\ No newline at end of file
erp5_full_text_mroonga_catalog
erp5_base
erp5_core_proxy_field_legacy
erp5_pdm
......
......@@ -46,7 +46,7 @@ class TestERP5Administration(InventoryAPITestCase):
"""
Same list as for Inventory API and add erp5_administration
"""
return InventoryAPITestCase.getBusinessTemplateList(self) + ('erp5_administration', )
return InventoryAPITestCase.getBusinessTemplateList(self) + ('erp5_full_text_mroonga_catalog', 'erp5_administration')
def test_01_RunCheckStockTableAlarm(self):
"""
......
erp5_full_text_mroonga_catalog
erp5_core_proxy_field_legacy
erp5_base
erp5_pdm
......
erp5_full_text_mroonga_catalog
\ No newline at end of file
......@@ -55,10 +55,6 @@
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>type_class</string> </key>
<value> <string>Delivery</string> </value>
</item>
</dictionary>
</pickle>
</record>
......
......@@ -82,10 +82,6 @@
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>type_class</string> </key>
<value> <string>Delivery</string> </value>
</item>
</dictionary>
</pickle>
</record>
......
erp5_full_text_mroonga_catalog
erp5_core_proxy_field_legacy
erp5_base
erp5_simulation
......
erp5_full_text_mroonga_catalog
\ No newline at end of file
......@@ -29,7 +29,7 @@
##############################################################################
from AccessControl import ClassSecurityInfo
from Products.CMFCore.WorkflowCore import WorkflowAction
from Products.ERP5Type.Base import WorkflowMethod
from Products.ERP5Type import Permissions, PropertySheet
from Products.ERP5Type.XMLObject import XMLObject
......@@ -64,7 +64,7 @@ class ApparelMeasurement(XMLObject, XMLMatrix, Image):
# Inheritance
_edit = Image._edit
security.declareProtected(Permissions.ModifyPortalContent, 'edit' )
edit = WorkflowAction( _edit )
edit = WorkflowMethod( _edit )
security.declareProtected(Permissions.View, 'index_html')
index_html = Image.index_html
......
erp5_full_text_myisam_catalog
erp5_mrp
\ No newline at end of file
......@@ -51,6 +51,7 @@ class TestArchive(InventoryAPITestCase):
def getBusinessTemplateList(self):
return InventoryAPITestCase.getBusinessTemplateList(self) + (
'erp5_archive',
'erp5_full_text_mroonga_catalog',
)
# Different variables used for this test
......
erp5_full_text_mroonga_catalog
erp5_core_proxy_field_legacy
erp5_base
erp5_pdm
......
......@@ -3,6 +3,7 @@
"""
from DateTime import DateTime
from Products.ZSQLCatalog.SQLCatalog import Query
from erp5.component.module.Log import log
request = context.REQUEST
portal = context.getPortalObject()
......@@ -17,6 +18,13 @@ one_second = 1/24.0/60.0/60.0
check_duration = portal_preferences.getPreferredAuthenticationFailureCheckDuration()
block_duration = portal_preferences.getPreferredAuthenticationFailureBlockDuration()
max_authentication_failures = portal_preferences.getPreferredMaxAuthenticationFailure()
if None in (check_duration,
block_duration,
max_authentication_failures):
log('Login block is not working because authentication policy in system preference is not set properly.')
return 0
check_time = now - check_duration*one_second
# some failures might be still unindexed
......
erp5_full_text_mroonga_catalog
erp5_core_proxy_field_legacy
erp5_base
erp5_web
......
erp5_full_text_mroonga_catalog
erp5_core_proxy_field_legacy
erp5_base
erp5_pdm
......
erp5_full_text_mroonga_catalog
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ActionInformation" module="Products.CMFCore.ActionInformation"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_jio_action</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_jio_action</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>change_function</string> </value>
</item>
<item>
<key> <string>permissions</string> </key>
<value>
<tuple>
<string>Modify portal content</string>
</tuple>
</value>
</item>
<item>
<key> <string>priority</string> </key>
<value> <float>2.0</float> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Change Function</string> </value>
</item>
<item>
<key> <string>visible</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>string:${object_url}/Base_viewChangeIdDialog</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -50,14 +50,7 @@ else:
from Products.ERP5Security.ERP5LoginUserManager import ERP5LoginUserManager
class UserExistsError(
ValidationFailed,
# to workaround pylint's false positive:
# Exception doesn't inherit from standard "Exception" class (nonstandard-exception)
# because it cannot import ValidationFailed (which is set by a monkey patch), we also
# inherit from Exception.
Exception,
):
class UserExistsError(ValidationFailed):
def __init__(self, user_id):
super(UserExistsError, self).__init__('user id %s already exists' % (user_id, ))
......
......@@ -24,4 +24,4 @@ for simulation_movement in simulation_movement_list:
if simulation_movement.getOrder() == delivery_movement.getRelativeUrl():
simulation_movement.setOrder(None)
context.DeliveryMovement_updateSimulation(state_change)
container.script_DeliveryMovement_updateSimulation(state_change)
......@@ -44,6 +44,7 @@ Embedded File | fullsize_view
Embedded File | view
Embedded File | web_view
Embedded Folder | view
External Identifier | change_function
External Identifier | view
Fax | change_function
Fax | view
......
erp5_full_text_mroonga_catalog
\ No newline at end of file
erp5_full_text_mroonga_catalog
\ No newline at end of file
erp5_full_text_myisam_catalog
erp5_core_proxy_field_legacy
erp5_pdm
erp5_simulation
......
erp5_full_text_myisam_catalog
\ No newline at end of file
......@@ -31,7 +31,7 @@ import os
import random
import unittest
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from Products.DCWorkflow.DCWorkflow import ValidationFailed
from Products.ERP5Type.Core.Workflow import ValidationFailed
from AccessControl import Unauthorized
class TestCertificateAuthority(ERP5TypeTestCase):
......
erp5_full_text_mroonga_catalog
\ No newline at end of file
erp5_full_text_mroonga_catalog
erp5_core_proxy_field_legacy
erp5_base
erp5_simulation
......
......@@ -145,28 +145,22 @@
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>content_translation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAY=</string> </persistent>
</value>
</item>
<item>
<key> <string>edit_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAc=</string> </persistent>
<persistent> <string encoding="base64">AAAAAAAAAAY=</string> </persistent>
</value>
</item>
<item>
<key> <string>user_account_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAg=</string> </persistent>
<persistent> <string encoding="base64">AAAAAAAAAAc=</string> </persistent>
</value>
</item>
<item>
<key> <string>validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAk=</string> </persistent>
<persistent> <string encoding="base64">AAAAAAAAAAg=</string> </persistent>
</value>
</item>
</dictionary>
......@@ -176,63 +170,6 @@
</pickle>
</record>
<record id="6" aka="AAAAAAAAAAY=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>validate_content_translation</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>System Processes</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>content_translation_state</string> </key>
<value> <string>latest</string> </value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass>
<global name="DateTime" module="DateTime.DateTime"/>
</klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1664349798.66</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
</list>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="7" aka="AAAAAAAAAAc=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
......@@ -295,7 +232,7 @@
</dictionary>
</pickle>
</record>
<record id="8" aka="AAAAAAAAAAg=">
<record id="7" aka="AAAAAAAAAAc=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
......@@ -354,7 +291,7 @@
</dictionary>
</pickle>
</record>
<record id="9" aka="AAAAAAAAAAk=">
<record id="8" aka="AAAAAAAAAAg=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
......
erp5_full_text_mroonga_catalog
\ No newline at end of file
erp5_full_text_myisam_catalog
erp5_base
\ No newline at end of file
......@@ -86,7 +86,7 @@ class TestLiveConfiguratorWorkflowMixin(SecurityTestCase):
def getBusinessTemplateList(self):
return ('erp5_core_proxy_field_legacy',
'erp5_full_text_catalog',
'erp5_full_text_mroonga_catalog',
'erp5_base',
'erp5_configurator',
'erp5_configurator_standard',)
......
......@@ -41,6 +41,7 @@ class TestConfiguratorItem(TestLiveConfiguratorWorkflowMixin):
def getBusinessTemplateList(self):
return ('erp5_core_proxy_field_legacy',
'erp5_full_text_mroonga_catalog',
'erp5_base',
'erp5_configurator',
'erp5_simulation',
......
......@@ -38,6 +38,7 @@ class TestConfiguratorTool(TestLiveConfiguratorWorkflowMixin):
def getBusinessTemplateList(self):
return ('erp5_core_proxy_field_legacy',
'erp5_full_text_mroonga_catalog',
'erp5_base',
'erp5_configurator',
'erp5_configurator_standard',)
......
erp5_full_text_mroonga_catalog
erp5_core_proxy_field_legacy
erp5_full_text_mroonga_catalog
erp5_base
erp5_configurator_standard
erp5_simulation
......
erp5_full_text_mroonga_catalog
\ No newline at end of file
erp5_core_proxy_field_legacy
erp5_full_text_mroonga_catalog
erp5_base
\ No newline at end of file
erp5_core_proxy_field_legacy
erp5_full_text_mroonga_catalog
erp5_base
\ No newline at end of file
......@@ -197,6 +197,7 @@ class TestOfficeJSSDKConfigurator(SecurityTestCase):
'erp5_dms_ui_test',
'erp5_font',
'erp5_forge',
'erp5_full_text_mroonga_catalog',
'erp5_gadget_interface_validator',
'erp5_hal_json_style',
'erp5_hr',
......
erp5_full_text_mroonga_catalog
erp5_administration
\ No newline at end of file
erp5_configurator_standard
\ No newline at end of file
erp5_configurator_standard
erp5_full_text_mroonga_catalog
\ No newline at end of file
......@@ -75,10 +75,6 @@
<key> <string>solver_action_title</string> </key>
<value> <string>First In, First Out</string> </value>
</item>
<item>
<key> <string>type_class</string> </key>
<value> <string>FIFODeliverySolver</string> </value>
</item>
</dictionary>
</pickle>
</record>
......
......@@ -75,10 +75,6 @@
<key> <string>solver_action_title</string> </key>
<value> <string>Last In, First Out</string> </value>
</item>
<item>
<key> <string>type_class</string> </key>
<value> <string>LIFODeliverySolver</string> </value>
</item>
</dictionary>
</pickle>
</record>
......
......@@ -75,10 +75,6 @@
<key> <string>solver_action_title</string> </key>
<value> <string>Minimise Price</string> </value>
</item>
<item>
<key> <string>type_class</string> </key>
<value> <string>MinimisePriceDeliverySolver</string> </value>
</item>
</dictionary>
</pickle>
</record>
......
......@@ -146,10 +146,6 @@
<key> <string>solver_action_title</string> </key>
<value> <string>Cancel Quantity</string> </value>
</item>
<item>
<key> <string>type_class</string> </key>
<value> <string>QuantityCancelSolver</string> </value>
</item>
<item>
<key> <string>workflow_list</string> </key>
<value>
......
......@@ -189,10 +189,6 @@
</tuple>
</value>
</item>
<item>
<key> <string>type_class</string> </key>
<value> <string>TradeModelSolver</string> </value>
</item>
<item>
<key> <string>workflow_list</string> </key>
<value>
......
......@@ -185,10 +185,6 @@
<key> <string>solver_action_title</string> </key>
<value> <string>Unify value</string> </value>
</item>
<item>
<key> <string>type_class</string> </key>
<value> <string>UnifySolver</string> </value>
</item>
<item>
<key> <string>workflow_list</string> </key>
<value>
......
erp5_full_text_mroonga_catalog
\ No newline at end of file
......@@ -39,7 +39,7 @@ class TestContentTranslation(ERP5TypeTestCase):
def getBusinessTemplateList(self):
return (
return ('erp5_full_text_mroonga_catalog',
'erp5_base',
'erp5_content_translation',
)
......
erp5_full_text_mroonga_catalog
erp5_l10n_fr
erp5_l10n_ja
\ No newline at end of file
......@@ -966,19 +966,23 @@ class TestBase(ERP5TypeTestCase, ZopeTestCase.Functional):
# Add a non-existent workflow.
pw = self.getWorkflowTool()
dummy_simulation_worlflow_id = 'fake_simulation_workflow'
dummy_validation_worlflow_id = 'fake_validation_workflow'
dummy_simulation_workflow_id = 'fake_simulation_workflow'
dummy_validation_workflow_id = 'fake_validation_workflow'
#Assume that erp5_styles workflow Manage permissions with acquired Role by default
addWorkflowByType(pw, 'erp5_workflow', dummy_simulation_worlflow_id)
addWorkflowByType(pw, 'erp5_workflow', dummy_validation_worlflow_id)
dummy_simulation_worlflow = pw[dummy_simulation_worlflow_id]
dummy_validation_worlflow = pw[dummy_validation_worlflow_id]
dummy_validation_worlflow.variables.setStateVar('validation_state')
dummy_simulation_workflow = pw.newContent(
portal_type='Workflow',
reference=dummy_simulation_workflow_id,
)
dummy_validation_workflow = pw.newContent(
portal_type='Workflow',
reference=dummy_validation_workflow_id,
state_variable='validation_state',
)
organisation_type = portal.portal_types.getTypeInfo(portal_type)
organisation_initial_workflow_list = organisation_type.getTypeWorkflowList()
organisation_type.setTypeWorkflowList([dummy_validation_worlflow_id,
dummy_simulation_worlflow_id])
permission_list = list(dummy_simulation_worlflow.permissions)
organisation_type.setTypeWorkflowList([dummy_validation_workflow_id,
dummy_simulation_workflow_id])
permission_list = dummy_simulation_workflow.getWorkflowManagedPermissionList()
manager_has_permission = {}
for permission in permission_list:
manager_has_permission[permission] = ('Manager',)
......@@ -989,7 +993,7 @@ class TestBase(ERP5TypeTestCase, ZopeTestCase.Functional):
user = getSecurityManager().getUser()
try:
self.assertTrue(permission_list)
self.assertFalse(dummy_simulation_worlflow.states.draft.permission_roles)
self.assertFalse(dummy_simulation_workflow['state_draft'].getStatePermissionRoleListDict())
#1
obj = module.newContent(portal_type=portal_type)
#No role is defined by default on workflow
......@@ -999,28 +1003,28 @@ class TestBase(ERP5TypeTestCase, ZopeTestCase.Functional):
for permission in permission_list:
self.assertTrue(user.has_permission(permission, obj))
#2 Now configure both workflow with same configuration
dummy_simulation_worlflow.states.draft.permission_roles = manager_has_permission.copy()
dummy_validation_worlflow.states.draft.permission_roles = manager_has_permission.copy()
dummy_simulation_worlflow.updateRoleMappingsFor(obj)
dummy_validation_worlflow.updateRoleMappingsFor(obj)
dummy_simulation_workflow['state_draft'].setStatePermissionRoleListDict(manager_has_permission.copy())
dummy_validation_workflow['state_draft'].setStatePermissionRoleListDict(manager_has_permission.copy())
dummy_simulation_workflow.updateRoleMappingsFor(obj)
dummy_validation_workflow.updateRoleMappingsFor(obj)
for permission in permission_list:
self.assertTrue(user.has_permission(permission, obj))
#3 change only dummy_simulation_worlflow
dummy_simulation_worlflow.states.draft.permission_roles = manager_has_no_permission.copy()
dummy_simulation_worlflow.updateRoleMappingsFor(obj)
#3 change only dummy_simulation_workflow
dummy_simulation_workflow['state_draft'].setStatePermissionRoleListDict(manager_has_no_permission.copy())
dummy_simulation_workflow.updateRoleMappingsFor(obj)
for permission in permission_list:
self.assertFalse(user.has_permission(permission, obj))
#4 enable acquisition for dummy_simulation_worlflow
dummy_simulation_worlflow.states.draft.permission_roles = None
dummy_simulation_worlflow.updateRoleMappingsFor(obj)
#4 enable acquisition for dummy_simulation_workflow
dummy_simulation_workflow['state_draft'].setAcquirePermissionList(permission_list)
dummy_simulation_workflow.updateRoleMappingsFor(obj)
for permission in permission_list:
self.assertTrue(user.has_permission(permission, obj))
finally:
# Make sure that the artificial workflow is not referred to any longer.
organisation_type.setTypeWorkflowList(organisation_initial_workflow_list)
pw.manage_delObjects([dummy_simulation_worlflow_id, dummy_validation_worlflow_id])
pw.manage_delObjects([dummy_simulation_workflow_id, dummy_validation_workflow_id])
def test_getViewPermissionOwnerDefault(self):
"""Test getViewPermissionOwner method behaviour"""
......
......@@ -39,7 +39,6 @@ from urllib import pathname2url
from Products.ERP5Type.Globals import PersistentMapping
from Products.ERP5Type.dynamic.lazy_class import ERP5BaseBroken
from Products.ERP5Type.tests.utils import LogInterceptor
from Products.ERP5Type.Workflow import addWorkflowByType
import shutil
import os
import random
......@@ -1315,8 +1314,11 @@ class BusinessTemplateMixin(ERP5TypeTestCase, LogInterceptor):
"""
wf_id = 'geek_workflow'
pw = self.getWorkflowTool()
addWorkflowByType(pw, WORKFLOW_TYPE, wf_id)
workflow = pw._getOb(wf_id, None)
workflow = pw.newContent(
portal_type='Workflow',
reference=wf_id,
)
self.tic()
self.assertTrue(workflow is not None)
sequence.edit(workflow_id=workflow.getId())
......@@ -2886,8 +2888,11 @@ class BusinessTemplateMixin(ERP5TypeTestCase, LogInterceptor):
"""
wf_id = 'custom_geek_workflow'
pw = self.getWorkflowTool()
addWorkflowByType(pw, WORKFLOW_TYPE, wf_id)
workflow = pw._getOb(wf_id, None)
workflow = pw.newContent(
portal_type='Workflow',
reference=wf_id,
)
self.tic()
self.assertTrue(workflow is not None)
sequence.edit(workflow_id=workflow.getId())
......@@ -3295,6 +3300,16 @@ class TestBusinessTemplate(BusinessTemplateMixin):
"""Tests the Title of the Template Tool."""
self.assertEqual('Business Templates', self.getTemplateTool().Title())
def test_business_template_properties_sorted(self):
bt = self.portal.portal_templates.newContent(
portal_type='Business Template')
bt.edit(template_path_list=['b', 'c', 'a'])
self.assertEqual(bt.getTemplatePathList(), ['a', 'b', 'c'])
bt.edit(template_keep_workflow_path_list=['b', 'c', 'a'])
self.assertEqual(bt.getTemplateKeepWorkflowPathList(), ['a', 'b', 'c'])
bt.edit(template_keep_last_workflow_history_only_path_list=['b', 'c', 'a'])
self.assertEqual(bt.getTemplateKeepLastWorkflowHistoryOnlyPathList(), ['a', 'b', 'c'])
def test_01_checkNewSite(self):
"""Test Check New Site"""
sequence_list = SequenceList()
......@@ -6812,7 +6827,7 @@ class TestBusinessTemplate(BusinessTemplateMixin):
portal_type='Business Template',
title=self.id(),
template_path_list=(
'portal_categories/test_category/**'
'portal_categories/test_category/**',
),
template_base_category_list=['test_category'],
)
......@@ -6883,7 +6898,7 @@ class TestBusinessTemplate(BusinessTemplateMixin):
portal_type='Business Template',
title=self.id(),
template_path_list=(
'portal_categories/test_category/**'
'portal_categories/test_category/**',
),
template_base_category_list=['test_category'],
)
......
......@@ -202,7 +202,7 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor):
return "ERP5Catalog"
def getBusinessTemplateList(self):
return ('erp5_base',)
return ('erp5_full_text_mroonga_catalog', 'erp5_base',)
# Different variables used for this test
username = 'seb'
......@@ -4123,6 +4123,9 @@ class CatalogToolUpgradeSchemaTestCase(ERP5TypeTestCase):
"""Tests for "upgrade schema" feature of ERP5 Catalog.
"""
def getBusinessTemplateList(self):
return ("erp5_full_text_mroonga_catalog",)
def afterSetUp(self):
# Add two connections
db1, db2 = getExtraSqlConnectionStringList()[:2]
......
......@@ -46,6 +46,7 @@ class TestERP5Coordinate(ERP5TypeTestCase):
Return the list of required business templates.
"""
return ('erp5_core_proxy_field_legacy',
'erp5_full_text_mroonga_catalog',
'erp5_base',)
def afterSetUp(self):
......
......@@ -31,10 +31,51 @@ from Products.Formulator.TALESField import TALESMethod
from Products.ERP5Type.Core.Folder import Folder
from Products.ERP5Form.Form import field_value_cache
class TestProxify(ERP5TypeTestCase):
def getTitle(self):
return "Proxify"
class TestERP5Form(ERP5TypeTestCase):
def afterSetUp(self):
self.portal.portal_skins.custom.manage_addProduct[
'PageTemplates'].manage_addPageTemplate(
'Base_viewTestRenderer', 'Base_viewTestRenderer')
self.page_template = self.portal.portal_skins.custom.Base_viewTestRenderer
self.page_template.write('''
<html>
<form>
<tal:block tal:repeat="field form/get_fields">
<tal:block tal:replace="structure field/render" />
</tal:block>
</form>
</html>
''')
self.portal.portal_skins.custom.manage_addProduct['ERP5Form'].addERP5Form(
'Base_viewTest', 'Test')
self.form = self.portal.portal_skins.custom.Base_viewTest
self.form.manage_addField('my_string_field', 'String Field', 'StringField')
self.form.my_string_field.values['default'] = "test string field"
self.form.pt = self.page_template.getId()
def beforeTearDown(self):
self.abort()
for custom_skin in (self.form.getId(), self.page_template.getId(),):
if custom_skin in self.portal.portal_skins.custom.objectIds():
self.portal.portal_skins.custom.manage_delObjects([custom_skin])
self.commit()
def test_call(self):
html = self.form()
self.assertIn("test string field", html)
def test_zmi(self):
# minimal tests for custom ZMI views
self.assertTrue(self.form.formProxify())
self.assertTrue(self.form.formUnProxify())
self.assertTrue(self.form.formShowRelatedProxyFields())
class TestProxify(ERP5TypeTestCase):
def afterSetUp(self):
# base field library
......
......@@ -8,15 +8,21 @@
<dictionary>
<item>
<key> <string>default_reference</string> </key>
<value> <string>testProxify</string> </value>
<value> <string>testERP5Form</string> </value>
</item>
<item>
<key> <string>default_source_reference</string> </key>
<value> <string>Products.ERP5Form.tests.testProxify</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>test.erp5.testProxify</string> </value>
<value> <string>test.erp5.testERP5Form</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
......
......@@ -131,6 +131,22 @@ class TestERP5Workflow(ERP5TypeTestCase):
history = doc.workflow_history['wf']
self.assertEqual(len(history), 2)# create, transition1
transition_variable = transition1.newContent(
portal_type='Workflow Transition Variable',
causality_value=variable1,
variable_default_expression='string:Set by transition variable',
)
workflow._executeTransition(doc,transition1)
self.assertEqual(
workflow.getCurrentStatusDict(doc)['variable1'],
"Set by transition variable")
# Without an expression, the variable is set to None
transition_variable.setVariableDefaultExpression(None)
workflow._executeTransition(doc,transition1)
self.assertEqual(workflow.getCurrentStatusDict(doc)['variable1'], None)
def test_afterScript(self):
......
......@@ -35,7 +35,8 @@ class TestI18NSearch(ERP5TypeTestCase):
return "I18N Search"
def getBusinessTemplateList(self):
return ('erp5_base', )
return ('erp5_full_text_mroonga_catalog',
'erp5_base',)
def afterSetUp(self):
self.person_module = self.portal.person_module
......
......@@ -398,9 +398,9 @@ return printed
'Base_viewGeek',
'View')
form = skin_folder._getOb('Base_viewGeek', None)
form.manage_addField('my_title', 'Title', 'ProxyField')
form.manage_addField('my_proxy_field', 'Proxy', 'ProxyField')
field = form.my_title
field = form.my_proxy_field
self.assertFalse(form.get_fields())
self.assertEqual([field], form.get_fields(include_disabled=True))
......@@ -409,10 +409,22 @@ return printed
self.assertEqual('', field.get_tales('default'))
regexp = '^%s$' % re.escape("Can't find the template field of"
" <ProxyField at /%s/portal_skins/erp5_geek/Base_viewGeek/my_title>"
" <ProxyField at /%s/portal_skins/erp5_geek/Base_viewGeek/my_proxy_field>"
% self.portal.getId())
for func in ( field.render
, partial(field.get_value, 'default')
, partial(field.get_recursive_tales, 'default')
):
self.assertRaisesRegexp(BrokenProxyField, regexp, func)
# we can still view the field in ZMI
form.manage_main()
field.manage_main()
# and repair it
form.manage_addField('my_field', 'Title', 'StringField')
field.manage_edit(
{
'field_form_id': 'Base_viewGeek',
'field_field_id': 'my_field',
})
self.assertEqual(field.getTemplateField(), form.my_field)
......@@ -43,7 +43,7 @@ class TestVanillaERP5Catalog(ERP5TypeTestCase, LogInterceptor):
return "VanillaERP5Catalog"
def getBusinessTemplateList(self):
return ('erp5_base', )
return ('erp5_full_text_mroonga_catalog', 'erp5_base')
# Different variables used for this test
username = 'seb'
......
......@@ -84,7 +84,6 @@ class TestZODBHistory(ERP5TypeTestCase):
self.assertTrue(len(history_list) > 0)
d = history_list[0]
changes = d['changes']
self.assertEqual(changes['portal_type'], 'Organisation')
self.assertEqual(changes['id'], 'org')
self.assertTrue(changes['uid'] is not None)
......@@ -134,6 +133,17 @@ class TestZODBHistory(ERP5TypeTestCase):
from zExceptions import Unauthorized
self.assertRaises(Unauthorized, document.Base_viewZODBHistory)
def test_ZODBHistoryNonAsciiProperty(self):
self.loginByUserName('tatuya')
document = self.addOrganisation(self.id())
document.edit(title='ネクセディ', default_address_city='千代田区')
self.commit()
_, change, = document.Base_getZODBHistoryList()
self.assertIn('title: ネクセディ', change.getProperty('changes'))
# no encoding error
document.Base_viewZODBHistory()
def test_ZODBHistoryBinaryData(self):
"""
Make sure ZODB History view works with binary content
......@@ -156,9 +166,9 @@ class TestZODBHistory(ERP5TypeTestCase):
document.Base_viewZODBHistory()
change, = document.Base_getZODBHistoryList()
self.assertIn('data:(binary)', change.getProperty('changes'))
self.assertIn('content_type:image/png', change.getProperty('changes'))
self.assertIn('title:ロゴ', change.getProperty('changes'))
self.assertIn('data: (binary)', change.getProperty('changes'))
self.assertIn('content_type: image/png', change.getProperty('changes'))
self.assertIn('title: ロゴ', change.getProperty('changes'))
def test_suite():
......
erp5_full_text_mroonga_catalog
erp5_base
erp5_csv_style
erp5_core_proxy_field_legacy
\ No newline at end of file
test.erp5.testAccessTab
test.erp5.testSequence
test.erp5.testActivityTool
test.erp5.testAlarm
test.erp5.testArrow
......@@ -8,8 +7,8 @@ test.erp5.testAutoLogout
test.erp5.testBase
test.erp5.testBusinessTemplate
test.erp5.testCache
test.erp5.testCacheTool
test.erp5.testCachedSkinsTool
test.erp5.testCacheTool
test.erp5.testConstraint
test.erp5.testContributionRegistryTool
test.erp5.testCookieCrumbler
......@@ -20,6 +19,7 @@ test.erp5.testERP5Catalog
test.erp5.testERP5Category
test.erp5.testERP5Coordinate
test.erp5.testERP5Core
test.erp5.testERP5Form
test.erp5.testERP5Type
test.erp5.testERP5TypeInterfaces
test.erp5.testERP5Workflow
......@@ -42,11 +42,11 @@ test.erp5.testPerson
test.erp5.testPredicate
test.erp5.testPreferences
test.erp5.testPropertyRecordable
test.erp5.testProxify
test.erp5.testProxyField
test.erp5.testQueryModule
test.erp5.testRestrictedPythonSecurity
test.erp5.testSelectionTool
test.erp5.testSequence
test.erp5.testSessionTool
test.erp5.testTimeout
test.erp5.testTimerService
......
theme_used = context.Base_getThemeDict(css_path="template_css/book")
if theme:
return theme_used.get('theme', '')
document_description = context.REQUEST.get('override_document_description', '')
document_short_title = context.REQUEST.get('override_document_short_title', '')
document_title = context.REQUEST.get('override_document_title', '')
document_version = context.REQUEST.get('override_document_version', '')
document_reference = context.REQUEST.get('override_document_reference', '')
if title:
return document_title if document_title else context.getTitle()
if short_title:
return document_short_title if document_short_title else context.getShortTitle()
if reference:
book_reference = document_reference if document_reference else context.getReference()
if not book_reference:
book_title = document_title if document_title else context.getTitle()
book_prefix = context.portal_preferences.getPreferredCorporateIdentityTemplateBookDocumentPrefix() or "Book."
book_reference = book_prefix + book_title.replace(" ", ".")
return book_reference
if description:
return document_description if document_description else context.getDescription()
if version:
return document_version if document_version else context.getVersion()
source = context.Base_getSourceDict(
source = context.getSource(),
override_source_person_title = context.REQUEST.get('override_source_person_title', None),
override_source_organisation_title = context.REQUEST.get('override_source_organisation_title', None),
override_logo_reference=context.REQUEST.get('override_logo_reference', None),
theme_logo_url=theme_used.get("theme_logo_url", None)
)
if source_organisation:
return source.get("organisation_title", "")
if source_person:
return source.get("name", "")
if logo:
url = source.get('enhanced_logo_url', '')
return url.split('?')[0]
return ''
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment