def handle_email(msg, email_username): multi_agent_permission_cache = MultiAgentPermissionCache() subject = get_subject(msg) if msg.is_multipart(): body = msg.get_payload(0).get_payload() else: body = msg.get_payload() from_email = get_from_email(msg) try: email_contact_method = EmailContactMethod.objects.filter(email__iexact=from_email)[:1].get() except ObjectDoesNotExist: raise UserException('Error: Your comment could not be created because there is no agent with email address %s' % from_email) try: item = Item.item_for_notification_email_username(email_username) except ObjectDoesNotExist: raise UserException('Error: Your comment could not be created because there does is no item for email account %s' % email_username) permission_cache = multi_agent_permission_cache.get(email_contact_method.agent) if not permission_cache.agent_can('comment_on', item): display_name = item.display_name(permission_cache.agent_can('view Item.name', item)) raise UserException('Error: Your comment could not be created because you do not have permission to comment on %s' % display_name) agent = email_contact_method.agent #TODO permissions to view Item.name: technically you could figure out the name of an item by commenting on it here (same issue in cms/views.py) if subject.lower().startswith('re: '): item_name = item.display_name() if item_name.lower().startswith('re: '): comment_name = item_name else: comment_name = 'Re: %s' % item_name else: comment_name = subject comment = TextComment(item=item, item_version_number=item.version_number, name=comment_name, body=body, from_contact_method=email_contact_method) comment.name = comment_name #TODO this is a hack due to multiple inheritance bug in Django. remove it when bug is fixed permissions = [OneToOnePermission(source=agent, ability='do_anything', is_allowed=True)] comment.save_versioned(action_agent=agent, initial_permissions=permissions)
class Viewer(object): """ Superclass of all viewers. Implements all of the common functionality like dispatching and convenience methods, but does not define any views. Although most viewers will want to inherit from ItemViewer, since it defines the basic views (like list, show, edit), some viewers may want to inherit from this abstract Viewer so they can ensure no views will be inherited. Subclasses must define the following fields: * `accepted_item_type`: The item type that this viewer is defined over. For example, if accepted_item_type == Agent, this viewer can be used to view Agents and all subclasses of Agents (e.g., Person), but an error will be rendered if a user tries to view something else (e.g., a Document) with this viewer. * `viewer_name`: The name of the viewer, which shows up in the URL. This should only consist of lowercase letters, in accordance with the Deme URL scheme. There are two types of views subclasses can define: item-specific views, and type-wide views. Item-specific views expect an item in the URL (the noun), while type-wide views do not expect a particular item. 1. To define an item-speicfic view, define a method with the name `item_method_format`, where `method` is the name of the view (which shows up in the URL as the action), and format is the output format (which shows up in the URL as the format). For example, the method item_edit_html(self) in an agent viewer represents the item-specific view with action="edit" and format="html", and would respond to the URL `/viewing/agent/123/edit.html`. 2. To define a type-wide view, define a method with the name `type_method_format`. For example, the method type_new_html(self) in an agent viewer represents the type-wide view with action="new" and format="html", and would respond to the URL `/viewing/agent/new.html`. View methods take no parameters (except self), since all of the details of the request are defined as instance variables in the viewer before dispatch. They must return an HttpResponse. They should take advantage of self.context, which is defined before the view is called. The following instance variables are defined on the viewer: * self.item (the requested Item, or None if there is no noun) * self.context (the Context object used to render templates) * self.action (the action part of the URL) * self.noun (the noun part of the URL) * self.format (the format part of the URL) * self.request (the HttpRequest that initiated this viewer) * self.method (the HTTP method of the request, so that require_POST works) * self.cur_agent (the currently authenticated Agent or AnonymousAgent) * self.cur_site (the Site that is being used for this request) * self.multi_agent_permission_cache (a MultiAgentPermissionCache for permission queries) * self.permission_cache (a PermissionCache for permission queries on cur_agent) * self.item (the downcasted requested item if there's a noun in the request) - If there is a `version` parameter in the query string, the item's fields will be populated with data from the requested version The following context variables are defined: * self.context['action'] (the action part of the URL) * self.context['item'] (the requested Item, or None if there is no noun) * self.context['specific_version'] (True if the user requested a specific version of the item in the query string, otherwise False) * self.context['viewer_name'] (value of viewer.viewer_name) * self.context['accepted_item_type'] (value of viewer.accepted_item_type) * self.context['accepted_item_type_name'] (verbose name of accepted_item_type) * self.context['accepted_item_type_name_plural'] (verbose name plural of accepted_item_type) * self.context['full_path'] (the path to the current page) * self.context['query_string'] (the query string for the current page) * self.context['cur_agent'] (the currently authenticated Agent) * self.context['cur_site'] (the Site that is being used for this request) * self.context['_viewer'] (the viewer itself, only for custom tags/filters) * self.context['layout'] (the layout that the template should inherit from) """ __metaclass__ = ViewerMetaClass ########################################################################### # Important functions (initializing and dispatching) ########################################################################### def __init__(self): # Nothing happens in the constructor. All of the initialization happens # in the init_for_* methods, based on how the viewer was loaded. pass def init_for_http(self, request, action, noun, format): """ This method sets up the Viewer from an incoming HttpRequest. The `action`, `noun`, and `format` parameters are extracted from the URL (or the ViewerRequest). This method should only be called from cms/dispatcher.py. """ self.context = Context() self.action = action or ('show' if noun else 'list') self.noun = noun self.format = format or 'html' self.request = request self.method = self.request.method self.cur_agent = get_logged_in_agent(request) self.cur_site = get_current_site(request) self.multi_agent_permission_cache = MultiAgentPermissionCache() self.permission_cache = self.multi_agent_permission_cache.get(self.cur_agent) if self.noun is None: self.item = None else: try: self.item = Item.objects.get(pk=self.noun) self.item = self.item.downcast() version_number = self.request.GET.get('version') if version_number is not None: self.item.copy_fields_from_version(version_number) self.context['specific_version'] = ('version' in self.request.GET) except ObjectDoesNotExist: self.item = None self.context['action'] = self.action self.context['item'] = self.item self.context['viewer_name'] = self.viewer_name self.context['accepted_item_type'] = self.accepted_item_type self.context['accepted_item_type_name'] = self.accepted_item_type._meta.verbose_name self.context['accepted_item_type_name_plural'] = self.accepted_item_type._meta.verbose_name_plural self.context['full_path'] = self.request.get_full_path() self.context['query_string'] = self.request.META['QUERY_STRING'] self.context['cur_agent'] = self.cur_agent self.context['cur_site'] = self.cur_site self.context['_viewer'] = self self.context['default_metadata_menu_option'] = self.default_metadata_menu_option() self._set_default_layout() def init_for_div(self, original_viewer, action, item, query_string): """ This method sets up the Viewer to be embedded (probably in a <div>) in the specified original_viewer. The action, item, and query_string specify the details of what will be embedded in this viewer. """ if item is None: path = reverse('item_type_url', kwargs={'viewer': self.viewer_name, 'action': action}) else: path = reverse('item_url', kwargs={'viewer': self.viewer_name, 'action': action, 'noun': item.pk}) self.request = VirtualRequest(original_viewer.request, path, query_string) self.method = self.request.method self.cur_agent = original_viewer.cur_agent self.cur_site = original_viewer.cur_site self.multi_agent_permission_cache = original_viewer.multi_agent_permission_cache self.permission_cache = original_viewer.permission_cache self.format = 'html' if item is None: self.noun = None else: self.noun = str(item.pk) self.item = item self.action = action self.context = Context() self.context['action'] = self.action self.context['item'] = self.item self.context['specific_version'] = False self.context['viewer_name'] = self.viewer_name self.context['accepted_item_type'] = self.accepted_item_type self.context['accepted_item_type_name'] = self.accepted_item_type._meta.verbose_name self.context['accepted_item_type_name_plural'] = self.accepted_item_type._meta.verbose_name_plural self.context['full_path'] = original_viewer.context['full_path'] self.context['query_string'] = '' self.context['cur_agent'] = self.cur_agent self.context['cur_site'] = self.cur_site self.context['_viewer'] = self self.context['layout'] = 'blank.html' def init_for_outgoing_email(self, agent): """ This method sets up the Viewer to render the body of an outgoing email to the specified `agent`. There is no request associated with this viewer. Viewers initialized with this function should not be used to render ordinary actions (like item_show_html). Instead, they can be used to render emails that want to take advantage of the item_tags and existing infrastructure that requires a viewer. """ self.request = None self.method = None self.cur_agent = agent self.cur_site = get_default_site() self.multi_agent_permission_cache = MultiAgentPermissionCache() self.permission_cache = self.multi_agent_permission_cache.get(self.cur_agent) self.format = 'html' self.noun = None self.item = None self.action = 'list' self.context = Context() self.context['action'] = self.action self.context['item'] = self.item self.context['specific_version'] = False self.context['viewer_name'] = self.viewer_name self.context['accepted_item_type'] = self.accepted_item_type self.context['accepted_item_type_name'] = self.accepted_item_type._meta.verbose_name self.context['accepted_item_type_name_plural'] = self.accepted_item_type._meta.verbose_name_plural self.context['full_path'] = '/' self.context['query_string'] = '' self.context['cur_agent'] = self.cur_agent self.context['cur_site'] = self.cur_site self.context['_viewer'] = self self.context['layout'] = 'blank.html' def dispatch(self): """ Perform the requested action and return an HttpResponse. In general, this should just be called from cms/dispatcher.py. Return None if there is no Python method that corresponds with the requested action. """ # Make sure there isn't a cycle in the virtual request graph. if hasattr(self.request, 'has_virtual_request_cycle'): if self.request.has_virtual_request_cycle(): return self.render_error( 'Cycle detected in embedded viewers', 'The embedded viewers cannot be rendered without an infinite loop.', HttpResponseNotFound) # Get the Python method that corresponds to the requested action if self.noun is None: action_method_name = 'type_%s_%s' % (self.action, self.format) else: action_method_name = 'item_%s_%s' % (self.action, self.format) action_method = getattr(self, action_method_name, None) if not action_method: return None # Return an error if there was a noun in the request but we weren't # able to find the corresponding item, or if the corresponding item # is not an instance of accepted_item_type (with the exception of # the 'copy' action, which will allow any viewer to copy any item). if self.noun != None: if self.item is None: return self.render_item_not_found() if not isinstance(self.item, self.accepted_item_type) and self.action != 'copy': return self.render_item_not_found() # Perform the action try: response = action_method() return response except DemePermissionDenied, e: # If a DemePermissionDenied exception was raised while trying to # perform the action, render a friendly error page. from cms.templatetags.item_tags import get_item_link_tag ability_friendly_name = friendly_name_for_ability(e.ability) or e.ability # Explain what the user is trying to do if self.context.get('action_title'): msg = u'You do not have permission to perform the "%s" action' % self.context['action_title'] else: msg = u'You do not have permission to perform the action' if self.item: msg += u' on %s' % get_item_link_tag(self.context, self.item) # Explain why the user cannot do it if e.item is None: msg += u' (you need the "%s" global ability)' % ability_friendly_name else: permission_item_text = 'it' if e.item == self.item else get_item_link_tag(self.context, e.item) if e.item.destroyed: msg += u' (%s is destroyed)' % permission_item_text else: msg += u' (you need the "%s" ability on %s)' % (ability_friendly_name, permission_item_text) return self.render_error('Permission Denied', msg)