# HG changeset patch # User sylvain.thenault@logilab.fr # Date 1241074314 -7200 # Node ID de0548cb0ab854a85adb9dc7204843e6ce561ae1 # Parent f63d090eb34a5e4f81acedbb48a8c736339f920b# Parent b130c6cec8c2cf978f13e5597f023d524809ab4c merge diff -r b130c6cec8c2 -r de0548cb0ab8 entities/authobjs.py --- a/entities/authobjs.py Wed Apr 29 19:54:50 2009 +0200 +++ b/entities/authobjs.py Thu Apr 30 08:51:54 2009 +0200 @@ -24,7 +24,7 @@ id = 'CWUser' fetch_attrs, fetch_order = fetch_config(['login', 'firstname', 'surname']) fetch_unrelated_order = fetch_order - + # used by repository to check if the user can log in or not AUTHENTICABLE_STATES = ('activated',) @@ -37,7 +37,7 @@ self._groups = groups if properties is not None: self._properties = properties - + @property def groups(self): try: @@ -45,7 +45,7 @@ except AttributeError: self._groups = set(g.name for g in self.in_group) return self._groups - + @property def properties(self): try: @@ -64,7 +64,7 @@ except ValueError: self.warning('incorrect value for eproperty %s of user %s', key, self.login) return self.vreg.property_value(key) - + def matching_groups(self, groups): """return the number of the given group(s) in which the user is @@ -86,7 +86,7 @@ """ checks if user is an anonymous user""" #FIXME on the web-side anonymous user is detected according # to config['anonymous-user'], we don't have this info on - # the server side. + # the server side. return self.groups == frozenset(('guests', )) def owns(self, eid): @@ -116,12 +116,12 @@ return self.req.execute(rql, kwargs, cachekey) except Unauthorized: return False - + # presentation utilities ################################################## - + def name(self): """construct a name using firstname / surname or login if not defined""" - + if self.firstname and self.surname: return self.req._('%(firstname)s %(surname)s') % { 'firstname': self.firstname, 'surname' : self.surname} diff -r b130c6cec8c2 -r de0548cb0ab8 etwist/server.py --- a/etwist/server.py Wed Apr 29 19:54:50 2009 +0200 +++ b/etwist/server.py Thu Apr 30 08:51:54 2009 +0200 @@ -44,6 +44,14 @@ # ensure no tasks will be further added repo._looping_tasks = () +def host_prefixed_baseurl(baseurl, host): + scheme, netloc, url, query, fragment = urlsplit(baseurl) + netloc_domain = '.' + '.'.join(netloc.split('.')[-2:]) + if host.endswith(netloc_domain): + netloc = host + baseurl = urlunsplit((scheme, netloc, url, query, fragment)) + return baseurl + class LongTimeExpiringFile(static.File): """overrides static.File and sets a far futre ``Expires`` date @@ -169,11 +177,8 @@ https = False baseurl = self.base_url if self.config['use-request-subdomain']: - scheme, netloc, url, query, fragment = urlsplit(baseurl) - if '.' in netloc: - netloc = '.'.join(host.split('.')[:1] + netloc.split('.')[1:]) - baseurl = urlunsplit((scheme, netloc, url, query, fragment)) - self.warning('base_url is %s for this request', baseurl) + baseurl = host_prefixed_baseurl(baseurl, host) + self.warning('used baseurl is %s for this request', baseurl) req = CubicWebTwistedRequestAdapter(request, self.appli.vreg, https, baseurl) if req.authmode == 'http': # activate realm-based auth diff -r b130c6cec8c2 -r de0548cb0ab8 etwist/test/unittest_server.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/etwist/test/unittest_server.py Thu Apr 30 08:51:54 2009 +0200 @@ -0,0 +1,35 @@ +from cubicweb.devtools.apptest import EnvBasedTC +from cubicweb.etwist.server import host_prefixed_baseurl + + +class HostPrefixedBaseURLTC(EnvBasedTC): + + def _check(self, baseurl, host, waited): + self.assertEquals(host_prefixed_baseurl(baseurl, host), waited, + 'baseurl %s called through host %s should be considered as %s' + % (baseurl, host, waited)) + + def test1(self): + self._check('http://www.cubicweb.org/hg/', 'code.cubicweb.org', + 'http://code.cubicweb.org/hg/') + + def test2(self): + self._check('http://www.cubicweb.org/hg/', 'cubicweb.org', + 'http://www.cubicweb.org/hg/') + + def test3(self): + self._check('http://cubicweb.org/hg/', 'code.cubicweb.org', + 'http://code.cubicweb.org/hg/') + + def test4(self): + self._check('http://www.cubicweb.org/hg/', 'localhost', + 'http://www.cubicweb.org/hg/') + + def test5(self): + self._check('http://www.cubicweb.org/cubes/', 'hg.code.cubicweb.org', + 'http://hg.code.cubicweb.org/cubes/') + + def test6(self): + self._check('http://localhost:8080/hg/', 'code.cubicweb.org', + 'http://localhost:8080/hg/') + diff -r b130c6cec8c2 -r de0548cb0ab8 goa/skel/views.py --- a/goa/skel/views.py Wed Apr 29 19:54:50 2009 +0200 +++ b/goa/skel/views.py Thu Apr 30 08:51:54 2009 +0200 @@ -1,10 +1,8 @@ # custom application views - -from mx.DateTime import DateTime +from calendar import monthrange +from datetime import date -from cubicweb.web.views import baseviews -from cubicweb.web.views.boxes import BoxTemplate -from cubicweb.web.views.calendar import MONTHNAMES +from cubicweb.web.views import baseviews, boxes, calendar from cubicweb.web.htmlwidgets import BoxLink, BoxWidget _ = unicode @@ -12,15 +10,15 @@ class BlogEntryPrimaryView(baseviews.PrimaryView): accepts = ('BlogEntry',) - + def cell_call(self, row, col): entity = self.entity(row, col) self.w(u'

%s

' % entity.dc_title()) entity.view('metadata', w=self.w) self.w(entity.printable_value('text')) - + -class BlogArchiveBox(BoxTemplate): +class BlogArchiveBox(boxes.BoxTemplate): """side box usually displaying some related entities in a primary view""" id = 'blog_archives_box' title = _('blog archives') @@ -37,12 +35,12 @@ blogmonths.append( (year, month) ) box = BoxWidget(_('Blog archives'), id=self.id) for year, month in blogmonths: - firstday = DateTime(year, month, 1) - lastday = DateTime(year, month, firstday.days_in_month) + firstday = date(year, month, 1) + lastday = date(year, month, monthrange(year, month)[1]) rql = ('Any B WHERE B is BlogEntry, B creation_date >= "%s", B creation_date <= "%s"' % (firstday.strftime('%Y-%m-%d'), lastday.strftime('%Y-%m-%d'))) url = self.build_url(rql=rql) - label = u'%s %s' % (_(MONTHNAMES[month-1]), year) + label = u'%s %s' % (_(calendar.MONTHNAMES[month-1]), year) box.append( BoxLink(url, label) ) box.render(self.w) diff -r b130c6cec8c2 -r de0548cb0ab8 interfaces.py --- a/interfaces.py Wed Apr 29 19:54:50 2009 +0200 +++ b/interfaces.py Thu Apr 30 08:51:54 2009 +0200 @@ -11,7 +11,7 @@ class IEmailable(Interface): """interface for emailable entities""" - + def get_email(self): """return email address""" @@ -28,7 +28,7 @@ def as_email_context(self): """returns the dictionary as used by the sendmail controller to build email bodies. - + NOTE: the dictionary keys should match the list returned by the `allowed_massmail_keys` method. """ @@ -45,12 +45,12 @@ """change the entity's state according to a state defined in given parameters """ - + def can_pass_transition(self, trname): """return true if the current user can pass the transition with the given name """ - + def latest_trinfo(self): """return the latest transition information for this entity """ @@ -73,7 +73,7 @@ @property def todo(self): """what remains to be done""" - + def progress_info(self): """returns a dictionary describing progress/estimated cost of the version. @@ -93,19 +93,19 @@ def progress(self): """returns the % progress of the task item""" - - + + class IMileStone(IProgress): """represents an ITask's item""" - + parent_type = None # specify main task's type - + def get_main_task(self): """returns the main ITask entity""" def initial_prevision_date(self): """returns the initial expected end of the milestone""" - + def eta_date(self): """returns expected date of completion based on what remains to be done @@ -128,7 +128,7 @@ def __iter__(self): """iterates over the item's children""" - + def is_leaf(self): """returns true if this node as no child""" @@ -146,7 +146,7 @@ """interface for entities which can be linked to a previous and/or next entity """ - + def next_entity(self): """return the 'next' entity""" def previous_entity(self): @@ -155,10 +155,10 @@ class IBreadCrumbs(Interface): """interface for entities which can be "located" on some path""" - + def breadcrumbs(self, view, recurs=False): """return a list containing some: - + * tuple (url, label) * entity * simple label string @@ -173,7 +173,7 @@ class IDownloadable(Interface): """interface for downloadable entities""" - + def download_url(self): # XXX not really part of this interface """return an url to download entity's content""" def download_content_type(self): @@ -188,31 +188,31 @@ class IEmbedable(Interface): """interface for embedable entities""" - + def embeded_url(self): """embed action interface""" - + class ICalendarable(Interface): """interface for items that do have a begin date 'start' and an end date 'stop' - """ - + """ + class ICalendarViews(Interface): """calendar views interface""" def matching_dates(self, begin, end): """ :param begin: day considered as begin of the range (`DateTime`) :param end: day considered as end of the range (`DateTime`) - + :return: a list of dates (`DateTime`) in the range [`begin`, `end`] on which this entity apply """ - + class ITimetableViews(Interface): """timetable views interface""" def timetable_date(self): """XXX explain - + :return: date (`DateTime`) """ @@ -231,17 +231,18 @@ """returns the icon that should be used as the marker (returns None for default) """ - + class IFeed(Interface): """interface for entities with rss flux""" - + def rss_feed_url(self): """return an url which layout sub-entities item """ + class ISiocItem(Interface): """interface for entities (which are item in sioc specification) with sioc views""" - + def isioc_content(self): """return content entity""" @@ -252,11 +253,11 @@ """return container type (post, BlogPost, MailMessage)""" def isioc_replies(self): - """return replies items""" + """return replies items""" def isioc_topics(self): """return topics items""" - + class ISiocContainer(Interface): """interface for entities (which are container in sioc specification) with sioc views""" @@ -267,5 +268,5 @@ def isioc_items(self): """return contained items""" - - + + diff -r b130c6cec8c2 -r de0548cb0ab8 rtags.py --- a/rtags.py Wed Apr 29 19:54:50 2009 +0200 +++ b/rtags.py Thu Apr 30 08:51:54 2009 +0200 @@ -42,6 +42,9 @@ keys.remove((rtype, tagged, stype, '*')) return keys + def tag_attribute(self, tag, stype, attr): + self._tagdefs[(str(attr), 'subject', str(stype), '*')] = tag + def tag_relation(self, tag, relation, tagged): assert tagged in ('subject', 'object'), tagged stype, rtype, otype = relation diff -r b130c6cec8c2 -r de0548cb0ab8 utils.py --- a/utils.py Wed Apr 29 19:54:50 2009 +0200 +++ b/utils.py Thu Apr 30 08:51:54 2009 +0200 @@ -8,10 +8,11 @@ import locale from md5 import md5 +from calendar import monthrange from datetime import datetime, timedelta, date from time import time from random import randint, seed - + # initialize random seed from current time seed() @@ -29,26 +30,43 @@ assert isinstance(somedate, date) return date +ONEDAY = timedelta(days=1) + +def days_in_month(date_): + return monthrange(date_.year, date_.month)[1] + +def previous_month(date_): + return first_day(date_) - ONEDAY + +def next_month(date_): + return last_day(date_) + ONEDAY + +def first_day(date_): + return date(date_.year, date_.month, 1) + +def last_day(date_): + return date(date_.year, date_.month, days_in_month(date_)) + def date_range(begin, end, incr=1, include=None): """yields each date between begin and end :param begin: the start date :param end: the end date :param incr: the step to use to iterate over dates. Default is - one day. + one day. :param include: None (means no exclusion) or a function taking a date as parameter, and returning True if the date should be included. """ incr = timedelta(incr, 0, 0) while begin <= end: - if include is None or include(begin): + if include is None or include(begin): yield begin begin += incr def ustrftime(date, fmt='%Y-%m-%d'): """like strftime, but returns a unicode string instead of an encoded string which may be problematic with localized date. - + encoding is guessed by locale.getpreferredencoding() """ # date format may depend on the locale @@ -79,7 +97,7 @@ dict1 = dict(dict1) dict1.update(dict2) return dict1 - + class SizeConstrainedList(list): """simple list that makes sure the list does not get bigger @@ -120,12 +138,12 @@ def __nonzero__(self): return True - + def write(self, value): assert isinstance(value, unicode), u"unicode required not %s : %s"\ % (type(value).__name__, repr(value)) self.append(value) - + def getvalue(self): return u''.join(self) @@ -164,8 +182,8 @@ self.add_post_inline_script(u"""jQuery(document).ready(function () { %s });""" % jscode) - - + + def add_js(self, jsfile): """adds `jsfile` to the list of javascripts used in the webpage @@ -231,18 +249,18 @@ if skiphead: return header return u'\n%s\n' % header - + class HTMLStream(object): """represents a HTML page. This is used my main templates so that HTML headers can be added at any time during the page generation. - + HTMLStream uses the (U)StringIO interface to be compliant with existing code. """ - + def __init__(self, req): # stream for self.head = req.html_headers diff -r b130c6cec8c2 -r de0548cb0ab8 web/form.py --- a/web/form.py Wed Apr 29 19:54:50 2009 +0200 +++ b/web/form.py Thu Apr 30 08:51:54 2009 +0200 @@ -75,9 +75,9 @@ # method on successful commit forminfo = self.req.get_session_data(sessionkey, pop=True) if forminfo: - req.data['formvalues'] = forminfo['values'] - req.data['formerrors'] = errex = forminfo['errors'] - req.data['displayederrors'] = set() + self.req.data['formvalues'] = forminfo['values'] + self.req.data['formerrors'] = errex = forminfo['errors'] + self.req.data['displayederrors'] = set() # if some validation error occured on entity creation, we have to # get the original variable name from its attributed eid foreid = errex.entity diff -r b130c6cec8c2 -r de0548cb0ab8 web/formfields.py --- a/web/formfields.py Wed Apr 29 19:54:50 2009 +0200 +++ b/web/formfields.py Thu Apr 30 08:51:54 2009 +0200 @@ -180,14 +180,13 @@ def __init__(self, max_length=None, **kwargs): super(StringField, self).__init__(**kwargs) self.max_length = max_length + if max_length < 513 and isinstance(self.widget, TextArea): + self.widget.attrs['cols'], self.widget.attrs['rows'] = 60, 5 -class TextField(Field): +class TextField(StringField): + """XXX string field not enough?""" widget = TextArea - def __init__(self, rows=10, cols=80, **kwargs): - super(TextField, self).__init__(**kwargs) - self.rows = rows - self.cols = cols class RichTextField(TextField): @@ -427,7 +426,7 @@ if isinstance(cstr, SizeConstraint) and cstr.max is not None: if cstr.max > 257: rows_cols_from_constraint(cstr, kwargs) - field = TextField(**kwargs) + field = TextField(max_length=cstr.max, **kwargs) else: field = StringField(max_length=cstr.max, **kwargs) return field or TextField(**kwargs) diff -r b130c6cec8c2 -r de0548cb0ab8 web/formwidgets.py --- a/web/formwidgets.py Wed Apr 29 19:54:50 2009 +0200 +++ b/web/formwidgets.py Thu Apr 30 08:51:54 2009 +0200 @@ -137,6 +137,8 @@ def render(self, form, field): name, values, attrs = self._render_attrs(form, field) attrs.setdefault('onkeypress', 'autogrow(this)') + attrs.setdefault('cols', 80) + attrs.setdefault('rows', 20) if not values: value = u'' elif len(values) == 1: diff -r b130c6cec8c2 -r de0548cb0ab8 web/uicfg.py --- a/web/uicfg.py Wed Apr 29 19:54:50 2009 +0200 +++ b/web/uicfg.py Thu Apr 30 08:51:54 2009 +0200 @@ -12,6 +12,48 @@ from cubicweb.rtags import RelationTags, RelationTagsSet +# primary view configuration ################################################## + +# how to display a relation in primary view. +# values a dict with the following keys: +# +# 'where', whose value may be one of: +# * 'attributes', display in the attributes section +# * 'relations', display in the relations section (below attributes) +# * 'sideboxes', display in the side boxes (beside attributes) +# if this key is missing, the relation won't be displayed at all. +# +# 'vid' is an optional view identifier +# +# 'label' is an optional label +# +# 'limit' is a boolean telling if the results should be limited according to +# the configuration +class RDisplayRelationTags(RelationTags): + def __init__(self): + super(RDisplayRelationTags, self).__init__() + self._counter = 0 + + def tag_relation(self, values, *args, **kwargs): + super(RDisplayRelationTags, self).tag_relation(values, *args, **kwargs) + if values: + values['order'] = self.get_timestamp() + + def get_timestamp(self): + self._counter += 1 + return self._counter + +rdisplay = RDisplayRelationTags() +for rtype in ('eid', 'creation_date', 'modification_date', + 'is', 'is_instance_of', 'identity', + 'owned_by', 'created_by', + 'in_state', 'wf_info_for', 'require_permission', + 'from_entity', 'to_entity', + 'see_also'): + rdisplay.tag_relation({}, ('*', rtype, '*'), 'subject') + rdisplay.tag_relation({}, ('*', rtype, '*'), 'object') + + # autoform.AutomaticEntityForm configuration ################################## # relations'category (eg primary/secondary/generic/metadata/generated) diff -r b130c6cec8c2 -r de0548cb0ab8 web/views/actions.py --- a/web/views/actions.py Wed Apr 29 19:54:50 2009 +0200 +++ b/web/views/actions.py Thu Apr 30 08:51:54 2009 +0200 @@ -154,7 +154,7 @@ title = _('manage permissions') category = 'moreactions' - order = 100 + order = 15 @classmethod def registered(cls, vreg): @@ -248,7 +248,7 @@ order = 20 def url(self): - return self.build_url('euser/%s'%self.req.user.login, vid='edition') + return self.build_url('cwuser/%s'%self.req.user.login, vid='edition') class LogoutAction(Action): diff -r b130c6cec8c2 -r de0548cb0ab8 web/views/basecomponents.py --- a/web/views/basecomponents.py Wed Apr 29 19:54:50 2009 +0200 +++ b/web/views/basecomponents.py Thu Apr 30 08:51:54 2009 +0200 @@ -16,7 +16,7 @@ from cubicweb.schema import display_name from cubicweb.web.htmlwidgets import MenuWidget, PopupBoxMenu, BoxSeparator, BoxLink -from cubicweb.web.component import Component, RelatedObjectsVComponent +from cubicweb.web import uicfg, component _ = unicode @@ -25,7 +25,7 @@ help=_('display the component or not')), } -class RQLInputForm(Component): +class RQLInputForm(component.Component): """build the rql input form, usually displayed in the header""" id = 'rqlinput' property_defs = VISIBLE_PROP_DEF @@ -54,7 +54,7 @@ self.w(u'') -class ApplLogo(Component): +class ApplLogo(component.Component): """build the application logo, usually displayed in the header""" id = 'logo' property_defs = VISIBLE_PROP_DEF @@ -66,7 +66,7 @@ % (self.req.base_url(), self.req.external_resource('LOGO'))) -class ApplHelp(Component): +class ApplHelp(component.Component): """build the help button, usually displayed in the header""" id = 'help' property_defs = VISIBLE_PROP_DEF @@ -76,7 +76,7 @@ self.req._(u'help'),)) -class UserLink(Component): +class UserLink(component.Component): """if the user is the anonymous user, build a link to login else a link to the connected user object with a loggout link """ @@ -120,7 +120,7 @@ % (self.build_url('login'), self.req._('login'))) -class ApplicationMessage(Component): +class ApplicationMessage(component.Component): """display application's messages given using the __message parameter into a special div section """ @@ -141,7 +141,7 @@ self.w(u'') -class ApplicationName(Component): +class ApplicationName(component.Component): """display the application name""" id = 'appliname' property_defs = VISIBLE_PROP_DEF @@ -151,19 +151,22 @@ self.req.property_value('ui.site-title'))) -class SeeAlsoVComponent(RelatedObjectsVComponent): +uicfg.rdisplay.tag_relation({}, ('*', 'see_also', '*'), 'subject') +uicfg.rdisplay.tag_relation({}, ('*', 'see_also', '*'), 'object') + +class SeeAlsoVComponent(component.RelatedObjectsVComponent): """display any entity's see also""" id = 'seealso' context = 'navcontentbottom' rtype = 'see_also' - target = 'object' + role = 'subject' order = 40 # register msg not generated since no entity use see_also in cubicweb itself title = _('contentnavigation_seealso') help = _('contentnavigation_seealso_description') -class EtypeRestrictionComponent(Component): +class EtypeRestrictionComponent(component.Component): """displays the list of entity types contained in the resultset to be able to filter accordingly. """ diff -r b130c6cec8c2 -r de0548cb0ab8 web/views/baseviews.py --- a/web/views/baseviews.py Wed Apr 29 19:54:50 2009 +0200 +++ b/web/views/baseviews.py Thu Apr 30 08:51:54 2009 +0200 @@ -165,7 +165,7 @@ self.w(u'#%s - ' % entity.eid) if entity.modification_date != entity.creation_date: self.w(u'%s ' % _('latest update on')) - self.w(u'%s, ;' + self.w(u'%s, ' % self.format_date(entity.modification_date)) # entities from external source may not have a creation date (eg ldap) if entity.creation_date: @@ -373,7 +373,6 @@ from cubicweb.web.views import boxes, xmlrss, primary PrimaryView = class_moved(primary.PrimaryView) -PRIMARY_SKIP_RELS = primary.PRIMARY_SKIP_RELS SideBoxView = class_moved(boxes.SideBoxView) XmlView = class_moved(xmlrss.XmlView) XmlItemView = class_moved(xmlrss.XmlItemView) diff -r b130c6cec8c2 -r de0548cb0ab8 web/views/cwuser.py --- a/web/views/cwuser.py Wed Apr 29 19:54:50 2009 +0200 +++ b/web/views/cwuser.py Thu Apr 30 08:51:54 2009 +0200 @@ -26,6 +26,8 @@ uicfg.rmode.tag_relation('link', ('*', 'owned_by', 'CWUser'), 'object') uicfg.rmode.tag_relation('link', ('*', 'created_by', 'CWUser'), 'object') uicfg.rmode.tag_relation('create', ('*', 'bookmarked_by', 'CWUser'), 'object') +uicfg.rdisplay.tag_attribute({}, 'CWUser', 'firstname') +uicfg.rdisplay.tag_attribute({}, 'CWUser', 'surname') class UserPreferencesEntityAction(action.Action): @@ -38,29 +40,15 @@ def url(self): login = self.rset.get_entity(self.row or 0, self.col or 0).login - return self.build_url('euser/%s'%login, vid='epropertiesform') + return self.build_url('cwuser/%s'%login, vid='epropertiesform') class CWUserPrimaryView(PrimaryView): __select__ = implements('CWUser') - skip_attrs = ('firstname', 'surname') - - def iter_relations(self, entity): - # don't want to display user's entities - for rschema, targetschemas, x in super(CWUserPrimaryView, self).iter_relations(entity): - if x == 'object' and rschema.type in ('owned_by', 'for_user'): - continue - yield rschema, targetschemas, x - def content_title(self, entity): return entity.name() - def is_side_related(self, rschema, eschema): - # XXX only bookmarked_by defined in cw... - return rschema.type in ['interested_in', 'tags', - 'todo_by', 'bookmarked_by'] - class FoafView(EntityView): id = 'foaf' diff -r b130c6cec8c2 -r de0548cb0ab8 web/views/emailaddress.py --- a/web/views/emailaddress.py Wed Apr 29 19:54:50 2009 +0200 +++ b/web/views/emailaddress.py Thu Apr 30 08:51:54 2009 +0200 @@ -20,7 +20,7 @@ self.skipeids = skipeids super(EmailAddressPrimaryView, self).cell_call(row, col) - def render_entity_attributes(self, entity, siderelations): + def render_entity_attributes(self, entity): self.w(u'

') entity.view('oneline', w=self.w) if not entity.canonical: @@ -53,7 +53,7 @@ emailofstr = ', '.join(e.view('oneline') for e in emailof) self.field(display_name(self.req, 'use_email', 'object'), emailofstr) - def render_entity_relations(self, entity, siderelations): + def render_entity_relations(self, entity): for i, email in enumerate(entity.related_emails(self.skipeids)): self.w(u'
' % (i%2 and 'even' or 'odd')) email.view('oneline', w=self.w, contexteid=entity.eid) @@ -64,7 +64,8 @@ __select__ = implements('EmailAddress') id = 'shortprimary' title = None # hidden view - def render_entity_attributes(self, entity, siderelations): + + def render_entity_attributes(self, entity): self.w(u'
') entity.view('oneline', w=self.w) self.w(u'
') diff -r b130c6cec8c2 -r de0548cb0ab8 web/views/idownloadable.py --- a/web/views/idownloadable.py Wed Apr 29 19:54:50 2009 +0200 +++ b/web/views/idownloadable.py Thu Apr 30 08:51:54 2009 +0200 @@ -94,19 +94,11 @@ self.w(u'%s' % (url, html_escape(title or entity.dc_title()))) - class IDownloadablePrimaryView(baseviews.PrimaryView): __select__ = implements(IDownloadable) - # XXX File/Image attributes but this is not specified in the IDownloadable interface - skip_attrs = baseviews.PrimaryView.skip_attrs + ('data', 'name') - def render_entity_title(self, entity): - self.w(u'

%s %s

' - % (entity.dc_type().capitalize(), - html_escape(entity.dc_title()))) - - def render_entity_attributes(self, entity, siderelations): - super(IDownloadablePrimaryView, self).render_entity_attributes(entity, siderelations) + def render_entity_attributes(self, entity): + super(IDownloadablePrimaryView, self).render_entity_attributes(entity) self.w(u'
') contenttype = entity.download_content_type() if contenttype.startswith('image/'): @@ -123,10 +115,6 @@ self.w('
%s
' % msg) self.w(u'
') - def is_side_related(self, rschema, eschema): - """display all relations as side related""" - return True - class IDownloadableLineView(baseviews.OneLineView): __select__ = implements(IDownloadable) diff -r b130c6cec8c2 -r de0548cb0ab8 web/views/isioc.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/views/isioc.py Thu Apr 30 08:51:54 2009 +0200 @@ -0,0 +1,82 @@ +"""Specific views for SIOC interfaces + +:organization: Logilab +:copyright: 2003-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +""" +__docformat__ = "restructuredtext en" + +from logilab.mtconverter import html_escape + +from cubicweb.view import EntityView +from cubicweb.selectors import implements +from cubicweb.interfaces import ISiocItem, ISiocContainer + +class SIOCView(EntityView): + id = 'sioc' + __select__ = EntityView.__select__ & implements(ISiocItem, ISiocContainer) + title = _('sioc') + templatable = False + content_type = 'text/xml' + + def call(self): + self.w(u'\n' % self.req.encoding) + self.w(u'''\n''') + for i in xrange(self.rset.rowcount): + self.cell_call(i, 0) + self.w(u'\n') + + def cell_call(self, row, col): + self.wview('sioc_element', self.rset, row=row, col=col) + +class SIOCContainerView(EntityView): + id = 'sioc_element' + __select__ = EntityView.__select__ & implements(ISiocContainer) + templatable = False + content_type = 'text/xml' + + def cell_call(self, row, col): + entity = self.complete_entity(row, col) + self.w(u'\n' % (html_escape(entity.isioc_type()), + html_escape(entity.absolute_url()))) + self.w(u'%s' % html_escape(entity.dc_title())) + self.w(u'%s' % entity.creation_date) + self.w(u'%s' % entity.modification_date) + self.w(u'')#entity.isioc_items() + self.w(u'\n' % entity.isioc_type()) + + +class SIOCItemView(EntityView): + id = 'sioc_element' + __select__ = EntityView.__select__ & implements(ISiocItem) + templatable = False + content_type = 'text/xml' + + def cell_call(self, row, col): + entity = self.complete_entity(row, col) + self.w(u'\n' % (html_escape(entity.isioc_type()), + html_escape(entity.absolute_url()))) + self.w(u'%s' % html_escape(entity.dc_title())) + self.w(u'%s' % entity.creation_date) + self.w(u'%s' % entity.modification_date) + if entity.content: + self.w(u'%s''' % html_escape(entity.isioc_content())) + if entity.related('entry_of'): + self.w(u'\n' % html_escape(entity.isioc_container().absolute_url())) + if entity.creator: + self.w(u'\n') + self.w(u'\n' % html_escape(entity.creator.absolute_url())) + self.w(entity.creator.view('foaf')) + self.w(u'\n') + self.w(u'\n') + self.w(u'')#entity.isioc_topics() + self.w(u'')#entity.isioc_replies() + self.w(u' \n' % 'Post') + diff -r b130c6cec8c2 -r de0548cb0ab8 web/views/primary.py --- a/web/views/primary.py Wed Apr 29 19:54:50 2009 +0200 +++ b/web/views/primary.py Thu Apr 30 08:51:54 2009 +0200 @@ -4,19 +4,18 @@ :copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved. :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr """ +__docformat__ = "restructuredtext en" from warnings import warn +from logilab.mtconverter import html_escape + from cubicweb import Unauthorized from cubicweb.view import EntityView +from cubicweb.web.uicfg import rdisplay _ = unicode -PRIMARY_SKIP_RELS = set(['is', 'is_instance_of', 'identity', - 'owned_by', 'created_by', - 'in_state', 'wf_info_for', 'require_permission', - 'from_entity', 'to_entity', - 'see_also']) class PrimaryView(EntityView): """the full view of an non final entity""" @@ -25,10 +24,44 @@ show_attr_label = True show_rel_label = True skip_none = True - skip_attrs = ('eid', 'creation_date', 'modification_date') - skip_rels = () + rdisplay = rdisplay main_related_section = True + @classmethod + def vreg_initialization_completed(cls): + """set default category tags for relations where it's not yet defined in + the category relation tags + """ + for eschema in cls.schema.entities(): + for rschema, tschemas, role in eschema.relation_definitions(True): + for tschema in tschemas: + if role == 'subject': + X, Y = eschema, tschema + card = rschema.rproperty(X, Y, 'cardinality')[0] + composed = rschema.rproperty(X, Y, 'composite') == 'object' + else: + X, Y = tschema, eschema + card = rschema.rproperty(X, Y, 'cardinality')[1] + composed = rschema.rproperty(X, Y, 'composite') == 'subject' + displayinfo = cls.rdisplay.get(rschema, role, X, Y) + if displayinfo is None: + if rschema.is_final(): + if rschema.meta or tschema.type in ('Password', 'Bytes'): + where = None + else: + where = 'attributes' + elif card in '1+': + where = 'attributes' + elif composed: + where = 'relations' + else: + where = 'sideboxes' + displayinfo = {'where': where, + 'order': cls.rdisplay.get_timestamp()} + cls.rdisplay.tag_relation(displayinfo, (X, rschema, Y), + role) + displayinfo.setdefault('label', '%s_%s' % (rschema, role)) + def html_headers(self): """return a list of html headers (eg something to be inserted between and of the returned page @@ -41,6 +74,7 @@ self.row = row # XXX move render_entity implementation here self.render_entity(self.complete_entity(row, col)) + self.maxrelated = self.req.property_value('navigation.related-limit') def render_entity(self, entity): """return html to display the given entity""" @@ -48,16 +82,16 @@ self.render_entity_metadata(entity) # entity's attributes and relations, excluding meta data # if the entity isn't meta itself - boxes = self._preinit_side_related(entity) - if boxes: - self.w(u'
') + boxes = self._prepare_side_boxes(entity) + if boxes or hasattr(self, 'render_side_related'): + self.w(u'
') self.w(u'
') self.w(u'
') try: self.render_entity_attributes(entity) except TypeError: # XXX bw compat warn('siderelations argument of render_entity_attributes is ' - 'deprecated') + 'deprecated (%s)' % self.__class__) self.render_entity_attributes(entity, []) self.w(u'
') self.content_navigation_components('navcontenttop') @@ -69,16 +103,14 @@ 'deprecated') self.render_entity_relations(entity, []) self.w(u'
') - if boxes: + if boxes or hasattr(self, 'render_side_related'): self.w(u'
') # side boxes self.w(u'
') - try: - self.render_side_related(entity) - except TypeError: # XXX bw compat - warn('siderelations argument of render_entity_relations is ' - 'deprecated') - self.render_entity_relations(entity, []) + if hasattr(self, 'render_side_related'): + warn('render_side_related is deprecated') + self.render_side_related(entity, []) + self.render_side_boxes(boxes) self.w(u'
') self.w(u'
') self.content_navigation_components('navcontentbottom') @@ -97,29 +129,16 @@ comp.dispatch(w=self.w, view=self) self.w(u'') - def iter_attributes(self, entity): - for rschema, targetschema in entity.e_schema.attribute_definitions(): - if rschema.type in self.skip_attrs: - continue - yield rschema, targetschema - - def iter_relations(self, entity): - skip = set(self.skip_rels) - skip.update(PRIMARY_SKIP_RELS) - for rschema, targetschemas, x in entity.e_schema.relation_definitions(): - if rschema.type in skip: - continue - yield rschema, targetschemas, x - def render_entity_title(self, entity): title = self.content_title(entity) # deprecate content_title? if title: self.w(u'

%s %s

' % (entity.dc_type().capitalize(), title)) + def content_title(self, entity): - """default implementation return an empty string""" - return u'' + """default implementation return dc_title""" + return html_escape(entity.dc_title()) def render_entity_metadata(self, entity): entity.view('metadata', w=self.w) @@ -132,67 +151,38 @@ return u'' def render_entity_attributes(self, entity, siderelations=None): - for rschema, targetschema in self.iter_attributes(entity): - attr = rschema.type - if targetschema.type in ('Password', 'Bytes'): - continue - try: - wdg = entity.get_widget(attr) - except Exception, ex: - value = entity.printable_value(attr, entity[attr], targetschema.type) + for rschema, tschemas, role, displayinfo in self._iter_display(entity, 'attributes'): + vid = displayinfo.get('vid', 'reledit') + if rschema.is_final() or vid == 'reledit': + value = entity.view(vid, rtype=rschema.type, role=role) else: - value = wdg.render(entity) + rset = self._relation_rset(entity, rschema, role, displayinfo) + if rset: + value = self.view(vid, rset) + else: + value = None if self.skip_none and (value is None or value == ''): continue - if rschema.meta: - continue - self._render_related_entities(entity, rschema, value) - - def _preinit_side_related(self, entity): - self._sideboxes = [] - if hasattr(self, 'get_side_boxes_defs'): - self._sideboxes = [(label, rset, 'sidebox') for label, rset in self.get_side_boxes_defs(entity) - if rset] - else: - eschema = entity.e_schema - maxrelated = self.req.property_value('navigation.related-limit') - for rschema, targetschemas, role in self.iter_relations(entity): - if self.is_side_related(rschema, eschema): - try: - related = entity.related(rschema.type, role, limit=maxrelated+1) - except Unauthorized: - continue - if not related: - continue - label = display_name(self.req, rschema.type, role) - self._sideboxes.append((label, related, 'autolimited')) - self._contextboxes = list(self.vreg.possible_vobjects('boxes', self.req, self.rset, - row=self.row, view=self, - context='incontext')) - return self._sideboxes or self._contextboxes + self._render_attribute(rschema, value) def render_entity_relations(self, entity, siderelations=None): - eschema = entity.e_schema - for rschema, targetschemas, x in self.iter_relations(entity): - if not self.is_side_related(rschema, eschema): - try: - related = entity.related(rschema.type, x, limit=maxrelated+1) - except Unauthorized: - continue - self._render_related_entities(entity, rschema, related, x) + for rschema, tschemas, role, displayinfo in self._iter_display(entity, 'relations'): + rset = self._relation_rset(entity, rschema, role, displayinfo) + if rset: + self._render_relation(rset, displayinfo, 'autolimited', + self.show_rel_label) - - def render_side_related(self, entity, siderelations=None): + def render_side_boxes(self, boxes): """display side related relations: non-meta in a first step, meta in a second step """ - if self._sideboxes: - for label, rset, vid in self._sideboxes: + for box in boxes: + if isinstance(box, tuple): + label, rset, vid, _ = box self.w(u'
') self.wview(vid, rset, title=label) self.w(u'
') - if self._contextboxes: - for box in self._contextboxes: + else: try: box.dispatch(w=self.w, row=self.row) except NotImplementedError: @@ -200,28 +190,73 @@ # .call() and not cell_call() box.dispatch(w=self.w) - def is_side_related(self, rschema, eschema): - return rschema.meta and \ - not rschema.schema_relation() == eschema.schema_entity() + def _prepare_side_boxes(self, entity): + sideboxes = [] + for rschema, tschemas, role, displayinfo in self._iter_display(entity, 'sideboxes'): + rset = self._relation_rset(entity, rschema, role, displayinfo) + if not rset: + continue + label = display_name(self.req, rschema.type, role) + vid = displayinfo.get('vid', 'autolimited') + sideboxes.append((label, rset, vid, displayinfo.get('order'))) + sideboxes = sorted(sideboxes, key=lambda x: x[-1]) + sideboxes += list(self.vreg.possible_vobjects('boxes', self.req, self.rset, + row=self.row, view=self, + context='incontext')) + return sideboxes - def _render_related_entities(self, entity, rschema, related, - role='subject'): + def _iter_display(self, entity, where): + eschema = entity.e_schema + for rschema, tschemas, role in eschema.relation_definitions(True): + matchtschemas = [] + for tschema in tschemas: + displayinfo = self.rdisplay.etype_get(eschema, rschema, role, + tschema) + assert displayinfo is not None, (str(rschema), role, + str(eschema), str(tschema)) + if displayinfo.get('where') == where: + matchtschemas.append(tschema) + if matchtschemas: + # XXX pick the latest displayinfo + yield rschema, matchtschemas, role, displayinfo + + def _relation_rset(self, entity, rschema, role, displayinfo): + try: + if displayinfo.get('limit'): + rset = entity.related(rschema.type, role, + limit=self.maxrelated+1) + else: + rset = entity.related(rschema.type, role) + except Unauthorized: + return + if 'filter' in displayinfo: + rset = displayinfo['filter'](rset) + return rset + + def _render_relation(self, rset, displayinfo, defaultvid, showlabel): + self.w('
') + if showlabel: + label = self.req._(displayinfo['label']) + self.w('

%s

' % label) + self.wview(displayinfo.get('vid', defaultvid), rset) + self.w('
') + + def _render_attribute(self, rschema, value, role='subject'): if rschema.is_final(): - value = related show_label = self.show_attr_label else: - if not related: - return - value = self.view('autolimited', related) + show_label = self.show_rel_label label = display_name(self.req, rschema.type, role) self.field(label, value, show_label=show_label, tr=False) class RelatedView(EntityView): id = 'autolimited' - def call(self): + def call(self, title=None, **kwargs): # if not too many entities, show them all in a list maxrelated = self.req.property_value('navigation.related-limit') + if title: + self.w(u'
%s
' % title) if self.rset.rowcount <= maxrelated: if self.rset.rowcount == 1: self.wview('incontext', self.rset, row=0) @@ -234,7 +269,7 @@ # else show links to display related entities else: rql = self.rset.printable_rql() - self.rset.limit(maxself.rset) + self.rset.limit(maxrelated) self.w(u'
') self.wview('simplelist', self.rset) self.w(u'[%s]' % (self.build_url(rql=rql), diff -r b130c6cec8c2 -r de0548cb0ab8 web/views/schema.py --- a/web/views/schema.py Wed Apr 29 19:54:50 2009 +0200 +++ b/web/views/schema.py Thu Apr 30 08:51:54 2009 +0200 @@ -14,9 +14,9 @@ from cubicweb.selectors import implements, yes from cubicweb.schemaviewer import SchemaViewer from cubicweb.view import EntityView, StartupView -from cubicweb.common.uilib import ureport_as_html +from cubicweb.common import tags, uilib from cubicweb.web import uicfg, action -from cubicweb.web.views import TmpFileViewMixin, baseviews +from cubicweb.web.views import TmpFileViewMixin, primary, baseviews uicfg.rcategories.tag_relation('primary', ('CWPermission', 'require_group', '*'), 'subject') @@ -33,6 +33,11 @@ uicfg.rmode.tag_relation('link', ('*', 'from_entity', 'CWEType'), 'object') uicfg.rmode.tag_relation('link', ('*', 'to_entity', 'CWEType'), 'object') +for attr in ('name', 'meta', 'final'): + uicfg.rdisplay.tag_attribute({}, 'CWRType', attr) +for attr in ('name', 'meta', 'final', 'symetric', 'inlined'): + uicfg.rdisplay.tag_attribute({}, 'CWRType', attr) + class ViewSchemaAction(action.Action): id = 'schema' @@ -48,25 +53,13 @@ # schema entity types views ################################################### -class _SchemaEntityPrimaryView(baseviews.PrimaryView): - show_attr_label = False +class CWRDEFPrimaryView(primary.PrimaryView): + __select__ = implements('CWAttribute', 'CWRelation') cache_max_age = 60*60*2 # stay in http cache for 2 hours by default def content_title(self, entity): return html_escape(entity.dc_long_title()) -class CWETypePrimaryView(_SchemaEntityPrimaryView): - __select__ = implements('CWEType') - skip_attrs = _SchemaEntityPrimaryView.skip_attrs + ('name', 'meta', 'final') - -class CWRTypePrimaryView(_SchemaEntityPrimaryView): - __select__ = implements('CWRType') - skip_attrs = _SchemaEntityPrimaryView.skip_attrs + ('name', 'meta', 'final', - 'symetric', 'inlined') - -class ErdefPrimaryView(_SchemaEntityPrimaryView): - __select__ = implements('CWAttribute', 'CWRelation') - show_attr_label = True class CWETypeOneLineView(baseviews.OneLineView): __select__ = implements('CWEType') @@ -82,41 +75,44 @@ # in memory schema views (yams class instances) ############################### +SKIPPED_RELS = ('is', 'is_instance_of', 'identity', 'created_by', 'owned_by', + 'has_text',) -class CWETypeSchemaView(CWETypePrimaryView): +class CWETypeSchemaView(primary.PrimaryView): id = 'eschema' + __select__ = implements('CWEType') title = _('in memory entity schema') main_related_section = False - skip_rels = ('is', 'is_instance_of', 'identity', 'created_by', 'owned_by', - 'has_text',) + skip_rels = SKIPPED_RELS - def render_entity_attributes(self, entity, siderelations): - super(CWETypeSchemaView, self).render_entity_attributes(entity, siderelations) + def render_entity_attributes(self, entity): + super(CWETypeSchemaView, self).render_entity_attributes(entity) eschema = self.vreg.schema.eschema(entity.name) viewer = SchemaViewer(self.req) layout = viewer.visit_entityschema(eschema, skiprels=self.skip_rels) - self.w(ureport_as_html(layout)) + self.w(uilib.ureport_as_html(layout)) if not eschema.is_final(): - self.w(u'%s' % ( - html_escape(entity.absolute_url(vid='eschemagraph')), - html_escape(self.req._('graphical schema for %s') % entity.name))) + msg = self.req._('graphical schema for %s') % entity.name + self.w(tags.img(src=entity.absolute_url(vid='eschemagraph'), + alt=msg)) -class CWRTypeSchemaView(CWRTypePrimaryView): +class CWRTypeSchemaView(primary.PrimaryView): id = 'eschema' + __select__ = implements('CWRType') title = _('in memory relation schema') main_related_section = False - def render_entity_attributes(self, entity, siderelations): - super(CWRTypeSchemaView, self).render_entity_attributes(entity, siderelations) + def render_entity_attributes(self, entity): + super(CWRTypeSchemaView, self).render_entity_attributes(entity) rschema = self.vreg.schema.rschema(entity.name) viewer = SchemaViewer(self.req) layout = viewer.visit_relationschema(rschema) - self.w(ureport_as_html(layout)) + self.w(uilib.ureport_as_html(layout)) if not rschema.is_final(): - self.w(u'%s' % ( - html_escape(entity.absolute_url(vid='eschemagraph')), - html_escape(self.req._('graphical schema for %s') % entity.name))) + msg = self.req._('graphical schema for %s') % entity.name + self.w(tags.img(src=entity.absolute_url(vid='eschemagraph'), + alt=msg)) # schema images ############################################################### @@ -198,8 +194,9 @@ class SchemaImageView(TmpFileViewMixin, StartupView): id = 'schemagraph' + content_type = 'image/png' - skip_rels = ('owned_by', 'created_by', 'identity', 'is', 'is_instance_of') + skip_rels = SKIPPED_RELS def _generate(self, tmpfile): """display global schema information""" skipmeta = not int(self.req.form.get('withmeta', 0)) @@ -209,9 +206,10 @@ class CWETypeSchemaImageView(TmpFileViewMixin, EntityView): id = 'eschemagraph' + __select__ = implements('CWEType') + content_type = 'image/png' - __select__ = implements('CWEType') - skip_rels = ('owned_by', 'created_by', 'identity', 'is', 'is_instance_of') + skip_rels = SKIPPED_RELS def _generate(self, tmpfile): """display schema information for an entity""" diff -r b130c6cec8c2 -r de0548cb0ab8 web/views/startup.py diff -r b130c6cec8c2 -r de0548cb0ab8 web/views/timetable.py --- a/web/views/timetable.py Wed Apr 29 19:54:50 2009 +0200 +++ b/web/views/timetable.py Thu Apr 30 08:51:54 2009 +0200 @@ -45,7 +45,7 @@ user = u"*" the_dates = [] if task.start and task.stop: - if task.start.absdate == task.stop.absdate: + if task.start.toordinal() == task.stop.toordinal(): the_dates.append(task.start) else: the_dates += date_range( task.start, task.stop ) diff -r b130c6cec8c2 -r de0548cb0ab8 web/views/xmlrss.py --- a/web/views/xmlrss.py Wed Apr 29 19:54:50 2009 +0200 +++ b/web/views/xmlrss.py Thu Apr 30 08:51:54 2009 +0200 @@ -155,11 +155,11 @@ self.w(u'\n' % req.encoding) self.w(u'\n') self.w(u' \n') - self.w(u' %s RSS Feed\n' % html_escape(self.page_title())) - self.w(u' %s\n' % html_escape(req.form.get('vtitle', ''))) + self.w(u' %s RSS Feed\n' % xml_escape(self.page_title())) + self.w(u' %s\n' % xml_escape(req.form.get('vtitle', ''))) params = req.form.copy() params.pop('vid', None) - self.w(u' %s\n' % html_escape(self.build_url(**params))) + self.w(u' %s\n' % xml_escape(self.build_url(**params))) def _close(self): self.w(u' \n') @@ -175,6 +175,7 @@ def cell_call(self, row, col): self.wview('rssitem', self.rset, row=row, col=col) + class RssItemView(EntityView): id = 'rssitem' date_format = '%%Y-%%m-%%dT%%H:%%M%+03i:00' % (timezone / 3600) @@ -183,9 +184,9 @@ def cell_call(self, row, col): entity = self.complete_entity(row, col) self.w(u'\n') - self.w(u'%s\n' % html_escape(entity.absolute_url())) + self.w(u'%s\n' % xml_escape(entity.absolute_url())) self.render_title_link(entity) - self._marker('description', html_escape(entity.dc_description())) + self._marker('description', xml_escape(entity.dc_description())) self._marker('dc:date', entity.dc_date(self.date_format)) self.render_entity_creator(entity) self.w(u'\n') @@ -201,4 +202,4 @@ def _marker(self, marker, value): if value: - self.w(u' <%s>%s\n' % (marker, html_escape(value), marker)) + self.w(u' <%s>%s\n' % (marker, xml_escape(value), marker)) diff -r b130c6cec8c2 -r de0548cb0ab8 web/widgets.py --- a/web/widgets.py Wed Apr 29 19:54:50 2009 +0200 +++ b/web/widgets.py Thu Apr 30 08:51:54 2009 +0200 @@ -63,7 +63,7 @@ autoid = True html_attributes = set(('id', 'class', 'tabindex', 'accesskey', 'onchange', 'onkeypress')) cubicwebns_attributes = set() - + def __init__(self, vreg, subjschema, rschema, objschema, role='subject', description=None, **kwattrs): @@ -83,12 +83,12 @@ because widget instances are cached) """ # brute force copy (subclasses don't have the - # same __init__ prototype) + # same __init__ prototype) widget = self.__new__(self.__class__) widget.__dict__ = dict(self.__dict__) widget.attrs = dict(widget.attrs) return widget - + @staticmethod def size_constraint_attrs(attrs, maxsize): """set html attributes in the attrs dict to consider maxsize""" @@ -105,7 +105,7 @@ elif name in self.html_attributes: attrs.append(u'%s="%s"' % (name, value)) return u' '.join(sorted(attrs)) - + def required(self, entity): """indicates if the widget needs a value to be filled in""" card = self.rschema.cardinality(self.subjtype, self.objtype, self.role) @@ -116,7 +116,7 @@ return self.rname except AttributeError: return eid_param(self.name, entity.eid) - + def render_label(self, entity, label=None): """render widget's label""" label = label or self.rschema.display_name(entity.req, self.role) @@ -130,7 +130,7 @@ else: label = u'%s' % (forattr, label) return label - + def render_error(self, entity): """return validation error for widget's field of the given entity, if any @@ -153,16 +153,16 @@ help.append(u'(%s: %s)' % (req._('sample format'), example)) return u' '.join(help) - + def render_example(self, req): return u'' - + def render(self, entity): """render the widget for a simple view""" if not entity.has_eid(): return u'' return entity.printable_value(self.name) - + def edit_render(self, entity, tabindex=None, includehelp=False, useid=None, **kwargs): """render the widget for edition""" @@ -180,7 +180,7 @@ if includehelp: output += self.render_help(entity) return output - + def _edit_render(self, entity): """do the actual job to render the widget for edition""" raise NotImplementedError @@ -196,7 +196,7 @@ elif entity.has_eid(): return [row[0] for row in entity.related(self.name, self.role)] return () - + def current_value(self, entity): return _value_from_values(self.current_values(entity)) @@ -213,13 +213,13 @@ if not isinstance(cdvalues, (list, tuple)): cdvalues = (cdvalues,) return cdvalues - + def current_display_value(self, entity): """same as .current_value but consider values stored in session in case of validation error """ return _value_from_values(self.current_display_values(entity)) - + def hidden_input(self, entity, qvalue): """return an hidden field which 1. indicates that a field is edited @@ -258,7 +258,7 @@ def __init__(self, vreg, subjschema, rschema, objschema, role='subject', **kwattrs): InputWidget.__init__(self, vreg, subjschema, rschema, objschema, - role='subject', + role='subject', **kwattrs) # disable access key del self.attrs['accesskey'] @@ -270,18 +270,18 @@ def current_display_value(self, entity): value = InputWidget.current_display_value(self, entity) return value or INTERNAL_FIELD_VALUE - + def render_label(self, entity, label=None): """render widget's label""" return u'' - + def render_help(self, entity): return u'' - + def hidden_input(self, entity, value): """no hidden input for hidden input""" return '' - + class EidWidget(HiddenWidget): @@ -297,15 +297,15 @@ """set html attributes in the attrs dict to consider maxsize""" attrs['size'] = min(maxsize, 40) attrs['maxlength'] = maxsize - - + + class AutoCompletionWidget(StringWidget): cubicwebns_attributes = (StringWidget.cubicwebns_attributes | set(('accesskey', 'size', 'maxlength'))) attrs = () - + wdgtype = 'SuggestField' - + def current_value(self, entity): value = StringWidget.current_value(self, entity) return value or INTERNAL_FIELD_VALUE @@ -344,22 +344,22 @@ class StaticFileAutoCompletionWidget(AutoCompletionWidget): wdgtype = 'StaticFileSuggestField' - + def _get_url(self, entity): return entity.req.datadir_url + entity.autocomplete_initfuncs[self.rschema] class RestrictedAutoCompletionWidget(AutoCompletionWidget): - wdgtype = 'RestrictedSuggestField' + wdgtype = 'RestrictedSuggestField' - + class PasswordWidget(InputWidget): input_type = 'password' - + def required(self, entity): if InputWidget.required(self, entity) and not entity.has_eid(): return True return False - + def current_values(self, entity): # on existant entity, show password field has non empty (we don't have # the actual value @@ -374,10 +374,10 @@ html, self.input_type, name, name, entity.req.next_tabindex(), entity.req._('confirm password')) - + class TextWidget(Widget): html_attributes = Widget.html_attributes | set(('rows', 'cols')) - + @staticmethod def size_constraint_attrs(attrs, maxsize): """set html attributes in the attrs dict to consider maxsize""" @@ -385,12 +385,12 @@ attrs['cols'], attrs['rows'] = 60, 5 else: attrs['cols'], attrs['rows'] = 80, 10 - + def render(self, entity): if not entity.has_eid(): return u'' return entity.printable_value(self.name) - + def _edit_render(self, entity, with_format=True): req = entity.req editor = self._edit_render_textarea(entity, with_format) @@ -398,7 +398,7 @@ if isinstance(value, basestring): value = html_escape(value) return u'%s%s' % (self.hidden_input(entity, value), editor) - + def _edit_render_textarea(self, entity, with_format): self.attrs.setdefault('cols', 80) self.attrs.setdefault('rows', 20) @@ -426,8 +426,8 @@ fmtwdgstr = '' return u'%s
' % ( fmtwdgstr, self.rname, self.format_attrs(), dvalue) - - + + class CheckBoxWidget(Widget): html_attributes = Widget.html_attributes | set(('checked', )) def _edit_render(self, entity): @@ -460,7 +460,7 @@ u'%s
' % (self.rname, attrs2, entity.req._('no'))] return '\n'.join(wdgs) - + class FileWidget(Widget): need_multipart = True def _file_wdg(self, entity): @@ -492,7 +492,7 @@ wdgs.append(u'
') wdgs.append(req._('currently attached file: %s' % entity.dc_title())) return '\n'.join(wdgs) - + def _edit_render(self, entity): return self.hidden_input(entity, None) + self._file_wdg(entity) @@ -510,7 +510,7 @@ 'You can either submit a new file using the browse button above' ', or edit file content online with the widget below.') return msg - + def _edit_render(self, entity): wdgs = [self._file_wdg(entity)] if entity.attr_metadata(self.name, 'format') in ('text/plain', 'text/html', 'text/rest'): @@ -534,7 +534,7 @@ class ComboBoxWidget(Widget): html_attributes = Widget.html_attributes | set(('multiple', 'size')) - + def __init__(self, vreg, subjschema, rschema, objschema, multiple=False, **kwattrs): super(ComboBoxWidget, self).__init__(vreg, subjschema, rschema, objschema, @@ -545,10 +545,10 @@ self.attrs['size'] = '5' # disable access key (dunno why but this is not allowed by xhtml 1.0) del self.attrs['accesskey'] - + def vocabulary(self, entity): raise NotImplementedError() - + def form_value(self, entity, value, values): if value in values: flag = 'selected="selected"' @@ -574,9 +574,9 @@ res.append(u'') return '\n'.join(res) - + class StaticComboBoxWidget(ComboBoxWidget): - + def __init__(self, vreg, subjschema, rschema, objschema, vocabfunc, multiple=False, sort=False, **kwattrs): super(StaticComboBoxWidget, self).__init__(vreg, subjschema, rschema, objschema, @@ -591,11 +591,11 @@ if self.rschema.rproperty(self.subjtype, self.objtype, 'internationalizable'): return zip((entity.req._(v) for v in choices), choices) return zip(choices, choices) - + class EntityLinkComboBoxWidget(ComboBoxWidget): """to be used be specific forms""" - + def current_values(self, entity): if entity.has_eid(): return [r[0] for r in entity.related(self.name, self.role)] @@ -603,13 +603,13 @@ if hasattr(entity, defaultmeth): return getattr(entity, defaultmeth)() return () - + def vocabulary(self, entity): return [('', INTERNAL_FIELD_VALUE)] + entity.vocabulary(self.rschema, self.role) class RawDynamicComboBoxWidget(EntityLinkComboBoxWidget): - + def vocabulary(self, entity, limit=None): req = entity.req # first see if its specified by __linkto form parameters @@ -632,7 +632,7 @@ class DynamicComboBoxWidget(RawDynamicComboBoxWidget): - + def vocabulary(self, entity, limit=None): return sorted(super(DynamicComboBoxWidget, self).vocabulary(entity, limit)) @@ -669,11 +669,11 @@ kwattrs['size'] = 5 kwattrs['maxlength'] = 15 StringWidget.__init__(self, vreg, subjschema, rschema, objschema, **kwattrs) - + def render_example(self, req): return '23' - - + + class FloatWidget(StringWidget): def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs): kwattrs['size'] = 5 @@ -683,7 +683,7 @@ def render_example(self, req): formatstr = req.property_value('ui.float-format') return formatstr % 1.23 - + def current_values(self, entity): values = entity.attribute_values(self.name) if values: @@ -702,7 +702,7 @@ kwattrs['size'] = 5 kwattrs['maxlength'] = 15 StringWidget.__init__(self, vreg, subjschema, rschema, objschema, **kwattrs) - + def render_example(self, req): return '345.0300' @@ -724,7 +724,7 @@ daynames = [_(dname) for dname in cls.daynames] req.html_headers.define_var('MONTHNAMES', monthnames) req.html_headers.define_var('DAYNAMES', daynames) - + def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs): kwattrs.setdefault('size', 10) kwattrs.setdefault('maxlength', 10) @@ -784,7 +784,7 @@ kwattrs['size'] = 16 kwattrs['maxlength'] = 16 DateWidget.__init__(self, vreg, subjschema, rschema, objschema, **kwattrs) - + def render_example(self, req): formatstr1 = req.property_value('ui.datetime-format') formatstr2 = req.property_value('ui.date-format') @@ -801,26 +801,26 @@ kwattrs['maxlength'] = 5 StringWidget.__init__(self, vreg, subjschema, rschema, objschema, **kwattrs) - + class EmailWidget(StringWidget): - + def render(self, entity): email = getattr(entity, self.name) if not email: return u'' return u'%s' % (email, email) - + class URLWidget(StringWidget): - + def render(self, entity): url = getattr(entity, self.name) if not url: return u'' url = html_escape(url) return u'%s' % (url, url) - + class EmbededURLWidget(StringWidget): - + def render(self, entity): url = getattr(entity, self.name) if not url: @@ -828,7 +828,7 @@ aurl = html_escape(entity.build_url('embed', url=url)) return u'%s' % (aurl, url) - + def widget_factory(vreg, subjschema, rschema, objschema, role='subject', **kwargs): @@ -857,14 +857,14 @@ # factories to find the most adapated widget according to a type and other constraints - + def _string_widget_factory(vreg, subjschema, rschema, objschema, wcls=None, **kwargs): w = None for c in rschema.rproperty(subjschema, objschema, 'constraints'): if isinstance(c, StaticVocabularyConstraint): # may have been set by a previous SizeConstraint but doesn't make sense # here (even doesn't have the same meaning on a combobox actually) - kwargs.pop('size', None) + kwargs.pop('size', None) return (wcls or StaticComboBoxWidget)(vreg, subjschema, rschema, objschema, vocabfunc=c.vocabulary, **kwargs) if isinstance(c, SizeConstraint) and c.max is not None: @@ -914,7 +914,7 @@ 'String' : StringWidget, 'Time': TimeWidget, } - + # widgets registry WIDGETS = {} def register(widget_list):