Index: quintagroup.analytics/tags/1.1.1/MANIFEST.in
===================================================================
--- quintagroup.analytics/tags/1.1.1/MANIFEST.in (revision 3470)
+++ quintagroup.analytics/tags/1.1.1/MANIFEST.in (revision 3470)
@@ -0,0 +1,4 @@
+recursive-include docs *
+recursive-include quintagroup *
+global-exclude *.pyc
+global-exclude *.mo
Index: quintagroup.analytics/tags/1.1.1/README.txt
===================================================================
--- quintagroup.analytics/tags/1.1.1/README.txt (revision 3470)
+++ quintagroup.analytics/tags/1.1.1/README.txt (revision 3470)
@@ -0,0 +1,60 @@
+Quintagroup Analytics Tool for Plone
+====================================
+
+.. figure:: http://quintagroup.com/services/plone-development/products/quintagroup.analytics/plone-analytics.png/
+
+quintagroup.analytics provides statistic information about your Plone site. It
+adds few content stats views of plone content workflow states, ownership and
+portlets registered on different contexts.
+
+Information provided by Quintagroup Analytics Tool allows you to see Plone site
+content from different perspectives. This information can be very useful while
+migrating your site into newer Plone version, or into another CMS.
+
+With its help you can visually audit the content setup in Plone site before migration
+and compare it with the migrated website structure.
+
+Usage
+-----
+
+To see your Plone site statistic information - navigate to 'Quintagroup Analytics' item
+under Add-on Products Configuarion. Browse through all configlet tabs to see all statistic
+information, generated by Quintagroup Analytics Tool:
+
+* Content Ownership by Type - information about most popular content types on your site. Here you
+ can see the most frequently created content types on your site and their owners.
+* Content Ownership by State - information about site's content workflow states. Here you can see
+ how many content object are published/submitted for review/etc. and their owners.
+* Content Types by State - information about site's most frequently created content types and their
+ workflow states.
+* Site Portlets - information about site portlets assigned throughout site sections. This information
+ can be exported into .csv format. You can see all portlets assigned on your site and edit them.
+* Legacy Portlets - information about legacy assigned throughout site sections. This information
+ can be exported into .csv format.
+* Properties stats - information on certain property values for all site objects, such as
+ titles, descriptions, etc. This information can be exported into .csv format.
+
+Compatibility
+-------------
+
+Plone 3.x, Plone 4.x
+
+Links
+-----
+
+* Product Homepage: http://quintagroup.com/services/plone-development/products/quintagroup.analytics
+* Documentation Area: http://projects.quintagroup.com/products/wiki/quintagroup.analytics
+* Repository: http://svn.quintagroup.com/products/quintagroup.analytics
+* Releases: http://plone.org/products/quintagroup.analytics/releases
+
+Authors and contributors
+------------------------
+
+* Myroslav Opyr
+* Volodymyr Cherepanyak
+* Taras Melnychuk
+* Bohdan Koval'
+* Roman Kozlovskyi
+* Vitaliy Podoba
+* Julien Stegle
+
Index: quintagroup.analytics/tags/1.1.1/docs/HISTORY.txt
===================================================================
--- quintagroup.analytics/tags/1.1.1/docs/HISTORY.txt (revision 3470)
+++ quintagroup.analytics/tags/1.1.1/docs/HISTORY.txt (revision 3470)
@@ -0,0 +1,20 @@
+Changelog
+=========
+
+1.1.1 - 2012-05-31
+------------------
+* fixed syntax for python 2.4
+* fixed tests for plone 4.2 and pyflakes
+* fixed pep8
+
+1.1 - 2012-04-11
+----------------
+* refactored configlet tabs
+* french translation added
+* translations added
+
+1.0 - 2010-11-19
+----------------
+
+* Initial release
+
Index: quintagroup.analytics/tags/1.1.1/docs/INSTALL.txt
===================================================================
--- quintagroup.analytics/tags/1.1.1/docs/INSTALL.txt (revision 3470)
+++ quintagroup.analytics/tags/1.1.1/docs/INSTALL.txt (revision 3470)
@@ -0,0 +1,31 @@
+quintagroup.analytics Installation
+----------------------------------
+
+To install quintagroup.analytics to your buildout-based project:
+
+* Add ``quintagroup.analytics`` to the list of eggs to install::
+
+ [buildout]
+ ...
+ eggs =
+ ...
+ quintagroup.analytics
+
+* Tell the plone.recipe.zope2instance recipe to install a ZCML slug::
+
+ [instance]
+ recipe = plone.recipe.zope2instance
+ ...
+ zcml =
+ quintagroup.analytics
+
+* Re-run buildout, e.g. with::
+
+ $ ./bin/buildout
+
+* Restart the Zope server, e.g with the following command in the terminal::
+
+ $ ./bin/instance restart
+
+* Install ``Quintagroup Analytics`` with Quickinstaller in Plone (Site Setup -> Add/Remove Products)
+
Index: quintagroup.analytics/tags/1.1.1/docs/LICENSE.GPL
===================================================================
--- quintagroup.analytics/tags/1.1.1/docs/LICENSE.GPL (revision 3470)
+++ quintagroup.analytics/tags/1.1.1/docs/LICENSE.GPL (revision 3470)
@@ -0,0 +1,225 @@
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
Index: quintagroup.analytics/tags/1.1.1/docs/LICENSE.txt
===================================================================
--- quintagroup.analytics/tags/1.1.1/docs/LICENSE.txt (revision 3470)
+++ quintagroup.analytics/tags/1.1.1/docs/LICENSE.txt (revision 3470)
@@ -0,0 +1,16 @@
+ quintagroup.analytics is copyright Quintagroup
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ MA 02111-1307 USA.
Index: quintagroup.analytics/tags/1.1.1/quintagroup/__init__.py
===================================================================
--- quintagroup.analytics/tags/1.1.1/quintagroup/__init__.py (revision 3470)
+++ quintagroup.analytics/tags/1.1.1/quintagroup/__init__.py (revision 3470)
@@ -0,0 +1,6 @@
+# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
+try:
+ __import__('pkg_resources').declare_namespace(__name__)
+except ImportError:
+ from pkgutil import extend_path
+ __path__ = extend_path(__path__, __name__)
Index: quintagroup.analytics/tags/1.1.1/quintagroup/analytics/README.txt
===================================================================
--- quintagroup.analytics/tags/1.1.1/quintagroup/analytics/README.txt (revision 3470)
+++ quintagroup.analytics/tags/1.1.1/quintagroup/analytics/README.txt (revision 3470)
@@ -0,0 +1,58 @@
+Introduction
+============
+
+Quintagroup Analytics Tool (quintagroup.analytics) provides statistic information
+about your Plone site. It adds few content stats views of plone content workflow
+states, ownership and portlets registered on different contexts.
+
+Information provided by Quintagroup Analytics Tool allows you to see Plone site
+content from different perspectives. This information can be very useful while
+migrating your site into newer Plone version, or into another CMS.
+
+With its help you can visually audit the content setup in Plone site before migration
+and compare it with the migrated website structure.
+
+Usage
+-----
+
+To see your Plone site statistic information - navigate to Quintagroup Analytics item
+under Add-on Products Configuarion. Browse through all configlet tabs to see all statistic
+information, generated by Quintagroup Analytics Tool:
+
+* Ownership by type - information about most popular content types on your site. Here you
+ can see the most frequently created content types on your site and their owners.
+* Ownership by state - information about site's content workflow states. Here you can see
+ how many content object are published/submitted for review/etc. and their owners.
+* Types by state - information about site's most frequently created content types and their
+ workflow states.
+* Portlets stats - information about site portlets and their location. You can see all
+ portlets assigned on your site and edit them.
+* Legacy portlets - information about legacy portlets and their location. You can see all
+ legacy portlets assigned on your site and edit them. This information can be exported into
+ .csv format.
+* Properties stats - information on certain property values for all site objects, such as
+ titles, descriptions, etc. This information can be exported into .csv format.
+
+Compatibility
+-------------
+
+Plone 3.x, Plone 4.0
+
+Links
+-----
+
+* Product Homepage: http://quintagroup.com/services/plone-development/products/quintagroup.analytics
+* Documentation Area: http://projects.quintagroup.com/products/wiki/quintagroup.analytics
+* Repository: http://svn.quintagroup.com/products/quintagroup.analytics
+* Releases: http://plone.org/products/quintagroup.analytics/releases
+
+Authors and contributors
+------------------------
+
+* Myroslav Opyr
+* Volodymyr Cherepanyak
+* Taras Melnychuk
+* Bohdan Koval'
+* Roman Kozlovskyi
+* Vitaliy Podoba
+
Index: quintagroup.analytics/tags/1.1.1/quintagroup/analytics/__init__.py
===================================================================
--- quintagroup.analytics/tags/1.1.1/quintagroup/analytics/__init__.py (revision 3470)
+++ quintagroup.analytics/tags/1.1.1/quintagroup/analytics/__init__.py (revision 3470)
@@ -0,0 +1,2 @@
+from zope.i18nmessageid import MessageFactory
+QuintagroupAnalyticsMessageFactory = MessageFactory('quintagroup.analytics')
Index: quintagroup.analytics/tags/1.1.1/quintagroup/analytics/browser/configure.zcml
===================================================================
--- quintagroup.analytics/tags/1.1.1/quintagroup/analytics/browser/configure.zcml (revision 3470)
+++ quintagroup.analytics/tags/1.1.1/quintagroup/analytics/browser/configure.zcml (revision 3470)
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Index: quintagroup.analytics/tags/1.1.1/quintagroup/analytics/browser/legacy_portlets.pt
===================================================================
--- quintagroup.analytics/tags/1.1.1/quintagroup/analytics/browser/legacy_portlets.pt (revision 3470)
+++ quintagroup.analytics/tags/1.1.1/quintagroup/analytics/browser/legacy_portlets.pt (revision 3470)
@@ -0,0 +1,85 @@
+
+
+
+
+
The following list can display site's legacy portlets, assigned as left and right slots on different
+ site contexts. Specify the search depth: portlets on what level(s) to be included. Select CSV to render
+ this information in .csv format.
The following chart displays workflow states of site's content objects in relation to their owners
+ (up to 10 most active site content contributors are displayed).
+
The table under the chart displays number of content objects in all site's workflow states in relation
+ to content owners.
+
The following chart displays the most frequently created content types in relation
+ to their owners (up to 10 most active site content contributors and up to 10 most popular
+ content types are displayed).
+
+
The table under the chart displays number of content objects of all site's content types
+ in relation to their owners. Besides, the total number of created objects by every contributor
+ is provided.
+
The following list can displays site's portlets, assigned to left and right columns on different
+ contexts. Specify the search depth: portlets on what level(s) to be included. Click on the porltet
+ title to land on the porltet edit form. Select CSV to render this information in .csv format.
The following list can display values for certain property for site objects. To generate the
+ list - specify the search depth (objects on what level(s) will be included) and type in
+ property name (a list of all available properties is provided below). For example: to display
+ all site objects titles type '-1' into 'Search depth' field and 'title' into 'Property name' field.
+ Select CSV to render this information in .csv format.
+
Quintagroup Analytics Tool provides statistic information about your Plone site.
+ You can see how many content objects are on you site, who are content owners,
+ what are content workflow states, and how many portlets are assigned on different contexts.
+
+
Information provided by Quintagroup Analytics Tool allows you to see your site's overall
+ content from different perspectives. This can be very useful while migrating your site into
+ newer Plone version, or into another CMS. With its help you can visually audit the content
+ setup in Plone site before migration and compare it with the migrated website structure.
+
+
Visit the following pages to see your site's statistics:
The following chart and table display workflow states of the site's most frequently created content types.
+ You can see the total number of site's content objects of every content type.
+
+ Chart will be here
+
+
+
+
+
User
+
+
+
Document
+
123
+
+
+
No workflow
+
123
+
+
+
Total
+
123
+
+
+
+
+
Index: quintagroup.analytics/tags/1.1.1/quintagroup/analytics/browser/views.py
===================================================================
--- quintagroup.analytics/tags/1.1.1/quintagroup/analytics/browser/views.py (revision 3470)
+++ quintagroup.analytics/tags/1.1.1/quintagroup/analytics/browser/views.py (revision 3470)
@@ -0,0 +1,585 @@
+from Acquisition import aq_base
+from zope.component import getUtility, getMultiAdapter, queryMultiAdapter
+
+from OFS.interfaces import IPropertyManager
+from Products.Five.browser import BrowserView
+from Products.CMFCore.utils import getToolByName
+from Products.CMFCore.interfaces import IFolderish
+from Products.Archetypes.interfaces import IBaseFolder
+try:
+ from plone.portlets.interfaces import IPortletManager
+ from plone.portlets.interfaces import IPortletAssignmentMapping
+ from plone.portlets.interfaces import ILocalPortletAssignmentManager
+ IPortletManager # pyflakes
+ IPortletAssignmentMapping # pyflakes
+ ILocalPortletAssignmentManager # pyflakes
+except ImportError:
+ IPortletManager = lambda assignment: {}
+ IPortletAssignmentMapping = lambda assignment: {}
+ ILocalPortletAssignmentManager = lambda assignment: {}
+try:
+ from plone.portlets.interfaces import IPortletAssignmentSettings
+ IPortletAssignmentSettings # pyflakes
+except ImportError:
+ # Before plon4 we don't have an annotation storage for settings.
+ IPortletAssignmentSettings = lambda assignment: {}
+
+from GChartWrapper import VerticalBarStack
+from quintagroup.analytics.config import COLORS, OTHER_TYPES, NO_WF_BIND
+from quintagroup.analytics import QuintagroupAnalyticsMessageFactory as _
+
+MENUEITEMS = [{'href':'qa_overview', 'content':_('Overview')},
+ {'href':'ownership_by_type', 'content':_('Ownership by type')},
+ {'href':'ownership_by_state', 'content':_('Ownership by state')},
+ {'href':'type_by_state', 'content':_('Types by state')},
+ {'href':'portlets_stats', 'content':_('Portlets stats')},
+ {'href':'legacy_portlets', 'content':_('Legacy portlets')},
+ {'href':'properties_stats', 'content':_('Properties stats')}]
+
+
+class AnalyticsBaseView(BrowserView):
+ def analiticsNavigation(self):
+ return MENUEITEMS
+
+
+class OwnershipByType(AnalyticsBaseView):
+ MAX = 10
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+ self.cat = getToolByName(self.context, 'portal_catalog')
+ self.users = None
+ self.total = None
+ self.types = None
+ self.data = {}
+
+ def getUsers(self):
+ if self.users is None:
+ index = self.cat._catalog.getIndex('Creator')
+ data = {}
+ for k in index._index.keys():
+ haslen = hasattr(index._index[k], '__len__')
+ if haslen:
+ data[k] = len(index._index[k])
+ else:
+ data[k] = 1
+ data = data.items()
+ data.sort(lambda a, b: a[1] - b[1])
+ data.reverse()
+ data = data[:self.MAX]
+ self.users = [i[0] for i in data]
+ self.total = [i[1] for i in data]
+ return self.users
+
+ def getTypes(self, all=False):
+ if self.types is None:
+ index = self.cat._catalog.getIndex('portal_type')
+ data = {}
+ for k in index._index.keys():
+ if not k:
+ continue
+ haslen = hasattr(index._index[k], '__len__')
+ if haslen:
+ data[k] = len(index._index[k])
+ else:
+ data[k] = 1
+ data = data.items()
+ data.sort(lambda a, b: a[1] - b[1])
+ data.reverse()
+ self.types = [i[0] for i in data]
+ return all and self.types or self.types[:self.MAX]
+
+ def getContent(self, type_):
+ if type_ not in self.data:
+ data = self.data[type_] = []
+ for user in self.getUsers():
+ res = self.cat(portal_type=type_, Creator=user)
+ l = len(res)
+ if l == 0:
+ data.append(0)
+ else:
+ data.append(l)
+ return self.data[type_]
+
+ def getTotal(self):
+ return self.total
+
+ def getChart(self):
+ data = []
+ types = self.getTypes()
+ for type_ in types:
+ data.append(self.getContent(type_))
+ other = [self.getContent(t) for t in self.getTypes(
+ all=True)[self.MAX:]]
+ if other:
+ data.append([sum(l) for l in zip(*other)])
+ max_value = max(self.getTotal())
+ chart = VerticalBarStack(data, encoding='text')
+ types = other and types + OTHER_TYPES or types
+ chart.title(_('Content ownership by type')).legend(*(types))
+ chart.bar('a', 10, 0).legend_pos("b")
+ chart.color(*COLORS)
+ chart.size(800, 375).scale(0, max_value).axes('xy').label(*self.users)
+ chart.axes.type("y")
+ chart.axes.range(0, 0, max_value)
+ return chart.img()
+
+
+class OwnershipByState(AnalyticsBaseView):
+ MAX = 10
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+ self.cat = getToolByName(self.context, 'portal_catalog')
+ self.users = None
+ self.states = None
+ self.total = None
+ self.data = {}
+
+ def getUsers(self):
+ if self.users is None:
+ index = self.cat._catalog.getIndex('Creator')
+ data = {}
+ for k in index._index.keys():
+ haslen = hasattr(index._index[k], '__len__')
+ if haslen:
+ data[k] = len(index._index[k])
+ else:
+ data[k] = 1
+ data = data.items()
+ data.sort(lambda a, b: a[1] - b[1])
+ data.reverse()
+ data = data[:self.MAX]
+ self.users = [i[0] for i in data]
+ self.total = [i[1] for i in data]
+ return self.users
+
+ def getStates(self):
+ if self.states is None:
+ index = self.cat._catalog.getIndex('review_state')
+ data = {}
+ for k in index._index.keys():
+ haslen = hasattr(index._index[k], '__len__')
+ if haslen:
+ data[k] = len(index._index[k])
+
+ else:
+ data[k] = 1
+
+ data = data.items()
+ data.sort(lambda a, b: a[1] - b[1])
+ data.reverse()
+ self.states = [i[0] for i in data]
+ return self.states
+
+ def getContent(self, type_):
+ if type_ not in self.data:
+ if NO_WF_BIND not in self.data:
+ self.data[NO_WF_BIND] = self.getTotal()
+ data = self.data[type_] = []
+ for user in self.getUsers():
+ res = self.cat(review_state=type_, Creator=user)
+ l = len(res)
+ if l == 0:
+ data.append(0)
+ else:
+ data.append(l)
+ if len(data) > 0:
+ self.data[NO_WF_BIND] = map(lambda t, d: t - d,
+ self.data[NO_WF_BIND], data)
+ return self.data[type_]
+
+ def getNoWFContentTitle(self):
+ return NO_WF_BIND
+
+ def getNoWFContent(self):
+ return self.getContent(NO_WF_BIND)
+
+ def getTotal(self):
+ if self.total is None:
+ self.getUsers()
+ return self.total
+
+ def getChart(self):
+ data = []
+ for state in self.getStates():
+ data.append(self.getContent(state))
+ data.append(self.getNoWFContent())
+ max_value = max(self.getTotal())
+ chart = VerticalBarStack(data, encoding='text')
+ title = _('Content ownership by state')
+ chart.title(title).legend(*self.states + [NO_WF_BIND])
+ chart.bar('a', 10, 0).legend_pos("b")
+ chart.color(*COLORS)
+ chart.size(800, 375).scale(0, max_value).axes('xy').label(*self.users)
+ chart.axes.type("y")
+ chart.axes.range(0, 0, max_value)
+ return chart.img()
+
+
+class TypeByState(AnalyticsBaseView):
+ MAX = 10
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+ self.cat = getToolByName(self.context, 'portal_catalog')
+ self.types = None
+ self.states = None
+ self.total = None
+ self.data = {}
+
+ def getTypes(self):
+ if self.types is None:
+ index = self.cat._catalog.getIndex('portal_type')
+ data = {}
+ for k in index._index.keys():
+ if not k:
+ continue
+ haslen = hasattr(index._index[k], '__len__')
+ if haslen:
+ data[k] = len(index._index[k])
+ else:
+ data[k] = 1
+ data = data.items()
+ data.sort(lambda a, b: a[1] - b[1])
+ data.reverse()
+ self.types = [i[0] for i in data[:self.MAX]]
+ self.total = [i[1] for i in data[:self.MAX]]
+ return self.types
+
+ def getStates(self):
+ if self.states is None:
+ index = self.cat._catalog.getIndex('review_state')
+ data = {}
+ for k in index._index.keys():
+ haslen = hasattr(index._index[k], '__len__')
+ if haslen:
+ data[k] = len(index._index[k])
+ else:
+ data[k] = 1
+ data = data.items()
+ data.sort(lambda a, b: a[1] - b[1])
+ data.reverse()
+ self.states = [i[0] for i in data]
+ return self.states
+
+ def getContent(self, state):
+ if state not in self.data:
+ if NO_WF_BIND not in self.data:
+ self.data[NO_WF_BIND] = self.getTotal()
+ data = self.data[state] = []
+ for type_ in self.getTypes():
+ res = self.cat(portal_type=type_, review_state=state)
+ l = len(res)
+ if l == 0:
+ data.append(0)
+ else:
+ data.append(l)
+ if len(data) > 0:
+ self.data[NO_WF_BIND] = map(lambda t, d: t - d,
+ self.data[NO_WF_BIND], data)
+ return self.data[state]
+
+ def getTotal(self):
+ if self.total is None:
+ self.getTypes()
+ return self.total
+
+ def getNoWFContentTitle(self):
+ return NO_WF_BIND
+
+ def getNoWFContent(self):
+ return self.getContent(NO_WF_BIND)
+
+ def getChart(self):
+ data = []
+ for state in self.getStates():
+ data.append(self.getContent(state))
+ data.append(self.getContent(NO_WF_BIND))
+ max_value = max(self.getTotal())
+ chart = VerticalBarStack(data, encoding='text')
+ chart.title(_('Content type by state')).legend(
+ *self.states + [NO_WF_BIND])
+ chart.bar('a', 10, 0).legend_pos("b")
+ chart.color(*COLORS)
+ chart.size(800, 375).scale(0, max_value).axes('xy').label(*self.types)
+ chart.axes.type("y")
+ chart.axes.range(0, 0, max_value)
+ return chart.img()
+
+
+class LegacyPortlets(AnalyticsBaseView):
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+ self.total = None
+ self.DEBUG = False
+ self.expressions = set()
+
+ def _getInfo(self, obj):
+ href = obj.absolute_url()
+ path = '/'.join(obj.getPhysicalPath())
+ info = {
+ 'path': path,
+ 'href': href,
+ 'left_slots': None,
+ 'right_slots': None,
+ }
+ if IPropertyManager.providedBy(obj):
+ obj = aq_base(obj)
+ if obj.hasProperty('left_slots'):
+ info['left_slots'] = obj.getProperty('left_slots')
+ self.expressions = self.expressions.union(
+ set(info['left_slots']))
+ if obj.hasProperty('right_slots'):
+ info['right_slots'] = obj.getProperty('right_slots')
+ self.expressions = self.expressions.union(
+ set(info['right_slots']))
+ return info
+
+ def _walk(self, obj, level=-1):
+ yield self._getInfo(obj)
+ if level != 0 and (IFolderish.providedBy(obj) \
+ or IBaseFolder.providedBy(obj)):
+ for v in obj.contentValues():
+ for i in self._walk(v, level - 1):
+ yield i
+
+ def getPortlets(self):
+ level = self.request.form.get('level', 1)
+ try:
+ level = level and int(level) or 1
+ except ValueError:
+ level = 1
+ infos = []
+ for i in self._walk(self.context, level):
+ if self.DEBUG or i['left_slots'] is not None \
+ or i['right_slots'] is not None:
+ infos.append(i)
+ self.total = len(infos)
+ return infos
+
+ def getTotal(self):
+ return self.total
+
+ def getAllPortletExpressions(self):
+ exprs = []
+ for name in self.expressions:
+ name = name.split('|')[0]
+ if not '/macros/' in name:
+ name = name.split('/')[-1]
+ name = name.strip()
+ if name not in exprs:
+ exprs.append(name)
+ exprs.sort()
+ return exprs
+
+
+class PropertiesStats(AnalyticsBaseView):
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+ self.total = None
+ self.DEBUG = False
+ self.expressions = set()
+ self.proplist = []
+ self.propname = self.request.form.get('propname') or ""
+
+ def _getInfo(self, obj):
+
+ href = obj.absolute_url()
+ path = '/'.join(obj.getPhysicalPath())
+ info = {
+ 'path': path,
+ 'href': href,
+ 'slots': None,
+ }
+ if IPropertyManager.providedBy(obj):
+ obj = aq_base(obj)
+ self.proplist.extend(
+ [i for i in obj.propertyIds() if i not in self.proplist])
+ if obj.hasProperty(self.propname):
+ info['slots'] = obj.getProperty(self.propname)
+ if isinstance(info['slots'], int):
+ info['slots'] = str(info['slots'])
+ if not isinstance(info['slots'], basestring):
+ self.expressions = self.expressions.union(
+ set(info['slots']))
+ else:
+ self.expressions = self.expressions.union(
+ set([info['slots']]))
+ return info
+
+ def _walk(self, obj, level=-1):
+ yield self._getInfo(obj)
+ if level != 0 and (IFolderish.providedBy(obj) \
+ or IBaseFolder.providedBy(obj)):
+ for v in obj.contentValues():
+ for i in self._walk(v, level - 1):
+ yield i
+
+ def getPropsList(self):
+ level = self.request.form.get('level', 1)
+ try:
+ level = level and int(level) or 1
+ except ValueError:
+ level = 1
+ infos = []
+ for i in self._walk(self.context, level):
+ if self.DEBUG or i['slots'] is not None:
+ infos.append(i)
+ self.total = len(infos)
+ return infos
+
+ def getTotal(self):
+ return self.total
+
+ def getAllPortletExpressions(self):
+ exprs = []
+ for name in self.expressions:
+ name = name.strip()
+ if name not in exprs:
+ exprs.append(name)
+ #exprs.sort()
+ return exprs
+
+
+class PortletsStats(AnalyticsBaseView):
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+ self.total = None
+ self.DEBUG = False
+ self.expressions = set()
+ self.proplist = []
+ self.propname = self.request.form.get('propname') or ""
+
+ def getAssignmentMappingUrl(self, context, manager):
+ baseUrl = str(getMultiAdapter((context, self.request),
+ name='absolute_url'))
+ return '%s/++contextportlets++%s' % (baseUrl, manager.__name__)
+
+ def getAssignmentsForManager(self, context, manager):
+ assignments = getMultiAdapter((context, manager),
+ IPortletAssignmentMapping)
+ return assignments.values()
+
+ def getPortletsMapping(self, context):
+ leftcolumn = getUtility(IPortletManager, name=u'plone.leftcolumn',
+ context=context)
+ rightcolumn = getUtility(IPortletManager, name=u'plone.rightcolumn',
+ context=context)
+ leftmapping = getMultiAdapter((context, leftcolumn,),
+ IPortletAssignmentMapping)
+ rightmapping = getMultiAdapter((context, rightcolumn,),
+ IPortletAssignmentMapping)
+ return (leftmapping, rightmapping)
+
+ def getLocalPortletsManager(self, context):
+ leftcolumn = getUtility(IPortletManager, name='plone.leftcolumn',
+ context=context)
+ rightcolumn = getUtility(IPortletManager, name='plone.rightcolumn',
+ context=context)
+ leftmanager = getMultiAdapter((context, leftcolumn,),
+ ILocalPortletAssignmentManager)
+ rightmanager = getMultiAdapter((context, rightcolumn,),
+ ILocalPortletAssignmentManager)
+ return (leftmanager, rightmanager)
+
+ def getPortletsManager(self, context):
+ left = getUtility(IPortletManager, name='plone.leftcolumn',
+ context=context)
+ right = getUtility(IPortletManager, name='plone.rightcolumn',
+ context=context)
+ return (left, right)
+
+ def portlets_for_assignments(self, assignments, manager, base_url):
+ data = []
+ for idx in range(len(assignments)):
+ name = assignments[idx].__name__
+
+ editview = queryMultiAdapter(
+ (assignments[idx], self.request), name='edit', default=None)
+
+ if editview is None:
+ editviewName = ''
+ else:
+ editviewName = '%s/%s/edit' % (base_url, name)
+
+ settings = IPortletAssignmentSettings(assignments[idx])
+
+ data.append({
+ 'title': assignments[idx].title,
+ 'editview': editviewName,
+ 'visible': settings.get('visible', True),
+ })
+ return data
+
+ def getPortlets(self, context, mapping, manager):
+ #import pdb; pdb.set_trace()
+ return mapping.keys()
+
+ def _getInfo(self, obj):
+ href = obj.absolute_url()
+ path = '/'.join(obj.getPhysicalPath())
+ info = {
+ 'path': path,
+ 'href': href,
+ 'left_slots': None,
+ 'right_slots': None,
+ }
+ left, right = self.getPortletsManager(obj)
+ #leftmapping, rightmapping = self.getPortletsMapping(obj)
+ #leftmanager, rightmanager = self.getLocalPortletsManager(obj)
+ #info['left_slots'] = self.getPortlets(obj, leftmapping, leftmanager)
+ #info['right_slots'] = self.getPortlets(obj, rightmapping,rightmanager)
+ lass = self.getAssignmentsForManager(obj, left)
+ rass = self.getAssignmentsForManager(obj, right)
+ lurl = self.getAssignmentMappingUrl(obj, left)
+ rurl = self.getAssignmentMappingUrl(obj, right)
+ plass = self.portlets_for_assignments(lass, left, lurl)
+ prass = self.portlets_for_assignments(rass, right, rurl)
+ #print obj, plass, prass
+ info['left_slots'] = plass # [i['title'] for i in plass]
+ info['right_slots'] = prass # [i['title'] for i in prass]
+ return info
+
+ def _walk(self, obj, level=-1):
+ try:
+ yield self._getInfo(obj)
+ except:
+ pass
+ if level != 0 and (IFolderish.providedBy(obj) \
+ or IBaseFolder.providedBy(obj)):
+ for v in obj.contentValues():
+ for i in self._walk(v, level - 1):
+ yield i
+
+ def getPropsList(self):
+ level = self.request.form.get('level', 1)
+ try:
+ level = level and int(level) or 1
+ except ValueError:
+ level = 1
+ infos = []
+ for i in self._walk(self.context, level):
+ if self.DEBUG or i['left_slots'] is not None \
+ or i['right_slots'] is not None:
+ infos.append(i)
+ self.total = len(infos)
+ return infos
+
+ def getTotal(self):
+ return self.total
+
+ def getAllPortletExpressions(self):
+ exprs = []
+ for name in self.expressions:
+ name = name.strip()
+ if name not in exprs:
+ exprs.append(name)
+ #exprs.sort()
+ return exprs
Index: quintagroup.analytics/tags/1.1.1/quintagroup/analytics/config.py
===================================================================
--- quintagroup.analytics/tags/1.1.1/quintagroup/analytics/config.py (revision 3470)
+++ quintagroup.analytics/tags/1.1.1/quintagroup/analytics/config.py (revision 3470)
@@ -0,0 +1,4 @@
+COLORS = ['669933', 'CC9966', '993300', 'FF6633', 'E8E4E3', 'A9A486',
+ 'DCB57E', 'FFCC99', '996633', '333300', '00FF00']
+OTHER_TYPES = ['Other types']
+NO_WF_BIND = 'No workflow'
Index: quintagroup.analytics/tags/1.1.1/quintagroup/analytics/configure.zcml
===================================================================
--- quintagroup.analytics/tags/1.1.1/quintagroup/analytics/configure.zcml (revision 3470)
+++ quintagroup.analytics/tags/1.1.1/quintagroup/analytics/configure.zcml (revision 3470)
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
Index: quintagroup.analytics/tags/1.1.1/quintagroup/analytics/locales/fr/LC_MESSAGES/quintagroup.analytics.po
===================================================================
--- quintagroup.analytics/tags/1.1.1/quintagroup/analytics/locales/fr/LC_MESSAGES/quintagroup.analytics.po (revision 3470)
+++ quintagroup.analytics/tags/1.1.1/quintagroup/analytics/locales/fr/LC_MESSAGES/quintagroup.analytics.po (revision 3470)
@@ -0,0 +1,240 @@
+# --- PLEASE EDIT THE LINES BELOW CORRECTLY ---
+# SOME DESCRIPTIVE TITLE.
+# FIRST AUTHOR , YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: Quintagroup Analytics\n"
+"POT-Creation-Date: 2012-04-05 08:00+0000\n"
+"PO-Revision-Date: 2012-03-27 14:52+0100\n"
+"Last-Translator: Julien Stegle \n"
+"Language-Team: Quadra Informatique \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0\n"
+"Language-Code: en\n"
+"Language-Name: English\n"
+"Preferred-Encodings: utf-8 latin1\n"
+"Domain: quintagroup.analytics\n"
+"Language: \n"
+"X-Poedit-Language: French\n"
+"X-Poedit-Country: FRANCE\n"
+
+#: quintagroup.analytics/quintagroup/analytics/browser/views.py:202
+msgid "Content ownership by state"
+msgstr "Appartenance par état"
+
+#: quintagroup.analytics/quintagroup/analytics/browser/views.py:110
+msgid "Content ownership by type"
+msgstr "Appartenance par type"
+
+#: quintagroup.analytics/quintagroup/analytics/browser/views.py:294
+msgid "Content type by state"
+msgstr "Types de contenu par état"
+
+#: quintagroup.analytics/quintagroup/analytics/browser/views.py:28
+msgid "Legacy portlets"
+msgstr "Portlets hérités"
+
+#: quintagroup.analytics/quintagroup/analytics/browser/views.py:23
+msgid "Overview"
+msgstr "Vue globale"
+
+#: quintagroup.analytics/quintagroup/analytics/browser/views.py:25
+msgid "Ownership by state"
+msgstr "Appartenance par état"
+
+#: quintagroup.analytics/quintagroup/analytics/browser/views.py:24
+msgid "Ownership by type"
+msgstr "Appartenance par type"
+
+#: quintagroup.analytics/quintagroup/analytics/browser/views.py:27
+msgid "Portlets stats"
+msgstr "Statistiques des portlets"
+
+#: quintagroup.analytics/quintagroup/analytics/browser/views.py:29
+msgid "Properties stats"
+msgstr "Statistiques des propriétés"
+
+#: quintagroup.analytics/quintagroup/analytics/browser/views.py:26
+msgid "Types by state"
+msgstr "Types par état"
+
+#. Default: "Search depth (-1 means no limit, 1 means that objects in the site root only will be searched):"
+#: quintagroup.analytics/quintagroup/analytics/browser/legacy_portlets.pt:22
+msgid "lg_form_helper"
+msgstr "Profondeur de recherche (-1 pour aucune limite, 1 signifie que les objects à la racine du site seront inclus dans la recherche)"
+
+#. Default: "The following chart displays workflow states of site's content objects in relation to their owners (up to 10 most active site content contributors are displayed)."
+#: quintagroup.analytics/quintagroup/analytics/browser/ownership_by_state.pt:18
+msgid "navigation_paragraph_own_by_state_desc"
+msgstr "Le graphique ci-dessous affiche les états de publication des contenus du site en relation avec leur(s) propriétaire(s) (jusqu'à 10 des contenus appartenant aux contributeurs les plus actifs sont affichés)."
+
+#. Default: "The following list can display site's legacy portlets, assigned as left and right slots on different site contexts. Specify the search depth: portlets on what level(s) to be included. Select CSV to render this information in .csv format."
+#: quintagroup.analytics/quintagroup/analytics/browser/legacy_portlets.pt:18
+msgid "navigation_paragraph_portlets_desc"
+msgstr "La liste suivante affiche les portlets hérités, assignés aux emplacements gauche ou droit sur des contenus différents. Veuillez spécifier la profondeur de recherche indiquant à quel(s) niveau(x) les portlets doivent être inclus. Sélectionner l'option \"CSV\" pour afficher cette information au format CSV."
+
+#. Default: "The table under the chart displays number of content objects in all site's workflow states in relation to content owners."
+#: quintagroup.analytics/quintagroup/analytics/browser/ownership_by_state.pt:20
+msgid "own_by_state_desc"
+msgstr "Le tableau ci-dessous affiche le nombre de contenus pour chaque état de publication ainsi que leurs propriétaire(s)."
+
+#. Default: "The following chart displays the most frequently created content types in relation to their owners (up to 10 most active site content contributors and up to 10 most popular content types are displayed)."
+#: quintagroup.analytics/quintagroup/analytics/browser/ownership_by_type.pt:18
+msgid "own_by_type_desc1"
+msgstr "Le graphique suivant affiche les types de contenus les plus fréquemment créés en relation avec leurs propriétaires (jusqu'à 10 des contributeurs les plus actifs et jusqu'à 10 des type de contenus les plus populaires sont listés)."
+
+#. Default: "The table under the chart displays number of content objects of all site's content types in relation to their owners. Besides, the total number of created objects by every contributor is provided."
+#: quintagroup.analytics/quintagroup/analytics/browser/ownership_by_type.pt:22
+msgid "own_by_type_desc2"
+msgstr "Le tableau en dessous de ce graphique affiche le nombre d'éléments créés pour chacun des types de contenus disponibles en relation avec leur(s) propriétaire(s). De plus, le total d'éléments créés par tous les contributeurs est affiché."
+
+#. Default: "The following list can displays site's portlets, assigned to left and right columns on different contexts. Specify the search depth: portlets on what level(s) to be included. Click on the porltet title to land on the porltet edit form. Select CSV to render this information in .csv format."
+#: quintagroup.analytics/quintagroup/analytics/browser/portlets_stats.pt:19
+msgid "portlets_stats_desc"
+msgstr "La liste qui suit affiche les portlets du site, assignés aux emplacements gauche et droit. Veuillez spécifier la profondeur de recherche indiquant à quel(s) niveau(x) les portlets doivent être inclus. Sélectionner l'option \"CSV\" pour afficher cette information au format CSV."
+
+#. Default: "Search depth (-1 means no limit, 1 means that objects in the site root only will be searched):"
+#: quintagroup.analytics/quintagroup/analytics/browser/portlets_stats.pt:23
+msgid "portlets_stats_form_desc"
+msgstr "Profondeur de recherche (-1 pour aucune limite, 1 signifie que les objects à la racine du site seront inclus dans la recherche)"
+
+#. Default: "The following list can display values for certain property for site objects. To generate the list - specify the search depth (objects on what level(s) will be included) and type in property name (a list of all available properties is provided below). For example: to display all site objects titles type '-1' into 'Search depth' field and 'title' into 'Property name' field. Select CSV to render this information in .csv format."
+#: quintagroup.analytics/quintagroup/analytics/browser/properties_stats.pt:19
+msgid "prop_stats_desc"
+msgstr "La liste suivante affiche les valeurs de certaines propriétés des contenus du site. Afin de générer cette liste, spécifiez la profondeur de recherche et renseigez le nom de la propriété (une liste des toutes les propriétés disponibles est fournie plus bas).Par exemple, pour afficher les titres des contenus du site, tapez -1 dans le champ \"profondeur de recherche\" et \"title\" dans le champ \"Nom de la propriété\". Sélectionner l'option \"CSV\" pour afficher cette information au format CSV."
+
+#. Default: "Search depth (-1 means no limit, 1 means that objects in the site root only will be searched):"
+#: quintagroup.analytics/quintagroup/analytics/browser/properties_stats.pt:26
+msgid "prop_stats_form_desc"
+msgstr "Profondeur de recherche (-1 pour aucune limite, 1 signifie que les objects à la racine du site seront inclus dans la recherche)"
+
+#. Default: "Quintagroup Analytics Tool for Plone"
+#: quintagroup.analytics/quintagroup/analytics/browser/legacy_portlets.pt:12
+#: quintagroup.analytics/quintagroup/analytics/browser/ownership_by_state.pt:11
+#: quintagroup.analytics/quintagroup/analytics/browser/ownership_by_type.pt:11
+msgid "qa_main_title"
+msgstr "Outil d'analyse de Plone par Quintagroup."
+
+#. Default: "Quintagroup Analytics Tool provides statistic information about your Plone site. You can see how many content objects are on you site, who are content owners, what are content workflow states, and how many portlets are assigned on different contexts."
+#: quintagroup.analytics/quintagroup/analytics/browser/qa_overview.pt:18
+msgid "qa_overview_paragraph_desc1"
+msgstr "Cet outil fournit des informations statistiques sur votre site Plone. Vous pourrez voir combien de contenus votre site contient, qui sont les propriétaires de ces contenus, quels sont leurs états de publication et combien de portlets sont assignés sur les différents contenus."
+
+#. Default: "Information provided by Quintagroup Analytics Tool allows you to see your site's overall content from different perspectives. This can be very useful while migrating your site into newer Plone version, or into another CMS. With its help you can visually audit the content setup in Plone site before migration and compare it with the migrated website structure."
+#: quintagroup.analytics/quintagroup/analytics/browser/qa_overview.pt:22
+msgid "qa_overview_paragraph_desc2"
+msgstr "Les informations fournies par l'outil d'analyse de Quintagroup vous permet de visualiser le contenu de votre site sous différents perspectives. Cela peut s'avérer utile lors d'une migration de version de Plone, ou vers un autre gestionnaire de contenus. Avec son aide vous pourrez effectuer un audit de manière visuelle des contenus avant migration et comparer les résultats une fois le site migré."
+
+#. Default: "Visit the following pages to see your site's statistics:"
+#: quintagroup.analytics/quintagroup/analytics/browser/qa_overview.pt:27
+msgid "qa_overview_paragraph_visit"
+msgstr "Visitez les pages suivants pour voir les statistiques de votre site."
+
+#. Default: "Welcome to Quintagroup Analytics Tool"
+#: quintagroup.analytics/quintagroup/analytics/browser/qa_overview.pt:17
+msgid "qa_overview_subtitle_welcome"
+msgstr "BIenvenu sur l'outil d'analyse de Quintagroup."
+
+#. Default: "Content Ownership by State"
+#: quintagroup.analytics/quintagroup/analytics/browser/qa_overview.pt:35
+msgid "qa_overview_visits_link_content_by_state"
+msgstr "Appartenance des contenus par état"
+
+#. Default: "Information about site's content workflow states. Here you can see how many content object are published/submitted for review/etc. and their owners."
+#: quintagroup.analytics/quintagroup/analytics/browser/qa_overview.pt:37
+msgid "qa_overview_visits_link_content_by_state_desc"
+msgstr "Des informations sur les états de publication des contenus. Vous pourrez voir le nombre de contenus publiés / soumis à la publication / etc..., ainsi que leurs propriétaires."
+
+#. Default: "Content Ownership by Type"
+#: quintagroup.analytics/quintagroup/analytics/browser/qa_overview.pt:30
+msgid "qa_overview_visits_link_content_by_type"
+msgstr "Appartenance des contenus par type"
+
+#. Default: "Information about most popular content types on your site. Here you can see the most frequently created content types on your site and their owners."
+#: quintagroup.analytics/quintagroup/analytics/browser/qa_overview.pt:32
+msgid "qa_overview_visits_link_content_by_type_desc"
+msgstr "Des informations sur les types de contenus les plus populaires. Vous pourrez voir les types de contenus les plus fréquemment créés ainsi que leurs propriétaires."
+
+#. Default: "Content Types by State"
+#: quintagroup.analytics/quintagroup/analytics/browser/qa_overview.pt:40
+msgid "qa_overview_visits_link_content_types_by_state"
+msgstr "Types de contenus par état"
+
+#. Default: "Information about site's most frequently created content types and their workflow states."
+#: quintagroup.analytics/quintagroup/analytics/browser/qa_overview.pt:42
+msgid "qa_overview_visits_link_content_types_by_state_desc"
+msgstr "Des informations usr les types de contenus les plus fréquemment créés ainsi que leur état de publication."
+
+#. Default: "Legacy Portlets"
+#: quintagroup.analytics/quintagroup/analytics/browser/qa_overview.pt:49
+msgid "qa_overview_visits_link_legacy_portlets"
+msgstr "Portlets hérités."
+
+#. Default: "Information about legacy portlets assigned throughout site sections. This information can be exported into .csv format."
+#: quintagroup.analytics/quintagroup/analytics/browser/qa_overview.pt:51
+msgid "qa_overview_visits_link_legacy_portlets_desc"
+msgstr "Des informations sur les portlets hérités sur l'ensemble des sections du site. Ces informations peuvent être exportées au format CSV."
+
+#. Default: "Site Portlets"
+#: quintagroup.analytics/quintagroup/analytics/browser/qa_overview.pt:44
+msgid "qa_overview_visits_link_portlets"
+msgstr "Portlets"
+
+#. Default: "Information about site portlets assigned throughout site sections. This information can be exported into .csv format."
+#: quintagroup.analytics/quintagroup/analytics/browser/qa_overview.pt:46
+msgid "qa_overview_visits_link_portlets_desc"
+msgstr "Des informations sur les portlets assignés sur les divers contenus du site.. Ces informations peuvent être exportées au format CSV."
+
+#. Default: "Properties Stats"
+#: quintagroup.analytics/quintagroup/analytics/browser/qa_overview.pt:54
+msgid "qa_overview_visits_link_properties"
+msgstr "Statistiques des propriétés"
+
+#. Default: "Information on certain property values for all site objects."
+#: quintagroup.analytics/quintagroup/analytics/browser/qa_overview.pt:56
+msgid "qa_overview_visits_link_properties_desc"
+msgstr "Des informations sur les valeurs de certaines propriétés des contenus du site."
+
+#. Default: "doesn't exist"
+#: quintagroup.analytics/quintagroup/analytics/browser/legacy_portlets.pt:61
+#: quintagroup.analytics/quintagroup/analytics/browser/portlets_stats.pt:77
+#: quintagroup.analytics/quintagroup/analytics/browser/properties_stats.pt:71
+msgid "span_doesnt_exist"
+msgstr "n'existe pas"
+
+#. Default: "None"
+#: quintagroup.analytics/quintagroup/analytics/browser/properties_stats.pt:54
+msgid "span_none"
+msgstr "Aucun"
+
+#. Default: "Expressions"
+#: quintagroup.analytics/quintagroup/analytics/browser/legacy_portlets.pt:75
+#: quintagroup.analytics/quintagroup/analytics/browser/properties_stats.pt:79
+msgid "table_header_expressions"
+msgstr "Expressions"
+
+#. Default: "left_slots"
+#: quintagroup.analytics/quintagroup/analytics/browser/legacy_portlets.pt:52
+#: quintagroup.analytics/quintagroup/analytics/browser/portlets_stats.pt:57
+msgid "table_header_left_slots"
+msgstr "Emplacements de gauche"
+
+#. Default: "right_slots"
+#: quintagroup.analytics/quintagroup/analytics/browser/legacy_portlets.pt:53
+#: quintagroup.analytics/quintagroup/analytics/browser/portlets_stats.pt:58
+msgid "table_header_right_slots"
+msgstr "Emplacements de droite"
+
+#. Default: "Total"
+#: quintagroup.analytics/quintagroup/analytics/browser/legacy_portlets.pt:71
+#: quintagroup.analytics/quintagroup/analytics/browser/properties_stats.pt:75
+msgid "table_header_total"
+msgstr "Total"
+
+#. Default: "The following chart and table display workflow states of the site's most frequently created content types. You can see the total number of site's content objects of every content type."
+#: quintagroup.analytics/quintagroup/analytics/browser/type_by_state.pt:19
+msgid "type_by_state_desc"
+msgstr "Le graphique et la tableau ci-dessous affiche les états de publication des types de contenus les plus fréquemment créés. Vous pouvez voir le total d'éléments créés pour chaque type de contenu."
+
Index: quintagroup.analytics/tags/1.1.1/quintagroup/analytics/locales/quintagroup.analytics.pot
===================================================================
--- quintagroup.analytics/tags/1.1.1/quintagroup/analytics/locales/quintagroup.analytics.pot (revision 3470)
+++ quintagroup.analytics/tags/1.1.1/quintagroup/analytics/locales/quintagroup.analytics.pot (revision 3470)
@@ -0,0 +1,237 @@
+# --- PLEASE EDIT THE LINES BELOW CORRECTLY ---
+# SOME DESCRIPTIVE TITLE.
+# FIRST AUTHOR , YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"POT-Creation-Date: 2012-04-05 08:00+0000\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0\n"
+"Language-Code: en\n"
+"Language-Name: English\n"
+"Preferred-Encodings: utf-8 latin1\n"
+"Domain: quintagroup.analytics\n"
+
+#: quintagroup.analytics/quintagroup/analytics/browser/views.py:202
+msgid "Content ownership by state"
+msgstr ""
+
+#: quintagroup.analytics/quintagroup/analytics/browser/views.py:110
+msgid "Content ownership by type"
+msgstr ""
+
+#: quintagroup.analytics/quintagroup/analytics/browser/views.py:294
+msgid "Content type by state"
+msgstr ""
+
+#: quintagroup.analytics/quintagroup/analytics/browser/views.py:28
+msgid "Legacy portlets"
+msgstr ""
+
+#: quintagroup.analytics/quintagroup/analytics/browser/views.py:23
+msgid "Overview"
+msgstr ""
+
+#: quintagroup.analytics/quintagroup/analytics/browser/views.py:25
+msgid "Ownership by state"
+msgstr ""
+
+#: quintagroup.analytics/quintagroup/analytics/browser/views.py:24
+msgid "Ownership by type"
+msgstr ""
+
+#: quintagroup.analytics/quintagroup/analytics/browser/views.py:27
+msgid "Portlets stats"
+msgstr ""
+
+#: quintagroup.analytics/quintagroup/analytics/browser/views.py:29
+msgid "Properties stats"
+msgstr ""
+
+#: quintagroup.analytics/quintagroup/analytics/browser/views.py:26
+msgid "Types by state"
+msgstr ""
+
+#. Default: "Search depth (-1 means no limit, 1 means that objects in the site root only will be searched):"
+#: quintagroup.analytics/quintagroup/analytics/browser/legacy_portlets.pt:22
+msgid "lg_form_helper"
+msgstr ""
+
+#. Default: "The following chart displays workflow states of site's content objects in relation to their owners (up to 10 most active site content contributors are displayed)."
+#: quintagroup.analytics/quintagroup/analytics/browser/ownership_by_state.pt:18
+msgid "navigation_paragraph_own_by_state_desc"
+msgstr ""
+
+#. Default: "The following list can display site's legacy portlets, assigned as left and right slots on different site contexts. Specify the search depth: portlets on what level(s) to be included. Select CSV to render this information in .csv format."
+#: quintagroup.analytics/quintagroup/analytics/browser/legacy_portlets.pt:18
+msgid "navigation_paragraph_portlets_desc"
+msgstr ""
+
+#. Default: "The table under the chart displays number of content objects in all site's workflow states in relation to content owners."
+#: quintagroup.analytics/quintagroup/analytics/browser/ownership_by_state.pt:20
+msgid "own_by_state_desc"
+msgstr ""
+
+#. Default: "The following chart displays the most frequently created content types in relation to their owners (up to 10 most active site content contributors and up to 10 most popular content types are displayed)."
+#: quintagroup.analytics/quintagroup/analytics/browser/ownership_by_type.pt:18
+msgid "own_by_type_desc1"
+msgstr ""
+
+#. Default: "The table under the chart displays number of content objects of all site's content types in relation to their owners. Besides, the total number of created objects by every contributor is provided."
+#: quintagroup.analytics/quintagroup/analytics/browser/ownership_by_type.pt:22
+msgid "own_by_type_desc2"
+msgstr ""
+
+#. Default: "The following list can displays site's portlets, assigned to left and right columns on different contexts. Specify the search depth: portlets on what level(s) to be included. Click on the porltet title to land on the porltet edit form. Select CSV to render this information in .csv format."
+#: quintagroup.analytics/quintagroup/analytics/browser/portlets_stats.pt:19
+msgid "portlets_stats_desc"
+msgstr ""
+
+#. Default: "Search depth (-1 means no limit, 1 means that objects in the site root only will be searched):"
+#: quintagroup.analytics/quintagroup/analytics/browser/portlets_stats.pt:23
+msgid "portlets_stats_form_desc"
+msgstr ""
+
+#. Default: "The following list can display values for certain property for site objects. To generate the list - specify the search depth (objects on what level(s) will be included) and type in property name (a list of all available properties is provided below). For example: to display all site objects titles type '-1' into 'Search depth' field and 'title' into 'Property name' field. Select CSV to render this information in .csv format."
+#: quintagroup.analytics/quintagroup/analytics/browser/properties_stats.pt:19
+msgid "prop_stats_desc"
+msgstr ""
+
+#. Default: "Search depth (-1 means no limit, 1 means that objects in the site root only will be searched):"
+#: quintagroup.analytics/quintagroup/analytics/browser/properties_stats.pt:26
+msgid "prop_stats_form_desc"
+msgstr ""
+
+#. Default: "Quintagroup Analytics Tool for Plone"
+#: quintagroup.analytics/quintagroup/analytics/browser/legacy_portlets.pt:12
+#: quintagroup.analytics/quintagroup/analytics/browser/ownership_by_state.pt:11
+#: quintagroup.analytics/quintagroup/analytics/browser/ownership_by_type.pt:11
+msgid "qa_main_title"
+msgstr ""
+
+#. Default: "Quintagroup Analytics Tool provides statistic information about your Plone site. You can see how many content objects are on you site, who are content owners, what are content workflow states, and how many portlets are assigned on different contexts."
+#: quintagroup.analytics/quintagroup/analytics/browser/qa_overview.pt:18
+msgid "qa_overview_paragraph_desc1"
+msgstr ""
+
+#. Default: "Information provided by Quintagroup Analytics Tool allows you to see your site's overall content from different perspectives. This can be very useful while migrating your site into newer Plone version, or into another CMS. With its help you can visually audit the content setup in Plone site before migration and compare it with the migrated website structure."
+#: quintagroup.analytics/quintagroup/analytics/browser/qa_overview.pt:22
+msgid "qa_overview_paragraph_desc2"
+msgstr ""
+
+#. Default: "Visit the following pages to see your site's statistics:"
+#: quintagroup.analytics/quintagroup/analytics/browser/qa_overview.pt:27
+msgid "qa_overview_paragraph_visit"
+msgstr ""
+
+#. Default: "Welcome to Quintagroup Analytics Tool"
+#: quintagroup.analytics/quintagroup/analytics/browser/qa_overview.pt:17
+msgid "qa_overview_subtitle_welcome"
+msgstr ""
+
+#. Default: "Content Ownership by State"
+#: quintagroup.analytics/quintagroup/analytics/browser/qa_overview.pt:35
+msgid "qa_overview_visits_link_content_by_state"
+msgstr ""
+
+#. Default: "Information about site's content workflow states. Here you can see how many content object are published/submitted for review/etc. and their owners."
+#: quintagroup.analytics/quintagroup/analytics/browser/qa_overview.pt:37
+msgid "qa_overview_visits_link_content_by_state_desc"
+msgstr ""
+
+#. Default: "Content Ownership by Type"
+#: quintagroup.analytics/quintagroup/analytics/browser/qa_overview.pt:30
+msgid "qa_overview_visits_link_content_by_type"
+msgstr ""
+
+#. Default: "Information about most popular content types on your site. Here you can see the most frequently created content types on your site and their owners."
+#: quintagroup.analytics/quintagroup/analytics/browser/qa_overview.pt:32
+msgid "qa_overview_visits_link_content_by_type_desc"
+msgstr ""
+
+#. Default: "Content Types by State"
+#: quintagroup.analytics/quintagroup/analytics/browser/qa_overview.pt:40
+msgid "qa_overview_visits_link_content_types_by_state"
+msgstr ""
+
+#. Default: "Information about site's most frequently created content types and their workflow states."
+#: quintagroup.analytics/quintagroup/analytics/browser/qa_overview.pt:42
+msgid "qa_overview_visits_link_content_types_by_state_desc"
+msgstr ""
+
+#. Default: "Legacy Portlets"
+#: quintagroup.analytics/quintagroup/analytics/browser/qa_overview.pt:49
+msgid "qa_overview_visits_link_legacy_portlets"
+msgstr ""
+
+#. Default: "Information about legacy portlets assigned throughout site sections. This information can be exported into .csv format."
+#: quintagroup.analytics/quintagroup/analytics/browser/qa_overview.pt:51
+msgid "qa_overview_visits_link_legacy_portlets_desc"
+msgstr ""
+
+#. Default: "Site Portlets"
+#: quintagroup.analytics/quintagroup/analytics/browser/qa_overview.pt:44
+msgid "qa_overview_visits_link_portlets"
+msgstr ""
+
+#. Default: "Information about site portlets assigned throughout site sections. This information can be exported into .csv format."
+#: quintagroup.analytics/quintagroup/analytics/browser/qa_overview.pt:46
+msgid "qa_overview_visits_link_portlets_desc"
+msgstr ""
+
+#. Default: "Properties Stats"
+#: quintagroup.analytics/quintagroup/analytics/browser/qa_overview.pt:54
+msgid "qa_overview_visits_link_properties"
+msgstr ""
+
+#. Default: "Information on certain property values for all site objects."
+#: quintagroup.analytics/quintagroup/analytics/browser/qa_overview.pt:56
+msgid "qa_overview_visits_link_properties_desc"
+msgstr ""
+
+#. Default: "doesn't exist"
+#: quintagroup.analytics/quintagroup/analytics/browser/legacy_portlets.pt:61
+#: quintagroup.analytics/quintagroup/analytics/browser/portlets_stats.pt:77
+#: quintagroup.analytics/quintagroup/analytics/browser/properties_stats.pt:71
+msgid "span_doesnt_exist"
+msgstr ""
+
+#. Default: "None"
+#: quintagroup.analytics/quintagroup/analytics/browser/properties_stats.pt:54
+msgid "span_none"
+msgstr ""
+
+#. Default: "Expressions"
+#: quintagroup.analytics/quintagroup/analytics/browser/legacy_portlets.pt:75
+#: quintagroup.analytics/quintagroup/analytics/browser/properties_stats.pt:79
+msgid "table_header_expressions"
+msgstr ""
+
+#. Default: "left_slots"
+#: quintagroup.analytics/quintagroup/analytics/browser/legacy_portlets.pt:52
+#: quintagroup.analytics/quintagroup/analytics/browser/portlets_stats.pt:57
+msgid "table_header_left_slots"
+msgstr ""
+
+#. Default: "right_slots"
+#: quintagroup.analytics/quintagroup/analytics/browser/legacy_portlets.pt:53
+#: quintagroup.analytics/quintagroup/analytics/browser/portlets_stats.pt:58
+msgid "table_header_right_slots"
+msgstr ""
+
+#. Default: "Total"
+#: quintagroup.analytics/quintagroup/analytics/browser/legacy_portlets.pt:71
+#: quintagroup.analytics/quintagroup/analytics/browser/properties_stats.pt:75
+msgid "table_header_total"
+msgstr ""
+
+#. Default: "The following chart and table display workflow states of the site's most frequently created content types. You can see the total number of site's content objects of every content type."
+#: quintagroup.analytics/quintagroup/analytics/browser/type_by_state.pt:19
+msgid "type_by_state_desc"
+msgstr ""
+
Index: quintagroup.analytics/tags/1.1.1/quintagroup/analytics/profiles/default/controlpanel.xml
===================================================================
--- quintagroup.analytics/tags/1.1.1/quintagroup/analytics/profiles/default/controlpanel.xml (revision 3470)
+++ quintagroup.analytics/tags/1.1.1/quintagroup/analytics/profiles/default/controlpanel.xml (revision 3470)
@@ -0,0 +1,11 @@
+
+
Index: quintagroup.analytics/tags/1.1.1/quintagroup/analytics/tests.py
===================================================================
--- quintagroup.analytics/tags/1.1.1/quintagroup/analytics/tests.py (revision 3470)
+++ quintagroup.analytics/tags/1.1.1/quintagroup/analytics/tests.py (revision 3470)
@@ -0,0 +1,533 @@
+import unittest
+import transaction
+
+from AccessControl.SecurityManagement import newSecurityManager
+from Testing import ZopeTestCase as ztc
+from Products.Five import zcml
+from Products.Five import fiveconfigure
+from zope.component import queryMultiAdapter, getUtility
+
+from Products.PloneTestCase import PloneTestCase as ptc
+from Products.PloneTestCase import setup as ptc_setup
+from Products.PloneTestCase.layer import PloneSite
+from plone.portlets.interfaces import IPortletType
+try:
+ from Products.PloneTestCase.version import PLONE40
+ PLONE40 = PLONE40
+except ImportError:
+ PLONE40 = False
+
+import quintagroup.analytics
+
+ptc.setupPloneSite()
+
+
+class Installed(PloneSite):
+
+ @classmethod
+ def setUp(cls):
+ fiveconfigure.debug_mode = True
+ zcml.load_config('configure.zcml',
+ quintagroup.analytics)
+ fiveconfigure.debug_mode = False
+ ztc.installPackage('quintagroup.analytics')
+ app = ztc.app()
+ portal = app[ptc_setup.portal_name]
+
+ # Sets the local site/manager
+ ptc_setup._placefulSetUp(portal)
+
+ qi = getattr(portal, 'portal_quickinstaller', None)
+ qi.installProduct('quintagroup.analytics')
+ transaction.commit()
+
+ @classmethod
+ def tearDown(cls):
+ pass
+
+
+class SetUpContent(Installed):
+
+ max = 10
+ types_ = ['Document', 'Event', 'Folder']
+ users = [('user%s' % i, 'user%s' % i, 'Member', None)
+ for i in xrange(max)]
+
+ @classmethod
+ def setupUsers(cls, portal):
+ """ Creates users."""
+ acl_users = portal.acl_users
+ mp = portal.portal_membership
+ map(acl_users._doAddUser, *zip(*cls.users))
+ if not mp.memberareaCreationFlag:
+ mp.setMemberareaCreationFlag()
+ map(mp.createMemberArea, [u[0] for u in cls.users])
+
+ @classmethod
+ def setupContent(cls, portal):
+ """ Creates test content."""
+ uf = portal.acl_users
+ pm = portal.portal_membership
+ #portal.portal_catalog
+ users = [u[0] for u in cls.users]
+ for u in users:
+ folder = pm.getHomeFolder(u)
+ user = uf.getUserById(u)
+ if not hasattr(user, 'aq_base'):
+ user = user.__of__(uf)
+ newSecurityManager(None, user)
+ for i in xrange(users.index(u) + cls.max):
+ map(folder.invokeFactory, cls.types_,
+ [t + str(i) for t in cls.types_])
+ transaction.commit()
+
+ @classmethod
+ def setUp(cls):
+ app = ztc.app()
+ portal = app[ptc_setup.portal_name]
+ cls.setupUsers(portal)
+ cls.setupContent(portal)
+
+ @classmethod
+ def tearDown(cls):
+ pass
+
+
+class TestCase(ptc.PloneTestCase):
+ layer = Installed
+
+
+class TestQAInstallation(TestCase):
+ """ This class veryfies registrations of all needed views and
+ actions.
+ """
+
+ def test_cp_action_installation(self):
+ """This test validates control panel action. """
+ control_panel = self.portal.portal_controlpanel
+ self.assert_(
+ 'QAnalytics' in [a.id for a in control_panel.listActions()],
+ "Configlet for quintagroup.analitycs isn't registered.")
+
+ def test_OwnershipByType(self):
+ """ This test validates registration of
+ ownership_by_type view.
+ """
+ view = queryMultiAdapter((self.portal, self.portal.REQUEST),
+ name="ownership_by_type")
+
+ self.assert_(view, "Ownership by type view isn't registered")
+
+ def test_OwnershipByState(self):
+ """ This test validates registration of
+ ownership_by_state view.
+ """
+ view = queryMultiAdapter((self.portal, self.portal.REQUEST),
+ name="ownership_by_state")
+
+ self.assert_(view, "Ownership by state view isn't registered")
+
+ def test_TypeByState(self):
+ """ This test validates registration of
+ type_by_state view.
+ """
+ view = queryMultiAdapter((self.portal, self.portal.REQUEST),
+ name="type_by_state")
+
+ self.assert_(view, "Type by state view isn't registered")
+
+ def test_LegacyPortlets(self):
+ """ This test validates registration of
+ legacy_portlets view.
+ """
+ view = queryMultiAdapter((self.portal, self.portal.REQUEST),
+ name="legacy_portlets")
+
+ self.assert_(view, "Legacy Portlets view isn't registered")
+
+ def test_PropertiesStats(self):
+ """ This test validates registration of
+ properties_stats view.
+ """
+ view = queryMultiAdapter((self.portal, self.portal.REQUEST),
+ name="properties_stats")
+
+ self.assert_(view, "Properties Stats view isn't registered")
+
+ def test_PortletsStats(self):
+ """ This test validates registration of
+ portlets_stats view.
+ """
+ view = queryMultiAdapter((self.portal, self.portal.REQUEST),
+ name="portlets_stats")
+
+ self.assert_(view, "Portlets Stats view isn't registered")
+
+
+class TestOwnershipByType(TestCase):
+ """Tests all ownership by type view methods."""
+
+ layer = SetUpContent
+
+ def afterSetUp(self):
+ self.view = queryMultiAdapter((self.portal, self.portal.REQUEST),
+ name="ownership_by_type")
+ self.pc = self.portal.portal_catalog
+ portal_migration = self.portal.portal_migration
+ version = portal_migration.getFileSystemVersion()
+ self.plone_version = version.replace(".", "")
+
+ def test_getUsers(self):
+ """ Tests method that returns ordered list of users."""
+ users = [u[0] for u in self.layer.users]
+ users.reverse()
+ self.assert_(False not in map(lambda u1, u2: u1 == u2,
+ users, self.view.getUsers()))
+
+ def test_getTypes(self):
+ """ Tests method that returns ordered list of types."""
+ data = {}
+ index = self.pc._catalog.getIndex('portal_type')
+ for k in index._index.keys():
+ if not k:
+ continue
+ haslen = hasattr(index._index[k], '__len__')
+
+ if haslen:
+ data[k] = len(index._index[k])
+ else:
+ data[k] = 1
+
+ data = data.items()
+ data.sort(lambda a, b: a[1] - b[1])
+ data.reverse()
+ types = [i[0] for i in data]
+ self.assert_(False not in map(lambda t1, t2: t1 == t2,
+ self.view.getTypes(), types))
+
+ def test_getContent(self):
+ """ This test verifies method that returns list of numbers.
+ Each number is amount of specified content type objects
+ that owned by particular user.
+ """
+ # we need to login in to the site as Manager to be able to
+ # see catalog results
+ self.loginAsPortalOwner()
+
+ for type_ in self.layer.types_:
+ self.assert_(False not in \
+ map(lambda i, j: i == j, [len(self.pc(portal_type=type_,
+ Creator=user))
+ for user in self.view.getUsers()],
+ self.view.getContent(type_)))
+
+ def test_getChart(self):
+ """ This test verifies creation of chart image tag."""
+ plone33chart_tag = \
+ ''
+ plone4chart_tag = \
+ ''
+ plone42chart_tag = \
+ ''
+
+ if self.plone_version < "40":
+ chart_tag = plone33chart_tag
+ elif self.plone_version > "42":
+ chart_tag = plone42chart_tag
+ else:
+ chart_tag = plone4chart_tag
+
+ self.loginAsPortalOwner()
+ self.assertEqual(*map(lambda s: ''.join(s.split()),
+ [chart_tag, self.view.getChart()]))
+
+
+class TestOwnershipByState(TestCase):
+ """Tests all ownership by state view methods."""
+
+ layer = SetUpContent
+
+ states = ['private', 'published', 'pending']
+
+ def afterSetUp(self):
+ self.view = queryMultiAdapter((self.portal, self.portal.REQUEST),
+ name="ownership_by_state")
+ self.pc = self.portal.portal_catalog
+
+ def test_getUsers(self):
+ """ Tests method that returns ordered list of users."""
+ users = [u[0] for u in self.layer.users]
+ users.reverse()
+ self.assert_(False not in map(lambda u1, u2: u1 == u2,
+ users, self.view.getUsers()))
+
+ def test_getStates(self):
+ """ Tests method that returns ordered list of states."""
+ self.assert_(False not in map(lambda s1, s2: s1 == s2,
+ ['private', 'published'], self.view.getStates()))
+
+ def test_getContent(self):
+ """ This test verifies method that returns list of numbers.
+ Each number is amount of specified content type objects
+ that are in particular workflow state.
+ """
+ # we need to login in to the site as Manager to be able to
+ # see catalog results
+ self.loginAsPortalOwner()
+
+ for state in self.states:
+ self.assert_(False not in \
+ map(lambda i, j: i == j, [len(self.pc(review_state=state,
+ Creator=user))
+ for user in self.view.getUsers()],
+ self.view.getContent(state)))
+
+ def test_getChart(self):
+ """ This test verifies creation of chart image tag."""
+ chart_tag = """"""
+ self.loginAsPortalOwner()
+ self.assertEqual(*map(lambda s: ''.join(s.split()),
+ [chart_tag, self.view.getChart()]))
+
+
+class TestTypeByState(TestCase):
+ """Tests all type_by_state view methods."""
+ layer = SetUpContent
+ states = ['private', 'published', 'pending']
+
+ def afterSetUp(self):
+ self.view = queryMultiAdapter((self.portal, self.portal.REQUEST),
+ name="type_by_state")
+ self.pc = self.portal.portal_catalog
+ portal_migration = self.portal.portal_migration
+ version = portal_migration.getFileSystemVersion()
+ self.plone_version = version.replace(".", "")
+
+ def test_getTypes(self):
+ """ Tests method that returns ordered list of types."""
+ index = self.pc._catalog.getIndex('portal_type')
+ data = {}
+
+ for k in index._index.keys():
+ if not k:
+ continue
+ haslen = hasattr(index._index[k], '__len__')
+
+ if haslen:
+ data[k] = len(index._index[k])
+
+ else:
+ data[k] = 1
+ data = data.items()
+ data.sort(lambda a, b: a[1] - b[1])
+ data.reverse()
+
+ types = [i[0] for i in data]
+ self.assert_(False not in map(lambda t1, t2: t1 == t2, types,
+ self.view.getTypes()))
+
+ def test_getStates(self):
+ """ Tests method that returns ordered list of states."""
+ self.assert_(False not in map(lambda s1, s2: s1 == s2,
+ ['private', 'published'], self.view.getStates()))
+
+ def test_getContent(self):
+ """ This test verifies method that returns list of numbers.
+ Each number is amount of specified content type objects
+ that owned by particular user.
+ """
+ # we need to login in to the site as Manager to be able to
+ # see catalog results
+ self.loginAsPortalOwner()
+
+ for state in self.states:
+ self.assert_(False not in \
+ map(lambda i, j: i == j, [len(self.pc(portal_type=type_,
+ review_state=state))
+ for type_ in self.view.getTypes()],
+ self.view.getContent(state)))
+
+ def test_getChart(self):
+ """ This test verifies creation of chart image tag."""
+ plone33chart_tag = \
+ ''
+ plone4chart_tag = \
+ ''
+ plone41chart_tag = \
+ ''
+ plone42chart_tag = \
+ ''
+
+ if self.plone_version < "40":
+ chart_tag = plone33chart_tag
+ elif self.plone_version > "40" and self.plone_version < "41":
+ chart_tag = plone4chart_tag
+ elif self.plone_version > "41" and self.plone_version < "42":
+ chart_tag = plone41chart_tag
+ elif self.plone_version > "42":
+ chart_tag = plone42chart_tag
+
+ self.loginAsPortalOwner()
+ self.assertEqual(*map(lambda s: ''.join(s.split()),
+ [chart_tag, self.view.getChart()]))
+
+
+class LegacyPortlets(TestCase):
+ """Test all legasy_portlets view methods."""
+
+ def afterSetUp(self):
+ self.view = queryMultiAdapter((self.portal, self.portal.REQUEST),
+ name='legacy_portlets')
+
+ def test_getPortlets(self):
+ """Tests method that returns portlets info."""
+
+ # this is true for Plone 4
+ self.assert_(self.view.getPortlets() == [])
+
+ def test_getAllPortletExpressions(self):
+ """Tests method that returns portlets expressions."""
+
+ # this is true for Plone 4
+ self.assert_(self.view.getAllPortletExpressions() == [])
+
+
+class TestPropertiesStats(TestCase):
+ """Tests all properties_stats view methods."""
+
+ def afterSetUp(self):
+ self.view = queryMultiAdapter((self.portal, self.portal.REQUEST),
+ name='properties_stats')
+
+ def test_getPropsList(self):
+ self.view.propname = 'title'
+ result = [u'Plone site', u'Welcome to Plone',
+ u'News', u'Events', u'Users']
+
+ for title in result:
+ self.assert_(title in [prop_info['slots']
+ for prop_info in self.view.getPropsList()])
+
+
+class TestPortletsStats(TestCase):
+ """Tests all properties_stats view methods."""
+
+ def afterSetUp(self):
+ self.view = queryMultiAdapter((self.portal, self.portal.REQUEST),
+ name='portlets_stats')
+
+ def test_getPropsList(self):
+ """Tests method that collects portlet information from site."""
+
+ self.loginAsPortalOwner()
+ portlet = getUtility(IPortletType, name='portlets.Calendar')
+ mapping = \
+ self.portal.restrictedTraverse('++contextportlets++plone.leftcolumn')
+ mapping.restrictedTraverse('+/' + portlet.addview)()
+
+ plone_portlets_info = filter(lambda info: info['path'] == '/plone',
+ self.view.getPropsList())
+ lslots = plone_portlets_info[0]['left_slots']
+ self.assert_(info for info in lslots if info['title'] == 'Calendar')
+
+
+def test_suite():
+ test_suite = unittest.TestSuite([
+
+ # Unit tests
+ #doctestunit.DocFileSuite(
+ # 'README.txt', package='quintagroup.contentstats',
+ # setUp=testing.setUp, tearDown=testing.tearDown),
+
+ #doctestunit.DocTestSuite(
+ # module='quintagroup.contentstats.mymodule',
+ # setUp=testing.setUp, tearDown=testing.tearDown),
+
+
+ # Integration tests that use PloneTestCase
+ #ztc.ZopeDocFileSuite(
+ # 'README.txt', package='quintagroup.contentstats',
+ # test_class=TestCase),
+
+ #ztc.FunctionalDocFileSuite(
+ # 'browser.txt', package='quintagroup.contentstats',
+ # test_class=TestCase),
+
+ ])
+
+ test_suite.addTest(unittest.makeSuite(TestQAInstallation))
+ test_suite.addTest(unittest.makeSuite(TestOwnershipByType))
+ test_suite.addTest(unittest.makeSuite(TestOwnershipByState))
+ test_suite.addTest(unittest.makeSuite(TestTypeByState))
+ test_suite.addTest(unittest.makeSuite(LegacyPortlets))
+ test_suite.addTest(unittest.makeSuite(TestPropertiesStats))
+ test_suite.addTest(unittest.makeSuite(TestPortletsStats))
+ return test_suite
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
Index: quintagroup.analytics/tags/1.1.1/setup.cfg
===================================================================
--- quintagroup.analytics/tags/1.1.1/setup.cfg (revision 3470)
+++ quintagroup.analytics/tags/1.1.1/setup.cfg (revision 3470)
@@ -0,0 +1,3 @@
+[egg_info]
+tag_build = dev
+tag_svn_revision = true
Index: quintagroup.analytics/tags/1.1.1/setup.py
===================================================================
--- quintagroup.analytics/tags/1.1.1/setup.py (revision 3470)
+++ quintagroup.analytics/tags/1.1.1/setup.py (revision 3470)
@@ -0,0 +1,32 @@
+from setuptools import setup, find_packages
+import os
+
+version = '1.1.1'
+
+setup(name='quintagroup.analytics',
+ version=version,
+ description="Plone site's statistics",
+ long_description=open("README.txt").read() + "\n" +
+ open(os.path.join("docs", "HISTORY.txt")).read(),
+ classifiers=[
+ "Framework :: Plone",
+ "Programming Language :: Python",
+ "Topic :: Software Development :: Libraries :: Python Modules",
+ ],
+ keywords='',
+ author='Quintagroup',
+ author_email='support@quintagroup.com',
+ url='http://svn.quintagroup.com/products/quintagroup.analytics',
+ license='GPL',
+ packages=find_packages(exclude=['ez_setup']),
+ namespace_packages=['quintagroup'],
+ include_package_data=True,
+ zip_safe=False,
+ install_requires=[
+ 'setuptools',
+ 'GChartWrapper'
+ # -*- Extra requirements: -*-
+ ],
+ entry_points="""
+ """,
+ )