diff --git a/README.txt b/README.txt index 4068bfd..4781468 100755 --- a/README.txt +++ b/README.txt @@ -913,6 +913,12 @@ relates ISO country codes with the full name of the country. >>> sorted(adapted(concepts['countries']).data.items()) [('at', ['Austria']), ('de', ['Germany'])] + >>> countries.dataAsRecords() + [{'value': 'Austria', 'key': 'at'}, {'value': 'Germany', 'key': 'de'}] + + >>> countries.getRowsByValue('value', 'Germany') + [{'value': 'Germany', 'key': 'de'}] + Caching ======= diff --git a/browser/action.py b/browser/action.py index 4ac0b03..b51e40a 100644 --- a/browser/action.py +++ b/browser/action.py @@ -92,6 +92,8 @@ class DialogAction(Action): urlParams['fixed_type'] = 'yes' if self.viewTitle: urlParams['view_title'] = self.viewTitle + #for k, v in self.page.sortInfo.items(): + # urlParams['sortinfo_' + k] = v['fparam'] urlParams.update(self.addParams) if self.target is not None: url = self.page.getUrlForTarget(self.target) diff --git a/browser/common.py b/browser/common.py index 9329f21..a6bd733 100755 --- a/browser/common.py +++ b/browser/common.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2013 Helmut Merz helmutm@cy55.de +# Copyright (c) 2015 Helmut Merz helmutm@cy55.de # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -22,7 +22,7 @@ Common base class for loops browser view classes. from cgi import parse_qs, parse_qsl #import mimetypes # use more specific assignments from cybertools.text -from datetime import datetime +from datetime import date, datetime import re from time import strptime from urllib import urlencode @@ -61,17 +61,21 @@ from cybertools.stateful.interfaces import IStateful from cybertools.text import mimetypes from cybertools.typology.interfaces import IType, ITypeManager from cybertools.util.date import toLocalTime +from cybertools.util.format import formatDate from cybertools.util.jeep import Jeep from loops.browser.util import normalizeForUrl from loops.common import adapted, baseObject from loops.config.base import DummyOptions from loops.i18n.browser import I18NView from loops.interfaces import IResource, IView, INode, ITypeConcept +from loops.organize.personal import favorite +from loops.organize.party import getPersonForUser from loops.organize.tracking import access from loops.organize.util import getRolesForPrincipal from loops.resource import Resource from loops.security.common import checkPermission from loops.security.common import canAccessObject, canListObject, canWriteObject +from loops.security.common import canEditRestricted from loops.type import ITypeConcept, LoopsTypeInfo from loops import util from loops.util import _, saveRequest @@ -132,7 +136,58 @@ class EditForm(form.EditForm): return parentUrl + '/contents.html' -class BaseView(GenericView, I18NView): +class SortableMixin(object): + + @Lazy + def sortInfo(self): + result = {} + for k, v in self.request.form.items(): + if k.startswith('sortinfo_'): + tableName = k[len('sortinfo_'):] + if ',' in v: + fn, dir = v.split(',') + else: + fn = v + dir = 'asc' + result[tableName] = dict( + colName=fn, ascending=(dir=='asc'), fparam=v) + result = favorite.updateSortInfo(getPersonForUser( + self.context, self.request), self.target, result) + return result + + def isSortableColumn(self, tableName, colName): + return False # overwrite in subclass + + def getSortUrl(self, tableName, colName): + url = str(self.request.URL) + paramChar = '?' in url and '&' or '?' + si = self.sortInfo.get(tableName) + if si is not None and si.get('colName') == colName: + dir = si['ascending'] and 'desc' or 'asc' + else: + dir = 'asc' + return '%s%ssortinfo_%s=%s,%s' % (url, paramChar, tableName, colName, dir) + + def getSortParams(self, tableName): + url = str(self.request.URL) + paramChar = '?' in url and '&' or '?' + si = self.sortInfo.get(tableName) + if si is not None: + colName = si['colName'] + dir = si['ascending'] and 'asc' or 'desc' + return '%ssortinfo_%s=%s,%s' % (paramChar, tableName, colName, dir) + return '' + + def getSortImage(self, tableName, colName): + si = self.sortInfo.get(tableName) + if si is not None and si.get('colName') == colName: + if si['ascending']: + return '/@@/cybertools.icons/arrowdown.gif' + else: + return '/@@/cybertools.icons/arrowup.gif' + + +class BaseView(GenericView, I18NView, SortableMixin): actions = {} portlet_actions = [] @@ -153,6 +208,10 @@ class BaseView(GenericView, I18NView): pass saveRequest(request) + def todayFormatted(self): + return formatDate(date.today(), 'date', 'short', + self.languageInfo.language) + def checkPermissions(self): return canAccessObject(self.context) @@ -204,6 +263,16 @@ class BaseView(GenericView, I18NView): result.append(view) return result + @Lazy + def urlParamString(self): + return self.getUrlParamString() + + def getUrlParamString(self): + qs = self.request.get('QUERY_STRING') + if qs: + return '?' + qs + return '' + @Lazy def principalId(self): principal = self.request.principal @@ -337,6 +406,10 @@ class BaseView(GenericView, I18NView): def isPartOfPredicate(self): return self.conceptManager.get('ispartof') + @Lazy + def queryTargetPredicate(self): + return self.conceptManager.get('querytarget') + @Lazy def memberPredicate(self): return self.conceptManager.get('ismember') @@ -732,6 +805,8 @@ class BaseView(GenericView, I18NView): return result def checkState(self): + if checkPermission('loops.ManageSite', self.context): + return True if not self.allStates: return True for stf in self.allStates: @@ -806,6 +881,10 @@ class BaseView(GenericView, I18NView): def canAccessRestricted(self): return checkPermission('loops.ViewRestricted', self.context) + @Lazy + def canEditRestricted(self): + return canEditRestricted(self.context) + def openEditWindow(self, viewName='edit.html'): if self.editable: if checkPermission('loops.ManageSite', self.context): @@ -928,6 +1007,12 @@ class BaseView(GenericView, I18NView): jsCall = 'dojo.require("dojox.image.Lightbox");' self.controller.macros.register('js-execute', jsCall, jsCall=jsCall) + def registerDojoComboBox(self): + self.registerDojo() + jsCall = ('dojo.require("dijit.form.ComboBox");') + self.controller.macros.register('js-execute', + 'dojo.require.ComboBox', jsCall=jsCall) + def registerDojoFormAll(self): self.registerDojo() self.registerDojoEditor() diff --git a/browser/concept.py b/browser/concept.py index 32b63fc..9e54655 100644 --- a/browser/concept.py +++ b/browser/concept.py @@ -282,8 +282,17 @@ class ConceptView(BaseView): def breadcrumbsTitle(self): return self.title + @Lazy + def showInBreadcrumbs(self): + return (self.options('show_in_breadcrumbs') or + self.typeOptions('show_in_breadcrumbs')) + @Lazy def breadcrumbsParent(self): + for p in self.context.getParents([self.defaultPredicate]): + view = self.nodeView.getViewForTarget(p) + if view.showInBreadcrumbs: + return view return None def getData(self, omit=('title', 'description')): @@ -449,7 +458,7 @@ class ConceptView(BaseView): if r.order != pos: r.order = pos - def getResources(self): + def getResources(self, relView=None, sort='default'): form = self.request.form #if form.get('loops.viewName') == 'index.html' and self.editable: if self.editable: @@ -458,13 +467,17 @@ class ConceptView(BaseView): tokens = form.get('resources_tokens') if tokens: self.reorderResources(tokens) - from loops.browser.resource import ResourceRelationView + if relView is None: + from loops.browser.resource import ResourceRelationView + relView = ResourceRelationView from loops.organize.personal.browser.filter import FilterView fv = FilterView(self.context, self.request) - rels = self.context.getResourceRelations() + rels = self.context.getResourceRelations(sort=sort) for r in rels: if fv.check(r.first): - yield ResourceRelationView(r, self.request, contextIsSecond=True) + view = relView(r, self.request, contextIsSecond=True) + if view.checkState(): + yield view def resources(self): return self.getResources() diff --git a/browser/concept_macros.pt b/browser/concept_macros.pt index 7bbea5a..7d61dbb 100644 --- a/browser/concept_macros.pt +++ b/browser/concept_macros.pt @@ -51,7 +51,7 @@