Example #1
0
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)
Example #2
0
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(),
            }
        }
Example #3
0
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
Example #4
0
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
Example #5
0
 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
Example #6
0
 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
Example #7
0
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)))
Example #8
0
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],
    )
Example #9
0
    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)
Example #10
0
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
Example #11
0
    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
Example #12
0
    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
Example #13
0
    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',