[Products.Silva][Sylvain Viollon] Implement a site-level quota s...
[email protected] Mon, 14 Oct 2013 15:30:47 +0200
| Newsgroups | gmane.comp.web.zope.silva.cvs |
|---|---|
| Message-ID | <[email protected]> |
author: Sylvain Viollon
date: Mon Oct 14 15:30:42 2013 +0200
revision: 11866:f959b428859d in Products.Silva
branch: 2.4
details: https://hg.infrae.com/Products.Silva?cmd=changeset;node=f959b428859d
modified: Products/Silva/ExtensionService.py Products/Silva/ExtensionService_templates/manageextensions.pt Products/Silva/Publication/content.py Products/Silva/Root.py Products/Silva/Root_templates/managesiteform.pt Products/Silva/Root_templates/zopewelcomepage.pt Products/Silva/static/welcome.css
added: Products/Silva/Root_templates/managesiteform.pt
removed:
log: Implement a site-level quota system.
diffstat:
Products/Silva/ExtensionService.py | 17 +-
Products/Silva/ExtensionService_templates/manageextensions.pt | 40 +-
Products/Silva/Publication/content.py | 27 +-
Products/Silva/Root.py | 153 +++++++++-
Products/Silva/Root_templates/managesiteform.pt | 58 +++
Products/Silva/Root_templates/zopewelcomepage.pt | 19 +-
Products/Silva/static/welcome.css | 4 +
7 files changed, 273 insertions(+), 45 deletions(-)
diffs (544 lines):
diff -r ffe5308f7cea -r f959b428859d Products/Silva/ExtensionService.py
--- a/Products/Silva/ExtensionService.py Wed Oct 09 17:51:49 2013 +0200
+++ b/Products/Silva/ExtensionService.py Mon Oct 14 15:30:42 2013 +0200
@@ -118,6 +118,7 @@
{'label': 'Logs', 'action':'manage_main'},
) + SilvaService.manage_options
+ _site_quota = 0
_quota_enabled = False
_quota_verify = False
@@ -179,7 +180,11 @@
def disable_quota_subsystem(self):
"""Disable quota sub-system.
"""
- assert (self._quota_enabled)
+ if not self._quota_enabled:
+ return False
+ if self._site_quota:
+ # You cannot disable the quota system if there is a site quota.
+ return False
root = self.get_root()
# Disable metadata for quota
@@ -192,13 +197,15 @@
self._quota_enabled = False
self._quota_verify = False
+ return True
security.declareProtected(
'View management screens', 'enable_quota_subsystem')
def enable_quota_subsystem(self):
"""Enable quota sub-system.
"""
- assert (not self._quota_enabled)
+ if self._quota_enabled:
+ return False
root = self.get_root()
# Setup metadata for quota
@@ -220,6 +227,7 @@
root.used_space = compute_used_space(root)
self._quota_enabled = True
self._quota_verify = True
+ return True
security.declareProtected(
'View management screens', 'upgrade_content')
@@ -247,6 +255,11 @@
return self._quota_verify
security.declareProtected(
+ 'Access contents information', 'get_site_quota')
+ def get_site_quota(self):
+ return self._site_quota
+
+ security.declareProtected(
'Access contents information', 'is_installed')
def is_installed(self, name):
"""Is extension installed?
diff -r ffe5308f7cea -r f959b428859d Products/Silva/ExtensionService_templates/manageextensions.pt
--- a/Products/Silva/ExtensionService_templates/manageextensions.pt Wed Oct 09 17:51:49 2013 +0200
+++ b/Products/Silva/ExtensionService_templates/manageextensions.pt Mon Oct 14 15:30:42 2013 +0200
@@ -27,22 +27,26 @@
<input type="submit"
name="install_documentation"
value="install documentation"
- tal:condition="python:context.is_installed('silva.app.document') and not hasattr(context, 'docs')"
+ tal:condition="python:context.is_installed('silva.app.document') and root._getOb('docs', None) is None"
i18n:attributes="value" />
<input type="submit"
name="refresh_catalog"
value="rebuild catalog"
i18n:attributes="value" />
- <input type="submit"
- name="disable_quota_subsystem"
- value="disable quota subsystem"
- tal:condition="view/quota_enabled"
- i18n:attributes="value" />
- <input type="submit"
- name="enable_quota_subsystem"
- value="enable quota subsystem"
- tal:condition="not:view/quota_enabled"
- i18n:attributes="value" />
+ <tal:quota tal:condition="not: context/get_site_quota">
+ <!-- Site quota is not enabled, so you can enable or not the
+ feature. -->
+ <input type="submit"
+ name="disable_quota_subsystem"
+ value="disable quota subsystem"
+ tal:condition="view/quota_enabled"
+ i18n:attributes="value" />
+ <input type="submit"
+ name="enable_quota_subsystem"
+ value="enable quota subsystem"
+ tal:condition="not:view/quota_enabled"
+ i18n:attributes="value" />
+ </tal:quota>
<input type="submit"
name="purge_old_versions"
value="purge old versions"
@@ -50,12 +54,18 @@
<input type="submit" name="upgrade_all" value="upgrade content"
onclick="if (!confirm('This will upgrade all Silva content, and may take a long time on a large site.\nThis is, however, necessary to make old Silva content work with new versions.\nAre you sure you want to continue?')) return false;"
i18n:attributes="value" />
- <br />
- <br />
- <span class="std-text">
+ <p class="std-text">
(Software - <span tal:replace="root/get_silva_software_version">version</span> /
Content - <span tal:replace="root/get_silva_content_version">version</span>)
- </span>
+ </p>
+ <p class="std-text"
+ tal:condition="context/get_site_quota">
+ This site has a global quota of <span tal:replace="context/get_site_quota" /> MB.
+ </p>
+ <p class="std-text"
+ tal:condition="not: context/get_site_quota">
+ This site has no global quota restrictions.
+ </p>
</form>
<h3 i18n:translate="">Available extensions</h3>
diff -r ffe5308f7cea -r f959b428859d Products/Silva/Publication/content.py
--- a/Products/Silva/Publication/content.py Wed Oct 09 17:51:49 2013 +0200
+++ b/Products/Silva/Publication/content.py Mon Oct 14 15:30:42 2013 +0200
@@ -11,15 +11,16 @@
from App.special_dtml import DTMLFile
from App.class_init import InitializeClass
from zExceptions import BadRequest
+from zope.component import getUtility
# Silva
from Products.Silva.Folder import Folder, FolderAddForm
from Products.Silva import SilvaPermissions, helpers
from silva.core import conf as silvaconf
-from silva.core.interfaces import (
- IPublication, IRoot, ISiteManager, IInvisibleService)
+from silva.core.interfaces import IPublication, ISiteManager, IInvisibleService
from silva.core.interfaces import ContentError
+from silva.core.services.interfaces import IMetadataService
from silva.translations import translate as _
@@ -102,8 +103,8 @@
if value < 0:
# Quota can't be negative.
return False
- if (not value) or IRoot.providedBy(self):
- # 0 means no quota, Root don't have any parents.
+ if not value:
+ # 0 or means no quota.
return True
parent = aq_parent(self).get_publication()
quota = parent.get_current_quota()
@@ -124,16 +125,16 @@
def get_current_quota(self):
"""Return the current quota value on the publication.
"""
- service_metadata = self.service_metadata
- binding = service_metadata.getMetadata(self)
+ binding = getUtility(IMetadataService).getMetadata(self)
try:
- return int(binding.get('silva-quota', element_id='quota') or 0)
- except KeyError: # This publication object doesn't have
- # this metadata set
- parent = aq_parent(self)
- if not IRoot.providedBy(parent):
- return parent.get_current_quota()
- return 0
+ local = int(binding.get('silva-quota', element_id='quota') or 0)
+ if local > 0:
+ return local
+ except KeyError:
+ pass
+ # Test parent publication/root.
+ parent = aq_parent(self)
+ return parent.get_current_quota()
security.declareProtected(
SilvaPermissions.AccessContentsInformation, 'get_publication')
diff -r ffe5308f7cea -r f959b428859d Products/Silva/Root.py
--- a/Products/Silva/Root.py Wed Oct 09 17:51:49 2013 +0200
+++ b/Products/Silva/Root.py Mon Oct 14 15:30:42 2013 +0200
@@ -5,8 +5,9 @@
# Zope 3
from five import grok
from five.localsitemanager import make_site
-from zope import schema, interface, component
+from zope import schema, interface
from zope.event import notify
+from zope.component import getUtility
from zope.lifecycleevent import ObjectCreatedEvent
from zope.site.hooks import setSite, setHooks
from zope.traversing.browser import absoluteURL
@@ -16,7 +17,7 @@
from App.class_init import InitializeClass
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from OFS.Application import Application
-from zExceptions import Unauthorized
+from zExceptions import Unauthorized, Redirect
import Globals
import transaction
@@ -33,10 +34,30 @@
from silva.core.interfaces.events import InstallRootEvent
from silva.core.interfaces.events import InstallRootServicesEvent
from silva.core.messages.interfaces import IMessageService
+from silva.core.services.interfaces import IExtensionService, IMetadataService
from silva.translations import translate as _
from zeam.form import silva as silvaforms
+class IQuotaRootManager(interface.Interface):
+ """Describe a quota for a Silva root.
+ """
+ identifier = schema.TextLine(
+ title=_(u"Site"),
+ readonly=True,
+ required=True)
+ quota = schema.Int(
+ title=_(u"Site quota"),
+ min=-1,
+ default=-1,
+ required=True)
+ used = schema.Float(
+ title=_(u'Used space'),
+ min=-1.0,
+ readonly=True,
+ required=True)
+
+
class ISilvaRootAddFields(interface.Interface):
"""Describe an add form for a new Silva root.
"""
@@ -59,6 +80,7 @@
class ZopeWelcomePage(silvaforms.ZMIForm):
grok.context(Application)
grok.name('index.html')
+ grok.require('zope2.Public')
fields = silvaforms.Fields(ISilvaRootAddFields)
@@ -67,21 +89,23 @@
self.is_dev = Globals.DevelopmentMode
self.version = extensionRegistry.get_extension('Silva').version
- def is_allowed_to_add_root(self):
+ def is_manager(self):
return getSecurityManager().checkPermission(
'View Management Screens', self.context)
@silvaforms.action(
_(u"Authenticate first to add a new site"),
- available=lambda form:not form.is_allowed_to_add_root())
+ identifier='login',
+ available=lambda form:not form.is_manager())
def login(self):
- if not self.is_allowed_to_add_root():
+ if not self.is_manager():
raise Unauthorized("You must authenticate to add a new Silva Site")
@silvaforms.action(
_(u"Add a new site"),
- available=lambda form:form.is_allowed_to_add_root())
- def new_root(self):
+ identifier='add_site',
+ available=lambda form:form.is_manager())
+ def add_site(self):
data, errors = self.extractData()
if errors:
return silvaforms.FAILURE
@@ -89,8 +113,7 @@
data['identifier'],
data.getDefault('title'))
root = self.context._getOb(data['identifier'])
- service = component.getUtility(IMessageService)
- service.send(
+ getUtility(IMessageService).send(
_(u"New Silva site ${identifier} added.",
mapping={'identifier': data['identifier']}),
self.request)
@@ -110,6 +133,76 @@
return silvaforms.SUCCESS
+class ManageSiteForm(silvaforms.ZMIComposedForm):
+ grok.context(Application)
+ grok.name('manage.html')
+
+
+class QuotaRootFactory(object):
+
+ def __init__(self, site):
+ self._site = site
+ self.identifier = site.getId()
+ self.quota = -1
+ self.used = -1
+ quota = self._site.service_extensions.get_site_quota()
+ if quota:
+ self.quota = quota
+ used = self._site.used_space
+ if used:
+ self.used = used / (1024*1014)
+
+ def update(self, value):
+ """Update the quota.
+ """
+ service = self._site.service_extensions
+ if value > 0:
+ service._site_quota = value
+ if not service.get_quota_subsystem_status():
+ try:
+ setSite(self._site)
+ setHooks()
+ service.enable_quota_subsystem()
+ finally:
+ setSite(None)
+ setHooks()
+ else:
+ service._site_quota = None
+
+
+class SetQuotaAction(silvaforms.Action):
+ title = _(u'Set Quota')
+
+ def __call__(self, form, quota, line):
+ data, errors = line.extractData(form.tableFields)
+ if errors:
+ raise silvaforms.ActionError('Invalid quota settings.')
+ quota.update(data['quota'])
+ return silvaforms.SUCCESS
+
+
+class ManageQuotaRootForm(silvaforms.ZMISubTableForm):
+ grok.context(Application)
+ grok.view(ManageSiteForm)
+
+ label = _(u'Manage sites')
+ description = _(u'Modify the space (in MB) allocated to a complete Silva '
+ u'site. This setting is not modifiable from within the site.')
+ ignoreContent = False
+ batchSize = 25
+ batchItemFactory = QuotaRootFactory
+ tableFields = silvaforms.Fields(IQuotaRootManager)
+ tableFields['identifier'].mode = silvaforms.DISPLAY
+ tableFields['used'].mode = silvaforms.DISPLAY
+ tableActions = silvaforms.TableActions(SetQuotaAction())
+
+ def getItems(self):
+ return self.context.objectValues('Silva Root')
+
+ def getItemIdentifier(self, item, position):
+ return item.identifier
+
+
class Root(Publication):
"""Root of Silva site.
"""
@@ -138,6 +231,24 @@
# MANIPULATORS
+ security.declareProtected(
+ SilvaPermissions.AccessContentsInformation, 'validate_wanted_quota')
+ def validate_wanted_quota(self, value, REQUEST=None):
+ """Validate the wanted quota is correct the current
+ publication.
+ """
+ if value < 0:
+ # Quota can't be negative.
+ return False
+ if not value:
+ # 0 or means no quota.
+ return True
+ # Quota can't be be bigger than the site quota.
+ if self.service_extensions._site_quota:
+ if self.service_extensions._site_quota < value:
+ return False
+ return True
+
security.declareProtected(SilvaPermissions.ApproveSilvaContent,
'to_folder')
def to_folder(self):
@@ -186,9 +297,27 @@
site.
"""
if not hasattr(self.aq_base, '_addables_forbidden'):
- return 0
- else:
- return self._addables_forbidden.has_key(meta_type)
+ return False
+ return meta_type in self._addables_forbidden
+
+ security.declareProtected(
+ SilvaPermissions.AccessContentsInformation, 'get_current_quota')
+ def get_current_quota(self):
+ """Return the current quota value on the publication.
+ """
+ site = getUtility(IExtensionService).get_site_quota()
+ binding = getUtility(IMetadataService).getMetadata(self)
+ try:
+ local = int(binding.get('silva-quota', element_id='quota') or 0)
+ if local < 0:
+ local = 0
+ except KeyError:
+ local = 0
+ if site and local:
+ return min(site, local)
+ if site:
+ return site
+ return local
security.declarePublic('get_silva_software_version')
def get_silva_software_version(self):
diff -r ffe5308f7cea -r f959b428859d Products/Silva/Root_templates/managesiteform.pt
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Products/Silva/Root_templates/managesiteform.pt Mon Oct 14 15:30:42 2013 +0200
@@ -0,0 +1,58 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html
+ i18n:domain="silva">
+ <head>
+ <title>Zope / Silva</title>
+ <link rel="stylesheet" href="#" type="text/css"
+ tal:attributes="href view/static/welcome.css" />
+ </head>
+ <body>
+ <div class="welcome">
+ <div class="title">
+ <a href="#" title="Site overview"
+ i18n:attributes="title"
+ tal:attributes="href context/@@absolute_url">
+ <img src="#" alt="Silva logo"
+ tal:attributes="src view/static/silva_logo.png" />
+ </a>
+ <h1 i18n:translate="">
+ Manage Silva sites
+ </h1>
+ </div>
+
+ <h2 tal:condition="view/label"
+ tal:content="view/label">Label</h2>
+
+ <p tal:condition="view/description"
+ tal:content="view/description">Description</p>
+
+ <p class="form-error"
+ tal:condition="view/errors">
+ There were errors:
+ <tal:error tal:repeat="error view/formErrors">
+ <br />
+ <span tal:replace="error/title" />
+ </tal:error>
+ </p>
+
+ <div class="subforms">
+ <div class="subform"
+ tal:repeat="subform view/subforms"
+ tal:content="structure subform/render">
+ Subform
+ </div>
+ </div>
+
+ <div class="actions"
+ tal:condition="view/actionWidgets">
+ <div class="action" tal:repeat="widget view/actionWidgets">
+ <tal:widget tal:content="structure widget/render" />
+ </div>
+ </div>
+
+ <div class="zope"
+ tal:content="structure context/ZopeAttributionButton">
+ </div>
+ </div>
+ </body>
+</html>
diff -r ffe5308f7cea -r f959b428859d Products/Silva/Root_templates/zopewelcomepage.pt
--- a/Products/Silva/Root_templates/zopewelcomepage.pt Wed Oct 09 17:51:49 2013 +0200
+++ b/Products/Silva/Root_templates/zopewelcomepage.pt Mon Oct 14 15:30:42 2013 +0200
@@ -9,8 +9,12 @@
<body>
<div class="welcome">
<div class="title">
- <img src="#" alt="Silva logo"
- tal:attributes="src view/static/silva_logo.png" />
+ <a href="#" title="Site overview"
+ i18n:attributes="title"
+ tal:attributes="href context/@@absolute_url">
+ <img src="#" alt="Silva logo"
+ tal:attributes="src view/static/silva_logo.png" />
+ </a>
<h1 i18n:translate="">
Welcome to Silva
<span i18n:name="version" tal:content="view/version" />
@@ -38,6 +42,14 @@
</tal:site>
</li>
</ul>
+ <p tal:condition="view/is_manager">
+ <i><a href="#" title="Manage quota"
+ tal:attributes="href string:${context/@@absolute_url}/manage.html"
+ i18n:attributes="title"
+ i18n:translate="">
+ Advanced management tasks...
+ </a></i>
+ </p>
</div>
<div class="new-site">
<form action="." tal:attributes="action request/URL" method="post"
@@ -54,7 +66,7 @@
</p>
<div class="fields"
- tal:condition="view/fieldWidgets">
+ tal:condition="view/is_manager">
<div class="field" tal:repeat="widget view/fieldWidgets">
<label class="field-label" for="#"
tal:attributes="for widget/htmlId"
@@ -82,6 +94,7 @@
<tal:widget tal:content="structure widget/render" />
</div>
</div>
+ <br />
</form>
</div>
<div class="info">
diff -r ffe5308f7cea -r f959b428859d Products/Silva/static/welcome.css
--- a/Products/Silva/static/welcome.css Wed Oct 09 17:51:49 2013 +0200
+++ b/Products/Silva/static/welcome.css Mon Oct 14 15:30:42 2013 +0200
@@ -33,6 +33,10 @@
text-align: right;
}
+.action {
+ float: left;
+}
+
.form-error, .field-error {
color: red;
font-weight: bold;