diff --git a/Extensions/Install.py b/Extensions/Install.py
new file mode 100644
index 0000000..6fe4926
--- /dev/null
+++ b/Extensions/Install.py
@@ -0,0 +1,44 @@
+# Python imports
+from StringIO import StringIO
+
+# CMF imports
+from Products.CMFCore.utils import getToolByName
+
+# Archetypes imports
+from Products.Archetypes import listTypes
+from Products.Archetypes.Extensions.utils import installTypes, install_subskin
+
+# Products imports
+from Products.CV.config import GLOBALS, PROJECTNAME
+
+
+def install(self):
+ """Install content types, skin layer, enable the portal factory
+ """
+
+ out = StringIO()
+
+ print >> out, "Installing CV"
+
+ # Install types
+ classes = listTypes(PROJECTNAME)
+ installTypes(self, out,
+ classes,
+ PROJECTNAME)
+ print >> out, "Installed types"
+
+ # Install skin
+ install_subskin(self, out, GLOBALS)
+ print >> out, "Installed skin"
+
+ # Register types with portal_factory
+ factory = getToolByName(self, 'portal_factory')
+ types = factory.getFactoryTypes().keys()
+ if 'CV' not in types:
+ types.append('CV')
+ factory.manage_setPortalFactoryTypes(listOfTypeIds = types)
+
+ print >> out, "Added CV to portal_factory"
+
+ return out.getvalue()
+
diff --git a/__init__.py b/__init__.py
new file mode 100644
index 0000000..f2cac0e
--- /dev/null
+++ b/__init__.py
@@ -0,0 +1,35 @@
+from Globals import package_home
+from Products.CMFCore import utils, CMFCorePermissions, DirectoryView
+from Products.CMFPlone.PloneUtilities import ToolInit
+from Products.Archetypes.public import *
+from Products.Archetypes import listTypes
+from Products.Archetypes.utils import capitalize
+
+import os, os.path, sys, content
+
+# Get configuration data, permissions
+from Products.CV.config import *
+
+# Register skin directories so they can be added to portal_skins
+DirectoryView.registerDirectory('skins', product_globals)
+DirectoryView.registerDirectory('skins/cv_images', product_globals)
+DirectoryView.registerDirectory('skins/cv_styles', product_globals)
+DirectoryView.registerDirectory('skins/cv_templates', product_globals)
+
+def initialize(context):
+
+ # Import the type, which results in registerType() being called
+ from content import CV, Employment, Education, Publication, Skill
+
+ # initialize the content, including types and add permissions
+ content_types, constructors, ftis = process_types(
+ listTypes(PROJECTNAME),
+ PROJECTNAME)
+
+ utils.ContentInit(
+ PROJECTNAME + ' Content',
+ content_types = content_types,
+ permission = DEFAULT_ADD_CONTENT_PERMISSION,
+ extra_constructors = constructors,
+ fti = ftis,
+ ).initialize(context)
diff --git a/config.py b/config.py
new file mode 100644
index 0000000..c797881
--- /dev/null
+++ b/config.py
@@ -0,0 +1,7 @@
+from Products.CMFCore.CMFCorePermissions import setDefaultRoles
+
+PROJECTNAME = "CV"
+DEFAULT_ADD_CONTENT_PERMISSION = "Add CV"
+product_globals=GLOBALS=globals()
+
+setDefaultRoles(DEFAULT_ADD_CONTENT_PERMISSION, ('Manager', 'Owner',))
diff --git a/content/__init__.py b/content/__init__.py
new file mode 100644
index 0000000..2e5fbd1
--- /dev/null
+++ b/content/__init__.py
@@ -0,0 +1 @@
+from cv import CV, Education, Employment, Publication, Skill
diff --git a/content/cv.py b/content/cv.py
new file mode 100644
index 0000000..b386f8b
--- /dev/null
+++ b/content/cv.py
@@ -0,0 +1,404 @@
+from Products.Archetypes.atapi import *
+from Products.ATContentTypes.content.folder import ATFolder
+from Products.ATContentTypes.content.file import ATFile
+from Products.ATContentTypes.content.base import ATCTContent
+from Products.ATContentTypes.content.schemata import ATContentTypeSchema, finalizeATCTSchema
+from Products.CMFPlone.interfaces import INonStructuralFolder
+from Products.CMFCore import permissions
+from Products.CV.config import PROJECTNAME
+from string import split, join
+
+CVSchema = ATFolder.schema.copy() + Schema((
+ ComputedField(
+ name='title',
+ searchable=1,
+ expression='context._computeTitle()',
+ accessor='Title',
+ widget=ComputedWidget(mode='view')
+ ),
+ ComputedField(
+ name='description',
+ searchable=1,
+ expression='context._computeDescription()',
+ accessor='Description',
+ widget=ComputedWidget(mode='view')
+ ),
+ StringField(
+ name='fullName',
+ widget=StringWidget(label='Full name'),
+ required=True,
+ ),
+ LinesField(
+ name='qualifications',
+ widget=LinesWidget(label='Qualifications'),
+ required=True,
+ ),
+ LinesField(
+ name='address',
+ widget=LinesWidget(label='Address'),
+ required=True,
+ ),
+ StringField(
+ name='homePhone',
+ widget=StringWidget(label='Home phone number'),
+ required=True,
+ ),
+ StringField(
+ name='mobilePhone',
+ widget=StringWidget(label='Mobile phone number'),
+ ),
+ StringField(
+ name='workPhone',
+ widget=StringWidget(label='Work phone number'),
+ ),
+ StringField(
+ name='email',
+ widget=StringWidget(label='Email address'),
+ required=True,
+ ),
+ DateTimeField(
+ name='birthDate',
+ widget=CalendarWidget(label='Date of birth',
+ show_hm=False,
+ starting_year=1970,
+ format='%Y-%m-%d'),
+ required=True,
+ ),
+ StringField(
+ name='maritalStatus',
+ widget=SelectionWidget(label='Marital status'),
+ vocabulary=['Single', 'Married', 'Divorced'],
+ required=True,
+ ),
+ StringField(
+ name='drivingLicence',
+ widget=StringWidget(label='Driving licence'),
+ required=True,
+ ),
+ TextField(
+ name='interests',
+ widget=RichWidget(label='Interests'),
+ required=True,
+ )
+ ),)
+
+EducationSchema = ATContentTypeSchema.copy() + Schema((
+ ComputedField(
+ name='title',
+ searchable=1,
+ expression='context._computeTitle()',
+ accessor='Title'),
+ DateTimeField(
+ name='fromDate',
+ widget=CalendarWidget(label='From date',
+ show_hm=False,
+ starting_year=1980,
+ format='%Y-%m-%d'),
+ required=True,
+ ),
+ DateTimeField(
+ name='toDate',
+ widget=CalendarWidget(label='To date',
+ show_hm=False,
+ starting_year=1980,
+ format='%Y-%m-%d'),
+ ),
+ StringField(
+ name='qualification',
+ widget=StringWidget(label='Qualifiction'),
+ ),
+ StringField(
+ name='institute',
+ widget=StringWidget(label='Institute'),
+ required=True,
+ ),
+ TextField(
+ name='description',
+ widget=RichWidget(label='Description'),
+ required=True,
+ default_content_type='text/html',
+ allowable_content_types=('text/html',),
+ default_output_type='text/html',
+ )
+ ),)
+
+EmploymentSchema = ATContentTypeSchema.copy() + Schema((
+ ComputedField(
+ name='title',
+ searchable=1,
+ expression='context._computeTitle()',
+ accessor='Title'),
+ DateTimeField(
+ name='fromDate',
+ widget=CalendarWidget(label='From date',
+ show_hm=False,
+ starting_year=1980,
+ format='%Y-%m-%d'),
+ required=True,
+ ),
+ DateTimeField(
+ name='toDate',
+ widget=CalendarWidget(label='To date',
+ show_hm=False,
+ starting_year=1980,
+ format='%Y-%m-%d'),
+ ),
+ StringField(
+ name='position',
+ widget=StringWidget(label='Position'),
+ ),
+ StringField(
+ name='company',
+ widget=StringWidget(label='Company'),
+ required=True,
+ ),
+ StringField(
+ name='location',
+ widget=StringWidget(label='Location'),
+ required=True,
+ ),
+ TextField(
+ name='description',
+ widget=RichWidget(label='Description'),
+ required=True,
+ default_content_type='text/html',
+ allowable_content_types=('text/html',),
+ default_output_type='text/html',
+ )
+ ),)
+
+PublicationSchema = ATFile.schema.copy() + Schema((
+ StringField(
+ name='publicationType',
+ widget=SelectionWidget(label='Publication type'),
+ vocabulary=['article', 'book', 'booklet', 'conference',
+ 'inBook', 'inCollection', 'inProceedings',
+ 'manual', 'mastersThesis', 'misc', 'phdThesis',
+ 'proceedings', 'techreport', 'unpublished'],
+ required=True,
+ ),
+ StringField(
+ name='address',
+ widget=StringWidget(label='Address')
+ ),
+ LinesField(
+ name='authors',
+ widget=LinesWidget(label='Authors'),
+ required=True,
+ ),
+ StringField(
+ name='booktitle',
+ widget=StringWidget(label='Book title'),
+ ),
+ StringField(
+ name='chapter',
+ widget=StringWidget(label='Chapter'),
+ ),
+ StringField(
+ name='edition',
+ widget=StringWidget(label='Edition'),
+ ),
+ StringField(
+ name='editor',
+ widget=StringWidget(label='Editor'),
+ ),
+ StringField(
+ name='howpublished',
+ widget=StringWidget(label='How published'),
+ ),
+ StringField(
+ name='institution',
+ widget=StringWidget(label='Institution'),
+ ),
+ StringField(
+ name='journal',
+ widget=StringWidget(label='Journal'),
+ ),
+ StringField(
+ name='key',
+ widget=StringWidget(label='Key'),
+ ),
+ StringField(
+ name='month',
+ widget=StringWidget(label='Month'),
+ ),
+ StringField(
+ name='note',
+ widget=StringWidget(label='Note'),
+ ),
+ StringField(
+ name='number',
+ widget=StringWidget(label='Number'),
+ ),
+ StringField(
+ name='organization',
+ widget=StringWidget(label='Organization'),
+ ),
+ StringField(
+ name='pages',
+ widget=StringWidget(label='Pages'),
+ ),
+ StringField(
+ name='publisher',
+ widget=StringWidget(label='Publisher'),
+ ),
+ StringField(
+ name='school',
+ widget=StringWidget(label='School'),
+ ),
+ StringField(
+ name='series',
+ widget=StringWidget(label='Series'),
+ ),
+ StringField(
+ name='techReportType',
+ widget=StringWidget(label='Technical report type'),
+ ),
+ StringField(
+ name='volume',
+ widget=StringWidget(label='Volume'),
+ ),
+ StringField(
+ name='year',
+ widget=StringWidget(label='Year'),
+ required=True,
+ ),
+ StringField(
+ name='onlineUrl',
+ widget=StringWidget(label='URL'),
+ ),
+ StringField(
+ name='onlineSize',
+ widget=StringWidget(label='Size'),
+ )
+ ),)
+
+SkillSchema = ATContentTypeSchema.copy() + Schema((
+ ComputedField(
+ name='title',
+ searchable=1,
+ expression='context._computeTitle()',
+ accessor='Title'),
+ StringField(
+ name='category',
+ widget=StringWidget(label='Category'),
+ required=True,
+ ),
+ TextField(
+ name='description',
+ widget=RichWidget(label='Description'),
+ default_content_type='text/html',
+ allowable_content_types=('text/html',),
+ default_output_type='text/html',
+ )
+ ),)
+
+finalizeATCTSchema(CVSchema)
+finalizeATCTSchema(EducationSchema)
+finalizeATCTSchema(EmploymentSchema)
+finalizeATCTSchema(PublicationSchema)
+finalizeATCTSchema(SkillSchema)
+
+class CV(ATFolder):
+ """
+ A CV lists details of someone's education and employment histories
+ as well as key skills and any publications, along with generic
+ properties like address, name, etc.
+ """
+
+ portal_type = meta_type = 'CV'
+ archetype_name = 'Curriculum Vitae'
+ content_icon = 'cv.png'
+ schema = CVSchema
+ typeDescription = 'A Curriculum Vitae for someone.'
+ allowed_content_types = ['Education', 'Employment', 'Publication', 'Skill']
+ default_view = 'cv_view'
+ immediate_view = 'cv_view'
+# __implements__ = ATFolder.__implements__ + [INonStructuralFolder]
+ actions = ATFolder.actions
+
+ def canSetDefaultPage(self):
+ return False
+
+ def _computeTitle(self):
+ return 'Curriculum Vitae for ' + self.getFullName();
+
+ def _computeDescription(self):
+ return "%s\n%s\n%s\n%s" % (self.getFullName(),
+ self.getAddress(),
+ self.getEmail(),
+ self.getBirthDate())
+
+registerType(CV)
+
+class Education(ATCTContent):
+ portal_type = meta_type = 'Education'
+ archetype_name = 'Education'
+ content_icon = 'education.png'
+ schema = EducationSchema
+ typeDescription = 'Details of education from some institute.'
+# __implements__ = ATCTContent.__implements__
+ actions = ATCTContent.actions
+ global_allow = False
+
+ def _computeTitle(self):
+ return 'Education at ' + self.getInstitute();
+
+registerType(Education)
+
+class Employment(ATCTContent):
+ portal_type = meta_type = 'Employment'
+ archetype_name = 'Employment'
+ content_icon = 'employment.png'
+ schema = EmploymentSchema
+ typeDescription = 'Details of employment with some employer.'
+# __implements__ = ATCTContent.__implements__
+ actions = ATCTContent.actions
+ global_allow = False
+
+ def _computeTitle(self):
+ return 'Employment at ' + self.getCompany();
+
+registerType(Employment)
+
+class Publication(ATFile):
+ portal_type = meta_type = 'Publication'
+ archetype_name = 'Publication'
+ content_icon = 'publication.png'
+ schema = PublicationSchema
+ typeDescription = 'A publication with citation details.'
+# __implements__ = ATFile.__implements__
+ actions = ATFile.actions
+ global_allow = False
+
+ def listAuthors(self):
+ name_list = []
+ for full_name in self.authors:
+ names = split(full_name, ' ')
+ surname = names[-1]
+ backward_name = surname
+ for name in names[:-1]:
+ backward_name = backward_name + ' ' + name[0] + '.'
+ name_list.append(backward_name)
+ if len(name_list) > 1:
+ return join(name_list[0:-1], ',') + ' and ' + name_list[-1]
+ else:
+ return name_list[0]
+
+registerType(Publication)
+
+class Skill(ATCTContent):
+ portal_type = meta_type = 'Skill'
+ archetype_name = 'Skill'
+ content_icon = 'skill.png'
+ schema = SkillSchema
+ typeDescription = 'Details of a skill.'
+# __implements__ = ATCTContent.__implements__
+ actions = ATCTContent.actions
+ global_allow = False
+
+ def _computeTitle(self):
+ return 'Skill in ' + self.getCategory();
+
+
+registerType(Skill)
diff --git a/skins/cv_images/cv.png b/skins/cv_images/cv.png
new file mode 100644
index 0000000..b85a467
--- /dev/null
+++ b/skins/cv_images/cv.png
Binary files differ
diff --git a/skins/cv_images/education.png b/skins/cv_images/education.png
new file mode 100644
index 0000000..8852ff8
--- /dev/null
+++ b/skins/cv_images/education.png
Binary files differ
diff --git a/skins/cv_images/employment.png b/skins/cv_images/employment.png
new file mode 100644
index 0000000..b6eb792
--- /dev/null
+++ b/skins/cv_images/employment.png
Binary files differ
diff --git a/skins/cv_images/publication.png b/skins/cv_images/publication.png
new file mode 100644
index 0000000..8b7968a
--- /dev/null
+++ b/skins/cv_images/publication.png
Binary files differ
diff --git a/skins/cv_images/skill.png b/skins/cv_images/skill.png
new file mode 100644
index 0000000..4bd0c07
--- /dev/null
+++ b/skins/cv_images/skill.png
Binary files differ
diff --git a/skins/cv_styles/cv_main.css b/skins/cv_styles/cv_main.css
new file mode 100644
index 0000000..d0d368a
--- /dev/null
+++ b/skins/cv_styles/cv_main.css
@@ -0,0 +1,90 @@
+.cv {
+ color: black;
+ background: beige;
+ margin-left: 5%;
+ margin-right: 5%;
+ margin-top: 0px;
+ margin-bottom: 0px;
+ font-family: "Georgia", serif;
+}
+
+.cv h1, .cv h2, .cv h3 {
+ font-family: "Georgia", serif;
+}
+
+.cv dl {
+ text-align: justify;
+}
+
+.cv dt {
+ font-weight: bold;
+}
+
+.cv p {
+ text-align: justify;
+ margin: 0px 0px 0px 0px;
+ padding-bottom: 1ex;
+ line-height: 120%;
+}
+
+.cv div.modified {
+ font-size: small;
+}
+
+.cv div.headMatter {
+ text-align: center;
+ vertical-align: middle;
+}
+
+.cv h2 {
+ font-size: 18pt;
+ font-weight: bold;
+ padding: 1ex;
+}
+
+.cv div.name {
+ font-size: 14pt;
+ font-weight: bold;
+ padding: 0.5ex;
+}
+
+.cv div.addressBlock {
+ font-style: italic;
+}
+
+.cv div.personal {
+ text-align: center;
+}
+
+.cv th {
+ font-weight: bold;
+ text-align: right;
+ vertical-align: top;
+}
+
+.cv td {
+ text-align: justify;
+ vertical-align: top;
+}
+
+.cv div.jobHead {
+ font-weight: bold;
+}
+
+.cv th.date {
+ width: 16%;
+ text-align: center;
+}
+
+.cv h3 {
+ font-size: 14pt;
+ font-weight: bold;
+ text-align: center;
+ padding-top: 2ex;
+ padding-bottom: 1ex;
+}
+
+.cv div.reference {
+ text-indent: -2em;
+ margin-left: 2em;
+}
\ No newline at end of file
diff --git a/skins/cv_templates/cv_view.pt b/skins/cv_templates/cv_view.pt
new file mode 100644
index 0000000..08701fa
--- /dev/null
+++ b/skins/cv_templates/cv_view.pt
@@ -0,0 +1,184 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
Curriculum Vitæ
+
+
+
+
+
+
+
+
+ Date of birth: |
+
+ stndrdth
+ ,
+ |
+
+
+ Marital status: |
+ |
+
+
+ Driving licence: |
+ |
+
+
+
+
+
+ Employment
+
+
+
+
+ –
+ |
+
+ –
+
+
+
+ Current
+ |
+
+
+
+ |
+
+
+
+
+
+ Education
+
+
+
+
+ –
+ |
+
+ –
+
+
+
+ Current
+ |
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Skills
+
+
+
+
+ Interests
+
+
+
+
+ Publications
+
+
+
(
)
+
+ PhD Thesis.
+
+
+
+
+ ,
+ .
+
+
+ ,
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/version b/version
new file mode 100644
index 0000000..aec258d
--- /dev/null
+++ b/version
@@ -0,0 +1 @@
+0.8