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