def send_accept_note(invite, request): """ Send a notification email to the issuer of an invitation when a user accepts the invitation. :param invite: ProjectInvite object :param request: HTTP request :return: Amount of sent email (int) """ subject = (SUBJECT_PREFIX + ' ' + SUBJECT_ACCEPT.format( user_name=request.user.get_full_name(), project_label=get_display_name(invite.project.type), project=invite.project.title, )) message = MESSAGE_HEADER.format(recipient=invite.issuer.get_full_name(), site_title=SITE_TITLE) message += MESSAGE_ACCEPT_BODY.format( role=invite.role.name, project=invite.project.title, user_name=request.user.get_full_name(), user_email=request.user.email, site_title=SITE_TITLE, project_label=get_display_name(invite.project.type), ) if not settings.PROJECTROLES_EMAIL_SENDER_REPLY: message += NO_REPLY_NOTE message += get_email_footer() return send_mail(subject, message, [invite.issuer.email], request)
class ProjectAppPlugin(ProjectAppPluginPoint): """Plugin for registering app with Projectroles""" # Properties required by django-plugins ------------------------------ #: Name (slug-safe, used in URLs) name = 'timeline' #: Title (used in templates) title = 'Timeline' #: App URLs (will be included in settings by djangoplugins) urls = urlpatterns # Properties defined in ProjectAppPluginPoint ----------------------- #: FontAwesome icon ID string icon = 'clock-o' #: Entry point URL ID (must take project sodar_uuid as "project" argument) entry_point_url_id = 'timeline:list_project' #: Description string description = 'Timeline of {} events'.format( get_display_name(SODAR_CONSTANTS['PROJECT_TYPE_PROJECT']) ) #: Required permission for accessing the app app_permission = 'timeline.view_timeline' #: Enable or disable general search from project title bar search_enable = False # Not allowed for timeline #: App card template for the project details page details_template = 'timeline/_details_card.html' #: App card title for the project details page details_title = '{} Timeline Overview'.format( get_display_name(SODAR_CONSTANTS['PROJECT_TYPE_PROJECT'], title=True) ) #: Position in plugin ordering plugin_ordering = 40 def get_statistics(self): return { 'event_count': { 'label': 'Events', 'value': ProjectEvent.objects.all().count(), } }
def get_role_change_body(change_type, project, user_name, role_name, issuer, project_url): """ Return role change email body :param change_type: Change type ('create', 'update', 'delete') :param project: Project object :param user_name: Name of target user :param role_name: Name of role as string :param issuer: User object for issuing user :param project_url: URL for the project :return: String """ body = MESSAGE_HEADER.format(recipient=user_name, site_title=SITE_TITLE) if change_type == 'create': body += MESSAGE_ROLE_CREATE.format( issuer_name=issuer.get_full_name(), issuer_email=issuer.email, role=role_name, project=project.title, project_url=project_url, site_title=SITE_TITLE, project_label=get_display_name(project.type), ) elif change_type == 'update': body += MESSAGE_ROLE_UPDATE.format( issuer_name=issuer.get_full_name(), issuer_email=issuer.email, role=role_name, project=project.title, project_url=project_url, site_title=SITE_TITLE, project_label=get_display_name(project.type), ) elif change_type == 'delete': body += MESSAGE_ROLE_DELETE.format( issuer_name=issuer.get_full_name(), issuer_email=issuer.email, project=project.title, project_label=get_display_name(project.type), ) if not issuer.email and not settings.PROJECTROLES_EMAIL_SENDER_REPLY: body += NO_REPLY_NOTE body += get_email_footer() return body
def get_invite_body(project, issuer, role_name, invite_url, date_expire_str): """ Return the invite content header. :param project: Project object :param issuer: User object :param role_name: Display name of the Role object :param invite_url: Generated URL for the invite :param date_expire_str: Expiry date as a pre-formatted string :return: string """ body = MESSAGE_HEADER_NO_RECIPIENT.format(site_title=SITE_TITLE) body += MESSAGE_INVITE_BODY.format( issuer_name=issuer.get_full_name(), issuer_email=issuer.email, project=project.title, role=role_name, invite_url=invite_url, date_expire=date_expire_str, site_title=SITE_TITLE, project_label=get_display_name(project.type), ) if not issuer.email and not settings.PROJECTROLES_EMAIL_SENDER_REPLY: body += NO_REPLY_NOTE return body
def get_context_data(self, *args, **kwargs): context = super().get_context_data(*args, **kwargs) context['timeline_title'] = '{} Timeline'.format( get_display_name(context['project'].type, title=True) ) context['timeline_mode'] = 'project' return context
def get_context_data(self, *args, **kwargs): context = super().get_context_data(*args, **kwargs) context['timeline_title'] = '{} Timeline'.format( get_display_name(SODAR_CONSTANTS['PROJECT_TYPE_PROJECT'], title=True)) context['timeline_mode'] = 'project' return context
def get_invite_subject(project): """ Return invite email subject :param project: Project object :return: string """ return (SUBJECT_PREFIX + ' ' + SUBJECT_INVITE.format( project=project.title, project_label=get_display_name(project.type)))
def send_project_move_mail(project, request): """ Send email about project being moved to the owner of the parent category, if they are a different user than the project creator. :param project: Project object for the newly created project :param request: Request object :return: Amount of sent email (int) """ parent = project.parent parent_owner = project.parent.get_owner() if project.parent else None project_owner = project.get_owner() if not parent or not parent_owner or parent_owner.user == request.user: return 0 subject = SUBJECT_PROJECT_MOVE.format( project_type=get_display_name(project.type, title=True), project=project.title, user_name=request.user.get_full_name(), ) message = MESSAGE_HEADER.format( recipient=parent_owner.user.get_full_name(), site_title=SITE_TITLE) message += MESSAGE_PROJECT_MOVE_BODY.format( user_name=request.user.get_full_name(), user_email=request.user.email, project_type=get_display_name(project.type), category=parent.title, project=project.title, owner_name=project_owner.user.get_full_name(), owner_email=project_owner.user.email, project_url=request.build_absolute_uri( reverse('projectroles:detail', kwargs={'project': project.sodar_uuid})), ) message += get_email_footer() return send_mail( subject, message, [parent_owner.user.email], request, reply_to=[request.user.email], )
def get(self, *args, **kwargs): """Override of GET for checking project settings""" try: file = File.objects.get(sodar_uuid=self.kwargs['file']) except File.DoesNotExist: messages.error(self.request, 'File not found!') return redirect(reverse('home')) if not app_settings.get_app_setting(APP_NAME, 'allow_public_links', file.project): messages.error( self.request, 'Sharing public links not allowed for this {}'.format( get_display_name(SODAR_CONSTANTS['PROJECT_TYPE_PROJECT'])), ) return redirect( reverse( 'filesfolders:list', kwargs={'project': file.project.sodar_uuid}, )) return super().get(*args, **kwargs)
def get_role_change_subject(change_type, project): """ Return role change email subject :param change_type: Change type ('create', 'update', 'delete') :param project: Project object :return: String """ subject = SUBJECT_PREFIX + ' ' subject_kwargs = { 'project': project.title, 'project_label': get_display_name(project.type), } if change_type == 'create': subject += SUBJECT_ROLE_CREATE.format(**subject_kwargs) elif change_type == 'update': subject += SUBJECT_ROLE_UPDATE.format(**subject_kwargs) elif change_type == 'delete': subject += SUBJECT_ROLE_DELETE.format(**subject_kwargs) return subject
def clean(self): """Function for custom form validation and cleanup""" instance_owner_as = self.instance.get_owner( ) if self.instance else None parent = self.cleaned_data.get('parent') # Check for category/project being placed in root if not parent and (not self.instance or self.instance.parent): if (self.cleaned_data.get('type') == PROJECT_TYPE_CATEGORY and not self.current_user.is_superuser): self.add_error( 'parent', 'You do not have permission to place a category under root', ) elif (self.cleaned_data.get('type') == PROJECT_TYPE_PROJECT and not DISABLE_CATEGORIES): self.add_error('parent', 'Projects can not be placed under root') # Ensure title does not match parent if parent and parent.title == self.cleaned_data.get('title'): self.add_error( 'title', '{} and parent titles can not be equal'.format( get_display_name(self.cleaned_data.get('type'), title=True)), ) # Ensure title is unique within parent existing_project = Project.objects.filter( parent=self.cleaned_data.get('parent'), title=self.cleaned_data.get('title'), ).first() if existing_project and (not self.instance or existing_project.pk != self.instance.pk): self.add_error('title', 'Title must be unique within parent') # Ensure owner has been set if not self.cleaned_data.get('owner'): self.add_error( 'owner', 'Owner must be set for {}'.format( get_display_name(self.cleaned_data.get('type'))), ) # Ensure owner is not changed on update (must use ownership transfer) if (instance_owner_as and self.cleaned_data.get('owner') != instance_owner_as.user): self.add_error( 'owner', 'Owner update is not allowed in this form, use Ownership ' 'Transfer instead', ) # Verify settings fields for plugin in self.app_plugins: p_settings = self.app_settings.get_setting_defs( APP_SETTING_SCOPE_PROJECT, plugin=plugin, **self.p_kwargs) for s_key, s_val in p_settings.items(): s_field = 'settings.{}.{}'.format(plugin.name, s_key) if s_val['type'] == 'JSON': # for some reason, there is a distinct possiblity, that the # initial value has been discarded and we get '' as value. # Seems to only happen in automated tests. Will catch that # here. if not self.cleaned_data.get(s_field): self.cleaned_data[s_field] = '{}' try: self.cleaned_data[s_field] = json.loads( self.cleaned_data.get(s_field)) except json.JSONDecodeError as err: # TODO: Shouldn't we use add_error() instead? raise forms.ValidationError('Couldn\'t encode JSON\n' + str(err)) if not self.app_settings.validate_setting( setting_type=s_val['type'], setting_value=self.cleaned_data.get(s_field), ): self.add_error(s_field, 'Invalid value') return self.cleaned_data
def __init__(self, project=None, current_user=None, *args, **kwargs): """Override for form initialization""" super().__init__(*args, **kwargs) # Get current user for checking permissions for form items if current_user: self.current_user = current_user # Add settings fields self._init_app_settings() # Access parent project if present parent_project = None if project: parent_project = Project.objects.filter(sodar_uuid=project).first() # Update help texts to match DISPLAY_NAMES self.fields['title'].help_text = 'Title' self.fields['type'].help_text = 'Type of container ({} or {})'.format( get_display_name(PROJECT_TYPE_CATEGORY), get_display_name(PROJECT_TYPE_PROJECT), ) self.fields['type'].choices = PROJECT_TYPE_CHOICES self.fields['parent'].help_text = 'Parent {} if nested'.format( get_display_name(PROJECT_TYPE_CATEGORY)) self.fields['description'].help_text = 'Short description' self.fields[ 'readme'].help_text = 'README (optional, supports markdown)' #################### # Form modifications #################### # Modify ModelChoiceFields to use sodar_uuid self.fields['parent'].to_field_name = 'sodar_uuid' # Set readme widget with preview self.fields['readme'].widget = PagedownWidget(show_preview=True) # Updating an existing project if self.instance.pk: # Set readme value as raw markdown self.initial['readme'] = self.instance.readme.raw # Hide project type selection self.fields['type'].widget = forms.HiddenInput() # Set hidden project field for autocomplete self.initial['project'] = self.instance # Set owner value self.initial['owner'] = self.instance.get_owner().user.sodar_uuid # Hide owner widget if updating (changed in member modification UI) self.fields['owner'].widget = forms.HiddenInput() # Set valid choices for parent if not DISABLE_CATEGORIES: self.fields['parent'].choices = self._get_parent_choices( self.instance, self.current_user) # Hide widget if no valid choices are available if len(self.fields['parent'].choices) == 0: self.fields['parent'].widget = forms.HiddenInput() # Set initial value for parent if self.instance.parent: self.initial['parent'] = self.instance.parent.sodar_uuid else: # Categories disabled # Hide parent selection self.fields['parent'].widget = forms.HiddenInput() # Project creation else: # Set hidden project field for autocomplete self.initial['project'] = None # Hide parent selection self.fields['parent'].widget = forms.HiddenInput() # Creating a subproject if parent_project: # Parent must be current parent self.initial['parent'] = parent_project.sodar_uuid # Set current user as initial value self.initial['owner'] = self.current_user.sodar_uuid # If current user is not parent owner, disable owner select if (not self.current_user.is_superuser and self.current_user != parent_project.get_owner().user): self.fields['owner'].widget = forms.HiddenInput() # Creating a top level project else: # Force project type if getattr(settings, 'PROJECTROLES_DISABLE_CATEGORIES', False): self.initial['type'] = PROJECT_TYPE_PROJECT else: self.initial['type'] = PROJECT_TYPE_CATEGORY # Hide project type selection self.fields['type'].widget = forms.HiddenInput() # Set up parent field self.initial['parent'] = None
get_user_display_name, build_secret, ) from projectroles.app_settings import AppSettingAPI # SODAR constants PROJECT_ROLE_OWNER = SODAR_CONSTANTS['PROJECT_ROLE_OWNER'] PROJECT_ROLE_CONTRIBUTOR = SODAR_CONSTANTS['PROJECT_ROLE_CONTRIBUTOR'] PROJECT_ROLE_DELEGATE = SODAR_CONSTANTS['PROJECT_ROLE_DELEGATE'] PROJECT_ROLE_GUEST = SODAR_CONSTANTS['PROJECT_ROLE_GUEST'] PROJECT_TYPE_CATEGORY = SODAR_CONSTANTS['PROJECT_TYPE_CATEGORY'] PROJECT_TYPE_PROJECT = SODAR_CONSTANTS['PROJECT_TYPE_PROJECT'] PROJECT_TYPE_CHOICES = [ ( PROJECT_TYPE_CATEGORY, get_display_name(PROJECT_TYPE_CATEGORY, title=True), ), (PROJECT_TYPE_PROJECT, get_display_name(PROJECT_TYPE_PROJECT, title=True)), ] SUBMIT_STATUS_OK = SODAR_CONSTANTS['SUBMIT_STATUS_OK'] SUBMIT_STATUS_PENDING = SODAR_CONSTANTS['SUBMIT_STATUS_PENDING'] SUBMIT_STATUS_PENDING_TASKFLOW = SODAR_CONSTANTS['SUBMIT_STATUS_PENDING'] SITE_MODE_SOURCE = SODAR_CONSTANTS['SITE_MODE_SOURCE'] SITE_MODE_TARGET = SODAR_CONSTANTS['SITE_MODE_TARGET'] APP_SETTING_SCOPE_PROJECT = SODAR_CONSTANTS['APP_SETTING_SCOPE_PROJECT'] # Local constants and settings APP_NAME = 'projectroles' INVITE_EXPIRY_DAYS = settings.PROJECTROLES_INVITE_EXPIRY_DAYS DELEGATE_LIMIT = getattr(settings, 'PROJECTROLES_DELEGATE_LIMIT', 1) DISABLE_CATEGORIES = getattr(settings, 'PROJECTROLES_DISABLE_CATEGORIES',