Source code for ploneintranet.workspace.browser.add_content

# -*- coding: utf-8 -*-
from AccessControl import Unauthorized
from DateTime import DateTime
from json import dumps
from plone import api
from plone.app.event.base import default_timezone
from plone.memoize import view
from ploneintranet import api as pi_api
from ploneintranet.core import ploneintranetCoreMessageFactory as _
from ploneintranet.layout.browser.base import BasePanel
from ploneintranet.workspace.basecontent.event import EventView
from ploneintranet.workspace.basecontent.utils import dexterity_update
from ploneintranet.workspace.basecontent.utils import get_selection_classes
from ploneintranet.workspace.events import ObjectModifiedAfterCreationEvent
from ploneintranet.workspace.utils import parent_workspace
from Products.CMFCore.permissions import AddPortalContent
from Products.CMFPlone.utils import safe_unicode
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from zope.container.interfaces import INameChooser
from zope.event import notify
from zope.lifecycleevent import Attributes
from zope.lifecycleevent import ObjectModifiedEvent


[docs]class AddBase(BasePanel): """ Basic stuff for adding contents """ title = _("Create document") template = ViewPageTemplateFile("templates/add_content.pt") can_edit = True _form_data_pat_inject_parts = ( "#document-body", "#workspace-documents", "#global-statusmessage; loading-class: ''", ) is_add_form = True @property @view.memoize def form_data_pat_validation(self): """ Proper pat-validation options. We need to match the timestamp of the create button. """ return "disable-selector:#form-buttons-create-{timestamp}".format( timestamp=self.form_timestamp() ) @property @view.memoize def portal(self): """ Return the portal """ return api.portal.get() @property @view.memoize def workspace_container(self): """ Return the workspace container """ return self.portal.workspaces @property @view.memoize def parent_workspace(self): """ Return the parent workspace """ return parent_workspace(self.context) @property @view.memoize def user(self): """ The currently authenticated ploneintranet user profile (if any) """ return pi_api.userprofile.get_current() @property @view.memoize def content_helper_view(self): """ Use the content_helper_view """ return api.content.get_view("content_helper_view", self.context, self.request) @property @view.memoize def allusers_json_url(self): """ Return @@allusers.json in the proper context """ return "{}/@@allusers.json".format(self.parent_workspace.absolute_url())
[docs] def get_data_pat_autosuggest(self, fieldname): """ Return the data-pat-autosuggest for a fieldname """ user = self.user if fieldname == "initiator" and self.request.method == "GET" and user: default_field_value = user.getId() else: default_field_value = "" prefill_json = self.content_helper_view.safe_member_prefill( self.context, fieldname, default=default_field_value ) if not prefill_json: prefill_json = "{}" if user and fieldname == "initiator" and prefill_json == user.getId(): prefill_json = dumps({user.username: user.fullname}) return "; ".join( ( "ajax-data-type: json", "maximum-selection-size: 1", "selection-classes: {}", "ajax-url: {}".format(self.allusers_json_url), "allow-new-words: false", "prefill-json: {}".format(prefill_json), ) )
[docs] def redirect(self, url): """ Has its own method to allow overriding """ url = "{}/view?show_sidebar".format(url) return self.request.response.redirect(url)
[docs] def validate(self): """ Validate input and return a truish """ return True
[docs] def get_new_unique_id(self, container): """ This will get a new unique id according to the request """ form = self.request.form suggested_id = form.get("id") or form.get("title") or self.title chooser = INameChooser(container) new_id = chooser.chooseName(suggested_id, container) return new_id
[docs] def get_new_object(self, container=None): """ This will create a new object """ if not container: container = self.context obj = api.content.create( container=container, type=self.portal_type, id=self.get_new_unique_id(container), title=self.request.get("title") or self.title, safe_id=False, ) return container[obj.getId()]
[docs] def update(self, obj): """ Update the object and returns the modified fields and errors """ modified, errors = dexterity_update(obj) return modified, errors
[docs] def create(self, container=None): """ Create content in the given container and return url. Uses dexterity_update to set the appropriate fields after creation. """ if not self.validate(): # BBB: do something clever that works with pat-inject # at the moment the @@add_something form is not a complete page # but just some markup, # so we cannot show that one here pass with pi_api.env.indexing_disabled(self.request): new = self.get_new_object(container) modified, errors = self.update(new) if not errors: api.portal.show_message( _("Item created."), request=self.request, type="success" ) new.reindexObject() descriptions = [] if modified: descriptions = [ Attributes(interface, *fields) for interface, fields in modified.items() ] notify(ObjectModifiedAfterCreationEvent(new, *descriptions)) else: api.portal.show_message( _("There was a problem: %s." % errors), request=self.request, type="error", ) return new.absolute_url()
def __call__(self): """Render form, or handle POST and redirect""" title = safe_unicode(self.request.form.get("title", None)) portal_type = self.request.form.get("portal_type", "") container_path = self.request.form.get("container", None) if container_path: container = self.context.restrictedTraverse(container_path) else: container = self.context if title is not None: self.portal_type = portal_type.strip() self.title = title.strip() if self.portal_type in api.portal.get_tool("portal_types"): url = self.create(container) return self.redirect(url) return self.template()
[docs]class AddContent(AddBase): """ """ tabs = [ { "css_class": "icon-doc-text current", "id": "sheet-text", "label": _("Rich text"), }, {"css_class": "icon-file-image", "id": "sheet-image", "label": _("Image")}, ]
[docs]class AddFolder(AddBase): template = ViewPageTemplateFile("templates/add_folder.pt") title = _("Create folder") _form_data_pat_inject_parts = ("#workspace-documents", "nav.breadcrumbs")
[docs]class AddEvent(AddBase): template = ViewPageTemplateFile("templates/add_event.pt") title = _("Create event") _add_event_tab = { "css_class": "icon-calendar", "id": "sheet-event", "label": _("Event"), } _add_calendar_tab = { "css_class": "icon-calendar-1", "id": "sheet-calendar", "label": _("Calendar"), } @property def tabs(self): tabs = [] if self.can_add_events(): tabs.append(self._add_event_tab) if self.can_add_calendars(): tabs.append(self._add_calendar_tab) if tabs: tabs[0]["css_class"] += " current" return tabs @property @view.memoize_contextless def calendar_container(self): return api.portal.get().get("calendars", None) @property @view.memoize_contextless def app_calendar_view(self): return api.content.get_view( "app-calendar", api.portal.get().apps.calendar, self.request )
[docs] @view.memoize def can_add_events(self): """ Check if we can add events. Start first checking the current context and then see if we can add them somewhere else """ if self.parent_workspace and api.user.has_permission( AddPortalContent, obj=self.parent_workspace ): return True view = api.content.get_view( context=self.context, request=self.request, name="calendar_picker.json" ) return view.has_writable_workspaces()
[docs] @view.memoize def can_add_calendars(self): if not self.request.get("app", None): return False if self.request.get("in_calendar", None): return False return api.user.has_permission( "Add portal content", obj=self.calendar_container )
[docs] def fix_start_end(self): """ If the start date is lower than the end one, modify the request setting end = start + 1 hour """ localized_start = DateTime( "%s %s" % ( " ".join(self.request.get("start")), self.request.get("timezone", default_timezone()), ) ) localized_end = localized_start + 1.0 / 24 # If you know a smarter way to hijack the request, # please modify the following lines:) self.request.end = [ localized_end.strftime("%Y-%m-%d"), localized_end.strftime("%H:%M"), ] self.request.form["end"] = self.request.end self.request.other["end"] = self.request.end ts = api.portal.get_tool("translation_service") msg = _( "dates_hijacked", default=( u"Start time should be lower than end time. " u"The system set the end time to: ${date}" ), mapping={u"date": ts.toLocalizedTime(localized_end)}, ) api.portal.show_message(msg, request=self.request, type="warning")
[docs] def validate(self): """ Override base content validation Return truish if valid """ if self.request.get("start") > self.request.get("end"): self.fix_start_end() return True
# The following two methods are used to determine the next step # after adding or editing events. # # This is an issue because we can add events or edit them # and this can happen from either within a workspace or from the app # or through a modal openend through the + icon. # All six combinations require different redirects. # The whole situation is not fully trivial, because once we are on # the event edit form, the context is the event and not the workspace # or app anymore, so we don't easily know where we are. @property @view.memoize def _form_data_pat_inject_parts(self): """ Returns the correct dpi config """ in_app = self.request.get("app") selectors = [] if in_app: # In the app, we want to reload the left hand sidebar # and the calendar part in document-content selectors.append("#sidebar") selectors.append("#document-content") else: # Probably on a workspace selectors.append("#workspace-events") selectors.append("#document-body") parts = ["source: {0}; target: {0};".format(selector) for selector in selectors] # and of course we want to update the status message parts.append( "source: {0}; target: {0}; loading-class: ''".format( "#global-statusmessage" ) ) return parts
[docs] def redirect(self, url): """ Try to find the proper redirect context. """ # See if we are an event that resides in a workspace # This is currently always the case, we don't have others. workspace = self.parent_workspace # in_calendar tells us if the add or edit has been triggered from # within the calendar view. false if the add or edit was through # + or direct edit in_calendar = self.request.get("in_calendar") # in_app tells us if we are working from within the calendar app. # contains the relative path to app without leading / if set. in_app = self.request.get("app") if in_app: # Just redirect to the calendar app view, injection will pick # what it needs from it url = "%s/@@app-calendar" % in_app else: # somewhere else, expectedly on a workspace if workspace: if in_calendar: # Stay in the calendar # Go to the workspace calendar container = self.request.get("container") if container and container != u"/".join( workspace.getPhysicalPath() ): url = "%s/@@workspace-calendar?all_calendars=1" else: url = "%s/@@workspace-calendar" url = url % workspace.absolute_url() else: # Load the event and show its form # we return the original object url url = "%s/event_view?show_sidebar=1" % url else: # if not render the app view pass return self.request.response.redirect(url)
[docs] def round_date(self, dt): """ Round the datetime minutes and seconds to the next quarter, i.e. '2000/01/01 00:35:21' becomes '2000/01/01 00:30:00' """ remainder = float(dt) % 900 if not remainder: return dt return dt + (900 - float(dt) % 900) / 86400
@property @view.memoize def default_datetime(self): """ Return the default date (the requested one or now) The request may come from several sources, e.g.: 1. from the sidebar 2. from the calendar month view 3. from the calendar day and week views Each of this cases may have a date parameter in different formats. We try to convert it to a DateTime. """ requested_date = self.request.get("date", "") # The requeste_date, when called by the calendar day and week view, # will look like '2017-03-29T13:30:00' if "T" in requested_date: # Strip the "T" to have the local timezone requested_date = requested_date.replace("T", " ") else: # 'T' in not there, assume we have only the date in the request requested_date += " 09:00" try: requested_date = " ".join((requested_date, DateTime().localZone())) date = DateTime(requested_date) except SyntaxError: date = DateTime() return date @property @view.memoize def default_start(self): """ The rounded default_datetime. """ return self.round_date(self.default_datetime) @property @view.memoize def default_end(self): """ Like default_start, but add a time interval (in hours) We will have a 'T' in the request parameter if the request comes from the day or the week calendar view """ delta = "T" in self.request.get("date", "") and 0.5 or 1.0 return self.default_start + delta / 24
[docs] def get_selection_classes(self, field, default=None): """ identify all groups in the invitees """ return get_selection_classes(self.context, field, default)
[docs]class EditEvent(EventView, AddEvent): """ Base class for editing events """ template = ViewPageTemplateFile("templates/panel-edit-event.pt") title = _("Edit event") is_add_form = False def __call__(self): """Render form, or handle POST and redirect""" if self.should_update(): if not self.can_edit: raise Unauthorized("You cannot modify this event") self.update() return self.redirect(self.context.absolute_url()) return self.template()
[docs]class EditCalendar(AddEvent): """ """ template = ViewPageTemplateFile("templates/panel-edit-calendar.pt") title = _("Edit calendar") def __call__(self): """Render form, or handle POST and redirect""" if "form.buttons.edit" in self.request: modified, errors = self.update(self.context) if not errors: api.portal.show_message( _("Your changes have been saved."), request=self.request, type="success", ) self.context.reindexObject() notify(ObjectModifiedEvent(self.context)) else: api.portal.show_message( _("There was a problem: %s." % errors), request=self.request, type="error", ) return self.redirect(self.request.get("app") or self.context.absolute_url()) return self.template()