text: # Copyright 2020 The Oppia Authors. All Rights Reserved.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  #      http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS-IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  """Domain objects for rights for various user actions."""
  
  from __future__ import absolute_import  # pylint: disable=import-only-modules
  from __future__ import unicode_literals  # pylint: disable=import-only-modules
  
  from constants import constants
  from core.domain import change_domain
  from core.domain import user_services
  import feconf
  import python_utils
  import utils
  
  # IMPORTANT: Ensure that all changes to how these cmds are interpreted preserve
  # backward-compatibility with previous exploration snapshots in the datastore.
  # Do not modify the definitions of CMD keys that already exist.
  CMD_CREATE_NEW = feconf.CMD_CREATE_NEW
  CMD_CHANGE_ROLE = feconf.CMD_CHANGE_ROLE
  CMD_REMOVE_ROLE = feconf.CMD_REMOVE_ROLE
  CMD_CHANGE_EXPLORATION_STATUS = feconf.CMD_CHANGE_EXPLORATION_STATUS
  CMD_CHANGE_COLLECTION_STATUS = feconf.CMD_CHANGE_COLLECTION_STATUS
  CMD_CHANGE_PRIVATE_VIEWABILITY = feconf.CMD_CHANGE_PRIVATE_VIEWABILITY
  CMD_RELEASE_OWNERSHIP = feconf.CMD_RELEASE_OWNERSHIP
  CMD_UPDATE_FIRST_PUBLISHED_MSEC = feconf.CMD_UPDATE_FIRST_PUBLISHED_MSEC
  
  ACTIVITY_STATUS_PRIVATE = constants.ACTIVITY_STATUS_PRIVATE
  ACTIVITY_STATUS_PUBLIC = constants.ACTIVITY_STATUS_PUBLIC
  
  ROLE_OWNER = feconf.ROLE_OWNER
  ROLE_EDITOR = feconf.ROLE_EDITOR
  ROLE_VOICE_ARTIST = feconf.ROLE_VOICE_ARTIST
  ROLE_VIEWER = feconf.ROLE_VIEWER
  ROLE_NONE = feconf.ROLE_NONE
  
  ASSIGN_ROLE_COMMIT_MESSAGE_TEMPLATE = 'Changed role of %s from %s to %s'
  ASSIGN_ROLE_COMMIT_MESSAGE_REGEX = '^Changed role of (.*) from (.*) to (.*)$'
  DEASSIGN_ROLE_COMMIT_MESSAGE_TEMPLATE = 'Remove %s from role %s'
  DEASSIGN_ROLE_COMMIT_MESSAGE_REGEX = '^Remove (.*) from role (.*)$'
  
  
  class ActivityRights(python_utils.OBJECT):
      """Domain object for the rights/publication status of an activity (an
      exploration or a collection).
      """
  
      def __init__(
              self, exploration_id, owner_ids, editor_ids, voice_artist_ids,
              viewer_ids, community_owned=False, cloned_from=None,
              status=ACTIVITY_STATUS_PRIVATE, viewable_if_private=False,
              first_published_msec=None):
          self.id = exploration_id
          self.owner_ids = owner_ids
          self.editor_ids = editor_ids
          self.voice_artist_ids = voice_artist_ids
          self.viewer_ids = viewer_ids
          self.community_owned = community_owned
          self.cloned_from = cloned_from
          self.status = status
          self.viewable_if_private = viewable_if_private
          self.first_published_msec = first_published_msec
  
      def validate(self):
          """Validates an ActivityRights object.
  
          Raises:
              utils.ValidationError. If any of the owners, editors, voice artists
                  and viewers lists overlap, or if a community-owned exploration
                  has owners, editors, voice artists or viewers specified.
          """
          if self.community_owned:
              if (self.owner_ids or self.editor_ids or self.voice_artist_ids or
                      self.viewer_ids):
                  raise utils.ValidationError(
                      'Community-owned explorations should have no owners, '
                      'editors, voice artists or viewers specified.')
  
          if self.community_owned and self.status == ACTIVITY_STATUS_PRIVATE:
              raise utils.ValidationError(
                  'Community-owned explorations cannot be private.')
  
          if self.status != ACTIVITY_STATUS_PRIVATE and self.viewer_ids:
              raise utils.ValidationError(
                  'Public explorations should have no viewers specified.')
  
          owner_editor = set(self.owner_ids) & set(self.editor_ids)
          owner_voice_artist = set(self.owner_ids) & set(self.voice_artist_ids)
          owner_viewer = set(self.owner_ids) & set(self.viewer_ids)
          editor_voice_artist = set(self.editor_ids) & set(self.voice_artist_ids)
          editor_viewer = set(self.editor_ids) & set(self.viewer_ids)
          voice_artist_viewer = set(self.voice_artist_ids) & set(self.viewer_ids)
          if owner_editor:
              raise utils.ValidationError(
                  'A user cannot be both an owner and an editor: %s' %
                  owner_editor)
          if owner_voice_artist:
              raise utils.ValidationError(
                  'A user cannot be both an owner and a voice artist: %s' %
                  owner_voice_artist)
          if owner_viewer:
              raise utils.ValidationError(
                  'A user cannot be both an owner and a viewer: %s' %
                  owner_viewer)
          if editor_voice_artist:
              raise utils.ValidationError(
                  'A user cannot be both an editor and a voice artist: %s' %
                  editor_voice_artist)
          if editor_viewer:
              raise utils.ValidationError(
                  'A user cannot be both an editor and a viewer: %s' %
                  editor_viewer)
          if voice_artist_viewer:
              raise utils.ValidationError(
                  'A user cannot be both a voice artist and a viewer: %s' %
                  voice_artist_viewer)
  
      def to_dict(self):
          """Returns a dict suitable for use by the frontend.
  
          Returns:
              dict. A dict version of ActivityRights suitable for use by the
              frontend.
          """
          if self.community_owned:
              return {
                  'cloned_from': self.cloned_from,
                  'status': self.status,
                  'community_owned': True,
                  'owner_names': [],
                  'editor_names': [],
                  'voice_artist_names': [],
                  'viewer_names': [],
                  'viewable_if_private': self.viewable_if_private,
              }
          else:
              return {
                  'cloned_from': self.cloned_from,
                  'status': self.status,
                  'community_owned': False,
                  'owner_names': user_services.get_human_readable_user_ids(
                      self.owner_ids),
                  'editor_names': user_services.get_human_readable_user_ids(
                      self.editor_ids),
                  'voice_artist_names': user_services.get_human_readable_user_ids(
                      self.voice_artist_ids),
                  'viewer_names': user_services.get_human_readable_user_ids(
                      self.viewer_ids),
                  'viewable_if_private': self.viewable_if_private,
              }
  
      def is_owner(self, user_id):
          """Checks whether given user is owner of activity.
  
          Args:
              user_id: str or None. Id of the user.
  
          Returns:
              bool. Whether user is an activity owner.
          """
          return bool(user_id in self.owner_ids)
  
      def is_editor(self, user_id):
          """Checks whether given user is editor of activity.
  
          Args:
              user_id: str or None. Id of the user.
  
          Returns:
              bool. Whether user is an activity editor.
          """
          return bool(user_id in self.editor_ids)
  
      def is_voice_artist(self, user_id):
          """Checks whether given user is voice artist of activity.
  
          Args:
              user_id: str or None. Id of the user.
  
          Returns:
              bool. Whether user is an activity voice artist.
          """
          return bool(user_id in self.voice_artist_ids)
  
      def is_viewer(self, user_id):
          """Checks whether given user is viewer of activity.
  
          Args:
              user_id: str or None. Id of the user.
  
          Returns:
              bool. Whether user is an activity viewer.
          """
          return bool(user_id in self.viewer_ids)
  
      def is_published(self):
          """Checks whether activity is published.
  
          Returns:
              bool. Whether activity is published.
          """
          return bool(self.status == ACTIVITY_STATUS_PUBLIC)
  
      def is_private(self):
          """Checks whether activity is private.
  
          Returns:
              bool. Whether activity is private.
          """
          return bool(self.status == ACTIVITY_STATUS_PRIVATE)
  
      def is_solely_owned_by_user(self, user_id):
          """Checks whether the activity is solely owned by the user.
  
          Args:
              user_id: str. The id of the user.
  
          Returns:
              bool. Whether the activity is solely owned by the user.
          """
          return user_id in self.owner_ids and len(self.owner_ids) == 1
  
  
  class ExplorationRightsChange(change_domain.BaseChange):
      """Domain object class for an exploration rights change.
  
      The allowed commands, together with the attributes:
          - 'create_new'
          - 'change_role' (with assignee_id, old_role, new_role)
          - 'change_exploration_status' (with old_status, new_status)
          - 'change_private_viewability' (with
              old_viewable_if_private, new_viewable_if_private)
          - 'release_ownership'
          - 'update_first_published_msec' (with
              old_first_published_msec, new_first_published_msec)
      A role must be one of the ALLOWED_ROLES.
      A status must be one of the ALLOWED_STATUS.
      """
  
      ALLOWED_COMMANDS = feconf.EXPLORATION_RIGHTS_CHANGE_ALLOWED_COMMANDS
  
  
  class CollectionRightsChange(change_domain.BaseChange):
      """Domain object class for an collection rights change.
  
      The allowed commands, together with the attributes:
          - 'create_new'
          - 'change_role' (with assignee_id, old_role, new_role)
          - 'change_collection_status' (with old_status, new_status)
          - 'change_private_viewability' (with
              old_viewable_if_private, new_viewable_if_private)
          - 'release_ownership'
          - 'update_first_published_msec' (with
              old_first_published_msec, new_first_published_msec)
      A role must be one of the ALLOWED_ROLES.
      A status must be one of the ALLOWED_STATUS.
      """
  
      ALLOWED_COMMANDS = feconf.COLLECTION_RIGHTS_CHANGE_ALLOWED_COMMANDS
  