Merge branch 'master' of github.com:collective/collective.contact.core
This commit is contained in:
commit
dde54736ca
15
CHANGES.rst
15
CHANGES.rst
|
@ -4,9 +4,24 @@ Changelog
|
|||
1.1 (unreleased)
|
||||
----------------
|
||||
|
||||
- Added logo and activity rich field on organization type.
|
||||
[thomasdesvenain]
|
||||
|
||||
- Fixed generate id from title on held positions and persons.
|
||||
[thomasdesvenain]
|
||||
|
||||
- When we get the address of a contact, if the most direct address is empty,
|
||||
look for the next.
|
||||
[thomasdesvenain]
|
||||
|
||||
- Added Fax and Website fields to IContactDetails and IContactable.
|
||||
[thomasdesvenain]
|
||||
|
||||
- Fixed javascript in @@add-organization view.
|
||||
[vincentfretin]
|
||||
|
||||
- Fixed use parent address if we set Contact Details behaviour on held positions.
|
||||
[thomasdesvenain]
|
||||
|
||||
|
||||
1.0 (2013-09-13)
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
[buildout]
|
||||
extends = buildout.cfg
|
||||
auto-checkout +=
|
||||
collective.contact.facetednav
|
||||
|
||||
[instance]
|
||||
eggs +=
|
||||
collective.contact.facetednav
|
||||
zcml +=
|
||||
collective.contact.facetednav
|
|
@ -13,5 +13,6 @@ entrouvert_push = git+ssh://git@repos.entrouvert.org
|
|||
|
||||
[sources]
|
||||
collective.contact.core = git ${remotes:collective}/collective.contact.core.git pushurl=${remotes:collective_push}/collective.contact.core.git
|
||||
collective.contact.facetednav = git ${remotes:collective}/collective.contact.facetednav.git pushurl=${remotes:collective_push}/collective.contact.facetednav.git
|
||||
collective.contact.widget = git ${remotes:collective}/collective.contact.widget.git pushurl=${remotes:collective_push}/collective.contact.widget.git
|
||||
plone.formwidget.masterselect = git ${remotes:collective}/plone.formwidget.masterselect.git pushurl=${remotes:collective_push}/plone.formwidget.masterselect.git
|
||||
|
|
|
@ -84,8 +84,10 @@ class IContactDetails(model.Schema):
|
|||
label=_(u'Contact details'),
|
||||
fields=('phone',
|
||||
'cell_phone',
|
||||
'fax',
|
||||
'email',
|
||||
'im_handle',
|
||||
'website',
|
||||
)
|
||||
)
|
||||
fieldset(
|
||||
|
@ -119,6 +121,16 @@ class IContactDetails(model.Schema):
|
|||
required=False,
|
||||
)
|
||||
|
||||
fax = schema.TextLine(
|
||||
title=_(u"Fax"),
|
||||
required=False,
|
||||
)
|
||||
|
||||
website = schema.TextLine(
|
||||
title=_(u"Website"),
|
||||
required=False,
|
||||
)
|
||||
|
||||
im_handle = schema.TextLine(
|
||||
title=_('Instant messenger handle'),
|
||||
required=False,
|
||||
|
|
|
@ -36,7 +36,7 @@ class Contact(BaseView):
|
|||
self.person = person
|
||||
self.fullname = person.Title()
|
||||
self.title = held_position.Title()
|
||||
|
||||
|
||||
if IBirthday.providedBy(person):
|
||||
birthday = person.birthday
|
||||
if birthday is not None:
|
||||
|
@ -57,7 +57,9 @@ class Contact(BaseView):
|
|||
self.email = contact_details['email']
|
||||
self.phone = contact_details['phone']
|
||||
self.cell_phone = contact_details['cell_phone']
|
||||
self.fax = contact_details['fax']
|
||||
self.im_handle = contact_details['im_handle']
|
||||
self.website = contact_details['website']
|
||||
self.address = contact_details['address']
|
||||
|
||||
# also show fields that were added TTW
|
||||
|
|
|
@ -35,7 +35,8 @@ class Contactable(grok.Adapter):
|
|||
return []
|
||||
|
||||
def _get_contactables(self):
|
||||
"""Build a list of objects which have the IContactDetails behavior
|
||||
"""
|
||||
Build a list of objects which have the IContactDetails behavior
|
||||
for each contact information (email, phone, ...)
|
||||
we use the one of the first object in this list which have this information
|
||||
"""
|
||||
|
@ -46,7 +47,7 @@ class Contactable(grok.Adapter):
|
|||
and IContactDetails.providedBy(related_item) \
|
||||
and related_item not in contactables:
|
||||
contactables.append(related_item)
|
||||
|
||||
|
||||
return contactables
|
||||
|
||||
def _get_address(self, contactables):
|
||||
|
@ -55,15 +56,19 @@ class Contactable(grok.Adapter):
|
|||
if obj.use_parent_address is True:
|
||||
continue
|
||||
else:
|
||||
return get_address(obj)
|
||||
address = get_address(obj)
|
||||
if address:
|
||||
return address
|
||||
|
||||
return {}
|
||||
|
||||
def get_contact_details(self):
|
||||
contact_details = {}
|
||||
contact_details_fields = ['email', 'phone', 'cell_phone', 'im_handle']
|
||||
contact_details_fields = ['email', 'phone', 'cell_phone', 'fax', 'website', 'im_handle']
|
||||
contactables = self._get_contactables()
|
||||
for field in contact_details_fields:
|
||||
# search the object that carries the field
|
||||
for obj in self._get_contactables():
|
||||
for obj in contactables:
|
||||
obj = aq_base(obj)
|
||||
value = getattr(obj, field, '') or ''
|
||||
if value:
|
||||
|
@ -71,8 +76,7 @@ class Contactable(grok.Adapter):
|
|||
break
|
||||
else:
|
||||
contact_details[field] = ''
|
||||
|
||||
contactables = self._get_contactables()
|
||||
|
||||
contact_details['address'] = self._get_address(contactables)
|
||||
return contact_details
|
||||
|
||||
|
@ -81,10 +85,12 @@ class Contactable(grok.Adapter):
|
|||
if self.context.is_created and self.context in contactables:
|
||||
# we don't want self.context address if the object is already created
|
||||
contactables.remove(self.context)
|
||||
|
||||
address = self._get_address(contactables)
|
||||
if not address:
|
||||
# Very important to return unicode here, RichTextWidget needs it.
|
||||
return u''
|
||||
|
||||
template_path = os.path.join(TEMPLATES_DIR, 'address.pt')
|
||||
template = ViewPageTemplateFile(template_path)
|
||||
self.request = getRequest()
|
||||
|
|
|
@ -35,6 +35,7 @@ class Organization(BaseView):
|
|||
parent_organizations = []
|
||||
sub_organizations = []
|
||||
positions = []
|
||||
activity = ''
|
||||
|
||||
def update(self):
|
||||
super(Organization, self).update()
|
||||
|
@ -45,6 +46,7 @@ class Organization(BaseView):
|
|||
factory = getUtility(IVocabularyFactory, "OrganizationTypesOrLevels")
|
||||
vocabulary = factory(self.context)
|
||||
self.type = vocabulary.getTerm(organization.organization_type).title
|
||||
self.activity = self.context.activity
|
||||
|
||||
contactable = IContactable(organization)
|
||||
organizations = contactable.organizations
|
||||
|
@ -63,7 +65,9 @@ class Organization(BaseView):
|
|||
self.email = contact_details['email']
|
||||
self.phone = contact_details['phone']
|
||||
self.cell_phone = contact_details['cell_phone']
|
||||
self.fax = contact_details['fax']
|
||||
self.im_handle = contact_details['im_handle']
|
||||
self.website = contact_details['website']
|
||||
self.address = contact_details['address']
|
||||
|
||||
# also show fields that were added TTW
|
||||
|
|
|
@ -14,7 +14,6 @@ class Person(BaseView):
|
|||
person_title = ''
|
||||
gender = ''
|
||||
held_positions = ''
|
||||
photo = ''
|
||||
|
||||
def update(self):
|
||||
super(Person, self).update()
|
||||
|
@ -43,8 +42,10 @@ class Person(BaseView):
|
|||
contact_details = contactable.get_contact_details()
|
||||
self.email = contact_details['email']
|
||||
self.phone = contact_details['phone']
|
||||
self.fax = contact_details['fax']
|
||||
self.cell_phone = contact_details['cell_phone']
|
||||
self.im_handle = contact_details['im_handle']
|
||||
self.website = contact_details['website']
|
||||
self.address = contact_details['address']
|
||||
|
||||
# also show fields that were added TTW
|
||||
|
|
|
@ -28,8 +28,10 @@ class Position(BaseView):
|
|||
self.email = contact_details['email']
|
||||
self.phone = contact_details['phone']
|
||||
self.cell_phone = contact_details['cell_phone']
|
||||
self.fax = contact_details['fax']
|
||||
self.im_handle = contact_details['im_handle']
|
||||
self.address = contact_details['address']
|
||||
self.website = contact_details['website']
|
||||
|
||||
# also show fields that were added TTW
|
||||
self.ttw_fields = get_ttw_fields(position)
|
||||
|
|
|
@ -69,12 +69,24 @@
|
|||
i18n:translate="" />
|
||||
</div>
|
||||
|
||||
<div class="field" tal:condition="view/fax">
|
||||
<label><tal:block i18n:translate="">Fax number</tal:block>:</label>
|
||||
<span tal:content="view/fax"
|
||||
i18n:translate="" />
|
||||
</div>
|
||||
|
||||
<div class="field" tal:condition="view/im_handle">
|
||||
<label><tal:block i18n:translate="">IM handle</tal:block>:</label>
|
||||
<span tal:content="view/im_handle"
|
||||
i18n:translate="" />
|
||||
</div>
|
||||
|
||||
<div class="field" tal:condition="view/website">
|
||||
<label><tal:block i18n:translate="">Website</tal:block>:</label>
|
||||
<a tal:content="view/website"
|
||||
tal:attributes="href view/website" />
|
||||
</div>
|
||||
|
||||
<div id="address"
|
||||
tal:replace="structure view/render_address" />
|
||||
|
||||
|
|
|
@ -7,12 +7,21 @@
|
|||
<div metal:fill-slot="main">
|
||||
<h1 tal:content="view/name"></h1>
|
||||
|
||||
<div id="logo" class="field" tal:condition="context/logo">
|
||||
<img tal:replace="structure python: view.widgets['logo'].render()" />
|
||||
</div>
|
||||
|
||||
<div id="type" class="field">
|
||||
<label><tal:block i18n:translate="">Organization type</tal:block>:</label>
|
||||
<span tal:content="view/type"
|
||||
i18n:translate="" />
|
||||
</div>
|
||||
|
||||
<div id="activity" class="field" tal:condition="view/activity">
|
||||
<label><tal:block i18n:translate="">Activity</tal:block>:</label>
|
||||
<span tal:content="structure view/activity/output" />
|
||||
</div>
|
||||
|
||||
<div id="organizations" class="field" tal:condition="view/parent_organizations">
|
||||
<label><tal:block i18n:translate="">Parent organizations</tal:block>:</label>
|
||||
<ul>
|
||||
|
@ -43,12 +52,24 @@
|
|||
i18n:translate="" />
|
||||
</div>
|
||||
|
||||
<div class="field" tal:condition="view/fax">
|
||||
<label><tal:block i18n:translate="">Fax number</tal:block>:</label>
|
||||
<span tal:content="view/fax"
|
||||
i18n:translate="" />
|
||||
</div>
|
||||
|
||||
<div class="field" tal:condition="view/im_handle">
|
||||
<label><tal:block i18n:translate="">IM handle</tal:block>:</label>
|
||||
<span tal:content="view/im_handle"
|
||||
i18n:translate="" />
|
||||
</div>
|
||||
|
||||
<div class="field" tal:condition="view/website">
|
||||
<label><tal:block i18n:translate="">Website</tal:block>:</label>
|
||||
<a tal:content="view/website"
|
||||
tal:attributes="href view/website" />
|
||||
</div>
|
||||
|
||||
<div id="address"
|
||||
tal:replace="structure view/render_address" />
|
||||
|
||||
|
|
|
@ -35,12 +35,24 @@
|
|||
i18n:translate="" />
|
||||
</div>
|
||||
|
||||
<div class="field" tal:condition="view/fax">
|
||||
<label><tal:block i18n:translate="">Fax number</tal:block>:</label>
|
||||
<span tal:content="view/fax"
|
||||
i18n:translate="" />
|
||||
</div>
|
||||
|
||||
<div class="field" tal:condition="view/im_handle">
|
||||
<label><tal:block i18n:translate="">IM handle</tal:block>:</label>
|
||||
<span tal:content="view/im_handle"
|
||||
i18n:translate="" />
|
||||
</div>
|
||||
|
||||
<div class="field" tal:condition="view/website">
|
||||
<label><tal:block i18n:translate="">Website</tal:block>:</label>
|
||||
<a tal:content="view/website"
|
||||
tal:attributes="href view/website" />
|
||||
</div>
|
||||
|
||||
<div id="address"
|
||||
tal:replace="structure view/render_address" />
|
||||
|
||||
|
|
|
@ -43,12 +43,24 @@
|
|||
i18n:translate="" />
|
||||
</div>
|
||||
|
||||
<div class="field" tal:condition="view/fax">
|
||||
<label><tal:block i18n:translate="">Fax number</tal:block>:</label>
|
||||
<span tal:content="view/fax"
|
||||
i18n:translate="" />
|
||||
</div>
|
||||
|
||||
<div class="field" tal:condition="view/im_handle">
|
||||
<label><tal:block i18n:translate="">IM handle</tal:block>:</label>
|
||||
<span tal:content="view/im_handle"
|
||||
i18n:translate="" />
|
||||
</div>
|
||||
|
||||
<div class="field" tal:condition="view/website">
|
||||
<label><tal:block i18n:translate="">Website</tal:block>:</label>
|
||||
<a tal:content="view/website"
|
||||
tal:attributes="href view/website" />
|
||||
</div>
|
||||
|
||||
<div id="address"
|
||||
tal:replace="structure view/render_address" />
|
||||
|
||||
|
|
|
@ -93,6 +93,14 @@ class HeldPosition(Container):
|
|||
use_parent_address = NO_VALUE
|
||||
parent_address = NO_VALUE
|
||||
|
||||
def set_title(self, val):
|
||||
return
|
||||
|
||||
def get_title(self):
|
||||
return self.Title()
|
||||
|
||||
title = property(get_title, set_title)
|
||||
|
||||
def get_person(self):
|
||||
"""Returns the person who holds the position
|
||||
"""
|
||||
|
|
|
@ -2,24 +2,26 @@ from Acquisition import aq_inner, aq_chain
|
|||
from zope.interface import implements
|
||||
from zope.interface import Attribute
|
||||
from zope import schema
|
||||
from zope.component import getUtility
|
||||
from zope.intid.interfaces import IIntIds
|
||||
|
||||
from z3c.form.interfaces import NO_VALUE
|
||||
from zc.relation.interfaces import ICatalog
|
||||
|
||||
from five import grok
|
||||
|
||||
from Products.CMFPlone.utils import base_hasattr
|
||||
|
||||
from Products.CMFCore.utils import getToolByName
|
||||
from plone.dexterity.content import Container
|
||||
from plone.supermodel import model
|
||||
from plone.dexterity.schema import DexteritySchemaPolicy
|
||||
from plone.namedfile.field import NamedImage
|
||||
|
||||
from collective.contact.core import _
|
||||
from collective.contact.core.browser.contactable import Contactable
|
||||
from collective.contact.widget.interfaces import IContactContent
|
||||
from zope.component._api import getUtility
|
||||
from zope.intid.interfaces import IIntIds
|
||||
from zc.relation.interfaces import ICatalog
|
||||
from collective.contact.core.content.held_position import IHeldPosition
|
||||
from Products.CMFCore.utils import getToolByName
|
||||
from plone.app.textfield import RichText
|
||||
|
||||
|
||||
class IOrganization(model.Schema, IContactContent):
|
||||
|
@ -27,11 +29,22 @@ class IOrganization(model.Schema, IContactContent):
|
|||
|
||||
is_created = Attribute(u"Marker to know if the object is already created")
|
||||
|
||||
activity = RichText(
|
||||
title=_("Activity"),
|
||||
required=False,
|
||||
)
|
||||
|
||||
organization_type = schema.Choice(
|
||||
title=_("Type or level"),
|
||||
vocabulary="OrganizationTypesOrLevels",
|
||||
)
|
||||
|
||||
logo = NamedImage(
|
||||
title=_("Logo"),
|
||||
required=False,
|
||||
)
|
||||
|
||||
|
||||
def get_organizations_chain(self):
|
||||
"""Returns the list of organizations and sub-organizations in this organization
|
||||
e.g. for HR service in Division Bar in Organization Foo :
|
||||
|
|
|
@ -74,9 +74,14 @@ class Person(Container):
|
|||
use_parent_address = NO_VALUE
|
||||
parent_address = NO_VALUE
|
||||
|
||||
def set_title(self, val):
|
||||
return
|
||||
|
||||
def get_title(self):
|
||||
return u' '.join([x for x in (self.person_title, self.firstname, self.lastname) if x])
|
||||
|
||||
title = property(get_title, set_title)
|
||||
|
||||
def Title(self):
|
||||
# must return utf8 and not unicode (Title() from basic behavior return utf8)
|
||||
# attributes are stored as unicode
|
||||
|
@ -84,10 +89,10 @@ class Person(Container):
|
|||
|
||||
def get_held_positions(self):
|
||||
return [obj for obj in self.values() if IHeldPosition.providedBy(obj)]
|
||||
|
||||
|
||||
def get_held_positions_titles(self):
|
||||
return [p.Title() for p in self.get_held_positions()]
|
||||
|
||||
|
||||
|
||||
class PersonSchemaPolicy(grok.GlobalUtility,
|
||||
DexteritySchemaPolicy):
|
||||
|
|
|
@ -48,6 +48,7 @@ class TestBehaviors(unittest.TestCase, BaseTest):
|
|||
self.assertIsNone(item.getAttributes())
|
||||
for attr in ('country', 'region', 'zip_code', 'city', 'street',
|
||||
'number', 'im_handle', 'cell_phone', 'phone', 'email',
|
||||
'fax', 'website',
|
||||
'additional_address_details', 'birthday'):
|
||||
self.assertTrue(hasattr(item, attr))
|
||||
item.phone = '0655443322'
|
||||
|
|
|
@ -53,6 +53,7 @@ class TestPerson(TestContentTypes):
|
|||
self.assertIn('degaulle', self.mydirectory)
|
||||
degaulle = self.degaulle
|
||||
self.assertEqual('Général Charles De Gaulle', degaulle.Title())
|
||||
self.assertEqual(u'Général Charles De Gaulle', degaulle.title)
|
||||
self.assertEqual('De Gaulle', degaulle.lastname)
|
||||
self.assertEqual('Charles', degaulle.firstname)
|
||||
self.assertEqual(datetime.date(1901, 11, 22), degaulle.birthday)
|
||||
|
@ -172,6 +173,8 @@ class TestHeldPosition(TestContentTypes):
|
|||
self.assertIn('adt', degaulle)
|
||||
self.assertEqual(adt.Title(),
|
||||
"Armée de terre")
|
||||
self.assertEqual(adt.title,
|
||||
"Armée de terre")
|
||||
self.assertIn('gadt', degaulle)
|
||||
self.assertEqual(gadt.Title(),
|
||||
"Général de l'armée de terre (Armée de terre)")
|
||||
|
|
Reference in New Issue