def datetime_loc_node():
    return colander.SchemaNode(colander.String(),
                               title = _(u"Datetime localisation"),
                               description = _(u"Pick localisation for date format for this user."),
                               widget = deferred_pick_language_widget,
                               missing = 'en',
                               default = 'en')
def translator_node():
    return colander.SchemaNode(deform.Set(allow_empty = True),
                               title = _(u"I am a translator and translate to the following languages"),
                               description = _(u"You will need the translator permission to actually translate."),
                               widget = deferred_translator_languages_widget,
                               missing=(),
                               )
    def manage_questions(self):
        manage_questions.need()
        post = self.request.POST
        if 'cancel' in self.request.POST:
            url = self.request.resource_url(self.context)
            return HTTPFound(location = url)
        if 'save' in post:
            self.process_question_ids(self.context)
            self.add_flash_message(_('Updated'))

        self.response['organisation'] = org = find_interface(self.context, IOrganisation)

        picked_questions = set()
        survey_sections = []
        for section in self.context.values():
            if not ISurveySection.providedBy(section):
                continue
            picked_questions.update(section.question_ids)
            survey_sections.append(section)
        self.response['survey_sections'] = survey_sections
        if not survey_sections:
            msg = _(u"no_sections_added_notice",
                    default = u"You need to add survey sections and then use this view to manage the questions.")
            self.add_flash_message(msg)
        #Load all question objects that haven't been picked
        questions = org.questions
        self.response['available_questions'] = [questions[x] for x in questions if x not in picked_questions]
        return self.response
def bootstrap_root():
    from madetomeasure.models.root import SiteRoot
    from madetomeasure.models.users import User
    from madetomeasure.models.users import Users
    from madetomeasure.models.participants import Participants
    from madetomeasure.models.questions import Questions
    from madetomeasure.models.question_types import QuestionTypes
    from madetomeasure.models.question_types import TextQuestionType
    from madetomeasure import security

    root = SiteRoot(creators=['admin'], title = u"Made To Measure")
    root['users'] = Users(title = _(u"Users"))
    #Create admin user with password admin as standard
    admin = User(password='******', first_name="M2M", last_name="Administrator")
    root['users']['admin'] = admin
    #Add admin to group managers
    root.add_groups('admin', [security.ROLE_ADMIN])
    root['participants'] = Participants(title=_(u"Participants"))
    root['questions'] = Questions(title=_(u"Questions"))
    root['question_types'] = QuestionTypes()
    #Note, free_text_question is also used in tests!
    root['question_types']['free_text_question'] = TextQuestionType(title = u"Free text question",
                                                                    description = u"Just a text field",
                                                                    input_widget = 'text_widget')
    return root
    def variant(self):
        question_name = self.request.GET.get('question_name', None)
        globalquestions = self.root['questions']

        if question_name not in globalquestions:
            self.add_flash_message(_(u"Invalid question name."))
            url = self.request.resource_url(self.context)
            return HTTPFound(location=url)

        question = globalquestions[question_name]
        schema = createSchema(question.schemas['translate'])
        schema.title = _(u"Edit question variant for this organisation")
        schema = schema.bind(context = question, request = self.request)
        # add default locale
        description = question.get_original_title()
        descriptions = question.get_question_text()
        self.trans_util.add_translation_schema(schema['question_text'], self.trans_util.default_locale_name, description=description)
        self.trans_util.add_translations_schema(schema['question_text'], self.context, descriptions=descriptions, only_with_description=True)
        
        form = Form(schema, action = self.request.url, buttons=(self.buttons['save'], self.buttons['cancel']))
        self.response['form_resources'] = form.get_widget_resources()
        
        if 'save' in self.request.POST:
            controls = self.request.POST.items()
            try:
                appstruct = form.validate(controls)
            except ValidationFailure, e:
                self.response['form'] = e.render()
                return self.response
            for (lang, value) in appstruct['question_text'].items():
                self.context.set_variant(question_name, lang, value)
            url = self.request.resource_url(self.context, 'variants')
            return HTTPFound(location = url)
def password_validation(node, value):
    """ check that password is
        - at least 6 chars and at most 100.
    """
    if len(value) < 6:
        raise colander.Invalid(node, _(u"Too short. At least 6 chars required."))
    if len(value) > 100:
        raise colander.Invalid(node, _(u"Less than 100 chars please."))
    def survey_view(self):
        if self.userid:
            return HTTPFound(location = self.request.resource_url(self.context, 'view'))
        try:
            self.context.check_open()
            self.response['survey_open'] = True
        except SurveyUnavailableError:
            self.response['survey_open'] = False
        self.response['allow_anonymous_to_participate'] = self.context.get_field_value('allow_anonymous_to_participate', False)
        if self.response['survey_open']:
            schema = createSchema('ParticipantControlsSchema')
            schema = schema.bind(context = self.context, request = self.request)            
            form = Form(schema, buttons=(self.buttons['send'],))
            self.response['form_resources'] = form.get_widget_resources()
            if 'send' in self.request.POST:
                controls = self.request.POST.items()
                try:
                    appstruct = form.validate(controls)
                except ValidationFailure, e:
                    self.response['form'] = e.render()
                    return self.response

                if appstruct['participant_actions'] == u"send_anon_invitation" and self.response['allow_anonymous_to_participate']:
                    self.send_invitation(appstruct['email'])

                if appstruct['participant_actions'] == u"start_anon" and self.context.get_field_value('allow_anonymous_to_start', False):
                    if appstruct['email'] in self.context.tickets.values():
                        #Check if participant actually started the survey, or was just invited
                        #FIXME: We're looping twice - we should use a reverse key registry instead.
                        for (participant_uid, email) in self.context.tickets.items():
                            #This will of course be true, since we already know email is in there.
                            if appstruct['email'] == email:
                                if self.root['participants'].participant_by_ids(self.context.__name__,  participant_uid):
                                    #Abort if survey data founds
                                    msg = _(u"email_already_used_notice",
                                            default = u"Your email address has already been used within this survey. "
                                                    u"If you need to change your replies, use the access link provided when you started "
                                                    u"the survey, or resend the link by using the form below.")
                                    self.add_flash_message(msg)
                                    return HTTPFound(location = self.request.resource_url(self.context))
                    invitation_uid = self.context.create_ticket(appstruct['email'])
                    access_link = self.request.resource_url(self.context, 'do', query = {'uid': invitation_uid})
                    msg = _(u"participant_unverified_link_notice",
                            default = u"Important! Since you're starting this survey without a verified email, please copy this "
                                    u"link in case you need to access the survey again: ${link}",
                            mapping = {'link': access_link})
                    self.add_flash_message(msg)
                    return HTTPFound(location = access_link)

                if appstruct['participant_actions'] == u"resend_access":
                    if appstruct['email'] not in self.context.tickets.values() and not self.response['allow_anonymous_to_participate']:
                        msg = _(u"cant_resend_access_error",
                                default = u"Unable to resend access code - your email wasn't found among the invited.")
                        self.add_flash_message(msg)
                        return HTTPFound(location = self.request.resource_url(self.context))
                    self.send_invitation(appstruct['email'])

            self.response['form'] = form.render()
 def __call__(self, node, value):
     #FIXME: Proper regexp validator of chars
     if len(value) < 2:
         raise colander.Invalid(node, _(u"Too short"))
     if len(value) > 50:
         raise colander.Invalid(node, _(u"Too long"))
     root = find_root(self.context)
     if value in root['users'].keys():
         raise colander.Invalid(node, _(u"Already exists"))
def time_zone_node():
    return colander.SchemaNode(
        colander.String(),
        title = _(u"Timezone"),
        description = _(u"Try start typing a timezone"),
        validator = colander.OneOf(common_timezones),
        widget = deform.widget.AutocompleteInputWidget(size=60,
                                                   values = list(common_timezones), #Conversion due to API change in pytz
                                                   min_length=1),
    )
def deferred_participant_actions(node, kw):
    request = kw['request']
    context = kw['context']
    choices = []
    if context.get_field_value('allow_anonymous_to_participate', False):
        choices.append((u'send_anon_invitation', _(u"I want to participate in the survey - send me a link.")))
    choices.append((u'resend_access', _(u"I lost my access link - send me a new one.")))
    if context.get_field_value('allow_anonymous_to_start', False):
        choices.append((u'start_anon', _(u"Start my survey without verifying my email.")))
    return deform.widget.RadioChoiceWidget(values = choices)
 def send_invitation(self, email):
     subject = self.localizer.translate(_(u"Invitation to survey '${s_title}'", mapping = {'s_title': self.context.title}))
     msg = _(u"self_added_invitation_text",
             default = u"You receive this email since someone (hopefully you) entered "
             u"your email address as a participant of this survey. Simply follow the link to participate in the survey. "
             u"In case you didn't ask for this email, or you've changed your mind about participating, simply do nothing. ")
     self.context.send_invitations(self.request, emails = [email],
                                   subject = subject, message = msg)
     msg = _(u"invitation_sent_notice",
             default = u"Invitation sent. Check your inbox in a few minutes. If it hasn't arrived within 15 minutes, check your spam folder.")
     self.add_flash_message(msg)
 def buttons(self):
     buttons = {}
     buttons['login'] = Button('login', _(u"Login"))
     buttons['save'] = Button('save', _(u"Save"))
     buttons['send'] = Button('send', _(u"Send"))
     buttons['next'] = Button('next', _(u"Next"))
     buttons['previous'] = Button('previous', _(u"Previous"))
     buttons['cancel'] = Button('cancel', _(u"Cancel"))
     buttons['request'] = Button('request', _(u"Request"))
     buttons['change'] = Button('change', _(u"Change"))
     buttons['delete'] = Button('delete', _(u"Delete"))
     return buttons
 def csv_header(self):
     response = []
     response.append(self.title)
     response.append(_(u"Total"))
     for (id, title) in self.widget().values:
         response.append(title.encode("utf-8"))
     return response
    def participants_view(self):
        """ Overview of participants. """
        self.response['participants'] = participants = self.context.get_participants_data()
        not_finished = [x for x in participants if x['finished']<100]
        self.response['not_finished'] = not_finished
        self.response['closed_survey'] = self._closed_survey(self.context)
        schema = createSchema(self.context.schemas['reminder'])
        schema = schema.bind(context = self.context, request = self.request)
        form = Form(schema, buttons=(self.buttons['send'],))
        self.response['form_resources'] = form.get_widget_resources()

        post = self.request.POST
        if 'send' in post:
            controls = self.request.POST.items()
            try:
                appstruct = form.validate(controls)
            except ValidationFailure, e:
                self.response['form'] = e.render()
                return self.response

            for participant in not_finished:
                # a participant with less then 100% completion will receive the invite ticket again with specified message
                ticket_uid = participant['uid']
                email = self.context.tickets[ticket_uid]
                self.context.send_invitation_email(self.request, email, ticket_uid, appstruct['subject'], appstruct['message'])

            self.add_flash_message(_(u"Reminder has been sent"))
 def select_lang_view(self):
     lang = self.request.GET.get('lang', None)
     msg = _(u"Language set to ${selected_lang}",
             mapping = {'selected_lang': self.trans_util.title_for_code(lang)})
     self.add_flash_message(msg)
     self.set_lang(lang)
     url = self.request.GET.get('return_url', self.request.resource_url(self.root))
     return HTTPFound(location = url, headers = self.request.response.headers)
 def __call__(self):
     try:
         #FIXME: This will probably change
         self.context.validate_password_token(None, self.request.GET.get('token', ''))
     except ValueError:
         self.add_flash_message(_(u"You haven't requested a new password"))
         raise HTTPForbidden("Access to password token view not allowed if user didn't request password change.")
     return super(TokenPasswordChangeForm, self).__call__()
 def new_request_password_token(self, request):
     """ Set a new request password token and email user. """
     locale = get_localizer(request)
     self.__token__ = RequestPasswordToken()
     #FIXME: Email should use a proper template
     pw_link = request.resource_url(self, 'token_pw', query = {'token': self.__token__()})
     body = locale.translate(_('request_new_password_text',
              default=u"password link: ${pw_link}",
              mapping={'pw_link':pw_link},))
     email = self.get_field_value('email')
     if not email:
         raise HTTPForbidden("No email set")
     msg = Message(subject=_(u"Password reset request from MadeToMeasure"),
                    recipients=[email],
                    body=body)
     mailer = get_mailer(request)
     mailer.send(msg)
 def __call__(self, node, value):
     #userid here can be either an email address or a login name
     if '@' in value:
         #assume email
         user = self.context.get_user_by_email(value)
     else:
         user = self.context.get(value)
     if not IUser.providedBy(user):
         raise colander.Invalid(node, _(u"Login incorrect"))
def deferred_tags_text_widget(node, kw):
    context = kw['context']
    questions = _fetch_questions_for_tags(context)
    tags = set()
    [tags.update(x.tags) for x in questions]
    return deform.widget.AutocompleteInputWidget(
                title = _(u"Tags"),
                size=60,
                values = tuple(tags),
            )
 def check_safe_delete(self, request):
     root = find_root(self)
     results = root["questions"].questions_by_type(self.__name__)
     if not results:
         return True
     # FIXME: Only flash messages can handle html right now
     out = u"<br/><br/>"
     rurl = request.resource_url
     out += ",<br/>".join([u'<a href="%s">%s</a>' % (rurl(x), x.title) for x in results])
     request.session.flash(_(u"Can't delete this since it's used in: ${out}", mapping={"out": out}))
     return False
 def request_success(self, appstruct):
     userid_or_email = appstruct['userid_or_email']
     #userid here can be either an email address or a login name
     if '@' in userid_or_email:
         #assume email
         user = self.context['users'].get_user_by_email(userid_or_email)
     else:
         user = self.context['users'].get(userid_or_email)
     #Validation is handled by validator in schema
     user.new_request_password_token(self.request)
     self.add_flash_message(_(u"Password change link emailed."))
     url = self.request.resource_url(self.context, 'login')
     return HTTPFound(location = url)
 def send_success(self, appstruct):
     emails = set()
     for email in appstruct['emails'].splitlines():
         emails.add(email.strip())
     message = appstruct['message']
     subject = appstruct['subject']
     self.context.send_invitations(self.request, emails, subject, message)
     msg = _(u"invitations_sent_notice",
             default = u"${count} Invitation(s) sent",
             mapping = {'count': len(emails)})
     self.add_flash_message(msg)
     url = self.request.resource_url(self.context)
     return HTTPFound(location = url)
def deferred_tags_select_widget(node, kw):
    """ Fetch all selectable tags. Context here probably be anything,
        but if it is a questions folder, only those tags should be fetched.
        Otherwise, if it's within an organisation, fetch those tags and apply the global tags.
    """
    context = kw['context']
    questions = _fetch_questions_for_tags(context)
    tags = {}
    for question in questions:
        for tag in question.tags:
            try:
                tags[tag] += 1
            except KeyError:
                tags[tag] = 1
    order = sorted(tags.keys())
    results = [(u'', _(u'<select>'))]
    for k in order:
        results.append((k, u"%s (%s)" % (k, tags[k])))
    return deform.widget.SelectWidget(
                title = _(u"Tags"),
                size = 60,
                values = tuple(results),
            )
 def __call__(self, form, value):
     exc = colander.Invalid(form, u"Login invalid") #Raised if trouble
     password = value['password']
     userid_or_email = value['userid_or_email']
     if '@' in userid_or_email:
         #assume email
         user = self.context['users'].get_user_by_email(userid_or_email)
     else:
         user = self.context['users'].get(userid_or_email)
     if not user:
         raise exc
     #Validate password
     pw_field = user.get_custom_field('password')
     if not pw_field.check_input(password):
         exc['password'] = _(u"Wrong password. Remember that passwords are case sensitive.")
         raise exc
def multiple_email_validator(node, value):
    """ Checks that each line of value is a correct email
    """
    validator = colander.Email()
    invalid = []
    for email in value.splitlines():
        email = email.strip()
        if not email:
            continue
        try:
            validator(node, email)
        except colander.Invalid:
            invalid.append(email)
    if invalid:
        emails = ", ".join(invalid)
        raise colander.Invalid(node, _(u"The following addresses is invalid: ${emails}", mapping={'emails': emails}))
 def __call__(self):
     return_url = self.request.resource_url(self.context)
     try:
         self.context.check_open()
     except SurveyUnavailableError as exc:
         self.add_flash_message(exc.msg)
         return HTTPFound(location = return_url)
     survey_sections = [x for x in self.context.values() if ISurveySection.providedBy(x)]
     question_count = sum([len(x.question_ids) for x in survey_sections])
     if not question_count:
         msg = _(u"no_questions_notice",
                 default = u"There aren't any questions yet. You need to add survey sections and questions, "
                           u"otherwise invited users won't be able to do anything.")
         self.add_flash_message(msg)
         return HTTPFound(location = return_url)
     return super(InvitationEmailsForm, self).__call__()
 def login_success(self, appstruct):
     userid_or_email = appstruct['userid_or_email']
     if '@' in userid_or_email:
         #assume email
         user = self.context['users'].get_user_by_email(userid_or_email)
     else:
         user = self.context['users'].get(userid_or_email)
     assert user is not None
     headers = remember(self.request, user.__name__)
     self.add_flash_message(_(u"Welcome"))
     came_from = self.request.GET.get('came_from', '')
     if came_from:
         url = came_from
     else:
         url = self.request.resource_url(self.context)
     return HTTPFound(location = url, headers = headers)
 def survey_admin_view(self):
     start_time = self.context.get_field_value('start_time', None)
     end_time = self.context.get_field_value('end_time', None)
     if start_time:
         start_time = self.user_dt.dt_format(start_time)
     if end_time:
         end_time = self.user_dt.dt_format(end_time)
     self.response['start_time'] = start_time
     self.response['end_time'] = end_time
     #Is survey active?
     try:
         self.context.check_open()
         msg = _(u"The survey is currently open.")
     except SurveyUnavailableError as e:
         msg = e.msg
     self.response['survey_state_msg'] = msg
     return self.response
 def reorder_folder(self):
     post = self.request.POST
     ui_sortable.need()
     if 'cancel' in self.request.POST:
         url = self.request.resource_url(self.context)
         return HTTPFound(location = url)
     if 'save' in post:
         controls = self.request.POST.items()
         items = []
         for (k, v) in controls:
             if k == 'items':
                 items.append(v)
         self.context.order = items
         self.add_flash_message(_('Order updated'))
     form = Form(colander.Schema())
     self.response['form_resources'] = form.get_widget_resources()
     self.response['dummy_form'] = form.render()
     return self.response
 def check_safe_delete(self, request):
     root = find_root(self)
     results = []
     #This code is ugly and could probably be done in a better way.
     for org in [x for x in root.values() if IOrganisation.providedBy(x)]:
         for surv in [x for x in org['surveys'].values() if ISurvey.providedBy(x)]:
             for surv_sect in [x for x in surv.values() if ISurveySection.providedBy(x)]:
                 if self.__name__ in surv_sect.question_ids:
                     results.append(surv_sect)
     if not results:
         return True
     #FIXME: Only flash messages can handle html right now
     out = u"<br/><br/>"
     rurl = request.resource_url
     out += ",<br/>".join([u'<a href="%s">%s</a>' % (rurl(x), x.title) for x in results])
     request.session.flash(_(u"Can't delete this since it's used in the following survey sections: ${out}",
                             mapping = {'out': out}))
     return False