Return-Path: X-Original-To: apmail-incubator-allura-commits-archive@minotaur.apache.org Delivered-To: apmail-incubator-allura-commits-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 4344BEF89 for ; Fri, 7 Dec 2012 22:26:37 +0000 (UTC) Received: (qmail 65332 invoked by uid 500); 7 Dec 2012 22:26:37 -0000 Delivered-To: apmail-incubator-allura-commits-archive@incubator.apache.org Received: (qmail 65230 invoked by uid 500); 7 Dec 2012 22:26:37 -0000 Mailing-List: contact allura-commits-help@incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: allura-dev@incubator.apache.org Delivered-To: mailing list allura-commits@incubator.apache.org Received: (qmail 64739 invoked by uid 99); 7 Dec 2012 22:26:36 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 07 Dec 2012 22:26:36 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id 50C3431D67E; Fri, 7 Dec 2012 22:26:36 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: brondsem@apache.org To: allura-commits@incubator.apache.org X-Mailer: ASF-Git Admin Mailer Subject: [30/49] [#5289] Added features to include personal details Message-Id: <20121207222636.50C3431D67E@tyr.zones.apache.org> Date: Fri, 7 Dec 2012 22:26:36 +0000 (UTC) http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/01c20a19/Allura/allura/controllers/auth.py ---------------------------------------------------------------------- diff --git a/Allura/allura/controllers/auth.py b/Allura/allura/controllers/auth.py index 0c2c060..0ef414a 100644 --- a/Allura/allura/controllers/auth.py +++ b/Allura/allura/controllers/auth.py @@ -9,6 +9,7 @@ from webob import exc as wexc import allura.tasks.repo_tasks from allura import model as M +from allura.model.project import TroveCategory from allura.lib import validators as V from allura.lib.oid_helper import verify_oid, process_oid from allura.lib.security import require_authenticated, has_access @@ -43,6 +44,19 @@ class F(object): registration_form = forms.RegistrationForm(action='/auth/save_new') oauth_application_form = OAuthApplicationForm(action='register') oauth_revocation_form = OAuthRevocationForm(action='revoke_oauth') + change_personal_data_form = forms.PersonalDataForm() + add_socialnetwork_form = forms.AddSocialNetworkForm() + remove_socialnetwork_form = forms.RemoveSocialNetworkForm() + add_telnumber_form = forms.AddTelNumberForm() + add_website_form = forms.AddWebsiteForm() + skype_account_form = forms.SkypeAccountForm() + remove_textvalue_form = forms.RemoveTextValueForm() + add_timeslot_form = forms.AddTimeSlotForm() + remove_timeslot_form = forms.RemoveTimeSlotForm() + add_inactive_period_form = forms.AddInactivePeriodForm() + remove_inactive_period_form = forms.RemoveInactivePeriodForm() + save_skill_form = forms.AddUserSkillForm() + remove_skill_form = forms.RemoveSkillForm() class AuthController(BaseController): @@ -278,8 +292,83 @@ class AuthController(BaseController): allow_write=has_access(c.app, 'write')(user=user), allow_create=has_access(c.app, 'create')(user=user)) +class UserSkillsController(BaseController): + + def __init__(self, category=None): + self.category = category + super(UserSkillsController, self).__init__() + + @expose() + def _lookup(self, catshortname, *remainder): + cat = M.TroveCategory.query.get(shortname=catshortname) + return UserSkillsController(category=cat), remainder + + @expose('jinja:allura:templates/user_skills.html') + def index(self, **kw): + require_authenticated() + + l = [] + parents = [] + if kw.get('selected_category') is not None: + selected_skill = M.TroveCategory.query.get(trove_cat_id=int(kw.get('selected_category'))) + elif self.category: + selected_skill = self.category + else: + l = M.TroveCategory.query.find(dict(trove_parent_id=0, show_as_skill=True)) + selected_skill = None + if selected_skill: + l = [scat for scat in selected_skill.subcategories + if scat.show_as_skill] + temp_cat = selected_skill.parent_category + while temp_cat: + parents = [temp_cat] + parents + temp_cat = temp_cat.parent_category + return dict( + skills_list = l, + selected_skill = selected_skill, + parents = parents, + add_details_fields=(len(l)==0)) + + @expose() + @require_post() + @validate(F.save_skill_form, error_handler=index) + def save_skill(self, **kw): + require_authenticated() + + trove_id = int(kw.get('selected_skill')) + category = M.TroveCategory.query.get(trove_cat_id=trove_id) + + new_skill = dict( + category_id=category._id, + level=kw.get('level'), + comment=kw.get('comment')) + + s = [skill for skill in c.user.skills + if str(skill.category_id) != str(new_skill['category_id'])] + s.append(new_skill) + c.user.set_pref('skills', s) + flash('Your skills list was successfully updated!') + redirect('/auth/prefs/user_skills') + + @expose() + @require_post() + @validate(F.remove_skill_form, error_handler=index) + def remove_skill(self, **kw): + require_authenticated() + + trove_id = int(kw.get('categoryid')) + category = M.TroveCategory.query.get(trove_cat_id=trove_id) + + s = [skill for skill in c.user.skills + if str(skill.category_id) != str(category._id)] + c.user.set_pref('skills', s) + flash('Your skills list was successfully updated!') + redirect('/auth/prefs/user_skills') + class PreferencesController(BaseController): + user_skills = UserSkillsController() + @with_trailing_slash @expose('jinja:allura:templates/user_preferences.html') def index(self, **kw): @@ -457,6 +546,119 @@ class PreferencesController(BaseController): @expose() @require_post() + @validate(F.change_personal_data_form, error_handler=index) + def change_personal_data(self, **kw): + require_authenticated() + c.user.set_pref('sex', kw['sex']) + c.user.set_pref('birthdate', kw.get('birthdate')) + localization={'country':kw.get('country'), 'city':kw.get('city')} + c.user.set_pref('localization', localization) + c.user.set_pref('timezone', kw['timezone']) + + flash('Your personal data was successfully updated!') + redirect('.') + + @expose() + @require_post() + @validate(F.add_socialnetwork_form, error_handler=index) + def add_social_network(self, **kw): + require_authenticated() + c.user.add_socialnetwork(kw['socialnetwork'], kw['accounturl']) + flash('Your personal contacts were successfully updated!') + redirect('.#Contacts') + + @expose() + @require_post() + @validate(F.remove_socialnetwork_form, error_handler=index) + def remove_social_network(self, **kw): + require_authenticated() + c.user.remove_socialnetwork(kw['socialnetwork'], kw['account']) + flash('Your personal contacts were successfully updated!') + redirect('.#Contacts') + + @expose() + @require_post() + @validate(F.add_telnumber_form, error_handler=index) + def add_telnumber(self, **kw): + require_authenticated() + c.user.add_telephonenumber(kw['newnumber']) + flash('Your personal contacts were successfully updated!') + redirect('.#Contacts') + + @expose() + @require_post() + @validate(F.remove_textvalue_form, error_handler=index) + def remove_telnumber(self, **kw): + require_authenticated() + c.user.remove_telephonenumber(kw['oldvalue']) + flash('Your personal contacts were successfully updated!') + redirect('.#Contacts') + + @expose() + @require_post() + @validate(F.add_website_form, error_handler=index) + def add_webpage(self, **kw): + require_authenticated() + c.user.add_webpage(kw['newwebsite']) + flash('Your personal contacts were successfully updated!') + redirect('.#Contacts') + + @expose() + @require_post() + @validate(F.remove_textvalue_form, error_handler=index) + def remove_webpage(self, **kw): + require_authenticated() + c.user.remove_webpage(kw['oldvalue']) + flash('Your personal contacts were successfully updated!') + redirect('.#Contacts') + + @expose() + @require_post() + @validate(F.skype_account_form, error_handler=index) + def skype_account(self, **kw): + require_authenticated() + c.user.set_pref('skypeaccount', kw['skypeaccount']) + flash('Your personal contacts were successfully updated!') + redirect('.#Contacts') + + @expose() + @require_post() + @validate(F.add_timeslot_form, error_handler=index) + def add_timeslot(self, **kw): + require_authenticated() + c.user.add_timeslot(kw['weekday'], kw['starttime'], kw['endtime']) + flash('Your availability timeslots were successfully updated!') + redirect('.#Availability') + + @expose() + @require_post() + @validate(F.remove_timeslot_form, error_handler=index) + def remove_timeslot(self, **kw): + require_authenticated() + c.user.remove_timeslot(kw['weekday'], kw['starttime'], kw['endtime']) + flash('Your availability timeslots were successfully updated!') + redirect('.#Availability') + + @expose() + @require_post() + @validate(F.add_inactive_period_form, error_handler=index) + def add_inactive_period(self, **kw): + require_authenticated() + c.user.add_inactive_period(kw['startdate'], kw['enddate']) + flash('Your inactivity periods were successfully updated!') + redirect('.#Availability') + + @expose() + @require_post() + @validate(F.remove_inactive_period_form, error_handler=index) + def remove_inactive_period(self, **kw): + require_authenticated() + c.user.remove_inactive_period(kw['startdate'], kw['enddate']) + flash('Your availability timeslots were successfully updated!') + redirect('.#Availability') + + @expose() + @require_post() def upload_sshkey(self, key=None): ap = plugin.AuthenticationProvider.get(request) try: http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/01c20a19/Allura/allura/controllers/root.py ---------------------------------------------------------------------- diff --git a/Allura/allura/controllers/root.py b/Allura/allura/controllers/root.py index 4fdf305..19719e1 100644 --- a/Allura/allura/controllers/root.py +++ b/Allura/allura/controllers/root.py @@ -22,6 +22,7 @@ from allura.controllers.error import ErrorController from allura import model as M from allura.lib.widgets import project_list as plw from .auth import AuthController +from .trovecategories import TroveCategoryController from .search import SearchController, ProjectBrowseController from .static import NewForgeController from .site_admin import SiteAdminController @@ -58,6 +59,8 @@ class RootController(WsgiDispatchController): nf.admin = SiteAdminController() search = SearchController() rest = RestController() + if config.get('trovecategories.enableediting', 'false')=='true': + categories=TroveCategoryController() def __init__(self): n_url_prefix = '/%s/' % request.path.split('/')[1] http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/01c20a19/Allura/allura/controllers/trovecategories.py ---------------------------------------------------------------------- diff --git a/Allura/allura/controllers/trovecategories.py b/Allura/allura/controllers/trovecategories.py new file mode 100644 index 0000000..773e506 --- /dev/null +++ b/Allura/allura/controllers/trovecategories.py @@ -0,0 +1,191 @@ +import logging, string, os +from urllib import urlencode + +import bson +from tg import expose, session, flash, redirect, validate, config +from tg.decorators import with_trailing_slash +from pylons import c, g, request, response +from string import digits, lowercase + +from allura.lib.security import require_authenticated +from allura import model as M +from allura.lib.decorators import require_post +from allura.controllers import BaseController +from allura.lib.widgets import forms +from allura.model import TroveCategory + +class F(object): + remove_category_form = forms.RemoveTroveCategoryForm() + add_category_form = forms.AddTroveCategoryForm() + +class TroveCategoryController(BaseController): + @expose() + def _lookup(self, catshortname, *remainder): + cat = M.TroveCategory.query.get(shortname=catshortname) + return TroveCategoryController(category=cat), remainder + + def __init__(self, category=None): + self.category = category + super(TroveCategoryController, self).__init__() + + @expose('jinja:allura:templates/trovecategories.html') + def index(self, **kw): + require_authenticated() + + if self.category: + selected_cat = self.category + l = self.category.subcategories + hierarchy = [] + temp_cat = self.category.parent_category + while temp_cat: + hierarchy = [temp_cat] + hierarchy + temp_cat = temp_cat.parent_category + else: + l = M.TroveCategory.query.find(dict(trove_parent_id=0)) + selected_cat = None + hierarchy = [] + return dict( + categories=l, + selected_cat=selected_cat, + hierarchy=hierarchy) + + @expose() + @require_post() + @validate(F.add_category_form, error_handler=index) + def create(self, **kw): + require_authenticated() + + name = kw.get('categoryname') + upper_id = int(kw.get('uppercategory_id', 0)) + + upper = M.TroveCategory.query.get(trove_cat_id=upper_id) + if upper_id == 0: + path = name + show_as_skill = True + elif upper is None: + flash('Invalid upper category.', "error") + redirect('/categories') + return + else: + path = upper.fullpath + " :: " + name + show_as_skill = upper.show_as_skill + + newid=max([el.trove_cat_id for el in M.TroveCategory.query.find()]) + 1 + shortname=name.replace(" ", "_").lower() + shortname=''.join([(c if (c in digits or c in lowercase) else "_") + for c in shortname]) + + oldcat=M.TroveCategory.query.get(shortname=shortname) + if oldcat: + flash('Category "%s" already exists.' % name, "error") + else: + category = M.TroveCategory( + trove_cat_id=newid, + trove_parent_id=upper_id, + fullname=name, + shortname=shortname, + fullpath=path, + show_as_skill=show_as_skill) + if category: + flash('Category "%s" successfully created.' % name) + else: + flash('An error occured while crearing the category.', "error") + if upper: + redirect('/categories/%s' % upper.shortname) + else: + redirect('/categories') + + @expose() + @require_post() + @validate(F.remove_category_form, error_handler=index) + def remove(self, **kw): + require_authenticated() + + cat = M.TroveCategory.query.get(trove_cat_id=int(kw['categoryid'])) + if cat.trove_parent_id: + parent=M.TroveCategory.query.get(trove_cat_id=cat.trove_parent_id) + redirecturl = '/categories/%s' % parent.shortname + else: + redirecturl = '/categories' + if len(cat.subcategories) > 0: + m = "This category contains at least one sub-category, " + m = m + "therefore it can't be removed." + flash(m, "error") + redirect(redirecturl) + return + + if len(M.User.withskill(cat)) > 0: + m = "This category is used as a skill by at least a user, " + m = m + "therefore it can't be removed." + flash(m, "error") + redirect(redirecturl) + return + + if M.Project.query.get(trove_root_database=cat._id): + m = "This category is used as a database by at least a project, " + m = m + "therefore it can't be removed." + flash(m, "error") + redirect(redirecturl) + return + + if M.Project.query.get(trove_developmentstatus=cat._id): + m = "This category is used as development status by at least a " + m = m + "project, therefore it can't be removed." + flash(m, "error") + redirect(redirecturl) + return + + if M.Project.query.get(trove_audience=cat._id): + m = "This category is used as intended audience by at least a " + m = m + "project, therefore it can't be removed." + flash(m, "error") + redirect(redirecturl) + return + + if M.Project.query.get(trove_license=cat._id): + m = "This category is used as a license by at least a " + m = m + "project, therefore it can't be removed." + flash(m, "error") + redirect(redirecturl) + return + + if M.Project.query.get(trove_os=cat._id): + m = "This category is used as operating system by at least a " + m = m + "project, therefore it can't be removed." + flash(m, "error") + redirect(redirecturl) + return + + if M.Project.query.get(trove_language=cat._id): + m = "This category is used as programming language by at least a " + m = m + "project, therefore it can't be removed." + flash(m, "error") + redirect(redirecturl) + return + + if M.Project.query.get(trove_topic=cat._id): + m = "This category is used as a topic by at least a " + m = m + "project, therefore it can't be removed." + flash(m, "error") + redirect(redirecturl) + return + + if M.Project.query.get(trove_natlanguage=cat._id): + m = "This category is used as a natural language by at least a " + m = m + "project, therefore it can't be removed." + flash(m, "error") + redirect(redirecturl) + return + + if M.Project.query.get(trove_environment=cat._id): + m = "This category is used as an environment by at least a " + m = m + "project, therefore it can't be removed." + flash(m, "error") + redirect(redirecturl) + return + + M.TroveCategory.delete(cat) + + flash('Category removed.') + redirect(redirecturl) + http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/01c20a19/Allura/allura/ext/user_profile/templates/user_index.html ---------------------------------------------------------------------- diff --git a/Allura/allura/ext/user_profile/templates/user_index.html b/Allura/allura/ext/user_profile/templates/user_index.html index 1f6819d..2614953 100644 --- a/Allura/allura/ext/user_profile/templates/user_index.html +++ b/Allura/allura/ext/user_profile/templates/user_index.html @@ -35,6 +35,207 @@ {% endfor %} + +
+
Personal data
+ {% if user.get_pref('sex') == 'Male' or user.get_pref('sex') == 'Female' %} +
+
Gender:
+
{{user.get_pref('sex')}}
+
+ {% endif %} + {% if user.get_pref('birthdate') %} +
+
Birthdate:
+
+ {{ user.get_pref('birthdate').strftime('%d %B %Y')}} +
+
+ {% endif %} + + {% if user.get_pref('localization').country or user.get_pref('localization').city %} +
+
Localization:
+
+ {% if user.get_pref('localization').city %} + {{user.get_pref('localization').city}}{{ ',' if user.get_pref('localization').country else '' }} + {% endif %} + {% if user.get_pref('localization').country %} + {{user.get_pref('localization').country}} + {% endif %} +
+
+ {% endif %} + + {% if user.get_pref('timezone') %} +
+
Timezone:
+
+ {{user.get_pref('timezone')}} +
+
+ {% endif %} + + {% if user.get_pref('socialnetworks')|length > 0 %} +
+
Social networks:
+
+ {{user.get_pref('display_name')}}'s account(s): +
    + {% for i in user.get_pref('socialnetworks') %} +
  • {{i.socialnetwork}}: {{i.accounturl}}
  • + {% endfor %} +
+
+
+ {% endif %} + + {% if user.get_pref('webpages')|length > 0 %} +
+
Websites:
+
+ {{user.get_pref('display_name')}}'s website(s): +
    + {% for i in user.get_pref('webpages') %} +
  • {{i}}
  • + {% endfor %} +
+
+
+ {% endif %} + + {% if user.get_pref('telnumbers')|length > 0 %} +
+
Telephone number(s):
+
+ {{user.get_pref('display_name')}}'s telephone number(s): +
    + {% for i in user.get_pref('telnumbers') %} +
  • {{i}}
  • + {% endfor %} +
+
+
+ {% endif %} + + {% if user.get_pref('skypeaccount') %} +
+
Skype account:
+
{{user.get_pref('skypeaccount')}}
+
+ {% endif %} + + {% if user.get_pref('timezone') and user.get_availability_timeslots() |length > 0 %} +
+
Availability:
+ + {% if c.user.get_pref('timezone') %} + + {% endif %} + +
+ {{user.get_pref('display_name')}}'s availability time-slots. +
+ See timeslots in: + UTC | + + {{user.get_pref('display_name')}}'s local time + + {% if c.user.get_pref('timezone') %} | + + Your local time + + {% endif %} +
+
    + {% for i in user.get_localized_availability('utc') %} +
  • {{i.week_day}}: from {{i.start_time.strftime("%H:%M")}} to {{i.end_time.strftime("%H:%M")}}
  • + {% endfor %} +
+
+ + + +
+
+ {% endif %} + + {% if user.get_inactive_periods(include_past_periods=False)|length > 0 %} +
+
Inactive periods:
+
+ This user won't be able to work on the forge in the following period(s): +
    + {% for p in user.get_inactive_periods(include_past_periods=False) %} +
  • From {{p.start_date.strftime('%d %B %Y')}} to {{p.end_date.strftime('%d %B %Y')}}.
  • + {% endfor %} +
+
+ {% endif %} + + +
+ Current {{user.get_pref('display_name')}}'s skills list +
+ {% if user.get_skills()|length > 0 %} + + + + + + + + + + {% for s in user.get_skills() %} + + + + + + {% endfor %} + +
SkillLevelComments
{{s.skill.fullpath}}{{s.level}}{{s.comment}}
+ {% else %} +
At the moment, {{user.get_pref('display_name')}}'s skills list is empty!
+ {% endif %} +
+
+ {% if c.user.username == user.username %}
Email Addresses @@ -63,3 +264,14 @@
{% endif %} {% endblock %} + +{% block extra_js %} + +{% endblock %} http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/01c20a19/Allura/allura/lib/plugin.py ---------------------------------------------------------------------- diff --git a/Allura/allura/lib/plugin.py b/Allura/allura/lib/plugin.py index 93f4e3a..0c2aca4 100644 --- a/Allura/allura/lib/plugin.py +++ b/Allura/allura/lib/plugin.py @@ -625,6 +625,150 @@ class ThemeProvider(object): from allura.lib.widgets.forms import PasswordChangeForm return PasswordChangeForm(action='/auth/prefs/change_password') + @LazyProperty + def personal_data_form(self): + ''' + :return: None, or an easywidgets Form to render on the user preferences page + ''' + from allura.lib.widgets.forms import PersonalDataForm + return PersonalDataForm() + + @LazyProperty + def add_telnumber_form(self): + ''' + :return: None, or an easywidgets Form to render on the user preferences page to + allow adding a telephone number. + ''' + from allura.lib.widgets.forms import AddTelNumberForm + return AddTelNumberForm() + + @LazyProperty + def add_website_form(self): + ''' + :return: None, or an easywidgets Form to render on the user preferences page to + allow adding a personal website url. + ''' + from allura.lib.widgets.forms import AddWebsiteForm + return AddWebsiteForm() + + @LazyProperty + def skype_account_form(self): + ''' + :return: None, or an easywidgets Form to render on the user preferences page to + allow setting the user's Skype account. + ''' + from allura.lib.widgets.forms import SkypeAccountForm + return SkypeAccountForm() + + @LazyProperty + def remove_textvalue_form(self): + ''' + :return: None, or an easywidgets Form to render on the user preferences page to + allow removing a single text value from a list. + ''' + from allura.lib.widgets.forms import RemoveTextValueForm + return RemoveTextValueForm() + + @LazyProperty + def add_socialnetwork_form(self): + ''' + :return: None, or an easywidgets Form to render on the user preferences page to + allow adding a social network account. + ''' + from allura.lib.widgets.forms import AddSocialNetworkForm + return AddSocialNetworkForm(action='/auth/prefs/add_social_network') + + @LazyProperty + def remove_socialnetwork_form(self): + ''' + :return: None, or an easywidgets Form to render on the user preferences page to + allow removing a social network account. + ''' + from allura.lib.widgets.forms import RemoveSocialNetworkForm + return RemoveSocialNetworkForm(action='/auth/prefs/remove_social_network') + + @LazyProperty + def add_timeslot_form(self): + ''' + :return: None, or an easywidgets Form to render on the user preferences page + to allow creating a new availability timeslot + ''' + from allura.lib.widgets.forms import AddTimeSlotForm + return AddTimeSlotForm() + + @LazyProperty + def remove_timeslot_form(self): + ''' + :return: None, or an easywidgets Form to render on the user preferences page + to remove a timeslot + ''' + from allura.lib.widgets.forms import RemoveTimeSlotForm + return RemoveTimeSlotForm() + + @LazyProperty + def add_inactive_period_form(self): + ''' + :return: None, or an easywidgets Form to render on the user preferences page + to allow creating a new period of inactivity + ''' + from allura.lib.widgets.forms import AddInactivePeriodForm + return AddInactivePeriodForm() + + @LazyProperty + def remove_inactive_period_form(self): + ''' + :return: None, or an easywidgets Form to render on the user preferences page + to allow removing an existing period of inactivity + ''' + from allura.lib.widgets.forms import RemoveInactivePeriodForm + return RemoveInactivePeriodForm() + + @LazyProperty + def add_trove_category(self): + ''' + :return: None, or an easywidgets Form to render on the page to create a + new trove_category + ''' + from allura.lib.widgets.forms import AddTroveCategoryForm + return AddTroveCategoryForm(action='/categories/create') + + @LazyProperty + def remove_trove_category(self): + ''' + :return: None, or an easywidgets Form to render on the page to remove + an existing trove_category + ''' + from allura.lib.widgets.forms import RemoveTroveCategoryForm + return RemoveTroveCategoryForm(action='/categories/remove') + + @LazyProperty + def add_user_skill(self): + ''' + :return: None, or an easywidgets Form to render on the page to add a + new skill to a user profile + ''' + from allura.lib.widgets.forms import AddUserSkillForm + return AddUserSkillForm(action='/auth/prefs/user_skills/save_skill') + + @LazyProperty + def select_subcategory_form(self): + ''' + :return: None, or an easywidgets Form to render on the page to add a + new skill to a user profile, allowing to select a category in + order to see its sub-categories + ''' + from allura.lib.widgets.forms import SelectSubCategoryForm + return SelectSubCategoryForm(action='/auth/prefs/user_skills') + + @LazyProperty + def remove_user_skill(self): + ''' + :return: None, or an easywidgets Form to render on the page to remove + an existing skill from a user profile + ''' + from allura.lib.widgets.forms import RemoveSkillForm + return RemoveSkillForm(action='/auth/prefs/user_skills/remove_skill') + @LazyProperty def upload_key_form(self): ''' http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/01c20a19/Allura/allura/lib/validators.py ---------------------------------------------------------------------- diff --git a/Allura/allura/lib/validators.py b/Allura/allura/lib/validators.py index 5bab308..b7ff459 100644 --- a/Allura/allura/lib/validators.py +++ b/Allura/allura/lib/validators.py @@ -3,6 +3,7 @@ from bson import ObjectId import formencode as fe from formencode import validators as fev from . import helpers as h +from datetime import datetime class Ming(fev.FancyValidator): @@ -61,3 +62,86 @@ class JsonValidator(fev.FancyValidator): except ValueError, e: raise fe.Invalid('Invalid JSON: ' + str(e), value, state) return value + +class DateValidator(fev.FancyValidator): + def _to_python(self, value, state): + value = convertDate(value) + if not value: + raise fe.Invalid( + "Please enter a valid date in the format DD/MM/YYYY.", + value, state) + return value + +class TimeValidator(fev.FancyValidator): + def _to_python(self, value, state): + value = convertTime(value) + if not value: + raise fe.Invalid( + "Please enter a valid time in the format HH:MM.", + value, state) + return value + +class OneOfValidator(fev.FancyValidator): + def __init__(self, validvalues, not_empty = True): + self.validvalues = validvalues + self.not_empty = not_empty + super(OneOfValidator, self).__init__() + + def _to_python(self, value, state): + if not value.strip(): + if self.not_empty: + raise fe.Invalid("This field can't be empty.", value, state) + else: + return None + if not value in self.validvalues: + allowed = '' + for v in self.validvalues: + if allowed != '': + allowed = allowed + ', ' + allowed = allowed + '"%s"' % v + raise fe.Invalid( + "Invalid value. The allowed values are %s." %allowed, + value, state) + return value + +class MapValidator(fev.FancyValidator): + def __init__(self, mapvalues, not_empty = True): + self.map = mapvalues + self.not_empty = not_empty + super(MapValidator, self).__init__() + + def _to_python(self, value, state): + if not value.strip(): + if self.not_empty: + raise fe.Invalid("This field can't be empty.", value, state) + else: + return None + conv_value = self.map.get(value) + if not conv_value: + raise fe.Invalid( + "Invalid value. Please, choose one of the valid values.", + value, state) + return conv_value + +def convertDate(datestring): + formats = ['%Y-%m-%d', '%Y.%m.%d', '%Y/%m/%d', '%Y\%m\%d', '%Y %m %d', + '%d-%m-%Y', '%d.%m.%Y', '%d/%m/%Y', '%d\%m\%Y', '%d %m %Y'] + + for f in formats: + try: + date = datetime.strptime(datestring, f) + return date + except: + pass + return None + +def convertTime(timestring): + formats = ['%H:%M', '%H.%M', '%H %M', '%H,%M'] + + for f in formats: + try: + time = datetime.strptime(timestring, f) + return {'h':time.hour, 'm':time.minute} + except: + pass + return None http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/01c20a19/Allura/allura/lib/widgets/forms.py ---------------------------------------------------------------------- diff --git a/Allura/allura/lib/widgets/forms.py b/Allura/allura/lib/widgets/forms.py index 35281d1..5fe3923 100644 --- a/Allura/allura/lib/widgets/forms.py +++ b/Allura/allura/lib/widgets/forms.py @@ -6,6 +6,7 @@ from allura.lib import helpers as h from allura.lib import plugin from allura.lib.widgets import form_fields as ffw from allura import model as M +from datetime import datetime from formencode import validators as fev import formencode @@ -13,8 +14,20 @@ import formencode import ew as ew_core import ew.jinja2_ew as ew +from pytz import common_timezones, country_timezones, country_names + log = logging.getLogger(__name__) +socialnetworks=['Facebook','Linkedin','Twitter','Google+'] +weekdays=['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'] + +class _HTMLExplanation(ew.InputField): + template=ew.Snippet( + ''' +
{{widget.text}}
+ ''', + 'jinja2') + class NeighborhoodProjectTakenValidator(fev.FancyValidator): def _to_python(self, value, state): @@ -88,6 +101,517 @@ class PasswordChangeForm(ForgeForm): raise formencode.Invalid('Passwords must match', value, state) return d +class PersonalDataForm(ForgeForm): + class fields(ew_core.NameList): + sex = ew.SingleSelectField( + label='Gender', + options=[ew.Option(py_value=v,label=v,selected=False) + for v in ['Male', 'Female', 'Unknown', 'Other']], + validator=formencode.All( + V.OneOfValidator(['Male', 'Female', 'Unknown', 'Other']), + fev.UnicodeString(not_empty=True))) + birthdate = ew.TextField( + label='Birth date', + validator=V.DateValidator(), + attrs=dict(value=None)) + exp = _HTMLExplanation( + text="Use the format DD/MM/YYYY", + show_errors=False) + country = ew.SingleSelectField( + label='Country of residence', + validator=V.MapValidator(country_names, not_empty=False), + options = [ + ew.Option( + py_value=" ", label=" -- Unknown -- ", selected=False)] +\ + [ew.Option(py_value=c, label=n, selected=False) + for c,n in sorted(country_names.items(), + key=lambda (k,v):v)], + attrs={'onchange':'selectTimezone(this.value)'}) + city = ew.TextField( + label='City of residence', + attrs=dict(value=None), + validator=fev.UnicodeString(not_empty=False)) + timezone=ew.SingleSelectField( + label='Timezone', + attrs={'id':'tz'}, + validator=V.OneOfValidator(common_timezones, not_empty=False), + options=[ + ew.Option( + py_value=" ", + label=" -- Unknown -- ")] + \ + [ew.Option(py_value=n, label=n) + for n in sorted(common_timezones)]) + + def display(self, **kw): + user = kw.get('user') + + for opt in self.fields['sex'].options: + if opt.label == user.sex: + opt.selected = True + else: + opt.selected = False + + if user.get_pref('birthdate'): + self.fields['birthdate'].attrs['value'] = \ + user.get_pref('birthdate').strftime('%d/%m/%Y') + else: + self.fields['birthdate'].attrs['value'] = '' + + for opt in self.fields['country'].options: + if opt.label == user.localization.country: + opt.selected = True + elif opt.py_value == " " and user.localization.country is None: + opt.selected = True + else: + opt.selected = False + + if user.localization.city: + self.fields['city'].attrs['value'] = user.localization.city + else: + self.fields['city'].attrs['value'] = '' + + for opt in self.fields['timezone'].options: + if opt.label == user.timezone: + opt.selected = True + elif opt.py_value == " " and user.timezone is None: + opt.selected = True + else: + opt.selected = False + + return super(ForgeForm, self).display(**kw) + + def resources(self): + def _append(x, y): + return x + y + + yield ew.JSScript(''' +var $allTimezones = $("#tz").clone(); +var $t = {}; +''' + \ + reduce(_append, [ + '$t["'+ el +'"] = ' + str([name.encode('utf-8') + for name in country_timezones[el]]) + ";\n" + for el in country_timezones]) + ''' +function selectTimezone($country){ + if($country == " "){ + $("#tz").replaceWith($allTimezones); + } + else{ + $("#tz option:gt(0)").remove(); + $.each($t[$country], function(index, value){ + $("#tz").append($("").attr("value", value).text(value)) + }) + } +}''') + +class AddTelNumberForm(ForgeForm): + defaults=dict(ForgeForm.defaults) + + class fields(ew_core.NameList): + newnumber = ew.TextField( + label='New telephone number', + attrs={'value':''}, + validator=fev.UnicodeString(not_empty=True)) + + def display(self, **kw): + initial_value = kw.get('initial_value','') + self.fields['newnumber'].attrs['value'] = initial_value + return super(ForgeForm, self).display(**kw) + +class AddWebsiteForm(ForgeForm): + defaults=dict(ForgeForm.defaults) + + class fields(ew_core.NameList): + newwebsite = ew.TextField( + label='New website url', + attrs={'value':''}, + validator=fev.UnicodeString(not_empty=True)) + + def display(self, **kw): + initial_value = kw.get('initial_value','') + self.fields['newwebsite'].attrs['value'] = initial_value + return super(ForgeForm, self).display(**kw) + +class SkypeAccountForm(ForgeForm): + defaults=dict(ForgeForm.defaults) + + class fields(ew_core.NameList): + skypeaccount = ew.TextField( + label='Skype account', + attrs={'value':''}, + validator=fev.UnicodeString(not_empty=False)) + + def display(self, **kw): + initial_value = kw.get('initial_value','') + self.fields['skypeaccount'].attrs['value'] = initial_value + return super(ForgeForm, self).display(**kw) + +class RemoveTextValueForm(ForgeForm): + defaults=dict(ForgeForm.defaults, submit_text=None, show_errors=False) + + def display(self, **kw): + initial_value = kw.get('value','') + label = kw.get('label','') + description = kw.get('description') + + self.fields = [ + ew.RowField( + show_errors=False, + hidden_fields=[ + ew.HiddenField( + name="oldvalue", + attrs={'value':initial_value}, + show_errors=False) + ], + fields=[ + ew.HTMLField( + text=label, + show_errors=False), + ew.HTMLField( + show_label=False, + text=initial_value), + ew.SubmitButton( + show_label=False, + attrs={'value':'Remove'}, + show_errors=False)])] + if description: + self.fields.append( + _HTMLExplanation( + text=description, + show_errors=False)) + return super(ForgeForm, self).display(**kw) + + @ew_core.core.validator + def to_python(self, kw, state): + d = super(RemoveTextValueForm, self).to_python(kw, state) + d["oldvalue"] = kw.get('oldvalue', '') + return d + +class AddSocialNetworkForm(ForgeForm): + defaults=dict(ForgeForm.defaults) + + class fields(ew_core.NameList): + socialnetwork = ew.SingleSelectField( + label='Social network', + options=[ew.Option(py_value=name, label=name) + for name in socialnetworks], + validator=formencode.All( + V.OneOfValidator(socialnetworks), + fev.UnicodeString(not_empty=True))) + accounturl = ew.TextField( + label='Account url', + validator=fev.UnicodeString(not_empty=True)) + +class RemoveSocialNetworkForm(ForgeForm): + defaults=dict(ForgeForm.defaults, submit_text=None, show_errors=False) + + def display(self, **kw): + account = kw.get('account','') + socialnetwork = kw.get('socialnetwork','') + + self.fields = [ + ew.RowField( + show_errors=False, + hidden_fields=[ + ew.HiddenField( + name="account", + attrs={'value':account}, + show_errors=False), + ew.HiddenField( + name="socialnetwork", + attrs={'value':socialnetwork}, + show_errors=False)], + fields=[ + ew.HTMLField( + text='%s account' % socialnetwork, + show_errors=False), + ew.HTMLField( + show_label=False, + text=account), + ew.SubmitButton( + show_label=False, + attrs={'value':'Remove'}, + show_errors=False)])] + return super(ForgeForm, self).display(**kw) + + @ew_core.core.validator + def to_python(self, kw, state): + d = super(RemoveSocialNetworkForm, self).to_python(kw, state) + d["account"] = kw.get('account', '') + d["socialnetwork"] = kw.get('socialnetwork', '') + return d + +class AddInactivePeriodForm(ForgeForm): + class fields(ew_core.NameList): + startdate = ew.TextField( + label='Start date', + validator=formencode.All( + V.DateValidator(), + fev.UnicodeString(not_empty=True))) + enddate = ew.TextField( + label='End date', + validator=formencode.All( + V.DateValidator(), + fev.UnicodeString(not_empty=True))) + + @ew_core.core.validator + def to_python(self, kw, state): + d = super(AddInactivePeriodForm, self).to_python(kw, state) + if d['startdate'] > d['enddate']: + raise formencode.Invalid( + 'Invalid period: start date greater than end date.', + kw, state) + return d + +class RemoveInactivePeriodForm(ForgeForm): + defaults=dict(ForgeForm.defaults, submit_text=None, show_errors=False) + + def display(self, **kw): + startdate = kw.get('startdate') + enddate = kw.get('enddate') + + self.fields = [ + ew.RowField( + show_label=False, + show_errors=False, + fields=[ + ew.HTMLField(text=startdate.strftime('%d/%m/%Y'), + show_errors=False), + ew.HTMLField(text=enddate.strftime('%d/%m/%Y'), + show_errors=False), + ew.SubmitButton( + attrs={'value':'Remove'}, + show_errors=False)], + hidden_fields=[ + ew.HiddenField( + name='startdate', + attrs={'value':startdate.strftime('%d/%m/%Y')}, + show_errors=False), + ew.HiddenField( + name='enddate', + attrs={'value':enddate.strftime('%d/%m/%Y')}, + show_errors=False)])] + return super(ForgeForm, self).display(**kw) + + @ew_core.core.validator + def to_python(self, kw, state): + d = super(RemoveInactivePeriodForm, self).to_python(kw, state) + d['startdate'] = V.convertDate(kw.get('startdate','')) + d['enddate'] = V.convertDate(kw.get('enddate','')) + return d + +class AddTimeSlotForm(ForgeForm): + class fields(ew_core.NameList): + weekday = ew.SingleSelectField( + label='Weekday', + options=[ew.Option(py_value=wd, label=wd) + for wd in weekdays], + validator=formencode.All( + V.OneOfValidator(weekdays), + fev.UnicodeString(not_empty=True))) + starttime = ew.TextField( + label='Start time', + validator=formencode.All( + V.TimeValidator(), + fev.UnicodeString(not_empty=True))) + endtime = ew.TextField( + label='End time', + validator=formencode.All( + V.TimeValidator(), + fev.UnicodeString(not_empty=True))) + + @ew_core.core.validator + def to_python(self, kw, state): + d = super(AddTimeSlotForm, self).to_python(kw, state) + if (d['starttime']['h'], d['starttime']['m']) > \ + (d['endtime']['h'], d['endtime']['m']): + raise formencode.Invalid( + 'Invalid period: start time greater than end time.', + kw, state) + return d + +class RemoveTimeSlotForm(ForgeForm): + defaults=dict(ForgeForm.defaults, submit_text=None, show_errors=False) + + def display(self, **kw): + weekday = kw.get('weekday','') + starttime = kw.get('starttime') + endtime = kw.get('endtime') + + self.fields = [ + ew.RowField( + show_errors=False, + show_label=False, + fields=[ + ew.HTMLField(text=weekday), + ew.HTMLField(text=starttime.strftime('%H:%M')), + ew.HTMLField(text=endtime.strftime('%H:%M')), + ew.SubmitButton( + show_errors=False, + attrs={'value':'Remove'})], + hidden_fields=[ + ew.HiddenField( + name='weekday', + attrs={'value':weekday}), + ew.HiddenField( + name='starttime', + attrs={'value':starttime.strftime('%H:%M')}), + ew.HiddenField( + name='endtime', + attrs={'value':endtime.strftime('%H:%M')})])] + return super(ForgeForm, self).display(**kw) + + @ew_core.core.validator + def to_python(self, kw, state): + d = super(RemoveTimeSlotForm, self).to_python(kw, state) + d["weekday"] = kw.get('weekday', None) + d['starttime'] = V.convertTime(kw.get('starttime','')) + d['endtime'] = V.convertTime(kw.get('endtime','')) + return d + +class RemoveTroveCategoryForm(ForgeForm): + defaults=dict(ForgeForm.defaults, submit_text=None, show_errors=False) + + def display(self, **kw): + cat = kw.get('category') + + self.fields = [ + ew.RowField( + show_errors=False, + show_label=False, + fields=[ + ew.LinkField( + text=cat.fullname, + href="/categories/%s" % cat.shortname), + ew.SubmitButton( + show_errors=False, + attrs={'value':'Remove'})], + hidden_fields=[ + ew.HiddenField( + name='categoryid', + attrs={'value':cat.trove_cat_id})])] + return super(ForgeForm, self).display(**kw) + + @ew_core.core.validator + def to_python(self, kw, state): + d = super(RemoveTroveCategoryForm, self).to_python(kw, state) + d["categoryid"] = kw.get('categoryid') + if d["categoryid"]: + d["categoryid"] = int(d['categoryid']) + return d + +class AddTroveCategoryForm(ForgeForm): + defaults=dict(ForgeForm.defaults) + + class fields(ew_core.NameList): + uppercategory_id = ew.HiddenField( + attrs={'value':''}, + show_errors=False) + categoryname = ew.TextField( + label="Category name", + validator=fev.UnicodeString(not_empty=True)) + + def display(self, **kw): + upper_category = kw.get('uppercategory_id',0) + + self.fields['uppercategory_id'].attrs['value'] = upper_category + return super(ForgeForm, self).display(**kw) + + @ew_core.core.validator + def to_python(self, kw, state): + d = super(AddTroveCategoryForm, self).to_python(kw, state) + d["uppercategory_id"] = kw.get('uppercategory_id', 0) + return d + +class AddUserSkillForm(ForgeForm): + defaults=dict(ForgeForm.defaults) + + class fields(ew_core.NameList): + selected_skill=ew.HiddenField( + attrs={'value':''}, + show_errors=False, + validator=fev.UnicodeString(not_empty=True)) + level=ew.SingleSelectField( + label="Level of knowledge", + options=[ + ew.Option(py_value="low",label="Low level"), + ew.Option(py_value="medium",label="Medium level"), + ew.Option(py_value="high",label="Advanced level")], + validator=formencode.All( + V.OneOfValidator(['low','medium','high']), + fev.UnicodeString(not_empty=True))) + comment=ew.TextArea( + label="Additional comments", + validator=fev.UnicodeString(not_empty=False), + attrs={'rows':5,'cols':30}) + + def display(self, **kw): + category = kw.get('selected_skill') + + self.fields["selected_skill"].attrs['value']=category + return super(ForgeForm, self).display(**kw) + +class SelectSubCategoryForm(ForgeForm): + defaults=dict(ForgeForm.defaults, submit_text="Confirm") + + class fields(ew_core.NameList): + selected_category=ew.SingleSelectField( + name="selected_category", + label="Available categories", + options=[]) + + def display(self, **kw): + categories = kw.get('categories') + + self.fields['selected_category'].options= \ + [ew.Option(py_value=el.trove_cat_id,label=el.fullname) + for el in categories] + self.fields['selected_category'].validator= \ + validator=formencode.All( + V.OneOfValidator(categories), + fev.UnicodeString(not_empty=True)) + return super(ForgeForm, self).display(**kw) + +class RemoveSkillForm(ForgeForm): + defaults=dict(ForgeForm.defaults, submit_text=None, show_errors=False) + + def display(self, **kw): + skill = kw.get('skill') + comment = skill['comment'] + if not comment: + comment = " " + + self.fields = [ + ew.RowField( + show_errors=False, + hidden_fields=[ + ew.HiddenField( + name="categoryid", + attrs={'value':skill['skill'].trove_cat_id}, + show_errors=False) + ], + fields=[ + ew.HTMLField( + text=skill['skill'].fullpath, + show_errors=False), + ew.HTMLField( + text=skill['level'], + show_errors=False), + ew.HTMLField( + text=comment, + show_errors=False), + ew.SubmitButton( + show_label=False, + attrs={'value':'Remove'}, + show_errors=False)])] + return super(ForgeForm, self).display(**kw) + + @ew_core.core.validator + def to_python(self, kw, state): + d = super(RemoveSkillForm, self).to_python(kw, state) + d["categoryid"] = kw.get('categoryid', None) + return d + class UploadKeyForm(ForgeForm): class fields(ew_core.NameList): key = ew.TextArea(label='SSH Public Key') http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/01c20a19/Allura/allura/model/auth.py ---------------------------------------------------------------------- diff --git a/Allura/allura/model/auth.py b/Allura/allura/model/auth.py index 349e05f..c1ff1f4 100644 --- a/Allura/allura/model/auth.py +++ b/Allura/allura/model/auth.py @@ -5,11 +5,13 @@ import logging import urllib import hmac import hashlib +import pytz from urlparse import urlparse from email import header -from datetime import timedelta, datetime from hashlib import sha256 import uuid +from pytz import timezone +from datetime import timedelta, date, datetime, time import iso8601 import pymongo @@ -299,6 +301,35 @@ class User(MappedClass, ActivityNode, ActivityObject): email_address=str, email_format=str)) + #Personal data + sex=FieldProperty( + S.OneOf('Male', 'Female', 'Other', 'Unknown', + if_missing='Unknown')) + birthdate=FieldProperty(S.DateTime, if_missing=None) + + #Availability information + availability=FieldProperty([dict( + week_day=str, + start_time=dict(h=int, m=int), + end_time=dict(h=int, m=int))]) + localization=FieldProperty(dict(city=str,country=str)) + timezone=FieldProperty(str) + inactiveperiod=FieldProperty([dict( + start_date=S.DateTime, + end_date=S.DateTime)]) + + #Additional contacts + socialnetworks=FieldProperty([dict(socialnetwork=str,accounturl=str)]) + telnumbers=FieldProperty([str]) + skypeaccount=FieldProperty(str) + webpages=FieldProperty([str]) + + #Skills list + skills = FieldProperty([dict( + category_id = S.ObjectId, + level = S.OneOf('low', 'high', 'medium'), + comment=str)]) + @property def activity_name(self): return self.display_name or self.username @@ -309,6 +340,139 @@ class User(MappedClass, ActivityNode, ActivityObject): def set_pref(self, pref_name, pref_value): return plugin.UserPreferencesProvider.get().set_pref(self, pref_name, pref_value) + def add_socialnetwork(self, socialnetwork, accounturl): + self.socialnetworks.append(dict( + socialnetwork=socialnetwork, + accounturl=accounturl)) + + def remove_socialnetwork(self, socialnetwork, oldurl): + for el in self.socialnetworks: + if el.socialnetwork==socialnetwork and el.accounturl==oldurl: + del self.socialnetworks[self.socialnetworks.index(el)] + return + + def add_telephonenumber(self, telnumber): + self.telnumbers.append(telnumber) + + def remove_telephonenumber(self, oldvalue): + for el in self.telnumbers: + if el==oldvalue: + del self.telnumbers[self.telnumbers.index(el)] + return + + def add_webpage(self, webpage): + self.webpages.append(webpage) + + def remove_webpage(self, oldvalue): + for el in self.webpages: + if el==oldvalue: + del self.webpages[self.webpages.index(el)] + return + + def add_timeslot(self, weekday, starttime, endtime): + self.availability.append( + dict(week_day=weekday, + start_time=starttime, + end_time=endtime)) + + def remove_timeslot(self, weekday, starttime, endtime): + oldel = dict(week_day=weekday, start_time=starttime, end_time=endtime) + for el in self.availability: + if el == oldel: + del self.availability[self.availability.index(el)] + return + + def add_inactive_period(self, startdate, enddate): + self.inactiveperiod.append( + dict(start_date=startdate, + end_date=enddate)) + + def remove_inactive_period(self, startdate, enddate): + oldel = dict(start_date=startdate, end_date=enddate) + for el in self.inactiveperiod: + if el == oldel: + del self.inactiveperiod[self.inactiveperiod.index(el)] + return + + def get_localized_availability(self, tz_name): + week_day = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', + 'Friday', 'Saturday', 'Sunday'] + avail = self.get_availability_timeslots() + usertimezone = timezone(self.get_pref('timezone')) + chosentimezone = timezone(tz_name) + retlist = [] + for t in avail: + today = datetime.today() + start = datetime( + today.year, today.month, today.day, + t['start_time'].hour, t['start_time'].minute, 0) + end = datetime( + today.year, today.month, today.day, + t['end_time'].hour, t['end_time'].minute, 0) + + loctime1 = usertimezone.localize(start) + loctime2 = usertimezone.localize(end) + convtime1 = loctime1.astimezone(chosentimezone) + convtime2 = loctime2.astimezone(chosentimezone) + + dif_days_start = convtime1.weekday() - today.weekday() + dif_days_end = convtime2.weekday() - today.weekday() + index = (week_day.index(t['week_day'])+dif_days_start) % 7 + week_day_start = week_day[index] + week_day_end = week_day[index] + + if week_day_start == week_day_end: + retlist.append(dict( + week_day = week_day_start, + start_time = convtime1.time(), + end_time = convtime2.time())) + else: + retlist.append(dict( + week_day = week_day_start, + start_time = convtime1.time(), + end_time = time(23, 59))) + retlist.append(dict( + week_day = week_day_end, + start_time = time(0, 0), + end_time = convtime2.time())) + + return sorted( + retlist, + key=lambda k:(week_day.index(k['week_day']), k['start_time'])) + + def get_skills(self): + from allura.model.project import TroveCategory + retval = [] + for el in self.skills: + d = dict( + skill=TroveCategory.query.get(_id=el["category_id"]), + level=el.level, + comment=el.comment) + retval.append(d) + return retval + + def get_availability_timeslots(self): + retval = [] + for el in self.availability: + start, end = (el.get('start_time'), el.get('end_time')) + (starth, startm) = (start.get('h'), start.get('m')) + (endh, endm) = (end.get('h'), end.get('m')) + newdict = dict( + week_day = el.get('week_day'), + start_time= time(starth,startm,0), + end_time = time(endh,endm,0)) + retval.append(newdict) + return retval + + def get_inactive_periods(self, include_past_periods=False): + retval = [] + for el in self.inactiveperiod: + d1, d2 = (el.get('start_date'), el.get('end_date')) + newdict = dict(start_date = d1, end_date = d2) + if include_past_periods or newdict['end_date'] > datetime.today(): + retval.append(newdict) + return retval + def url(self): return plugin.AuthenticationProvider.get(request).project_url(self) @@ -464,6 +628,10 @@ class User(MappedClass, ActivityNode, ActivityObject): def update_notifications(self): return plugin.AuthenticationProvider.get(request).update_notifications(self) + @classmethod + def withskill(cls, skill): + return cls.query.find({"skills.category_id" : skill._id}) + class OldProjectRole(MappedClass): class __mongometa__: session = project_orm_session http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/01c20a19/Allura/allura/model/project.py ---------------------------------------------------------------------- diff --git a/Allura/allura/model/project.py b/Allura/allura/model/project.py index b5c1567..d4e53ac 100644 --- a/Allura/allura/model/project.py +++ b/Allura/allura/model/project.py @@ -69,6 +69,7 @@ class TroveCategory(MappedClass): fullname = FieldProperty(str, if_missing='') fullpath = FieldProperty(str, if_missing='') parent_only = FieldProperty(bool, if_missing=False) + show_as_skill = FieldProperty(bool, if_missing=True) @property def parent_category(self): http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/01c20a19/Allura/allura/templates/trovecategories.html ---------------------------------------------------------------------- diff --git a/Allura/allura/templates/trovecategories.html b/Allura/allura/templates/trovecategories.html new file mode 100644 index 0000000..b97f20b --- /dev/null +++ b/Allura/allura/templates/trovecategories.html @@ -0,0 +1,60 @@ +{% set hide_left_bar = True %} +{% extends g.theme.master %} + +{% block title %}Trove categories{% endblock %} + +{% block header %}Managing trove categories{% endblock %} + +{% block content %} +
+ {% if selected_cat %} +
+ Top-level categories + {% for cat in hierarchy %} + > {{cat.fullname}} + {% endfor %} + > {{selected_cat.fullname}} +
+

+ Sub-categories of {{selected_cat.fullname}} +

+ {% else %} +

+ List of all top-level categories +

+ {% endif %} + + {% if categories|length > 0 %} + + + + + + + + + {% for cat in categories %} + {{g.theme.remove_trove_category.display(category=cat)}} + {% endfor %} + +
NameActions
+ {% else %} +
+ There are no categories in this list. +
+ {% endif %} +
+ +
+

Create a new item in this category

+ {% if selected_cat %} + {{g.theme.add_trove_category.display(uppercategory_id=selected_cat.trove_cat_id)}} + {% else %} + {{g.theme.add_trove_category.display(uppercategory_id=0)}} + {% endif %} +
+ Are you done creating new categories? Click here to configure your skills! +
+ +
+{% endblock %} http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/01c20a19/Allura/allura/templates/user_preferences.html ---------------------------------------------------------------------- diff --git a/Allura/allura/templates/user_preferences.html b/Allura/allura/templates/user_preferences.html index 5d7ab23..be4dfe6 100644 --- a/Allura/allura/templates/user_preferences.html +++ b/Allura/allura/templates/user_preferences.html @@ -17,6 +17,118 @@ {%- endfor %} +
+

Personal Settings

+ {{g.theme.personal_data_form.display(action="/auth/prefs/change_personal_data", user=c.user)}} +
+ +
+ +

Personal Contacts

+

Skype account

+ + {{g.theme.skype_account_form.display(action="/auth/prefs/skype_account", + initial_value=c.user.get_pref('skypeaccount'))}} + + {%if c.user.get_pref('socialnetworks') or c.user.get_pref('telnumbers') or c.user.get_pref('webpages') %} +

Other existing contacts

+ + + + + + + + + {% for sn in c.user.get_pref('socialnetworks') %} + {{g.theme.remove_socialnetwork_form.display(account=sn.accounturl, socialnetwork=sn.socialnetwork)}} + {% endfor %} + + {% for tn in c.user.get_pref('telnumbers') %} + {{g.theme.remove_textvalue_form.display(action="/auth/prefs/remove_telnumber", value=tn, label="Telephone number")}} + {%endfor%} + + {% for ws in c.user.get_pref('webpages') %} + {{g.theme.remove_textvalue_form.display(action="/auth/prefs/remove_webpage", value=ws, label="Website url")}} + {%endfor%} +
TypeContactActions
+ {% endif %} + +

Add a social network account

+ {{g.theme.add_socialnetwork_form.display(action="/auth/prefs/add_social_network")}} +

Add a telephone number

+ {{g.theme.add_telnumber_form.display(action="/auth/prefs/add_telnumber")}} +

Add a personal website

+ {{g.theme.add_website_form.display(action="/auth/prefs/add_webpage")}} +
+ + +
+

Availability

+
+ If you want, you can set the weekly timeslot during which you are usually available to support other users of the forge. + Please, set your time intervals choosing a weekday and entering the time interval according to the timezone specified in your + personal data, using the format HH:MM. If you didn't set any timezone, your timeslots could be meaningless to other users, + therefore they will be ignored. +
+
+ You can also specify periods of time during which you won't be able to work on the forge, in orther to communicate other users + that they can't contact you during those days. Please, do it specifying date intervals in format DD/MM/YYYY. +
+
+
+ {%if c.user.get_availability_timeslots() %} +

Existing availability timeslots

+ + + + + + + + + + {% for ts in c.user.get_availability_timeslots() %} + {{g.theme.remove_timeslot_form.display( + action="/auth/prefs/remove_timeslot", + weekday=ts.week_day, + starttime=ts.start_time, + endtime=ts.end_time)}} + {%endfor%} +
WeekdayStart timeEnd timeActions
+ {% endif %} +

Add a new availability timeslot

+ {{g.theme.add_timeslot_form.display(action="/auth/prefs/add_timeslot")}} +
+ +
+ {%if c.user.get_inactive_periods() %} +

Existing periods of inactivity on the forge

+ + + + + + + + + {% for ip in c.user.get_inactive_periods() %} + {{g.theme.remove_inactive_period_form.display( + action="/auth/prefs/remove_inactive_period", + startdate=ip.start_date, + enddate=ip.end_date)}} + {%endfor%} +
Start dateEnd dateActions
+ {% endif %} +

Add a new period of inactivity on the forge

+ {{g.theme.add_inactive_period_form.display(action="/auth/prefs/add_inactive_period")}} +
+ + + {% if g.theme.password_change_form %}

Change Password

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/01c20a19/Allura/allura/templates/user_skills.html ---------------------------------------------------------------------- diff --git a/Allura/allura/templates/user_skills.html b/Allura/allura/templates/user_skills.html new file mode 100644 index 0000000..667ff70 --- /dev/null +++ b/Allura/allura/templates/user_skills.html @@ -0,0 +1,86 @@ +{% set hide_left_bar = True %} +{% extends g.theme.master %} + +{% block title %}{{c.user.username}} / Skills{% endblock %} + +{% block header %}Skills manager for {{c.user.username}} {% endblock %} + +{% block content %} +
+ {% if c.user.get_skills()|length > 0 %} +

Your current skills list:

+ + + + + + + + + + + {% for s in c.user.get_skills() %} + {{g.theme.remove_user_skill.display(skill=s)}} + {% endfor %} + +
SkillLevelCommentsActions
+ {% else %} +

At the moment, your skills list is empty!

+
+ You can set your skills so that other users will be able to know what you can do best. + To do it, you just need to choose the options that best fit your skills in the section below. You + can also specify your skill level and some additional free comments. +
+ {% endif %} +
+ +
+

Add a new skill

+ + {% if selected_skill %} +
+
+ You selected: +
+
+ List of all skills + {% for cat in parents %} + > {{cat.fullname}} + {% endfor %} + > {{selected_skill.fullname}} + +
+
+ {% endif %} + + {% if skills_list %} + {% if selected_skill %} +

Select a subcategory of "{{selected_skill.fullname}}"

+ {% else %} +

Select a category

+ {%endif%} + {{g.theme.select_subcategory_form.display(categories=skills_list)}} + {% endif %} + {% if selected_skill %} +

Add "{{selected_skill.fullname}}" to you set of skills

+ {{g.theme.add_user_skill.display(selected_skill=selected_skill.trove_cat_id, + action="/auth/prefs/user_skills/" + selected_skill.shortname + "/save_skill")}} + {% endif %} +

Other possible actions

+
+ +
+
+{% endblock %} http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/01c20a19/Allura/allura/templates/widgets/forge_form.html ---------------------------------------------------------------------- diff --git a/Allura/allura/templates/widgets/forge_form.html b/Allura/allura/templates/widgets/forge_form.html index a997522..6be3d86 100644 --- a/Allura/allura/templates/widgets/forge_form.html +++ b/Allura/allura/templates/widgets/forge_form.html @@ -24,12 +24,14 @@ {{field.display(**ctx)}} {% endif %} {% endfor %} - -
- {% for b in buttons %} - {{b.display()}} - {% endfor %} -
+ {% if buttons %} + +
+ {% for b in buttons %} + {{b.display()}} + {% endfor %} +
+ {% endif %} {% if widget.antispam %}{% for fld in g.antispam.extra_fields() %} {{fld}}{% endfor %}{% endif %}